O novo crash reporter do Bun
(bun.sh)- O Bun v1.1.5 adiciona o crash reporter
bun.report, que envia informações de stack em Zig/C++ usando apenas uma URL de cerca de 150 bytes, sem dados pessoais, mesmo quando ocorre um crash ou pânico - Os crash reporters nativos do sistema operacional e os core dumps têm custos altos em símbolos de depuração, desempenho, privacidade e tamanho do executável, o que dificulta seu uso em uma ferramenta CLI como o Bun
- A nova abordagem converte endereços que perdem significado por causa do ASLR em endereços relativos ao módulo, e o servidor restaura os nomes de função usando símbolos de depuração compatíveis com o commit SHA e a plataforma
- A URL inclui plataforma, subcomando, commit SHA, feature flags, endereços da stack, tipo de crash e mensagem, e os endereços da stack são codificados de forma curta com base64 VLQ
- Nenhum código-fonte JavaScript/TypeScript nem variáveis de ambiente é enviado; a equipe do Bun recebe apenas as informações de stack em Zig/C++ e alguns metadados necessários para diagnóstico
Por que o Bun criou seu próprio crash reporter
- No momento da escrita, o Bun tem mais de 2.600 issues abertas no GitHub, e algumas delas são especialmente difíceis de reproduzir e depurar
- Serviços de crash reporting como o Sentry funcionam bem para apps e produtos SaaS, mas, em uma ferramenta CLI como o Bun, fazer upload de core dumps amplia muito os problemas de privacidade, desempenho e tamanho do executável
- O Bun v1.1.5 introduz um novo formato pequeno para relatórios de crash em Zig e C++
- O relatório de crash cabe em uma URL de cerca de 150 bytes
- Não inclui dados pessoais
Limitações de usar apenas o crash reporter do sistema operacional
- Alguns sistemas operacionais, como o macOS, têm crash reporters embutidos, mas, para usá-los de forma adequada, normalmente é preciso distribuir símbolos de depuração junto com a aplicação
- Os símbolos de depuração aumentam bastante o tamanho da distribuição do Bun
- Símbolos de depuração no Linux: cerca de 30MB
- Símbolos de depuração no macOS: cerca de 9MB
- Arquivos
.pdbno Windows: mais de 250MB
- Um exemplo do executável do Bun cai de
60Mpara51Mantes e depois dellvm-strip - Sem símbolos de depuração, quando ocorre um crash, o stack trace fica apenas com
???e endereços, o que tem pouca utilidade - Por causa do ASLR (Address space layout randomization), os endereços de função incluem offsets aleatórios, e assim não é possível restaurar os nomes de função diretamente
Como o bun.report funciona
- Quando ocorre um crash ou pânico no Bun v1.1.5, o Bun imprime um link
bun.reportjunto com versão, plataforma, argumentos de execução, uso de memória e mensagem de crash - Quando o usuário abre o link, ele é redirecionado para um formulário de issue do GitHub já preenchido
- Dentro da URL, há um stack trace remapeado e codificado
- Com base nas informações contidas na URL, o servidor reconstrói os endereços da stack e os converte em um relatório de crash legível pela equipe do Bun
Processo para transformar endereços em um stack trace legível
- Os endereços de função são ponteiros para a posição em que o código da aplicação foi carregado na memória, e incluem um offset aleatório por motivos de segurança
- A ideia básica é subtrair o endereço-base (base address) do binário do endereço bruto para obter um endereço relativo
- A implementação real é mais complexa por causa das diferenças de API entre plataformas
- No Windows, usa
GetModuleHandleExWcom a flagGET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, e usa o ponteiro do módulo como endereço-base - No Linux, usa
dl_iterate_phdrpara percorrer os módulos carregados e usadl_phdr_info.dlpi_addrdo módulo que contém o endereço como endereço-base - No macOS, percorre os módulos com
_dyld_image_counte_dyld_get_image_header, e obtém o slide de ASLR com_dyld_get_image_vmaddr_slide- O endereço resultante no macOS ainda contém o offset da imagem, que no caso do Bun é
0x100000000 - Para encurtar a URL, esse offset é removido, mas precisa ser somado novamente antes do remapeamento com
llvm-symbolizer
- O endereço resultante no macOS ainda contém o offset da imagem, que no caso do Bun é
- No Windows, usa
- No Linux e no macOS, o primeiro módulo aponta para o binário principal da aplicação
- No Windows, é possível determinar se é o binário principal comparando o nome do módulo com
peb.ProcessParameters.ImagePathName - O Bun não baixa nem analisa símbolos de depuração localmente; ele delega o demangling ao servidor
- O servidor pode armazenar os símbolos de depuração em cache
- Pode fazer o demangling do stack trace em poucos segundos
- E, ao mesmo tempo, funciona como link para abrir uma nova issue no GitHub
Estrutura da URL do bun.report
- A URL do
bun.reportcodifica as seguintes informações- Platform: uma letra que representa a plataforma. Por exemplo,
wé Windows x86_64 eMé macOS aarch64 - Subcommand: uma letra que representa subcomandos como
bun test,bun installebun run - Commit SHA: o commit SHA da versão atual do Bun, usado depois para buscar símbolos de depuração
- Feature Flags: marcas que indicam quais APIs e recursos estavam em uso antes do crash
- Stack Trace Addresses: os endereços calculados na etapa anterior
- Crash Type: uma letra que representa o tipo de crash
- Crash Message: uma mensagem cujo formato varia conforme o tipo de crash
- Platform: uma letra que representa a plataforma. Por exemplo,
- O número de versão presente na URL serve mais para leitura humana do que para o processamento real
- Só com essas informações já dá para identificar manualmente algumas características do crash
- Ao ver o identificador
w, é possível saber rapidamente que foi um crash no Windows - Ao ver
A2no fim da string, é possível identificar uma falha de segmentação
- Ao ver o identificador
Codificação VLQ para URLs curtas
- Os endereços do stack trace são codificados como números base64 Variable Length Quantity (VLQ) para manter a URL curta
- O VLQ permite representar números pequenos com menos caracteres, sem deixar de codificar números grandes
- A mesma técnica também é usada para armazenar números de linha em source maps de JavaScript
- O servidor decodifica os valores VLQ de volta para endereços relativos, baixa os símbolos de depuração usando o hash do commit e a plataforma, e depois faz o demangling dos nomes de função com
llvm-symbolizer - No crash de exemplo, isso revela que uma assertion falhou em
dirInfoCachedMaybeLog, parte do código do resolvedor de módulos no Windows
Codificação de feature flags
- A URL também codifica um inteiro de 64 bits, em que cada bit corresponde ao uso de um recurso específico do Bun
- Essas flags dão pistas sobre quais APIs e sistemas podem ter influenciado o crash
- Se um arquivo
.envfoi carregado automaticamente, a featuredotenvé marcada - Se
fetch()foi usado, a featurefetché marcada
- Se um arquivo
- O Bun rastreia o uso de recursos por meio de um contêiner de variáveis globais e marca o uso incrementando o número correspondente dentro de cada API
- Usando metaprogramação em tempo de compilação no Zig, ele percorre a lista de features e gera dinamicamente uma packed struct que usa 1 bit para cada recurso
- Com
inline for, é possível percorrer a lista de features em tempo de compilação, enquanto a marcação real dos bits acontece em tempo de execução - Ao adicionar uma nova feature à struct
Featuresexistente, o crash reporter também passa a tratá-la sem necessidade de código repetido - O mesmo tipo de abordagem seria possível com macros em C ou Rust, mas, na implementação do Bun, o
comptimedo Zig foi usado de forma mais simples e legível
Diferença em relação aos core dumps
- Core dumps contêm muito mais informação, mas são grandes, só são realmente úteis com símbolos de depuração e podem incluir muitas informações sensíveis ou confidenciais
- O novo formato de relatório do Bun evita situações em que código-fonte JavaScript/TypeScript, variáveis de ambiente e outras informações sensíveis seriam enviados
- Em vez de mandar tudo por padrão, ele envia apenas o stack trace em Zig/C++ e alguns detalhes com maior chance de serem úteis para diagnosticar o problema
- Se forem necessárias informações adicionais, elas podem ser solicitadas separadamente ao usuário
- Isso torna mais fácil para a equipe do Bun diagnosticar crashes do que no cenário anterior, em que restavam apenas endereços sem mapeamento
Demo
- Um pequeno webapp para testar o crash reporter está disponível em bun.report
- Ao adicionar
/viewao final de qualquer URL de relatório de crash, é possível abrir a tela desse webapp
1 comentários
Opiniões no Hacker News
Se o motivo para usar essa abordagem em vez de um stack trace comum é evitar distribuir vários MB de símbolos de depuração, parece que ignoraram uma opção melhor: colocar apenas os nomes das funções nas tabelas de depuração.
É um método muito melhor do que ter que usar um serviço web para ver o stack trace, e isso não é só teoria; já está implementado no LLVM: https://clang.llvm.org/docs/UsersManual.html#cmdoption-gline...
Se uma única URL preencher quase automaticamente o conteúdo necessário, fica fácil o bastante, e é assim que desenvolvedores de fato enviam relatórios de crash. O tamanho também importa, para não criar desvantagens para o usuário, mas o ponto central é tornar todo o processo muito fácil
Neste cenário de uso, precisar ver o stack trace por meio de um serviço web não é uma grande desvantagem. É quase igual a ofuscar/minificar bundles JavaScript de frontend, enviar source maps para o Sentry e depois usar o Sentry para restaurar stack traces vindos do navegador do usuário. O usuário, de qualquer forma, não vai olhar aquele stack trace, e para mim não é inconveniente vê-lo via Sentry. Se não fosse assim, eu nem conseguiria vê-lo
Há várias formas de propor alternativas
Nessas plataformas, é assim que a simbolização consegue descobrir nomes de funções de bibliotecas do sistema mesmo sem os símbolos de depuração completos
Excelente e muito criativo. Muitos projetos fariam bem em seguir essa abordagem. O ponto principal é registrar stack traces usando o program counter relativo ao executável/objeto compartilhado.
Pelo que sei, o Bun é linkado estaticamente; em um sistema com linking dinâmico, seria preciso prefixar cada program counter normalizado com um ID de objeto compartilhado em forma de número pequeno
Por exemplo, o crash reporter da Unreal Engine já conseguia enviar esse formato simples há anos e restaurar números de função/linha com bastante precisão em cada frame da stack. Normalmente, porém, prefere-se um minidump, porque ter até as variáveis da stack pode dar pistas adicionais sobre o que aconteceu
A Microsoft é realmente muito boa nisso. No SQL Server, usavam minidumps com informações pessoais removidas, que eram muito pequenos e extremamente úteis.
Mesmo naquela época, há 15 anos, dumps completos de SQL Server em produção eram arquivos enormes, difíceis de transferir
Venho acompanhando o Bun há alguns anos, desde que vi o primeiro tweet relacionado ao Zig, e comecei a usá-lo recentemente; simplesmente funciona bem, sem muita complicação
O Bun é bem atraente. Usei em alguns pequenos projetos de exemplo, é rápido e gosto do fato de combinar gerenciamento de pacotes com runtime JavaScript.
Porém, uso Dependabot na maioria dos projetos sérios. Pelo que sei, o suporte do Dependabot ao Bun está em andamento, ou pelo menos em discussão em algumas issues de repositórios, então estou adiando o uso até esse suporte ser lançado
Não me arrependo nem um pouco. As economias acumuladas pelas partes que ficaram mais rápidas e a grande melhoria na experiência do desenvolvedor valem o quanto esperávamos
Pouca gente vai perceber quanto cuidado foi colocado nisso. É bom ver o quanto a equipe do Bun se importa com seu craft
O Bun é impressionante, mas recentemente tentei criar um servidor HTTP/2 com Fastify e não funcionou.
Recebi o erro
node:http2 createServer is not yet implemented in Bun, e a issue indicada pela mensagem na verdade é sobre suporte a cliente HTTP/2. O suporte a cliente já foi lançado na v1.0.13: https://bun.sh/blog/bun-v1.0.13#http2-client-supportA mensagem
NotImplementedErrordeveria ser alterada para apontar para a issue do lado servidor: https://github.com/oven-sh/bun/issues/8823O suporte a servidor HTTP/2 está entre as solicitações de recurso mais populares: https://github.com/oven-sh/bun/issues?q=is%3Aissue+is%3Aopen...
Quando esse recurso sair, acho que muito mais gente poderá migrar para o Bun
O Bun ainda está em uma fase muito inicial do ciclo de vida. Mesmo assim, tenho grandes expectativas para o projeto
Fico curioso se há pessoas usando Bun de verdade. É tão bom quanto se espera?
Configurar ts-node, ts-jest, suporte a ESM, top-level await etc. em um ambiente TypeScript Node dá mais trabalho do que deveria. Lançamentos recentes do Node reduziram parte desse incômodo, mas ainda não é tão simples quanto
bun init. Também tenho gostado de usar a API de shell do bun: https://bun.sh/blog/the-bun-shellAs mensagens de erro também são bem piores que as do Node. Usei por um tempo, mas hoje em dia o Node com
—loader tsxfaz tudo o que eu quero e sem as desvantagens. Para um servidor simples, por exemplo usando WebSocket e se você tiver certeza de que não precisa de módulos nativos, vale considerar o Bun. De fato, mantenho alguns serviços assim em operaçãoA velocidade de inicialização instantânea ainda me surpreende
Ainda há coisas faltando, mas para mim já é melhor que o Node
Este texto também parece um excelente estudo de caso de Zig. Interessante
O Bun precisa baixar 37 pacotes antes de usar o REPL. Sem internet, nem o REPL funciona.
Ao executar
bun repl, aparece um erro dizendo que falhou ao baixar o manifesto do pacotebun-repl. Não é um grande problema, mas eu esperava — e estava bem animado com a ideia — que bastasse colocar um único executável no PATH para ele funcionar imediatamente, sem instalaçãoInternamente,
bun replfaz o mesmo quebunx bun-repl