Men-debug WebAssembly dengan alat modern

Ingvar Stepanyan
Ingvar Stepanyan

Jalan sejauh ini

Setahun yang lalu, Chrome mengumumkan dukungan awal untuk proses debug WebAssembly native di Chrome DevTools.

Kami mendemonstrasikan dukungan langkah dasar dan berbicara tentang peluang penggunaan informasi DWARF, bukan membuka peta sumber untuk kami di masa mendatang:

  • Menyelesaikan nama variabel
  • Jenis Pretty-printing
  • Mengevaluasi ekspresi dalam bahasa sumber
  • ...dan banyak lagi.

Hari ini, kami sangat senang dapat menunjukkan fitur yang dijanjikan akan diterapkan serta progres yang dicapai tim Emscripten dan Chrome DevTools selama tahun ini, khususnya, untuk aplikasi C dan C++.

Sebelum memulai, perlu diingat bahwa ini masih versi beta dari pengalaman baru. Anda harus menggunakan versi terbaru dari semua alat dengan risiko Anda sendiri. Jika mengalami masalah, laporkan ke https://bugs.chromium.org/p/chromium/issues/entry?template=DevTools+issue.

Mari kita mulai dengan contoh C sederhana yang sama seperti yang terakhir kali:

#include <stdlib.h>

void assert_less(int x, int y) {
  if (x >= y) {
    abort();
  }
}

int main() {
  assert_less(10, 20);
  assert_less(30, 20);
}

Untuk mengompilasinya, kami menggunakan latest Emscripten dan meneruskan flag -g, seperti pada postingan asli, untuk menyertakan informasi debug:

emcc -g temp.c -o temp.html

Sekarang, kita dapat menayangkan halaman yang dihasilkan dari server HTTP localhost (misalnya, dengan serve), dan membukanya di Chrome Canary terbaru.

Kali ini, kita juga memerlukan ekstensi helper yang terintegrasi dengan Chrome DevTools dan membantu memahami semua informasi proses debug yang dienkode dalam file WebAssembly. Instal ekstensi tersebut dengan membuka link ini: goo.gle/wasm-debugging-extension

Anda juga perlu mengaktifkan proses debug WebAssembly di Experiments DevTools. Buka Chrome DevTools, klik ikon roda gigi () di sudut kanan atas panel DevTools, buka panel Experiments, dan centang WebAssembly Debugging: Enable DWARF support.

Panel eksperimen di setelan DevTools

Saat Anda menutup Settings, DevTools akan menyarankan untuk memuat ulang sendiri guna menerapkan setelan, jadi mari kita lakukan. Itu saja untuk pengaturan satu kali.

Sekarang kita dapat kembali ke panel Sources, mengaktifkan Pause on exceptions (ikon ⏸), lalu centang Pause on caught exception, lalu muat ulang halaman. Anda akan melihat DevTools dijeda pada pengecualian:

Screenshot panel Sumber yang menampilkan cara mengaktifkan &#39;Menjeda pengecualian yang tertangkap&#39;

Secara default, ini berhenti pada kode glue yang dihasilkan Emscripten, tetapi di sebelah kanan Anda dapat melihat tampilan Call Stack yang mewakili stacktrace error, dan dapat menavigasi ke baris C asli yang memanggil abort:

DevTools dijeda di fungsi `statement_less` dan menampilkan nilai `x` dan `y` dalam tampilan Cakupan

Sekarang, jika melihat dalam tampilan Cakupan, Anda dapat melihat nama asli dan nilai variabel dalam kode C/C++, dan tidak perlu lagi mencari tahu arti nama rusak seperti $localN dan kaitannya dengan kode sumber yang Anda tulis.

Hal ini tidak hanya berlaku untuk nilai primitif seperti bilangan bulat, tetapi juga untuk jenis majemuk seperti struktur, class, array, dll..

Dukungan jenis multimedia

Mari kita lihat contoh yang lebih rumit untuk menunjukkannya. Kali ini, kita akan menggambar fraktal Mandelbrot dengan kode C++ berikut:

