Inspector de módulos WebAssembly
Un módulo WebAssembly son ocho bytes de preámbulo — la firma \0asm y una versión — seguidos de una secuencia de secciones, cada una con un byte de identificador, una longitud LEB128 y exactamente esos bytes. Esta herramienta recorre esa secuencia dentro del navegador y muestra todo lo que el módulo declara de sí mismo: la versión, cada sección en el orden del archivo con su tamaño, su desplazamiento y su número de elementos, las importaciones y exportaciones con el tipo completo, los límites de memoria en páginas y en bytes, la función start si existe y las secciones personalizadas del final. El desglose de tamaños suele ser el motivo real para abrir un binario. En la mayoría de las compilaciones la sección de código es casi todo el archivo, pero no es una regla: un módulo de Go arrastra una sección de datos que puede superar el megabyte, una compilación de emscripten con recursos incrustados es sobre todo datos, y una compilación de depuración esconde su peso en secciones personalizadas que no afectan a lo que el módulo hace. Las importaciones son el contrato con el anfitrión: emscripten pide env y wasi_snapshot_preview1, wasm-bindgen pide por ruta su archivo de pegamento en JavaScript, Go pide gojs, y una sola entrada sin cubrir es el LinkError que aparece al instanciar. Las exportaciones son la superficie que puedes llamar, y cada firma se lee de la sección de tipos en lugar de deducirse del nombre. En un binario sin símbolos hay dos secciones personalizadas que valen más que el resto: name, que devuelve los nombres del módulo y de sus funciones, y producers, que registra el compilador y las herramientas que tocaron el archivo. Aquí se decodifican las dos cuando están presentes. Lo que esto no puede decirte es qué hace el módulo. La sección de código se mide, nunca se desensambla, y no se ejecuta nada, así que una propuesta usada solo dentro del cuerpo de una función no deja rastro salvo que aparezca también en una firma o en una bandera de límites.
Cómo usar
- Arrastra un archivo .wasm hasta el recuadro o haz clic para elegirlo. Se lee en la propia página y nunca sale de tu navegador.
- Empieza por la tabla de secciones: da el tamaño en bytes y el porcentaje del archivo de cada una, que es la respuesta a «por qué este módulo pesa tres megabytes».
- Lee las importaciones como el contrato que el anfitrión debe cumplir: módulo, nombre, tipo de entidad y la firma exacta que espera cada una.
- Lee las exportaciones como la API que puedes llamar desde JavaScript, con los tipos de parámetros y resultados tomados de la sección de tipos del módulo.
- Revisa el panel de memoria para ver las páginas inicial y máxima (una página son 65.536 bytes) y las secciones personalizadas por si conservan name o producers.
Preguntas frecuentes
- ¿Por qué pesa tanto mi archivo .wasm?
- Mira primero la tabla de secciones. En una compilación normal de Rust o C la sección de código es el 70-90 % del archivo, y entonces la solución honesta es tener menos código: menos instanciaciones genéricas, sin formateo de mensajes de pánico, pasar wasm-opt -Oz. Pero el reparto sorprende a menudo. Go incluye los datos estáticos de su runtime, así que su sección de datos es enorme; una compilación de emscripten con archivos incrustados es sobre todo datos; y una compilación que conservó la información de depuración DWARF la lleva en secciones personalizadas como .debug_info, que no se usan en tiempo de ejecución y son justo lo que elimina wasm-strip.
- ¿Qué significa una memoria de «17..∞ páginas»?
- La memoria de WebAssembly se cuenta en páginas de 65.536 bytes, así que un valor inicial de 17 páginas reserva alrededor de 1,1 MB en el momento de instanciar. El segundo número es el máximo declarado; cuando falta, el módulo puede crecer hasta donde permita el anfitrión, que en WebAssembly de 32 bits son 65.536 páginas, es decir 4 GiB. Declarar un máximo no promete austeridad: es un techo para el que el anfitrión puede reservar espacio de direcciones por adelantado. La memoria compartida que usan los hilos es el único caso en el que el máximo es obligatorio.
- ¿Por qué se llaman a, b, c todas las importaciones y exportaciones?
- Es emscripten con optimización y Closure activados. Los nombres de importación y exportación son cadenas dentro del binario, así que acortarlos reduce el archivo, y el pegamento de JavaScript que lo acompaña se minifica igual. En ejecución no se pierde nada, pero el binario deja de ser legible por sí solo. La sección personalizada name, que devuelve los índices a nombres del código fuente, se elimina en el mismo paso. Cuando sobrevive, esta herramienta la decodifica; cuando no, la sección producers suele ser lo único que queda para saber quién construyó el archivo.
- El módulo importa env y wasi_snapshot_preview1: ¿qué tengo que proporcionar?
- Cada importación es una casilla que tu objeto anfitrión debe rellenar, emparejada por nombre de módulo y nombre de campo, y con un tipo que el motor comprueba al instanciar. env es el espacio de nombres donde emscripten agrupa las llamadas del runtime de C que espera de JavaScript; wasi_snapshot_preview1 significa que el módulo se compiló contra WASI y necesita una capa de compatibilidad en el navegador; gojs es el puente del runtime de Go; y una ruta como ./algo_bg.js es wasm-bindgen esperando su módulo de pegamento generado. Si falta una entrada o cambia su aridad, WebAssembly.instantiate lanza un LinkError que la nombra.
- ¿Esto ejecuta el módulo o lo desensambla?
- Ninguna de las dos cosas. Analiza las declaraciones del módulo: secciones, tipos, importaciones, exportaciones, límites, el índice de la función start y las secciones personalizadas. Los cuerpos de las funciones se cuentan y se mide su tamaño total, pero sus instrucciones nunca se decodifican y no se instancia ningún motor, de modo que un módulo malicioso no puede hacer nada aquí. A cambio, el comportamiento es invisible: no sabrás qué calcula una función, qué importaciones se llaman de verdad, cuánta memoria acaba usando el módulo ni si hay instrucciones SIMD o atómicas dentro de un cuerpo.
- Si no lee el código, ¿cómo puede afirmar que usa tipos de referencia o multivalor?
- Porque algunas propuestas posteriores al MVP cambian la forma de la codificación, no solo el juego de instrucciones. Un tipo de función con dos o más resultados es multivalor; un externref en una firma o una segunda tabla son tipos de referencia; el tercer bit de las banderas de límites de una memoria es el bit de memoria compartida que exigen los hilos; una sección data count, o un segmento pasivo en lugar de activo, solo existe con bulk memory; y un v128 en una firma es SIMD. Se afirma solo lo que los bytes demuestran, por lo que la lista puede quedarse corta frente a lo que el módulo usa de verdad.
Herramientas relacionadas
Inspector de estructura GIF
Abre un .gif y lee sus bloques: retardos y descarte de cada fotograma, bucle, paletas, transparencia y cuántos bytes cuesta realmente cada fotograma.
Probador de EditorConfig
Pega un .editorconfig y unas rutas de archivo: verás las propiedades que resuelve cada archivo y qué sección y línea decidió cada valor.
Probador de .gitignore
Pega un .gitignore y una lista de rutas: verás cuáles se ignoran y qué línea lo decidió, igual que responde git check-ignore -v.
Inspector de binarios ELF
Abre un ejecutable de Linux, un .so o un .o en tu navegador: arquitectura, bibliotecas que necesita, Build ID y si trae PIE, NX y RELRO.
Inspector de archivos de fuente
Abre un .ttf, .otf o .woff y lee su nombre de familia real, el número de glifos y caracteres, la versión, el texto de licencia y los permisos de incrustación.
Validador de JSON Schema
Comprueba un documento JSON contra un JSON Schema en el navegador: cada error trae su JSON Pointer, la línea donde cae y la palabra clave que falló.