Inspetor de módulos WebAssembly
Um módulo WebAssembly são oito bytes de preâmbulo — a assinatura \0asm e uma versão — seguidos de uma sequência de seções, cada uma com um byte de identificador, um comprimento em LEB128 e exatamente esses bytes. Esta ferramenta percorre essa sequência dentro do navegador e mostra tudo o que o módulo declara sobre si mesmo: a versão, cada seção na ordem do arquivo com o tamanho em bytes, o deslocamento e a contagem de itens, as importações e exportações com o tipo completo, os limites de memória em páginas e em bytes, a função start quando existe e as seções personalizadas do fim. O detalhamento de tamanho costuma ser o motivo real para abrir um binário. Na maioria das compilações a seção de código é quase todo o arquivo, mas isso não é uma regra: um módulo em Go carrega uma seção de dados que passa de um megabyte, uma compilação do emscripten com arquivos embutidos é sobretudo dados, e uma compilação de depuração esconde o peso em seções personalizadas que não mudam nada do que o módulo faz. As importações são o contrato com o hospedeiro: o emscripten pede env e wasi_snapshot_preview1, o wasm-bindgen pede por caminho o seu arquivo de cola em JavaScript, o Go pede gojs, e uma única entrada não atendida é o LinkError que aparece na instanciação. As exportações são a superfície que você pode chamar, e cada assinatura é lida da seção de tipos em vez de deduzida do nome. Num binário sem símbolos, duas seções personalizadas valem mais do que todo o resto: name, que devolve os nomes do módulo e das funções, e producers, que registra o compilador e as ferramentas que passaram pelo arquivo. As duas são decodificadas aqui quando estão presentes. O que isto não diz é o que o módulo faz. A seção de código é medida, nunca desmontada, e nada é executado, então um recurso usado apenas dentro do corpo de uma função não deixa rastro a menos que também apareça numa assinatura ou num sinalizador de limites.
Como usar
- Arraste um arquivo .wasm para a caixa ou clique para escolher. Ele é lido na própria página e nunca sai do seu navegador.
- Comece pela tabela de seções: ela dá o tamanho em bytes e a fatia do arquivo de cada seção, que é a resposta para «por que este módulo tem três megabytes».
- Leia as importações como o contrato que o hospedeiro precisa cumprir: módulo, nome, espécie e a assinatura exata que cada uma espera.
- Leia as exportações como a API que você pode chamar do JavaScript, com os tipos de parâmetros e resultados tirados da seção de tipos do próprio módulo.
- Confira o painel de memória para as páginas inicial e máxima (uma página tem 65.536 bytes) e as seções personalizadas, caso ainda tragam name ou producers.
Perguntas frequentes
- Por que meu arquivo .wasm é tão grande?
- Olhe primeiro a tabela de seções. Numa compilação comum de Rust ou C a seção de código fica com 70-90 % do arquivo, e aí a solução honesta é ter menos código: menos instanciações genéricas, sem formatação de mensagens de pânico, rodar wasm-opt -Oz. Mas a divisão surpreende com frequência. O Go embute os dados estáticos do runtime, então a seção de dados é enorme; uma compilação do emscripten com arquivos embutidos é sobretudo dados; e uma compilação que manteve informação de depuração DWARF a carrega em seções personalizadas como .debug_info, que não são usadas em tempo de execução e são exatamente o que o wasm-strip remove.
- O que significa uma memória de «17..∞ páginas»?
- A memória do WebAssembly é contada em páginas de 65.536 bytes, então um valor inicial de 17 páginas reserva cerca de 1,1 MB no instante da instanciação. O segundo número é o máximo declarado; quando ele falta, o módulo pode crescer até onde o hospedeiro permitir, que no WebAssembly de 32 bits são 65.536 páginas, ou 4 GiB. Declarar um máximo não é promessa de economia: é um teto para o qual o hospedeiro pode reservar espaço de endereçamento de antemão. A memória compartilhada usada por threads é o único caso em que o máximo é obrigatório.
- Por que todas as importações e exportações se chamam a, b, c?
- É o emscripten com otimização e Closure ligados. Nomes de importação e exportação são cadeias dentro do binário, então encurtá-los diminui o arquivo, e a cola em JavaScript distribuída junto é minificada do mesmo jeito. Em execução nada se perde, mas o binário deixa de ser legível sozinho. A seção personalizada name, que devolve os índices aos nomes originais, é removida no mesmo passo. Quando sobrevive, esta ferramenta a decodifica; quando não, a seção producers costuma ser a única pista de quem construiu o arquivo.
- O módulo importa env e wasi_snapshot_preview1: o que preciso fornecer?
- Cada importação é um espaço que o seu objeto hospedeiro tem de preencher, casado por nome de módulo e nome de campo, com um tipo que o motor confere na instanciação. env é o espaço de nomes onde o emscripten junta os retornos de chamada do runtime de C que espera do JavaScript; wasi_snapshot_preview1 quer dizer que o módulo foi compilado para WASI e precisa de uma camada de compatibilidade no navegador; gojs é a ponte do runtime do Go; e um caminho como ./algo_bg.js é o wasm-bindgen esperando o módulo de cola que ele gerou. Se faltar uma entrada ou a aridade não bater, WebAssembly.instantiate lança um LinkError citando o nome.
- Isto executa o módulo ou o desmonta?
- Nenhum dos dois. Ele interpreta as declarações do módulo: seções, tipos, importações, exportações, limites, o índice da função start e as seções personalizadas. Os corpos das funções são contados e o tamanho total é medido, mas as instruções nunca são decodificadas e nenhum motor é instanciado, de modo que um módulo malicioso não faz nada aqui. Em troca, o comportamento fica invisível: não dá para saber o que uma função calcula, quais importações são de fato chamadas, quanta memória o módulo usa na prática, nem se há instruções SIMD ou atômicas dentro de um corpo.
- Se não lê o código, como pode afirmar que há tipos de referência ou multivalor?
- Porque algumas propostas posteriores ao MVP mudam o formato da codificação, e não apenas o conjunto de instruções. Um tipo de função com dois ou mais resultados é multivalor; um externref numa assinatura ou uma segunda tabela são tipos de referência; o terceiro bit dos sinalizadores de limites de uma memória é o bit de memória compartilhada que as threads exigem; uma seção data count, ou um segmento passivo em vez de ativo, só existe com bulk memory; e um v128 numa assinatura é SIMD. Só se afirma o que os bytes provam, e por isso a lista pode ser menor do que o que o módulo realmente usa.
Ferramentas relacionadas
Inspetor de estrutura GIF
Abra um .gif e leia seus blocos: atraso e descarte de cada quadro, repetição, paletas, transparência e quantos bytes cada quadro realmente custa.
Testador de EditorConfig
Cole um .editorconfig e alguns caminhos de arquivo: veja as propriedades que cada arquivo resolve e qual seção, em qual linha, decidiu cada valor.
Testador de .gitignore
Cole um .gitignore e uma lista de caminhos: veja quais são ignorados e qual linha decidiu, como responde o git check-ignore -v.
Inspetor de binários ELF
Abra um executável Linux, um .so ou um .o no navegador: arquitetura, bibliotecas necessárias, Build ID e se tem PIE, NX e RELRO.
Inspetor de arquivos de fonte
Abra um .ttf, .otf ou .woff e leia o nome de família real, a contagem de glifos e caracteres, a versão, o texto da licença e as permissões de incorporação.
Validador de JSON Schema
Verifique um documento JSON contra um JSON Schema no navegador: cada erro traz o JSON Pointer, a linha onde cai e a palavra-chave que falhou.