#include <SDL2/SDL.h>
#include <complex>

int main() {
  // Init SDL.
  int width = 600, height = 600;
  SDL_Init(SDL_INIT_VIDEO);
  SDL_Window* window;
  SDL_Renderer* renderer;
  SDL_CreateWindowAndRenderer(width, height, SDL_WINDOW_OPENGL, &window,
                              &renderer);

  // Generate a palette with random colors.
  enum { MAX_ITER_COUNT = 256 };
  SDL_Color palette[MAX_ITER_COUNT];
  srand(time(0));
  for (int i = 0; i < MAX_ITER_COUNT; ++i) {
    palette[i] = {
        .r = (uint8_t)rand(),
        .g = (uint8_t)rand(),
        .b = (uint8_t)rand(),
        .a = 255,
    };
  }

  // Calculate and draw the Mandelbrot set.
  std::complex<double> center(0.5, 0.5);
  double scale = 4.0;
  for (int y = 0; y < height; y++) {
    for (int x = 0; x < width; x++) {
      std::complex<double> point((double)x / width, (double)y / height);
      std::complex<double> c = (point - center) * scale;
      std::complex<double> z(0, 0);
      int i = 0;
      for (; i < MAX_ITER_COUNT - 1; i++) {
        z = z * z + c;
        if (abs(z) > 2.0)
          break;
      }
      SDL_Color color = palette[i];
      SDL_SetRenderDrawColor(renderer, color.r, color.g, color.b, color.a);
      SDL_RenderDrawPoint(renderer, x, y);
    }
  }

  // Render everything we've drawn to the canvas.
  SDL_RenderPresent(renderer);

  // SDL_Quit();
}

Anda dapat melihat bahwa aplikasi ini masih cukup kecil. Ini adalah satu file yang berisi 50 baris kode. Namun, kali ini saya juga menggunakan beberapa API eksternal, seperti library SDL untuk grafis serta angka kompleks dari library standar C++.

Saya akan mengompilasinya dengan flag -g yang sama seperti di atas untuk menyertakan informasi debug, dan juga akan meminta Emscripten untuk menyediakan library SDL2 dan memungkinkan memori berukuran arbitrer:

emcc -g mandelbrot.cc -o mandelbrot.html \
     -s USE_SDL=2 \
     -s ALLOW_MEMORY_GROWTH=1

Saat mengunjungi halaman yang dibuat di browser, saya dapat melihat bentuk fraktal yang indah dengan beberapa warna acak:

Halaman demo

Ketika saya membuka DevTools, sekali lagi, saya bisa melihat file C++ asli. Namun, kali ini, kita tidak memiliki error dalam kode (wow!), jadi mari kita tetapkan beberapa titik henti sementara di awal kode.

Saat kami memuat ulang halaman lagi, debugger akan dijeda tepat di dalam sumber C++ kami:

DevTools dijeda pada panggilan `SDL_Init`

Kita sudah dapat melihat semua variabel di sebelah kanan, tetapi hanya width dan height yang diinisialisasi saat ini, sehingga tidak banyak yang harus diperiksa.

Mari kita tetapkan titik henti sementara lain di dalam loop Mandelbrot utama, lalu lanjutkan eksekusi untuk sedikit melangkah maju.

DevTools dijeda di dalam loop bertingkat

Pada tahap ini, palette telah diisi dengan beberapa warna acak, dan kita dapat memperluas array itu sendiri, serta struktur SDL_Color individual dan memeriksa komponennya untuk memverifikasi bahwa semuanya terlihat bagus (misalnya, saluran "alpha" selalu disetel ke opasitas penuh). Demikian pula, kita dapat memperluas dan memeriksa bagian nyata dan imajiner dari bilangan kompleks yang disimpan dalam variabel center.

