2 puntos por GN⁺ 2024-04-27 | 1 comentarios | Compartir por WhatsApp
  • Bun v1.1.5 agrega el reportero de fallos bun.report, que permite transmitir información de stack de Zig/C++ usando solo una URL de unos 150 bytes sin datos personales, incluso ante un crash o panic
  • Los reporteros de fallos del sistema operativo y los core dumps tradicionales tienen costos altos en símbolos de depuración, rendimiento, privacidad y tamaño del ejecutable, lo que dificulta aplicarlos a una herramienta CLI como Bun
  • El nuevo enfoque convierte las direcciones cuyo significado queda difuminado por ASLR en direcciones relativas al módulo, y el servidor restaura los nombres de funciones usando símbolos de depuración correspondientes al commit SHA y la plataforma
  • La URL incluye la plataforma, el subcomando, el commit SHA, feature flags, direcciones del stack, el tipo de fallo y el mensaje; las direcciones del stack se codifican de forma compacta con base64 VLQ
  • No se envía código fuente JavaScript/TypeScript ni variables de entorno; el equipo de Bun solo transmite información de stack de Zig/C++ y algunos metadatos necesarios para el diagnóstico

Por qué Bun creó su propio reportero de fallos

  • Al momento de escribir esto, Bun tiene más de 2,600 issues abiertos en GitHub, y algunos son especialmente difíciles de reproducir y depurar
  • Los servicios de reporte de fallos como Sentry son adecuados para apps y productos SaaS, pero si una herramienta CLI como Bun sube core dumps, aumentan los problemas de privacidad, rendimiento y tamaño del ejecutable
  • Bun v1.1.5 introduce un nuevo formato pequeño para reportes de fallos de Zig y C++
    • El reporte de fallo cabe en una URL de unos 150 bytes
    • No incluye datos personales

Por qué los reporteros de fallos del sistema operativo no alcanzan

  • Algunos sistemas operativos, como macOS, incluyen reporteros de fallos integrados, pero para aprovecharlos correctamente normalmente hay que distribuir símbolos de depuración junto con la aplicación
  • Los símbolos de depuración aumentan mucho el tamaño de distribución de Bun
    • Los símbolos de depuración de Linux pesan unos 30MB
    • Los símbolos de depuración de macOS pesan unos 9MB
    • Los archivos .pdb de Windows pesan más de 250MB
  • En un ejemplo del ejecutable de Bun, el tamaño baja de 60M a 51M después de ejecutar llvm-strip
  • Si ocurre un fallo sin símbolos de depuración, el stack trace queda con solo ??? y direcciones, por lo que resulta poco útil
  • Debido a ASLR (Address space layout randomization), las direcciones de funciones incluyen un desplazamiento aleatorio y no se pueden usar tal cual para restaurar los nombres de funciones

Cómo funciona bun.report

  • Cuando ocurre un crash o panic en Bun v1.1.5, Bun imprime un enlace de bun.report junto con la versión, la plataforma, los argumentos de ejecución, el uso de memoria y el mensaje del fallo
  • Cuando el usuario abre el enlace, se lo redirige a un formulario de issue de GitHub prellenado
  • Dentro de la URL se codifica un stack trace remapeado
  • El servidor restaura las direcciones del stack con base en la información incluida en la URL y las convierte en un reporte de fallo legible para el equipo de Bun

Procedimiento para convertir direcciones en un stack trace legible

  • Una dirección de función es un puntero que señala dónde se cargó en memoria el código de la aplicación, e incluye un desplazamiento aleatorio por seguridad
  • La idea básica es restar la dirección base (base address) del binario a la dirección cruda para obtener una dirección relativa
  • La implementación real es más compleja por las diferencias entre APIs de cada plataforma
    • En Windows se usa la bandera GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS con GetModuleHandleExW, y se toma el puntero del módulo como dirección base
    • En Linux se recorren los módulos cargados con dl_iterate_phdr, y se usa como dirección base el dl_phdr_info.dlpi_addr del módulo que contiene la dirección
    • En macOS se recorren los módulos con _dyld_image_count y _dyld_get_image_header, y se obtiene el slide de ASLR con _dyld_get_image_vmaddr_slide
      • Las direcciones resultantes en macOS conservan el desplazamiento de imagen, que en el caso de Bun es 0x100000000
      • Para acortar la URL se elimina este desplazamiento, pero hay que volver a sumarlo antes de remapear con llvm-symbolizer
  • En Linux y macOS, el primer módulo apunta al binario principal de la aplicación
  • En Windows, se puede determinar si es el binario principal comparando el nombre del módulo con peb.ProcessParameters.ImagePathName
  • Bun no descarga ni parsea símbolos de depuración localmente, sino que delega la demangling al servidor
    • El servidor puede cachear símbolos de depuración
    • Puede hacer demangling del stack trace en pocos segundos
    • A la vez funciona como enlace para abrir un nuevo issue de GitHub

