- Bun v1.1.5 ajoute
bun.report, un rapporteur de crash qui transmet les informations de pile Zig/C++ via une URL d’environ 150 octets sans données personnelles, même en cas de crash ou de panic - Les rapporteurs de crash de l’OS et les core dumps existants sont difficiles à appliquer à un outil CLI comme Bun à cause du coût en symboles de débogage, performances, confidentialité et taille de l’exécutable
- Cette nouvelle approche convertit les adresses brouillées par l’ASLR en adresses relatives au module, puis le serveur restaure les noms de fonctions à l’aide des symboles de débogage correspondant au commit SHA et à la plateforme
- L’URL contient la plateforme, la sous-commande, le commit SHA, les feature flags, les adresses de pile, le type de crash et le message, les adresses de pile étant encodées de façon compacte en base64 VLQ
- Aucun code source JavaScript/TypeScript ni variable d’environnement n’est envoyé ; l’équipe Bun ne reçoit que les informations de pile Zig/C++ et quelques métadonnées nécessaires au diagnostic
Pourquoi Bun a créé son propre rapporteur de crash
- Bun compte plus de 2 600 issues GitHub ouvertes au moment de la rédaction, et certaines sont particulièrement difficiles à reproduire et à déboguer
- Les services de crash reporting comme Sentry conviennent aux applications et produits SaaS, mais pour un outil CLI comme Bun, l’envoi de core dumps pose de gros problèmes de confidentialité, de performances et de taille d’exécutable
- Bun v1.1.5 introduit un petit nouveau format pour les rapports de crash Zig et C++
- Le rapport de crash tient dans une URL d’environ 150 octets
- Il n’inclut aucune donnée personnelle
Les limites des rapporteurs de crash de l’OS
- Certains systèmes d’exploitation, comme macOS, disposent d’un rapporteur de crash intégré, mais pour l’exploiter correctement il faut généralement distribuer les symboles de débogage avec l’application
- Les symboles de débogage augmentent fortement la taille des distributions de Bun
- Les symboles de débogage Linux pèsent environ 30MB
- Les symboles de débogage macOS pèsent environ 9MB
- Les fichiers Windows
.pdbdépassent 250MB
- Exemple pour l’exécutable Bun :
llvm-striple fait passer de60Mà51M - Sans symboles de débogage, lorsqu’un crash survient, la stack trace ne contient souvent que
???et des adresses, ce qui la rend peu exploitable - À cause de l’ASLR (Address space layout randomization), les adresses de fonctions incluent un décalage aléatoire, ce qui empêche de restaurer directement les noms de fonctions
Fonctionnement de bun.report
- Lorsqu’un crash ou un panic survient dans Bun v1.1.5, Bun affiche un lien
bun.reportavec la version, la plateforme, les arguments d’exécution, l’utilisation mémoire et le message de crash - Quand l’utilisateur ouvre ce lien, il est redirigé vers un formulaire d’issue GitHub prérempli
- L’URL contient une stack trace remappée et encodée
- À partir des informations présentes dans l’URL, le serveur reconstruit les adresses de pile et les transforme en rapport de crash lisible par l’équipe Bun
Comment transformer les adresses en stack trace lisible
- Les adresses de fonction sont des pointeurs vers l’emplacement où le code applicatif est chargé en mémoire, avec un décalage aléatoire ajouté pour des raisons de sécurité
- L’idée de base consiste à soustraire l’adresse de base (base address) du binaire à l’adresse brute afin d’obtenir une adresse relative
- L’implémentation réelle est plus complexe à cause des différences d’API selon les plateformes
- Windows utilise
GetModuleHandleExWavec le flagGET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, puis prend le pointeur du module comme adresse de base - Linux parcourt les modules chargés avec
dl_iterate_phdret utilisedl_phdr_info.dlpi_addrcomme adresse de base pour le module contenant l’adresse - macOS parcourt les modules avec
_dyld_image_countet_dyld_get_image_header, puis récupère le slide ASLR via_dyld_get_image_vmaddr_slide- Sur macOS, l’adresse résultante conserve un offset d’image, qui vaut
0x100000000dans le cas de Bun - Cet offset est supprimé pour raccourcir l’URL, mais il doit être réajouté avant le remappage avec
llvm-symbolizer
- Sur macOS, l’adresse résultante conserve un offset d’image, qui vaut
- Windows utilise
- Sur Linux et macOS, le premier module correspond au binaire principal de l’application
- Sous Windows, on peut déterminer s’il s’agit du binaire principal en comparant le nom du module avec
peb.ProcessParameters.ImagePathName - Bun ne télécharge ni n’analyse localement les symboles de débogage, et confie au serveur le démanglage
- Le serveur peut mettre les symboles de débogage en cache
- Il peut démangler une stack trace en quelques secondes
- Il sert aussi de lien pour ouvrir simultanément une nouvelle issue GitHub
Structure de l’URL bun.report
- L’URL
bun.reportencode les informations suivantes- Platform : un caractère représentant la plateforme. Par exemple,
wpour Windows x86_64,Mpour macOS aarch64 - Subcommand : un caractère représentant une sous-commande comme
bun test,bun installoubun run - Commit SHA : le commit SHA de la version actuelle de Bun, utilisé ensuite pour récupérer les symboles de débogage
- Feature Flags : des indicateurs des API et fonctionnalités utilisées avant le crash
- Stack Trace Addresses : les adresses calculées à l’étape précédente
- Crash Type : un caractère représentant le type de crash
- Crash Message : un message dont le format varie selon le type de crash
- Platform : un caractère représentant la plateforme. Par exemple,
- Le numéro de version présent dans l’URL sert surtout à la lecture humaine, pas au traitement effectif
- Avec ces seules informations, on peut déjà identifier manuellement certaines caractéristiques du crash
- L’identifiant
wpermet de voir immédiatement qu’il s’agit d’un crash Windows - Le
A2à la fin de la chaîne permet d’identifier une faute de segmentation
- L’identifiant
Encodage VLQ pour des URL courtes
- Les adresses de stack trace sont encodées en nombres base64 Variable Length Quantity (VLQ) afin de garder des URL courtes
- Le VLQ permet de représenter les petits nombres avec moins de caractères tout en restant capable d’encoder de grands nombres
- La même technique est utilisée dans les source maps JavaScript pour stocker les numéros de ligne
- Le serveur redécode les valeurs VLQ en adresses relatives, télécharge les symboles de débogage à partir du hash de commit et de la plateforme, puis démangle les noms de fonctions avec
llvm-symbolizer - Dans l’exemple de crash, on découvre qu’une assertion a échoué dans
dirInfoCachedMaybeLog, une partie du code de résolution de modules sous Windows
Encodage des feature flags
- L’URL encode aussi un entier 64 bits, où chaque bit correspond à l’utilisation d’une fonctionnalité précise de Bun
- Ces flags donnent des indices sur les API et systèmes susceptibles d’avoir contribué au crash
- Si un fichier
.envest chargé automatiquement, la fonctionnalitédotenvest activée - Si
fetch()est utilisé, la fonctionnalitéfetchest activée
- Si un fichier
- Bun suit l’utilisation des fonctionnalités dans un conteneur de variables globales, et chaque API incrémente la valeur correspondante en interne pour signaler son usage
- Grâce à la métaprogrammation à la compilation de Zig, la liste des fonctionnalités est parcourue pour générer dynamiquement une packed struct utilisant un bit par fonctionnalité
- Avec
inline for, il est possible de parcourir cette liste à la compilation tout en effectuant le réglage effectif des bits à l’exécution - Lorsqu’une nouvelle fonctionnalité est ajoutée à la struct
Featuresexistante, le rapporteur de crash la prend en charge sans code répétitif supplémentaire - La même approche pourrait être reproduite en C ou avec des macros Rust, mais dans l’implémentation de Bun, le
comptimede Zig est utilisé d’une manière plus simple et plus lisible
Différence avec les core dumps
- Les core dumps contiennent bien plus d’informations, mais ils sont volumineux, nécessitent des symboles de débogage pour être utiles et peuvent embarquer beaucoup de données sensibles ou confidentielles
- La nouvelle méthode de rapport de Bun évite d’envoyer du code source JavaScript/TypeScript, des variables d’environnement et d’autres informations sensibles
- Au lieu de tout envoyer par défaut, elle ne transmet que les stack traces Zig/C++ et quelques détails susceptibles d’être utiles au diagnostic
- Si des informations supplémentaires sont nécessaires, elles peuvent être demandées séparément à l’utilisateur
- Par rapport à l’ancienne situation où il ne restait que des adresses non mappées, cela facilite nettement le diagnostic des crashes pour l’équipe Bun
Démo
- Une petite webapp permettant de tester le rapporteur de crash est disponible sur bun.report
- En ajoutant
/viewà la fin de n’importe quelle URL de rapport de crash, on ouvre l’écran correspondant de cette webapp
1 commentaires
Avis sur Hacker News
Si la raison d’utiliser cette méthode au lieu d’une trace de pile classique est d’éviter de distribuer des symboles de débogage de plusieurs Mo, on dirait qu’ils ont ignoré une meilleure option : n’inclure que les noms de fonctions dans la table de débogage
C’est bien préférable au fait de devoir passer par un service web pour consulter une trace de pile, et ce n’est pas seulement théorique : c’est déjà implémenté dans LLVM : https://clang.llvm.org/docs/UsersManual.html#cmdoption-gline...
Si une simple URL préremplit presque automatiquement les informations nécessaires, cela devient suffisamment facile pour que les développeurs envoient réellement des rapports de crash. La taille comptait aussi, car on voulait éviter tout inconvénient pour l’utilisateur, mais l’essentiel est de rendre tout le processus extrêmement simple
Dans ce cas d’usage, devoir consulter la trace de pile via un service web n’est pas un gros inconvénient. C’est presque la même chose que d’obfusquer/minifier un bundle JavaScript frontend, d’envoyer les source maps à Sentry, puis de laisser Sentry reconstituer les traces de pile venant du navigateur de l’utilisateur. L’utilisateur n’allait de toute façon jamais voir cette trace de pile, et personnellement je n’ai aucun problème à la consulter dans Sentry. Sinon, je ne pourrais même pas la voir du tout
Il y a plusieurs façons de proposer une alternative
C’est ainsi que, sur ces plateformes, la symbolisation parvient à retrouver les noms de fonctions des bibliothèques système sans symboles de débogage complets
C’est excellent et très créatif. Beaucoup de projets pourraient s’en inspirer. L’idée clé est d’enregistrer la trace de pile avec un compteur ordinal relatif par exécutable/objet partagé
Bun étant, à ma connaissance, lié statiquement, sur un système à liaison dynamique il faudrait probablement préfixer chaque compteur ordinal normalisé avec un petit identifiant numérique de l’objet partagé
Par exemple, le crash reporter d’Unreal Engine peut envoyer ce format simple depuis des années, et il permet de reconstituer avec assez de précision la fonction et le numéro de ligne de chaque frame de pile. En général, on préfère tout de même les mini-dumps, car disposer aussi des variables de pile donne des indices supplémentaires sur ce qui s’est passé
Microsoft est vraiment très fort pour ce genre de choses. Dans SQL Server, ils utilisaient des mini-dumps dépourvus de données personnelles, et c’était à la fois très compact et extrêmement utile
Déjà à l’époque, il y a 15 ans, les dumps complets d’un SQL Server en production étaient des fichiers tellement énormes qu’ils étaient difficiles à déplacer
J’observe Bun depuis des années depuis le premier tweet lié à Zig, et je me suis mis à l’utiliser récemment ; ça fonctionne simplement, sans complications particulières
Bun est assez séduisant. Je l’ai essayé sur quelques petits projets d’exemple, c’est rapide, et j’aime bien le fait qu’il combine gestion de paquets et runtime JavaScript
En revanche, j’utilise Dependabot sur la plupart de mes projets sérieux. De ce que je sais, le support de Bun dans Dependabot est en cours ou au moins discuté dans plusieurs issues du dépôt, donc j’attends qu’il soit publié avant de l’adopter
Aucun regret. Les gains cumulés apportés par les accélérations et la nette amélioration de l’expérience développeur valent vraiment ce qu’on en attendait
Peu de gens se rendront compte du soin apporté à ce genre de détails. J’aime ce que cela montre de l’attention portée par l’équipe de Bun à son craft
Bun est impressionnant, mais récemment j’ai essayé de créer un serveur HTTP/2 avec Fastify et ça n’a pas marché
J’ai obtenu l’erreur
node:http2 createServer is not yet implemented in Bun, et l’issue pointée par le message concerne en réalité le support du client HTTP/2. Ce support client a déjà été publié dans la v1.0.13 : https://bun.sh/blog/bun-v1.0.13#http2-client-supportLe message
NotImplementedErrordevrait être modifié pour pointer vers l’issue côté serveur : https://github.com/oven-sh/bun/issues/8823Le support du serveur HTTP/2 fait partie des demandes de fonctionnalités les plus votées : https://github.com/oven-sh/bun/issues?q=is%3Aissue+is%3Aopen...
Quand cette fonctionnalité arrivera, beaucoup plus de gens pourront sans doute passer à Bun
Bun en est encore à un stade bien trop précoce de son cycle de vie. Cela dit, j’ai beaucoup d’espoir pour le projet
Je me demande s’il y a vraiment des gens qui utilisent Bun. Est-ce aussi bien qu’on le dit ?
Dans un environnement TypeScript sur Node, configurer ts-node, ts-jest, le support ESM, le top-level await, etc., est inutilement fastidieux. Les versions récentes de Node ont réduit une partie de cette friction, mais ce n’est toujours pas aussi simple que
bun init. J’apprécie aussi beaucoup l’API shell de Bun : https://bun.sh/blog/the-bun-shellLes messages d’erreur sont aussi nettement moins bons que ceux de Node. Je l’ai utilisé un moment, mais maintenant
—loader tsxsur Node fait tout ce que je veux sans les inconvénients. Pour des serveurs simples, par exemple avec WebSocket, si vous êtes sûr de ne pas avoir besoin de modules natifs, Bun peut valoir le coup. J’exploite d’ailleurs quelques services comme çaLa vitesse de démarrage instantanée reste encore étonnante
Il manque encore des choses, mais pour moi c’est déjà meilleur que Node
Cet article ressemble aussi à une excellente étude de cas sur Zig. Très intéressant
Avec Bun, il faut télécharger 37 paquets avant de pouvoir utiliser le REPL. Sans connexion Internet, on ne peut même pas lancer le REPL
En exécutant
bun repl, j’obtiens une erreur disant que le téléchargement du manifeste du paquetbun-repla échoué. Ce n’est pas un gros problème, mais je m’attendais à ce qu’un simple exécutable unique placé dans le PATH fonctionne immédiatement sans installation, et j’en avais beaucoup d’espoirEn interne,
bun replfait la même chose quebunx bun-repl