Jika Anda ingin mengakses properti bertingkat yang dalam dan sulit untuk dinavigasi melalui tampilan Cakupan, Anda juga dapat menggunakan evaluasi Konsol. Namun, perlu diperhatikan bahwa ekspresi C++ yang lebih kompleks belum didukung.

Panel konsol yang menampilkan hasil `palette[10].r`

Mari kita lanjutkan eksekusi beberapa kali dan kita dapat melihat bagaimana x bagian dalam berubah juga dengan melihat tampilan Cakupan lagi, menambahkan nama variabel ke daftar pantauan, mengevaluasinya di konsol, atau dengan mengarahkan kursor ke variabel dalam kode sumber:

Tooltip tentang variabel `x` di sumber yang menampilkan nilainya `3`

Dari sini, kita dapat melakukan step-in atau step-over pada pernyataan C++, dan mengamati juga perubahan variabel lain:

Tooltip dan Tampilan cakupan yang menunjukkan nilai `warna`, `titik`, dan variabel lainnya

Oke, jadi ini semua berfungsi dengan baik saat informasi debug tersedia, tetapi bagaimana jika kita ingin men-debug kode yang tidak dibuat dengan opsi debug?

Proses debug WebAssembly mentah

Misalnya, kami meminta Emscripten untuk menyediakan library SDL bawaan untuk kami, alih-alih mengompilasinya sendiri dari sumbernya, sehingga setidaknya saat ini tidak ada cara bagi debugger untuk menemukan sumber terkait. Mari kita lakukan lagi untuk masuk ke SDL_RenderDrawColor:

DevTools menunjukkan tampilan pembongkaran `mandelbrot.wasm`

Kita kembali ke pengalaman proses debug WebAssembly mentah.

Sekarang, hal ini terlihat agak menakutkan dan bukan sesuatu yang perlu ditangani oleh kebanyakan developer Web, tetapi terkadang Anda mungkin ingin men-debug library yang dibangun tanpa informasi debug-entah itu library pihak 3rd yang tidak dapat Anda kendalikan, atau karena Anda mengalami salah satu bug yang hanya terjadi pada produksi.

Untuk membantu Anda mengatasi kasus tersebut, kami juga telah melakukan beberapa peningkatan pada pengalaman proses debug dasar.

Pertama-tama, jika sebelumnya Anda menggunakan proses debug WebAssembly mentah, Anda mungkin melihat bahwa seluruh pembongkaran kini ditampilkan dalam satu file, tidak perlu lagi menebak fungsi mana yang mungkin sesuai dengan entri Sumber wasm-53834e3e/ wasm-53834e3e-7.

Skema pembuatan nama baru

Kami juga memperbaiki nama dalam tampilan pembongkaran. Sebelumnya Anda hanya akan melihat indeks numerik, atau, untuk fungsi, tidak ada nama sama sekali.

Sekarang kita membuat nama yang mirip dengan alat pembongkaran lainnya, dengan menggunakan petunjuk dari bagian nama WebAssembly, jalur impor/ekspor, dan, terakhir, jika semua hal lainnya gagal, membuatnya berdasarkan jenis dan indeks item seperti $func123. Anda dapat melihat bagaimana, dalam screenshot di atas, hal ini sudah membantu mendapatkan stack trace dan pembongkaran yang sedikit lebih mudah dibaca.

Jika tidak ada informasi jenis yang tersedia, mungkin sulit untuk memeriksa nilai apa pun selain primitif. Misalnya, pointer akan muncul sebagai bilangan bulat biasa, tanpa mengetahui apa yang tersimpan di belakangnya dalam memori.

Pemeriksaan memori

Sebelumnya, Anda hanya dapat memperluas objek memori WebAssembly, yang diwakili oleh env.memory dalam tampilan Scope untuk mencari byte individual. Hal ini berfungsi dalam beberapa skenario sederhana, tetapi tidak sangat mudah untuk diperluas dan tidak memungkinkan interpretasi ulang data dalam format selain nilai byte. Kami juga telah menambahkan fitur baru untuk membantu hal ini: pemeriksa memori linear.

