Buns neuer Crash-Reporter
(bun.sh)- Bun v1.1.5 fügt mit
bun.reporteinen Crash-Reporter hinzu, der selbst bei Crashs oder Panics Zig/C++-Stack-Informationen über eine etwa 150 Byte große URL ohne personenbezogene Daten übermittelt - Bestehende OS-Crash-Reporter und Core Dumps bringen hohe Kosten bei Debug-Symbolen, Performance, Datenschutz und Binärgröße mit sich und sind deshalb für ein CLI-Tool wie Bun schwer einsetzbar
- Der neue Ansatz wandelt durch ASLR unklare Adressen in relative Adressen bezogen auf das Modul um, und der Server rekonstruiert mithilfe von Commit-SHA und plattformspezifischen Debug-Symbolen wieder die Funktionsnamen
- Die URL enthält Plattform, Subcommand, Commit-SHA, Feature-Flags, Stack-Adressen, Crash-Typ und Nachricht; die Stack-Adressen werden kompakt mit base64 VLQ kodiert
- Es werden weder JavaScript-/TypeScript-Quellcode noch Umgebungsvariablen gesendet; das Bun-Team erhält nur die für die Diagnose nötigen Zig/C++-Stack-Informationen und einige Metadaten
Warum Bun einen eigenen Crash-Reporter gebaut hat
- Für Bun gibt es zum Zeitpunkt des Schreibens mehr als 2.600 offene GitHub-Issues, und einige davon sind besonders schwer zu reproduzieren und zu debuggen
- Crash-Reporting-Dienste wie Sentry eignen sich gut für Apps und SaaS-Produkte, aber bei einem CLI-Tool wie Bun führen hochgeladene Core Dumps zu Problemen bei Datenschutz, Performance und Binärgröße
- Bun v1.1.5 führt ein kleines neues Format für Crash-Reports aus Zig und C++ ein
- Ein Crash-Report passt in eine URL von etwa 150 Byte
- Er enthält keine personenbezogenen Daten
Was reine OS-Crash-Reporter nicht leisten
- Einige Betriebssysteme wie macOS haben eingebaute Crash-Reporter, aber um sie sinnvoll zu nutzen, müssen in der Regel Debug-Symbole zusammen mit der Anwendung ausgeliefert werden
- Debug-Symbole vergrößern die Distribution von Bun erheblich
- Linux-Debug-Symbole: etwa 30 MB
- macOS-Debug-Symbole: etwa 9 MB
- Windows-
.pdb-Dateien: mehr als 250 MB
- Ein Beispiel für die Bun-Binärdatei schrumpft mit
llvm-stripvon60Mauf51M - Tritt ein Crash ohne Debug-Symbole auf, enthält der Stack-Trace nur
???und Adressen und ist damit kaum nützlich - Wegen ASLR (Address space layout randomization) enthalten Funktionsadressen zufällige Offsets, sodass sich Funktionsnamen nicht direkt rekonstruieren lassen
So funktioniert bun.report
- Wenn in Bun v1.1.5 ein Crash oder Panic auftritt, gibt Bun zusammen mit Version, Plattform, Ausführungsargumenten, Speicherverbrauch und Crash-Meldung einen
bun.report-Link aus - Öffnet der Nutzer den Link, erfolgt eine Weiterleitung zu einem vorausgefüllten GitHub-Issue-Formular
- In der URL ist ein neu zugeordnetes Stack-Trace-Format kodiert
- Der Server stellt anhand der in der URL enthaltenen Informationen die Stack-Adressen wieder her und wandelt sie in einen für das Bun-Team lesbaren Crash-Report um
Verfahren zur Umwandlung von Adressen in lesbare Stack-Traces
- Funktionsadressen sind Pointer auf die Speicherposition, an der der Anwendungscode geladen wurde, und enthalten aus Sicherheitsgründen einen zufälligen Offset
- Die Grundidee ist, von der Rohadresse die Basisadresse (base address) der Binärdatei abzuziehen, um eine relative Adresse zu erhalten
- Die tatsächliche Implementierung ist wegen plattformspezifischer API-Unterschiede komplexer
- Unter Windows wird
GetModuleHandleExWmit dem FlagGET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESSverwendet; der Modul-Pointer dient als Basisadresse - Unter Linux werden mit
dl_iterate_phdrdie geladenen Module durchlaufen, unddl_phdr_info.dlpi_addrdes Moduls, das die Adresse enthält, wird als Basisadresse verwendet - Unter macOS werden Module mit
_dyld_image_countund_dyld_get_image_headerdurchlaufen, und mit_dyld_get_image_vmaddr_slidewird der ASLR-Slide ermittelt- In den resultierenden macOS-Adressen bleibt ein Image-Offset erhalten; bei Bun ist das
0x100000000 - Um die URL kurz zu halten, wird dieser Offset entfernt, muss aber vor der Rückzuordnung mit
llvm-symbolizerwieder addiert werden
- In den resultierenden macOS-Adressen bleibt ein Image-Offset erhalten; bei Bun ist das
- Unter Windows wird
- Unter Linux und macOS verweist das erste Modul auf die Binärdatei der Hauptanwendung
- Unter Windows lässt sich die Hauptbinärdatei durch Vergleich des Modulnamens mit
peb.ProcessParameters.ImagePathNamebestimmen - Bun lädt und parst Debug-Symbole nicht lokal, sondern überlässt das Demangling dem Server
- Der Server kann Debug-Symbole cachen
- Er kann Stack-Traces innerhalb weniger Sekunden demangeln
- Gleichzeitig dient er als Link zum Erstellen eines neuen GitHub-Issues
Aufbau der bun.report-URL
- In der
bun.report-URL sind die folgenden Informationen kodiert- Platform: ein Zeichen für die Plattform, zum Beispiel
wfür x86_64 Windows undMfür aarch64 macOS - Subcommand: ein Zeichen für Subcommands wie
bun test,bun installoderbun run - Commit SHA: der Commit-SHA der aktuellen Bun-Version, der später zum Abrufen der Debug-Symbole verwendet wird
- Feature Flags: Kennzeichen für verwendete APIs und Features vor dem Crash
- Stack Trace Addresses: die im vorherigen Schritt berechneten Adressen
- Crash Type: ein Zeichen für den Crash-Typ
- Crash Message: eine je nach Crash-Typ unterschiedlich formatierte Nachricht
- Platform: ein Zeichen für die Plattform, zum Beispiel
- Die in der URL enthaltene Versionsnummer dient in erster Linie der Lesbarkeit für Menschen, nicht der eigentlichen Verarbeitung
- Schon diese Informationen allein erlauben es, einige Crash-Merkmale manuell zu erkennen
- Am Kennzeichen
wlässt sich schnell erkennen, dass es sich um einen Windows-Crash handelt - Am
A2am Ende der Zeichenkette lässt sich ein Segmentation Fault identifizieren
- Am Kennzeichen
VLQ-Kodierung für kurze URLs
- Die Stack-Trace-Adressen werden als base64 Variable Length Quantity (VLQ) kodiert, um die URL kurz zu halten
- VLQ stellt kleine Zahlen mit weniger Zeichen dar und kann dennoch große Zahlen kodieren
- Dieselbe Technik wird auch in JavaScript-Source-Maps zum Speichern von Zeilennummern verwendet
- Der Server dekodiert die VLQ-Werte wieder in relative Adressen, lädt mit Commit-Hash und Plattform die Debug-Symbole herunter und demangelt die Funktionsnamen mit
llvm-symbolizer - Im Beispiel-Crash zeigt sich, dass eine Assertion in
dirInfoCachedMaybeLogfehlgeschlagen ist, also in einem Teil des Modul-Resolver-Codes unter Windows
Kodierung von Feature-Flags
- Die URL kodiert auch 64-Bit-Ganzzahlen, wobei jedes Bit dafür steht, ob ein bestimmtes Feature von Bun verwendet wurde
- Diese Flags geben Hinweise darauf, welche APIs und Systeme zum Crash beigetragen haben könnten
- Wenn
.env-Dateien automatisch geladen werden, wird das Featuredotenvgesetzt - Wenn
fetch()verwendet wird, wird das Featurefetchgesetzt
- Wenn
- Bun verfolgt die Feature-Nutzung mit einem globalen Variablencontainer und markiert die Verwendung, indem innerhalb der jeweiligen API die entsprechende Zahl erhöht wird
- Mit Zig-Compile-Time-Metaprogrammierung wird die Feature-Liste durchlaufen und für jedes Feature dynamisch eine Packed-Struct mit je 1 Bit erzeugt
- Mit
inline forkann die Feature-Liste zur Compile-Zeit iteriert werden, während das eigentliche Setzen der Bits zur Laufzeit erfolgt - Wird dem bestehenden
Features-Struct ein neues Feature hinzugefügt, verarbeitet der Crash-Reporter es ohne zusätzliche Wiederholungsarbeit - Dasselbe Prinzip wäre auch mit C- oder Rust-Makros möglich, in der Bun-Implementierung wird Zig-
comptimeaber einfacher und besser lesbar eingesetzt
Unterschied zu Core Dumps
- Core Dumps enthalten deutlich mehr Informationen, sind aber groß, nur mit Debug-Symbolen sinnvoll nutzbar und können viele sensible oder vertrauliche Daten enthalten
- Der neue Report-Ansatz von Bun vermeidet Situationen, in denen JavaScript-/TypeScript-Quellcode, Umgebungsvariablen oder andere sensible Informationen gesendet werden
- Statt standardmäßig alles zu übertragen, sendet er nur den Zig/C++-Stack-Trace und einige Details, die mit hoher Wahrscheinlichkeit für die Diagnose relevant sind
- Wenn zusätzliche Informationen nötig sind, können diese separat vom Nutzer angefragt werden
- Für das Bun-Team wird es damit einfacher, Crashs zu diagnostizieren, als wenn wie zuvor nur nicht zugeordnete Adressen übrig bleiben
Demo
- Eine kleine Web-App zum Testen des Crash-Reporters steht unter bun.report bereit
- Hängt man an jede beliebige Crash-Report-URL
/viewan, gelangt man zur Ansicht dieser Web-App
1 Kommentare
Hacker-News-Meinungen
Wenn der Grund für diesen Ansatz statt eines normalen Stacktraces darin liegt, mehrere Megabyte an Debug-Symbolen nicht ausliefern zu müssen, wirkt es so, als sei die bessere Option ignoriert worden, nur Funktionsnamen in die Debug-Tabellen aufzunehmen.
Das ist eine deutlich bessere Methode, als einen Webservice nutzen zu müssen, um den Stacktrace anzusehen, und es ist nicht nur Theorie, sondern bereits in LLVM implementiert: https://clang.llvm.org/docs/UsersManual.html#cmdoption-gline...
Wenn eine URL den nötigen Inhalt fast automatisch ausfüllt, wird es einfach genug, und nur dann reichen Entwickler tatsächlich Crash-Reports ein. Die Größe ist ebenfalls wichtig, damit für Nutzer kein Nachteil entsteht, aber der Kern ist, den gesamten Prozess extrem einfach zu machen.
In diesem Nutzungsszenario ist es kein großer Nachteil, dass man den Stacktrace über einen Webservice ansehen muss. Es ist fast dasselbe, wie ein Frontend-JavaScript-Bundle zu obfuskieren/minifizieren, Source Maps zu Sentry hochzuladen und dann Stacktraces aus den Browsern der Nutzer über Sentry wiederherzustellen. Der Nutzer wird diesen Stacktrace ohnehin nicht ansehen, und für mich ist es nicht unbequem, ihn mit Sentry anzusehen. Sonst hätte ich ihn überhaupt nicht sehen können.
Es gibt viele Arten, Alternativen vorzuschlagen.
Auf diesen Plattformen findet die Symbolizierung so die Funktionsnamen von Systembibliotheken auch ohne vollständige Debug-Symbole.
Hervorragend und sehr kreativ. Viele Projekte könnten diesen Ansatz übernehmen. Der Kern ist, Stacktraces mit einem relativen Program Counter bezogen auf die ausführbare Datei/das Shared Object zu speichern.
Soweit ich weiß, ist Bun statisch gelinkt; bei einem dynamisch gelinkten System müsste man jedem normalisierten Program Counter wohl eine kleine numerische Shared-Object-ID voranstellen.
Der Crash Reporter der Unreal Engine konnte zum Beispiel schon vor Jahren ein so einfaches Format senden und daraus für jeden Stackframe recht genaue Funktions-/Zeilennummern rekonstruieren. Meist bevorzugt man allerdings Minidumps, weil Stackvariablen zusätzliche Hinweise darauf geben können, was passiert ist.
Microsoft ist in solchen Dingen wirklich gut. In SQL Server wurden Minidumps verwendet, aus denen personenbezogene Daten entfernt waren; sie waren sehr klein und äußerst nützlich.
Schon damals, vor 15 Jahren, waren vollständige Dumps eines produktiven SQL Servers riesige Dateien, die sich kaum übertragen ließen.
Nachdem ich den ersten Tweet zu Zig gesehen hatte, habe ich Bun jahrelang verfolgt und vor Kurzem angefangen, es zu nutzen; es funktioniert einfach gut, ohne großen Aufwand.
Bun ist ziemlich reizvoll. Ich habe es in ein paar kleinen Beispielprojekten ausprobiert; die Geschwindigkeit ist gut, und mir gefällt, dass Paketverwaltung und JavaScript-Runtime zusammengeführt sind.
Allerdings nutze ich in den meisten ernsthaften Projekten Dependabot. Soweit ich weiß, ist Bun-Support in Dependabot in Arbeit oder wird zumindest in einigen Repository-Issues diskutiert, daher schiebe ich die Nutzung auf, bis der Support veröffentlicht ist.
Keinerlei Reue. Die kumulierten Einsparungen durch die schnelleren Teile und die große Verbesserung der Developer Experience sind genau so wertvoll wie erwartet.
Viele werden gar nicht bemerken, wie viel Sorgfalt in solche Dinge geflossen ist. Ich finde es gut, weil es zeigt, wie sehr dem Bun-Team sein Handwerk am Herzen liegt.
Bun ist erstaunlich, aber als ich kürzlich mit Fastify einen HTTP/2-Server bauen wollte, ging es nicht.
Es kam der Fehler
node:http2 createServer is not yet implemented in Bun, und das Issue, auf das die Meldung verweist, betrifft eigentlich HTTP/2-Client-Support. Der Client-Support wurde bereits in v1.0.13 veröffentlicht: https://bun.sh/blog/bun-v1.0.13#http2-client-supportDie
NotImplementedError-Meldung sollte so geändert werden, dass sie auf das serverseitige Issue verweist: https://github.com/oven-sh/bun/issues/8823HTTP/2-Server-Support gehört zu den am höchsten priorisierten Feature Requests: https://github.com/oven-sh/bun/issues?q=is%3Aissue+is%3Aopen...
Wenn dieses Feature kommt, dürften deutlich mehr Menschen zu Bun wechseln können.
Bun ist in seinem Lebenszyklus noch in einer sehr frühen Phase. Trotzdem habe ich große Erwartungen an das Projekt.
Ich frage mich, ob es Leute gibt, die Bun tatsächlich verwenden. Ist es so gut wie erhofft?
In einer TypeScript-Node-Umgebung ts-node, ts-jest, ESM-Support, Top-Level await usw. einzurichten, ist unnötig umständlich. Neuere Node-Releases haben einige Unannehmlichkeiten reduziert, aber so einfach wie
bun initist es nicht. Auch die Bun-Shell-API nutze ich mit Freude: https://bun.sh/blog/the-bun-shellDie Fehlermeldungen sind ebenfalls deutlich schlechter als bei Node. Ich habe es eine Zeit lang genutzt, aber heutzutage erledigt Node mit
—loader tsxalles, was ich möchte, ohne Nachteile. Für einen einfachen Server, etwa mit WebSocket und wenn man sicher ist, dass keine nativen Module nötig sind, ist Bun eine Überlegung wert. Ich betreibe tatsächlich ein paar solche Dienste.Die sofortige Startgeschwindigkeit überrascht mich immer noch.
Es fehlen noch ein paar Dinge, aber für mich ist es bereits besser als Node.
Dieser Artikel fühlt sich auch wie eine hervorragende Zig-Fallstudie an. Interessant.
Bun muss 37 Pakete herunterladen, bevor man den REPL nutzen kann. Ohne Internet kann man nicht einmal den REPL verwenden.
Beim Ausführen von
bun replerscheint ein Fehler, dass der Download des Paketmanifests vonbun-replfehlgeschlagen ist. Kein großes Problem, aber ich hatte erwartet und mich ziemlich darauf gefreut, dass es ohne Installation sofort funktioniert, wenn man nur eine einzelne ausführbare Datei in den PATH legt.Intern macht
bun repldasselbe wiebunx bun-repl.