Men-debug WebAssembly dengan alat modern

Ingvar Stepanyan
Ingvar Stepanyan

Perjalanan sejauh ini

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

Kami telah menunjukkan dukungan langkah dasar dan membahas peluang penggunaan informasi DWARF, bukan peta sumber yang terbuka untuk kita di masa mendatang:

  • Me-resolve nama variabel
  • Jenis pencetakan yang rapi
  • Mengevaluasi ekspresi dalam bahasa sumber
  • …dan banyak lagi.

Hari ini, kami dengan senang hati menampilkan fitur yang dijanjikan yang telah diwujudkan dan progres yang telah dibuat oleh tim Emscripten dan Chrome DevTools selama tahun ini, khususnya, untuk aplikasi C dan C++.

Sebelum memulai, perlu diingat bahwa ini masih merupakan versi beta dari pengalaman baru. Anda harus menggunakan versi terbaru dari semua alat dengan risiko Anda sendiri, dan jika Anda mengalami masalah, laporkan ke https://issues.chromium.org/issues/new?noWizard=true&template=0&component=1456350.

Mari kita mulai dengan contoh C sederhana yang sama seperti sebelumnya:

#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 mengompilasi, kita menggunakan Emscripten terbaru dan meneruskan flag -g, seperti dalam 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 akan membutuhkan ekstensi bantuan yang terintegrasi dengan Chrome DevTools membantu memahami semua informasi proses debug dikodekan dalam file WebAssembly. Instal dengan membuka link: goo.gle/wasm-debugging-extension

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

Panel Eksperimen di setelan DevTools

Saat Anda menutup Settings, DevTools akan menyarankan untuk memuat ulang itself untuk menerapkan setelan, jadi mari kita lakukan hal itu. Itu saja untuk penyiapan satu kali.

Sekarang kita dapat kembali ke panel Sumber, mengaktifkan Jeda pada pengecualian (ikon ⏸), lalu mencentang Jeda pada pengecualian yang tertangkap dan memuat ulang halaman. Anda akan melihat DevTools dijeda pada pengecualian:

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

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

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

Sekarang, jika melihat di tampilan Cakupan, Anda dapat melihat nama aslinya variabel dalam kode C/C++, dan tidak lagi harus mencari apa arti nama yang rusak seperti $localN dan bagaimana kaitannya dengan kode sumber yang Anda tulis.

Ini tidak hanya berlaku untuk nilai primitif seperti bilangan bulat, tetapi juga untuk gabungan seperti struktur, class, array, dll.!

Dukungan jenis kaya

Mari lihat contoh yang lebih rumit untuk menunjukkannya. 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-hanya satu file yang berisi 50 baris kode-tetapi kali ini saya juga menggunakan beberapa API eksternal, seperti library SDL untuk grafik serta bilangan kompleks dari library standar C++.

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

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

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

Halaman demo

Saat membuka DevTools, sekali lagi, saya dapat melihat file C++ asli. Ini waktu, namun, kita tidak memiliki kesalahan di kode (wow!), jadi mari kita atur beberapa titik henti sementara di awal kode.

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

DevTools dijeda pada panggilan `SDL_Init`

Kita sudah bisa melihat semua variabel di sebelah kanan, tetapi hanya width dan height diinisialisasi saat ini, sehingga tidak ada banyak memeriksa.

Mari kita tetapkan titik henti sementara lain di dalam loop Mandelbrot utama kita, dan lanjutkan {i>executable<i} untuk melewati sedikit ke depan.

DevTools dijeda di dalam loop bertingkat

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

Jika Anda ingin mengakses properti bersarang yang dalam yang sulit untuk buka melalui tampilan Cakupan, Anda dapat menggunakan Konsol evaluasi! 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 dengan melihat di tampilan Cakupan lagi, menambahkan nama variabel ke daftar pantauan, mengevaluasinya di konsol, atau dengan arahkan kursor ke variabel tersebut di kode sumber:

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

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

Tooltip dan tampilan Cakupan yang menunjukkan nilai `warna`, `titik`, dan variabel lainnya

Baik, jadi semua ini berfungsi dengan baik jika informasi debug tersedia, tetapi bagaimana jika kita ingin men-debug kode yang tidak di-build dengan opsi proses debug?

Proses debug WebAssembly mentah

Misalnya, kami meminta Emscripten untuk menyediakan library SDL bawaan bagi kami, bukan mengompilasi sendiri dari sumber, sehingga-setidaknya saat ini-debugger tidak dapat menemukan sumber terkait. Mari kita masuk lagi untuk masuk ke SDL_RenderDrawColor:

DevTools menampilkan tampilan disassembly `mandelbrot.wasm`

Kita kembali ke pengalaman proses debug WebAssembly mentah.

Sekarang, terlihat agak menakutkan dan bukan sesuatu yang akan dialami oleh banyak pengembang Web perlu ditangani, tetapi terkadang Anda mungkin ingin men-debug yang dibangun tanpa informasi debug--baik karena itu Libraryrd pihak ketiga yang tidak dapat Anda kendalikan, atau karena Anda menemui salah satu {i>bug<i} yang hanya terjadi pada tahap produksi.