Jika mengklik kanan env.memory, Anda kini akan melihat opsi baru yang disebut Inspect memory:

Menu konteks di `env.memory` di panel Scope yang menampilkan item &#39;Inspect Memory&#39;

Setelah diklik, Memory Inspector akan muncul, sehingga Anda dapat memeriksa memori WebAssembly dalam tampilan heksadesimal dan ASCII, membuka alamat tertentu, serta menafsirkan data dalam format yang berbeda:

Panel Memory Inspector di DevTools yang menampilkan tampilan heksadesimal dan ASCII dari memori

Skenario dan peringatan lanjutan

Membuat profil kode WebAssembly

Saat Anda membuka DevTools, kode WebAssembly akan "ditingkatkan" ke versi yang tidak dioptimalkan untuk mengaktifkan proses debug. Versi ini jauh lebih lambat, artinya Anda tidak dapat mengandalkan console.time, performance.now, dan metode lain untuk mengukur kecepatan kode Anda saat DevTools terbuka, karena angka yang Anda dapatkan sama sekali tidak akan mewakili performa sebenarnya.

Sebagai gantinya, Anda harus menggunakan panel Performa DevTools yang akan menjalankan kode dengan kecepatan penuh dan memberi Anda perincian mendetail tentang waktu yang dihabiskan dalam berbagai fungsi:

Panel pembuatan profil yang menampilkan berbagai fungsi Wasm

Atau, Anda dapat menjalankan aplikasi dengan DevTools ditutup, dan membukanya setelah selesai untuk memeriksa Konsol.

Kami akan meningkatkan skenario pembuatan profil di masa mendatang, tetapi untuk saat ini hal ini perlu diperhatikan. Jika Anda ingin mempelajari lebih lanjut skenario pengaturan peringkat WebAssembly, lihat dokumen kami tentang pipeline kompilasi WebAssembly.

Membangun dan melakukan proses debug di berbagai mesin (termasuk Docker / host)

Saat membangun di Docker, mesin virtual, atau di server build jarak jauh, Anda mungkin akan menghadapi situasi saat jalur ke file sumber yang digunakan selama build tidak cocok dengan jalur di sistem file Anda sendiri tempat Chrome DevTools berjalan. Dalam hal ini, file akan muncul di panel Sources, tetapi gagal dimuat.

Untuk memperbaiki masalah ini, kami telah mengimplementasikan fungsi pemetaan jalur dalam opsi ekstensi C/C++. Anda dapat menggunakannya untuk memetakan ulang jalur arbitrer dan membantu DevTools menemukan sumber.

Misalnya, jika project di mesin host Anda berada di jalur C:\src\my_project, tetapi dibuat di dalam penampung Docker yang jalur tersebut direpresentasikan sebagai /mnt/c/src/my_project, Anda dapat memetakan ulang kembali selama proses debug dengan menentukan jalur tersebut sebagai awalan:

Halaman opsi ekstensi proses debug C/C++

Awalan pertama yang cocok "wins". Jika Anda sudah terbiasa dengan debugger C++ lainnya, opsi ini mirip dengan perintah set substitute-path di GDB atau setelan target.source-map di LLDB.

Melakukan proses debug build yang dioptimalkan

Seperti bahasa lainnya, proses debug akan berfungsi optimal jika pengoptimalan dinonaktifkan. Pengoptimalan dapat menyisipkan fungsi satu ke lainnya secara inline, menyusun ulang kode, atau menghapus bagian-bagian kode sepenuhnya-dan semua ini dapat membingungkan debugger dan, akibatnya, Anda sebagai pengguna.

Jika Anda tidak keberatan dengan pengalaman proses debug yang lebih terbatas dan masih ingin men-debug build yang dioptimalkan, sebagian besar pengoptimalan akan berfungsi seperti yang diharapkan, kecuali untuk inline fungsi. Kami berencana untuk mengatasi masalah yang tersisa di masa mendatang, tetapi untuk saat ini, gunakan -fno-inline untuk menonaktifkannya saat mengompilasi dengan pengoptimalan level -O apa pun, misalnya:

