2 poin oleh GN⁺ 2024-04-27 | 1 komentar | Bagikan ke WhatsApp
  • Bun v1.1.5 menambahkan crash reporter bun.report yang menyampaikan informasi stack Zig/C++ hanya melalui URL sekitar 150 byte tanpa informasi pribadi, bahkan saat terjadi crash atau panic
  • Crash reporter OS dan core dump yang ada saat ini memiliki beban besar terkait debug symbol, performa, informasi pribadi, dan ukuran executable, sehingga sulit diterapkan pada Bun sebagai tool CLI
  • Pendekatan baru ini mengubah alamat yang maknanya dikaburkan oleh ASLR menjadi alamat relatif terhadap modul, lalu server memulihkan nama fungsi dengan debug symbol yang sesuai commit SHA dan platform
  • URL berisi platform, subcommand, commit SHA, feature flag, alamat stack, tipe crash, dan pesan; alamat stack dienkode singkat dengan base64 VLQ
  • Kode sumber JavaScript/TypeScript atau environment variable tidak dikirim; tim Bun hanya mengirim informasi stack Zig/C++ dan sebagian metadata yang dibutuhkan untuk diagnosis

Mengapa Bun membuat crash reporter sendiri

  • Saat penulisan, Bun memiliki lebih dari 2.600 issue GitHub terbuka, dan sebagian issue sangat sulit direproduksi dan di-debug
  • Layanan crash reporting seperti Sentry cocok untuk aplikasi dan produk SaaS, tetapi jika tool CLI seperti Bun mengunggah core dump, masalah informasi pribadi, performa, dan ukuran executable menjadi besar
  • Bun v1.1.5 memperkenalkan format kecil baru untuk laporan crash Zig dan C++
    • Laporan crash masuk ke dalam URL sekitar 150 byte
    • Tidak menyertakan informasi pribadi

Kekurangan jika hanya mengandalkan crash reporter OS

  • Beberapa sistem operasi seperti macOS memiliki crash reporter bawaan, tetapi agar dapat dimanfaatkan dengan benar biasanya perlu mendistribusikan debug symbol bersama aplikasi
  • Debug symbol memperbesar ukuran distribusi Bun secara signifikan
    • Debug symbol Linux sekitar 30MB
    • Debug symbol macOS sekitar 9MB
    • File .pdb Windows lebih dari 250MB
  • Contoh executable Bun menyusut dari 60M menjadi 51M setelah llvm-strip
  • Jika crash terjadi tanpa debug symbol, stack trace hanya menyisakan ??? dan alamat, sehingga kurang berguna
  • Karena ASLR(Address space layout randomization), alamat fungsi bercampur dengan offset acak, sehingga nama fungsi tidak dapat dipulihkan apa adanya

Cara kerja bun.report

  • Saat crash atau panic terjadi di Bun v1.1.5, Bun menampilkan tautan bun.report bersama versi, platform, argumen eksekusi, penggunaan memori, dan pesan crash
  • Ketika pengguna membuka tautan itu, mereka dialihkan ke form issue GitHub yang sudah terisi sebelumnya
  • Stack trace yang sudah dipetakan ulang dienkode di dalam URL
  • Server memulihkan alamat stack berdasarkan informasi dalam URL, lalu mengubahnya menjadi laporan crash yang dapat dibaca tim Bun

Prosedur mengubah alamat menjadi stack trace yang bisa dibaca

  • Alamat fungsi adalah pointer yang menunjuk ke lokasi kode aplikasi dimuat di memori, dan demi keamanan menyertakan offset acak
  • Ide dasarnya adalah mengurangkan base address biner dari alamat mentah untuk mendapatkan alamat relatif
  • Implementasi sebenarnya lebih rumit karena perbedaan API di tiap platform
    • Windows menggunakan flag GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS pada GetModuleHandleExW, dan menjadikan pointer modul sebagai base address
    • Linux mengiterasi modul yang dimuat dengan dl_iterate_phdr, lalu menggunakan dl_phdr_info.dlpi_addr dari modul yang memuat alamat tersebut sebagai base address
    • macOS mengiterasi modul dengan _dyld_image_count, _dyld_get_image_header, lalu mendapatkan ASLR slide dengan _dyld_get_image_vmaddr_slide
      • Alamat hasil di macOS masih menyisakan image offset, yang untuk Bun adalah 0x100000000
      • Untuk membuat URL lebih pendek, offset ini dihapus, tetapi harus ditambahkan kembali sebelum dipetakan ulang dengan llvm-symbolizer
  • Di Linux dan macOS, modul pertama menunjuk ke biner aplikasi utama
  • Di Windows, modul dapat dinilai sebagai biner utama dengan membandingkan nama modul dan peb.ProcessParameters.ImagePathName
  • Bun tidak mengunduh dan mem-parsing debug symbol secara lokal, melainkan menyerahkan demangling ke server
    • Server dapat melakukan cache debug symbol
    • Stack trace dapat di-demangle dalam beberapa detik
    • Pada saat yang sama, server berperan sebagai tautan untuk membuka issue GitHub baru