Untuk membantu dalam 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 proses disassembly 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 di tampilan pembongkaran. Sebelumnya Anda akan melihat hanya indeks numerik, atau, untuk fungsi, tanpa 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 semuanya gagal, menghasilkan berdasarkan jenis dan indeks item seperti $func123. Anda dapat melihat bagaimana, dalam screenshot di atas, hal ini sudah membantu mendapatkan stacktrace dan disassembly yang sedikit lebih mudah dibaca.

Jika tidak ada informasi jenis yang tersedia, pemeriksaan akan sulit dilakukan nilai apa pun selain primitif-misalnya, pointer akan muncul sebagai bilangan bulat biasa, tidak ada cara untuk mengetahui apa yang disimpan di belakangnya memori.

Pemeriksaan memori

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

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

Menu konteks pada `env.memory` di panel Cakupan yang menampilkan item &#39;Periksa Memori&#39;

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

Panel Pemeriksa Memori di DevTools yang menampilkan tampilan hex dan ASCII memori

Skenario dan peringatan lanjutan

Membuat profil kode WebAssembly

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

Sebagai gantinya, Anda harus menggunakan panel Performance DevTools yang akan menjalankan kode dengan kecepatan penuh dan memberi Anda rincian waktu yang dihabiskan di berbagai fungsi:

Panel pembuatan profil yang menampilkan berbagai fungsi Wasm

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

Kami akan meningkatkan skenario pembuatan profil di masa mendatang, tetapi untuk saat ini peringatan yang harus diperhatikan. Jika Anda ingin mempelajari lebih lanjut skenario tingkatan 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 mengalami situasi di mana jalur ke file sumber tidak cocok dengan jalur pada sistem file Anda sendiri, Chrome DevTools sedang berjalan. Dalam hal ini, file akan muncul di Sumber tetapi gagal dimuat.

Untuk memperbaiki masalah ini, kami telah menerapkan fungsi pemetaan jalur di 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 di-build di dalam penampung Docker tempat jalur tersebut direpresentasikan sebagai /mnt/c/src/my_project, Anda dapat memetakan ulang jalur tersebut selama proses debug dengan menentukan jalur tersebut sebagai awalan:

Halaman opsi ekstensi proses debug C/C++

Awalan pertama yang cocok, "kemenangan". 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.

Men-debug build yang dioptimalkan

Seperti bahasa lainnya, proses debug akan berfungsi optimal jika pengoptimalan dinonaktifkan. Pengoptimalan dapat menyisipkan fungsi satu ke yang lain, menyusun ulang kode, atau menghapus bagian dari kode secara bersamaan-dan semua ini memiliki untuk 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, maka sebagian besar pengoptimalan akan berfungsi sebagai diharapkan, kecuali untuk fungsi inline. Kami berencana untuk menangani sisa masalah pada masa mendatang. Namun, untuk saat ini, gunakan -fno-inline untuk menonaktifkannya saat mengompilasi dengan pengoptimalan level -O, mis.:

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

Memisahkan informasi debug

Informasi debug menyimpan banyak detail tentang kode Anda, yang ditentukan jenis, variabel, fungsi, ruang lingkup, dan lokasi-apa pun yang mungkin berguna bagi debugger. Akibatnya, file ini sering kali dapat lebih besar dari kode itu sendiri.

Untuk mempercepat pemuatan dan kompilasi modul WebAssembly, Anda mungkin ingin memisahkan informasi debug ini ke dalam file WebAssembly terpisah. Untuk melakukannya di Emscripten, teruskan tanda -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 dan memuatnya saat Anda membuka DevTools.

Jika digabungkan dengan pengoptimalan seperti yang dijelaskan di atas, fitur ini dapat digunakan untuk mengirimkan build produksi yang hampir dioptimalkan aplikasi, dan kemudian men-debug-nya dengan file sisi lokal. Dalam hal ini, kita juga harus 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]

Untuk dilanjutkan...

Wah, banyak sekali fitur barunya!

Dengan semua integrasi baru tersebut, Chrome DevTools menjadi layak, kuat, debugger tidak hanya untuk JavaScript, tetapi juga untuk aplikasi C dan C++, membuatnya semakin mudah untuk mengambil aplikasi, yang dibangun dalam berbagai dan membawanya ke Web lintas platform bersama.

Namun, perjalanan kita belum berakhir. Beberapa hal yang akan kami kerjakan mulai sekarang:

  • Membersihkan kekurangan dalam pengalaman proses debug.
  • Menambahkan dukungan untuk pemformat jenis kustom.
  • Sedang mengerjakan perbaikan pembuatan profil untuk aplikasi WebAssembly.
  • Menambahkan dukungan untuk cakupan kode agar lebih mudah ditemukan kode yang tidak digunakan.
  • Meningkatkan dukungan untuk ekspresi dalam evaluasi konsol.
  • Menambahkan dukungan untuk lebih banyak bahasa.
  • …dan lainnya!

Sementara itu, bantu kami dengan mencoba versi beta saat ini pada kode Anda sendiri dan laporkan masalah apa pun yang ditemukan ke https://issues.chromium.org/issues/new?noWizard=true&template=0&component=1456350.

Mendownload saluran pratinjau

Sebaiknya gunakan Chrome Canary, Dev, atau Beta sebagai browser pengembangan default Anda. Saluran pratinjau ini memberi Anda akses ke fitur DevTools terbaru, menguji API platform web canggih, dan menemukan masalah di situs Anda sebelum pengguna menemukannya.

Menghubungi tim Chrome DevTools

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