emcc -g temp.c -o temp.html \
     -O3 -fno-inline

Memisahkan informasi debug

Informasi debug mempertahankan banyak detail tentang kode Anda, jenis yang ditentukan, variabel, fungsi, cakupan, dan lokasi-apa pun yang mungkin bermanfaat bagi debugger. Akibatnya, ukuran itu sering kali bisa lebih besar daripada kode itu sendiri.

Untuk mempercepat pemuatan dan kompilasi modul WebAssembly, sebaiknya Anda membagi informasi debug ini menjadi file WebAssembly yang terpisah. Untuk melakukannya di Emscripten, teruskan flag -gseparate-dwarf=… dengan nama file yang diinginkan:

emcc -g temp.c -o temp.html \
     -gseparate-dwarf=temp.debug.wasm

Dalam hal ini, aplikasi utama hanya akan menyimpan nama file temp.debug.wasm, dan ekstensi helper akan dapat menemukan serta memuatnya saat Anda membuka DevTools.

Jika dikombinasikan dengan pengoptimalan seperti yang dijelaskan di atas, fitur ini bahkan dapat digunakan untuk mengirimkan build produksi aplikasi yang hampir dioptimalkan, dan kemudian men-debug-nya dengan file samping lokal. Dalam hal ini, kita juga perlu mengganti URL yang disimpan untuk membantu ekstensi menemukan file samping, misalnya:

emcc -g temp.c -o temp.html \
     -O3 -fno-inline \
     -gseparate-dwarf=temp.debug.wasm \
     -s SEPARATE_DWARF_URL=file://[local path to temp.debug.wasm]

Akan dilanjutkan...

Wah, banyak sekali fitur barunya!

Dengan semua integrasi baru tersebut, Chrome DevTools menjadi debugger yang andal dan andal, tidak hanya untuk JavaScript, tetapi juga untuk aplikasi C dan C++, sehingga memudahkan pengambilan aplikasi, yang dibangun dalam berbagai teknologi, dan membawanya ke Web lintas platform bersama.

Namun, perjalanan kita belum berakhir. Beberapa hal yang akan kita kerjakan mulai dari sini:

  • Membersihkan bagian kasar dalam pengalaman proses debug.
  • Menambahkan dukungan untuk pemformat jenis kustom.
  • Sedang berupaya meningkatkan pembuatan profil untuk aplikasi WebAssembly.
  • Menambahkan dukungan untuk cakupan kode agar lebih mudah menemukan kode yang tidak digunakan.
  • Meningkatkan dukungan untuk ekspresi dalam evaluasi konsol.
  • Menambahkan dukungan untuk bahasa lainnya.
  • …dan lainnya!

Sementara itu, bantu kami dengan mencoba versi beta saat ini pada kode Anda sendiri dan melaporkan masalah yang ditemukan ke https://bugs.chromium.org/p/chromium/issues/entry?template=DevTools+issue.

Mendownload saluran pratinjau

Pertimbangkan untuk menggunakan Chrome Canary, Dev, atau Beta sebagai browser pengembangan default Anda. Saluran pratinjau ini memberi Anda akses ke fitur DevTools terbaru, menguji API platform web tercanggih, dan menemukan masalah di situs Anda sebelum pengguna melakukannya.

Menghubungi tim Chrome DevTools

Gunakan opsi berikut untuk membahas fitur dan perubahan baru di postingan, atau hal lain yang berkaitan dengan DevTools.

  • Kirim saran atau masukan kepada kami melalui crbug.com.
  • Laporkan masalah DevTools menggunakan Opsi lainnya   Lainnya   > Bantuan > Laporkan masalah DevTools di DevTools.
  • Tweet di @ChromeDevTools.
  • Berikan komentar di video YouTube Apa yang baru di DevTools atau video YouTube Tips DevTools.