Estructura de la URL de bun.report

  • En una URL de bun.report se codifica la siguiente información
    • Platform: una letra que representa la plataforma. Por ejemplo, w es x86_64 Windows y M es aarch64 macOS
    • Subcommand: una letra que representa subcomandos como bun test, bun install o bun run
    • Commit SHA: el commit SHA de la versión actual de Bun; se usa luego para obtener los símbolos de depuración
    • Feature Flags: marcas que indican las APIs y funciones usadas antes del fallo
    • Stack Trace Addresses: las direcciones calculadas en el paso anterior
    • Crash Type: una letra que representa el tipo de fallo
    • Crash Message: un mensaje cuyo formato varía según el tipo de fallo
  • El número de versión incluido en la URL es una indicación para lectura humana, más que para el procesamiento real
  • Incluso solo con esta información se pueden identificar manualmente algunas características de un fallo
    • Al ver el identificador w, se puede saber rápidamente que es un crash en Windows
    • Al ver A2 al final de la cadena, se puede identificar una falla de segmentación

Codificación VLQ para URLs cortas

  • Las direcciones del stack trace se codifican como números base64 Variable Length Quantity (VLQ) para mantener corta la URL
  • VLQ puede representar números pequeños con menos caracteres y también codificar números grandes
  • La misma técnica se usa en source maps de JavaScript para almacenar números de línea
  • El servidor decodifica los valores VLQ nuevamente a direcciones relativas, usa el hash del commit y la plataforma para descargar los símbolos de depuración, y luego aplica demangling a los nombres de funciones con llvm-symbolizer
  • En el crash de ejemplo se revela que falló una aserción en dirInfoCachedMaybeLog, parte del código de resolución de módulos en Windows

Codificación de feature flags

  • La URL también codifica un entero de 64 bits, donde cada bit corresponde al uso de una función específica de Bun
  • Estas flags dan pistas sobre qué APIs y sistemas pudieron haber influido en el fallo
    • Si un archivo .env se carga automáticamente, se activa la función dotenv
    • Si se usa fetch(), se activa la función fetch
  • Bun rastrea el uso de funciones con un contenedor de variables globales, y dentro de cada API incrementa ese número para marcar su uso
  • Mediante metaprogramación en tiempo de compilación de Zig, se recorre la lista de funciones y se genera dinámicamente un packed struct que usa 1 bit por función
  • Con inline for se puede recorrer la lista de funciones en tiempo de compilación, mientras que el ajuste real de los bits se realiza en runtime
  • Al agregar una nueva función al struct Features existente, el reportero de fallos también la maneja sin escribir lógica repetida
  • Lo mismo podría hacerse con macros de C o Rust, pero en la implementación de Bun, comptime de Zig se usa como una opción más simple y legible

Diferencias frente a los core dumps

  • Los core dumps contienen mucha más información, pero son grandes, solo son útiles con símbolos de depuración y pueden incluir mucha información sensible o confidencial
  • El nuevo enfoque de reporte de Bun evita enviar código fuente JavaScript/TypeScript, variables de entorno u otra información sensible
  • En lugar de enviar todo de forma predeterminada, solo envía el stack trace de Zig/C++ y algunos detalles que probablemente sean necesarios para diagnosticar el problema
  • Si se necesita información adicional, se puede pedir al usuario por separado
  • Para el equipo de Bun, esto facilita diagnosticar fallos en comparación con la situación anterior, donde solo quedaban direcciones sin mapear

Demo

  • Hay una pequeña webapp para probar el reportero de fallos disponible en bun.report
  • Si se agrega /view al final de cualquier URL de reporte de fallo, se abre la pantalla de esa webapp