Struktur URL bun.report

  • URL bun.report mengenkode informasi berikut
    • Platform: satu huruf yang menunjukkan platform. Misalnya w berarti x86_64 Windows, M berarti aarch64 macOS
    • Subcommand: satu huruf yang menunjukkan subcommand seperti bun test, bun install, bun run
    • Commit SHA: commit SHA dari versi Bun saat ini, digunakan nanti untuk mengambil debug symbol
    • Feature Flags: penanda yang menunjukkan API dan fitur yang digunakan sebelum crash
    • Stack Trace Addresses: alamat yang dihitung pada tahap sebelumnya
    • Crash Type: satu huruf yang menunjukkan tipe crash
    • Crash Message: pesan yang formatnya berbeda sesuai tipe crash
  • Nomor versi dalam URL adalah penanda agar mudah dibaca manusia, bukan untuk pemrosesan sebenarnya
  • Dengan informasi ini saja, sebagian karakteristik crash dapat dikenali secara manual
    • Identifier w membuat crash Windows cepat dikenali
    • A2 di bagian akhir string dapat digunakan untuk mengidentifikasi segmentation fault

Encoding VLQ untuk URL pendek

  • Alamat stack trace dienkode sebagai angka base64 Variable Length Quantity(VLQ) agar URL tetap pendek
  • VLQ dapat merepresentasikan angka kecil dengan lebih sedikit karakter sekaligus tetap dapat mengenkode angka besar
  • Teknik yang sama juga digunakan di source map JavaScript untuk menyimpan nomor baris
  • Server mendekode kembali nilai VLQ menjadi alamat relatif, mengunduh debug symbol menggunakan commit hash dan platform, lalu men-demangle nama fungsi dengan llvm-symbolizer
  • Pada contoh crash, terlihat bahwa assertion gagal di dirInfoCachedMaybeLog, bagian dari kode module resolver Windows

Encoding feature flag

  • URL juga mengenkode integer 64-bit, dan tiap bit berkorespondensi dengan penggunaan fitur tertentu di Bun
  • Flag ini memberi petunjuk API dan sistem mana yang mungkin memengaruhi crash
    • Jika file .env dimuat otomatis, fitur dotenv disetel
    • Jika fetch() digunakan, fitur fetch disetel
  • Bun melacak penggunaan fitur dengan kontainer variabel global, dan di dalam tiap API menaikkan angka terkait untuk menandai penggunaan
  • Dengan metaprogramming compile-time Zig, Bun mengiterasi daftar fitur dan secara dinamis membuat packed struct yang memakai 1 bit per fitur
  • Dengan inline for, daftar fitur dapat diiterasi pada compile-time, sementara pengaturan bit sebenarnya dilakukan saat runtime
  • Jika fitur baru ditambahkan ke struct Features yang ada, crash reporter juga menanganinya tanpa penulisan berulang
  • Pendekatan yang sama juga mungkin dilakukan dengan macro C atau Rust, tetapi dalam implementasi Bun, comptime Zig digunakan sebagai cara yang lebih sederhana dan mudah dibaca

Perbedaan dengan core dump

  • Core dump memuat jauh lebih banyak informasi, tetapi ukurannya besar, baru berguna jika ada debug symbol, dan dapat berisi banyak informasi sensitif atau rahasia
  • Metode laporan baru Bun menghindari pengiriman kode sumber JavaScript/TypeScript, environment variable, dan informasi sensitif lainnya
  • Alih-alih mengirim semuanya secara default, Bun hanya mengirim stack trace Zig/C++ dan beberapa detail yang kemungkinan besar diperlukan untuk diagnosis masalah
  • Jika informasi tambahan diperlukan, pengguna dapat diminta secara terpisah
  • Dibanding kondisi sebelumnya yang hanya menyisakan alamat yang tidak dipetakan, tim Bun menjadi lebih mudah mendiagnosis crash

Demo

  • Webapp kecil untuk menguji crash reporter tersedia di bun.report
  • Tambahkan /view di akhir URL laporan crash mana pun untuk membuka layar webapp tersebut