1 comentarios

 
GN⁺ 2024-04-27
Opiniones de Hacker News
  • Si la razón para usar este método en vez de un seguimiento de pila normal es evitar distribuir varios MB de símbolos de depuración, parece que se ignoró una mejor opción: incluir solo los nombres de funciones en las tablas de depuración.
    Es una solución mucho mejor que tener que usar un servicio web para ver el seguimiento de pila, y no es solo teoría: ya está implementada en LLVM: https://clang.llvm.org/docs/UsersManual.html#cmdoption-gline...

    • La razón principal para usar este método en vez de un seguimiento de pila normal no es el tamaño de los símbolos de depuración, sino que casi nadie tiene la paciencia suficiente para subir reportes de crash a issues de GitHub.
      Si una sola URL autocompleta casi todo lo necesario, se vuelve lo bastante fácil, y así los desarrolladores sí envían reportes de crash. El tamaño también importa, porque querían que no hubiera desventajas para el usuario, pero lo central es hacer que todo el proceso sea muy fácil.
    • Frases como “ignoraron una mejor opción” y “claramente es mejor” suenan un poco tajantes. Probablemente sí conocían esa posibilidad.
      En este caso de uso, tener que ver el seguimiento de pila mediante un servicio web no es una gran desventaja. Es casi lo mismo que ofuscar/minificar bundles de JavaScript de frontend, subir los source maps a Sentry y luego usar Sentry para reconstruir los seguimientos de pila que vienen del navegador del usuario. El usuario de todos modos no va a ver ese seguimiento de pila, y a mí no me molesta verlo con Sentry. Si no fuera por eso, directamente no podría verlo.
    • Criticar diciendo que “obviamente” o “simplemente” deberían hacer otra cosa, sin conocer el contexto de la discusión ni qué trade-offs hubo, suena un poco arrogante.
      Hay muchas formas de proponer alternativas.
    • En macOS/iOS también se puede distribuir el binario Mach-O incluyendo solo la sección LC_FUNCTION_STARTS.
      En esas plataformas, así es como la simbolización puede encontrar nombres de funciones de las bibliotecas del sistema sin tener todos los símbolos de depuración.
    • Aun así puede crecer bastante. Personalmente creo que vale la pena, así que normalmente lo incluyo siempre, pero la mayoría del software no lo hace.
  • Excelente y muy creativo. Ojalá muchos proyectos copien este enfoque. La clave es dejar el seguimiento de pila usando contadores de programa relativos al ejecutable/objeto compartido.
    Tengo entendido que Bun está enlazado estáticamente, pero en un sistema con enlace dinámico habría que anteponer a cada contador de programa normalizado un ID pequeño, numérico, del objeto compartido.

    • No es un enfoque completamente nuevo. Se usa mucho en entornos donde no se pueden distribuir símbolos, como en juegos, cuando ocurre un crash en la PC de un jugador.
      Por ejemplo, desde hace años el crash reporter de Unreal Engine puede enviar un formato simple como este y reconstruir con bastante precisión la función y el número de línea de cada frame de la pila. Aunque normalmente se prefieren los minidumps, porque si también se tienen las variables de la pila se obtienen pistas adicionales sobre lo que ocurrió.
  • Microsoft es realmente muy buena en esto. En SQL Server usaban minidumps sin datos personales, que eran muy pequeños y extremadamente útiles.
    Incluso en ese entonces, hace 15 años, un volcado completo de un SQL Server en producción era un archivo tan enorme que resultaba difícil de mover.

    • Me da curiosidad si era para servicios internos de Microsoft o para entornos distribuidos a clientes. Si era lo segundo, ¿cómo sabían qué era información de identificación personal?
  • Llevo años siguiendo Bun desde que vi el primer tuit relacionado con Zig, y empecé a usarlo hace poco. Simplemente funciona bien, sin mayor complicación.

  • Bun es bastante atractivo. Lo probé en algunos proyectos pequeños de ejemplo y es rápido; me gusta que combine gestión de paquetes y runtime de JavaScript.
    Sin embargo, uso Dependabot en la mayoría de los proyectos serios. Tengo entendido que el soporte de Dependabot para Bun está en desarrollo, o al menos se está discutiendo en algunos issues de repositorios, así que estoy postergando su uso hasta que ese soporte esté disponible.

    • Nosotros también dudamos antes de migrar por la falta de soporte en Dependabot, pero descubrimos que Renovate funciona con Bun y por ahora es un reemplazo suficiente.
      No nos arrepentimos en absoluto. Los ahorros acumulados por las mejoras de velocidad y la gran mejora en la experiencia de desarrollo valen tanto como esperábamos.
  • No mucha gente notará cuánta atención pusieron en algo como esto. Me gusta porque muestra cuánto se preocupa el equipo de Bun por su craft.

  • Bun es sorprendente, pero hace poco intenté crear un servidor HTTP/2 con Fastify y no funcionó.
    Apareció el error node:http2 createServer is not yet implemented in Bun, y el issue al que apunta el mensaje en realidad trata sobre soporte de cliente HTTP/2. El soporte de cliente ya se lanzó en v1.0.13: https://bun.sh/blog/bun-v1.0.13#http2-client-support
    El mensaje de NotImplementedError debería cambiarse para apuntar al issue del lado servidor: https://github.com/oven-sh/bun/issues/8823
    El soporte para servidor HTTP/2 es una de las solicitudes de funcionalidades más populares: https://github.com/oven-sh/bun/issues?q=is%3Aissue+is%3Aopen...
    Cuando salga esta función, parece que mucha más gente podrá pasarse a Bun.

    • Ese es el estado actual de Bun. Esperas a que implementen algo y, cuando terminan, descubres que falta otra implementación de API; vuelves a esperar, eventualmente llega, pero crashea en varios casos límite, y así se repite.
      Bun todavía está en una etapa demasiado temprana de su ciclo de vida. Aun así, tengo muchas expectativas para el proyecto.
  • Me pregunto si hay gente usando Bun de verdad. ¿Es tan bueno como promete?

    • Todavía no lo usé en producción, pero para scripts puntuales y proyectos secundarios me resultó excelente.
      Configurar ts-node, ts-jest, soporte ESM, top-level await, etc. en un entorno TypeScript con Node es más engorroso de lo necesario. Las versiones recientes de Node redujeron algunas molestias, pero no es tan simple como bun init. También estoy disfrutando la API de bun shell: https://bun.sh/blog/the-bun-shell
    • Está bien si necesitas un REPL o no planeas usar módulos nativos. Aunque dicen que tiene REPL, cada vez que se actualiza siempre tiene una demora de más de 6 segundos, lo que resulta muy incómodo.
      Los mensajes de error también son bastante peores que los de Node. Lo usé durante un tiempo, pero hoy Node con —loader tsx hace todo lo que quiero y sin esas desventajas. Para un servidor simple, por ejemplo con WebSocket, y si tienes la certeza de que no necesitas módulos nativos, vale la pena considerar Bun. De hecho, tengo varios servicios así en operación.
    • Empecé a usarlo apenas salió la 1.0 y no volví atrás. Ahora lo estoy adoptando en todos mis proyectos.
    • Lo estoy usando como runner de desarrollo y pruebas en un proyecto de lenguaje de programación de unas 15 mil líneas, y hasta ahora no tuve problemas específicos de Bun.
      La velocidad de arranque instantánea todavía me sorprende.
    • Lo estoy usando recientemente y es muy bueno. Las mejoras de calidad de vida, como no tener que preocuparse por compilar TypeScript, son realmente cómodas, y además es rápido.
      Todavía le faltan cosas, pero para mí ya es mejor que Node.
  • Este artículo también me parece un excelente caso de estudio de Zig. Interesante.

  • Bun tiene que descargar 37 paquetes antes de poder usar el REPL. Sin internet, ni siquiera puedes usar el REPL.
    Al ejecutar bun repl, aparece un error diciendo que falló la descarga del manifiesto del paquete bun-repl. No es un gran problema, pero esperaba que con solo poner un ejecutable único en el PATH funcionara de inmediato, sin instalación, y me hacía bastante ilusión.

    • Todavía no priorizaron la implementación del REPL. El REPL actual es el paquete npm bun-repl implementado por la comunidad.
      Internamente, bun repl hace lo mismo que bunx bun-repl.