1 komentar

 
GN⁺ 2024-04-27
Komentar Hacker News
  • Jika alasan memakai pendekatan ini alih-alih stack trace biasa adalah agar tidak perlu mendistribusikan simbol debug berukuran beberapa MB, rasanya mereka mengabaikan opsi yang lebih baik, yaitu hanya memasukkan nama fungsi ke dalam tabel debug
    Ini jauh lebih baik daripada harus memakai layanan web untuk melihat stack trace, dan ini bukan cuma teori karena LLVM sudah mengimplementasikannya: https://clang.llvm.org/docs/UsersManual.html#cmdoption-gline...

    • Alasan utama memakai pendekatan ini alih-alih stack trace biasa bukan ukuran simbol debug, melainkan karena hampir tidak ada orang yang cukup sabar untuk mengirimkan laporan crash ke issue GitHub
      Jika cukup memberi satu URL yang hampir otomatis mengisi hal-hal yang diperlukan, prosesnya jadi cukup mudah, dan barulah developer benar-benar mengirimkan laporan crash. Ukuran juga penting karena kami ingin menghindari kerugian bagi pengguna, tetapi inti utamanya adalah membuat seluruh proses semudah mungkin
    • Ungkapan seperti “mengabaikan opsi yang lebih baik” atau “jelas lebih baik” terdengar agak terlalu pasti. Mereka mungkin tahu kemungkinan itu
      Dalam skenario penggunaan ini, harus melihat stack trace lewat layanan web bukanlah kekurangan besar. Ini hampir sama seperti mengobfuscate/meminify bundle JavaScript frontend lalu mengunggah source map ke Sentry, kemudian memulihkan stack trace dari browser pengguna lewat Sentry. Pengguna toh tidak akan pernah melihat stack trace itu, dan saya juga tidak merasa tidak nyaman melihatnya lewat Sentry. Kalau tidak begitu, kemungkinan saya malah tidak akan bisa melihatnya sama sekali
    • Mengkritik dengan mengatakan “jelas” atau “tinggal” pakai hal lain tanpa tahu konteks pembahasan dan tanpa tahu kompromi apa saja yang ada memang terdengar agak arogan
      Ada banyak cara untuk mengusulkan alternatif
    • Di macOS/iOS, ada juga cara mendistribusikan biner Mach-O hanya dengan menyertakan bagian LC_FUNCTION_STARTS
      Begitulah cara simbolisasi bisa menemukan nama fungsi pustaka sistem di platform tersebut tanpa simbol debug penuh
    • Meski begitu, ukurannya tetap bisa cukup besar. Secara pribadi saya menganggapnya layak, jadi biasanya saya selalu menyertakannya, tetapi kebanyakan perangkat lunak tidak melakukannya
  • Hebat dan sangat kreatif. Banyak proyek sebaiknya meniru pendekatan ini. Intinya adalah menyimpan stack trace dengan program counter relatif terhadap executable/shared object
    Sepemahaman saya Bun ditautkan secara statis, tetapi dalam sistem dynamic linking, setiap program counter yang dinormalisasi perlu diawali dengan ID shared object berbentuk angka kecil

    • Ini bukan pendekatan yang benar-benar baru. Ini umum dipakai saat simbol tidak bisa didistribusikan, misalnya ketika game crash di PC pemain
      Misalnya, crash reporter Unreal Engine sudah bisa mengirim format sederhana seperti ini selama bertahun-tahun, dan nomor fungsi/baris yang cukup akurat bisa dipulihkan dari tiap stack frame. Hanya saja, biasanya minidump lebih disukai karena jika ada variabel stack, kita bisa mendapat petunjuk tambahan tentang apa yang terjadi
  • Microsoft memang sangat ahli dalam hal seperti ini. Di SQL Server mereka memakai minidump yang sudah dibersihkan dari informasi pribadi, ukurannya sangat kecil dan sangat berguna
    Bahkan saat itu, 15 tahun lalu pun, dump penuh dari SQL Server produksi sudah berupa file raksasa yang sulit dipindahkan

    • Menarik, saya tidak tahu apakah itu untuk layanan internal Microsoft atau untuk lingkungan deployment pelanggan. Jika yang kedua, bagaimana mereka tahu apa yang termasuk informasi identitas pribadi?
  • Saya mengikuti Bun selama beberapa tahun sejak melihat tweet pertamanya yang terkait Zig, lalu belakangan mulai memakainya, dan sejauh ini ia bekerja begitu saja tanpa banyak repot

  • Bun cukup menarik. Saya sudah mencobanya di beberapa proyek contoh kecil dan performanya bagus, dan saya suka bahwa ia menggabungkan manajemen paket dengan runtime JavaScript
    Hanya saja, saya memakai Dependabot di sebagian besar proyek serius saya. Sepemahaman saya dukungan Dependabot untuk Bun masih sedang dikerjakan atau setidaknya sedang dibahas di beberapa issue repositori, jadi saya menunda pemakaian sampai dukungan itu dirilis

    • Kami juga sempat ragu untuk pindah karena Dependabot belum didukung, tetapi lalu mengetahui bahwa Renovate bekerja dengan Bun dan untuk sementara cukup menjadi pengganti
      Kami sama sekali tidak menyesal. Penghematan dari berbagai bagian yang menjadi lebih cepat dan peningkatan besar pada pengalaman developer benar-benar sepadan dengan harapan kami
  • Tidak banyak orang yang akan menyadari seberapa besar perhatian yang dicurahkan ke hal seperti ini. Menyenangkan melihat betapa tim Bun peduli pada craft mereka sendiri

  • Bun memang mengagumkan, tetapi baru-baru ini saya mencoba membuat server HTTP/2 dengan Fastify dan ternyata tidak bisa
    Muncul error node:http2 createServer is not yet implemented in Bun, dan issue yang dirujuk pesan itu sebenarnya tentang dukungan klien HTTP/2. Dukungan klien sudah dirilis di v1.0.13: https://bun.sh/blog/bun-v1.0.13#http2-client-support
    Pesan NotImplementedError seharusnya diubah agar menunjuk ke issue sisi server: https://github.com/oven-sh/bun/issues/8823
    Dukungan server HTTP/2 adalah salah satu permintaan fitur teratas: https://github.com/oven-sh/bun/issues?q=is%3Aissue+is%3Aopen...
    Jika fitur ini hadir, tampaknya jauh lebih banyak orang akan bisa beralih ke Bun

    • Begitulah keadaan Bun saat ini. Setelah menunggu implementasi, setelah selesai Anda akan sadar bahwa masih ada implementasi API lain yang dibutuhkan, lalu menunggu lagi sampai muncul, tetapi kemudian crash di berbagai edge case, lalu menunggu lagi, dan begitu seterusnya
      Bun masih terlalu dini dalam siklus hidupnya. Meski begitu, ekspektasi saya terhadap proyek ini tetap tinggi
  • Saya penasaran apakah benar ada orang yang memakai Bun. Apakah sebagus hype-nya?

    • Saya belum memakainya di produksi, tetapi untuk skrip sekali pakai dan proyek sampingan, Bun sangat bagus
      Menyiapkan lingkungan TypeScript Node dengan ts-node, ts-jest, dukungan ESM, top-level await, dan sebagainya terasa lebih merepotkan daripada yang seharusnya. Rilis-rilis Node terbaru memang mengurangi sebagian kerepotan itu, tetapi tetap tidak sesederhana bun init. Saya juga senang memakai API bun shell: https://bun.sh/blog/the-bun-shell
    • Kalau Anda butuh REPL atau berencana memakai modul native, sebaiknya pikir-pikir. Memang ada REPL, tetapi setiap kali diperbarui selalu terlambat lebih dari 6 detik sehingga sangat mengganggu
      Pesan error-nya juga jauh lebih buruk daripada Node. Saya sempat memakainya beberapa waktu, tetapi sekarang —loader tsx di Node sudah melakukan semua yang saya butuhkan tanpa kekurangan tersebut. Untuk server sederhana, misalnya memakai WebSocket dan Anda yakin tidak butuh modul native, Bun layak dipertimbangkan. Saya benar-benar menjalankan beberapa layanan seperti itu
    • Saya mulai memakainya begitu 1.0 keluar dan tidak pernah kembali lagi. Sekarang saya memakainya untuk semua proyek
    • Saya memakainya sebagai runner untuk pengembangan dan pengujian pada proyek bahasa pemrograman sekitar 15 ribu baris, dan sejauh ini belum ada masalah khas Bun
      Kecepatan startup yang instan masih terasa mengejutkan
    • Saya baru-baru ini memakainya dan menurut saya sangat bagus. Peningkatan kualitas hidup seperti tidak perlu memikirkan kompilasi TypeScript benar-benar nyaman, dan performanya juga cepat
      Memang masih ada beberapa hal yang belum ada, tetapi bagi saya ini sudah lebih baik daripada Node
  • Artikel ini juga terasa sangat bagus sebagai studi kasus Zig. Menarik

  • Untuk memakai REPL di Bun, harus mengunduh 37 paket dulu. Kalau tidak ada internet, REPL pun tidak bisa dipakai
    Saat menjalankan bun repl, muncul error bahwa pengunduhan manifest paket bun-repl gagal. Ini bukan masalah besar, tetapi saya berharap cukup menaruh satu executable di PATH lalu langsung bisa jalan tanpa instalasi, dan saya cukup menantikannya

    • Mereka memang belum memprioritaskan implementasi REPL. REPL saat ini adalah paket npm bun-repl buatan komunitas
      Secara internal bun repl melakukan hal yang sama seperti bunx bun-repl