Como depurar o WebAssembly com ferramentas modernas

Ingvar Stepanyan
Ingvar Stepanyan

A estrada até agora

Há um ano, o Chrome anunciou o suporte inicial. para depuração nativa do WebAssembly no Chrome DevTools.

Demonstramos suporte básico de caminhada e conversamos sobre oportunidades uso de informações DWARF em vez de mapas de origem serão abertos no futuro:

  • Como resolver nomes de variáveis
  • Tipos de estilo de formatação
  • Avaliação de expressões nos idiomas de origem
  • …e muito mais!

Hoje, estamos felizes em mostrar que os recursos prometidos ganham vida e o progresso feito pelas equipes de Emscripten e Chrome DevTools em especial para apps em C e C++.

Antes de começarmos, tenha em mente que esta ainda é uma versão Beta da nova experiência, você precisa usar a versão mais recente de todas as ferramentas por sua conta e risco. Se você encontrar algum problema, informe-o para https://issues.chromium.org/issues/new?noWizard=true&template=0&component=1456350.

Vamos começar com o mesmo exemplo simples de C da última vez:

#include <stdlib.h>

void assert_less(int x, int y) {
  if (x >= y) {
    abort();
  }
}

int main() {
  assert_less(10, 20);
  assert_less(30, 20);
}

Para compilá-lo, usamos a versão mais recente do Emscripten (em inglês). e transmitir uma flag -g, assim como na postagem original, para incluir instruções informações:

emcc -g temp.c -o temp.html

Agora podemos exibir a página gerada de um servidor HTTP localhost (por exemplo, com serve), e abra-o na versão mais recente do Chrome Canary.

Desta vez, também vamos precisar de uma extensão auxiliar que se integre ao Chrome. DevTools e o ajuda a entender todas as informações de depuração codificado no arquivo WebAssembly. Para instalar, acesse link: goo.gle/wasm-debugging-extension

Além disso, ative a depuração do WebAssembly no DevTools Experimentos. Abra o Chrome DevTools e clique no ícone de engrenagem () No canto superior direito do painel do DevTools, acesse o painel Experimentos. e marque WebAssembly Debugging: ativar DWARF support.

Painel de experimentos das configurações do DevTools

Quando você fechar as Configurações, o DevTools vai sugerir a atualização automática para aplicar configurações, vamos fazer isso. Isso é tudo o que foi feito uma vez. configuração da infraestrutura.

Agora podemos voltar ao painel Origens, ativar Pausar em exceções (ícone ⏸), marcar Pausar em exceções detectadas e recarregar a página. O DevTools vai ser pausado em uma exceção:

Captura de tela do painel &quot;Sources&quot; mostrando como ativar a opção &quot;Pausar em exceções capturadas&quot;

Por padrão, ele para em um código de cola gerado pelo Emscripten, mas à direita, você pode ver uma visualização Call Stack que representa o stacktrace do erro e pode navegar até a linha C original que invocou abort:

O DevTools foi pausado na função &quot;assert_less&quot; e mostra os valores de &quot;x&quot; e &quot;y&quot; na visualização de escopo.

Na visualização Escopo, você vai encontrar os nomes originais e valores de variáveis no código C/C++, e não precisar mais descobrir o que significa nomes distorcidos como $localN e como eles se relacionam com o o código-fonte criado por você.

Isso se aplica não apenas a valores primitivos, como números inteiros, mas também a tipos compostos, como estruturas, classes, matrizes etc.

Suporte a tipos avançados

Vejamos um exemplo mais complicado para mostrar esses casos. Isso vamos desenhar um fractal de Mandelbrot com seguinte código C++:

#include <SDL2/SDL.h>
#include <complex>

int main() {
  // Init SDL.
  int width = 600, height = 600;
  SDL_Init(SDL_INIT_VIDEO);
  SDL_Window* window;
  SDL_Renderer* renderer;
  SDL_CreateWindowAndRenderer(width, height, SDL_WINDOW_OPENGL, &window,
                              &renderer);

  // Generate a palette with random colors.
  enum { MAX_ITER_COUNT = 256 };
  SDL_Color palette[MAX_ITER_COUNT];
  srand(time(0));
  for (int i = 0; i < MAX_ITER_COUNT; ++i) {
    palette[i] = {
        .r = (uint8_t)rand(),
        .g = (uint8_t)rand(),
        .b = (uint8_t)rand(),
        .a = 255,
    };
  }

  // Calculate and draw the Mandelbrot set.
  std::complex<double> center(0.5, 0.5);
  double scale = 4.0;
  for (int y = 0; y < height; y++) {
    for (int x = 0; x < width; x++) {
      std::complex<double> point((double)x / width, (double)y / height);
      std::complex<double> c = (point - center) * scale;
      std::complex<double> z(0, 0);
      int i = 0;
      for (; i < MAX_ITER_COUNT - 1; i++) {
        z = z * z + c;
        if (abs(z) > 2.0)
          break;
      }
      SDL_Color color = palette[i];
      SDL_SetRenderDrawColor(renderer, color.r, color.g, color.b, color.a);
      SDL_RenderDrawPoint(renderer, x, y);
    }
  }

  // Render everything we've drawn to the canvas.
  SDL_RenderPresent(renderer);

  // SDL_Quit();
}

Este aplicativo ainda é bem pequeno, ele é um de código aberto com 50 linhas de código, mas dessa vez também APIs externas, como a biblioteca SDL para gráficos, bem como números complexos do biblioteca C++ padrão.

Vou compilá-lo com a mesma flag -g acima para incluir informações de depuração. Também vou pedir que o Emscripten forneça os e permitem memória dimensionada arbitrariamente:

emcc -g mandelbrot.cc -o mandelbrot.html \
     -s USE_SDL=2 \
     -s ALLOW_MEMORY_GROWTH=1

Quando acesso a página gerada no navegador, posso ver a bela forma fractal com algumas cores aleatórias:

Página de demonstração

Quando abro o DevTools, novamente, vejo o arquivo C++ original. Isso momento, porém, não temos um erro no código (uau!), então vamos definir algum ponto de interrupção no início do nosso código.

Quando a página for recarregada novamente, o depurador será pausado dentro da Fonte C++:

DevTools pausado na chamada &quot;SDL_Init&quot;

Já podemos ver todas as variáveis à direita, mas apenas width e height são inicializados no momento, então não há muito o que ser inspecionado.

Vamos definir outro ponto de interrupção dentro do loop de Mandelbrot principal e retomar a execução para avançar um pouco.

DevTools pausadas dentro dos loops aninhados

Neste ponto, o palette foi preenchido com algumas cores aleatórias. e podemos expandir a matriz e as colunas individuais SDL_Color estrutura e inspecione seus componentes para verificar se está tudo certo (por exemplo, o canal "alfa" está sempre definido para opacidade total). Da mesma forma, podemos expandir e verificar partes imaginárias do número complexo armazenado na variável center.

Se você quiser acessar uma propriedade profundamente aninhada que é difícil de navegar pela visualização Escopo, use a avaliação do Console. No entanto, expressões C++ mais complexas ainda não são compatíveis.

Painel do console mostrando o resultado de &quot;palette[10].r&quot;

Vamos retomar a execução algumas vezes e conferir como o x interno está analisando novamente a visualização Escopo, adicionando o nome da variável à lista de observação, avaliando-a no console ou passe o cursor sobre a variável no código-fonte:

dica sobre a variável &quot;x&quot; na origem mostrando o valor &quot;3&quot;

A partir daqui, podemos inserir ou ignorar instruções em C++ e observar como outras variáveis também estão mudando:

Dicas e visualização de escopo mostrando os valores de &quot;color&quot;, &quot;point&quot; e outras variáveis

Tudo isso funciona muito bem quando há informações de depuração disponíveis, mas e se quisermos depurar um código que não foi criado com as opções de depuração?

Depuração de WebAssembly bruto

Por exemplo, pedimos ao Emscripten para fornecer uma biblioteca SDL pré-criada para em vez de compilá-los nós mesmos da fonte, pelo menos não há como o depurador encontrar fontes associadas. Vamos entrar novamente no SDL_RenderDrawColor:

DevTools mostrando a visualização de desmontagem de &quot;mandelbrot.wasm&quot;

Voltamos à experiência de depuração bruta do WebAssembly.

Isso parece um pouco assustador e não é algo que a maioria dos desenvolvedores Web mas você pode querer depurar um criada sem informações de depuração, seja por um Biblioteca de terceiros sobre a qual você não tem controle ou porque encontrar um desses bugs que só ocorrem na produção.

Para ajudar nesses casos, realizamos algumas melhorias e a experiência de depuração.

Em primeiro lugar, se você já usou a depuração RAW do WebAssembly antes, pode observe que todo o desmontagem agora é mostrado em um único arquivo, mais adivinhando a qual função uma entrada wasm-53834e3e/ wasm-53834e3e-7 de Sources possivelmente corresponde.

Novo esquema de geração de nomes

Também melhoramos os nomes na visualização de desmontagem. Antes, você só via índices numéricos ou, no caso de funções, nenhum nome.

Agora estamos gerando nomes de forma semelhante a outras ferramentas de desmontagem, usando dicas da seção de nome da WebAssembly, caminhos de importação/exportação e, por fim, se tudo mais falhar, gerando com base no tipo e no índice do item, como $func123. Você pode veja como, na captura de tela acima, isso já ajuda a obter um pouco stack traces e desmontagem mais legíveis.

Quando não há informações de tipo disponíveis, pode ser difícil inspecionar valores além dos primitivos, por exemplo, ponteiros serão exibidos como números inteiros regulares, sem a possibilidade de saber o que está armazenado por trás deles memória.

Inspeção da memória

Antes, só era possível expandir o objeto de memória do WebAssembly, representado por env.memory na visualização Scope, para pesquisar. bytes individuais. Isso funcionou em alguns cenários triviais, mas não foi muito conveniente para expandir e não permitiam reinterpretar dados em formatos diferentes de valores de bytes. Também adicionamos um novo recurso para ajudar com isso: um inspetor de memória linear.

Se você clicar com o botão direito do mouse em env.memory, uma nova opção chamada Inspecionar memória vai aparecer:

Menu de contexto em &quot;env.memory&quot; no painel &quot;Escopo&quot; mostrando uma mensagem &quot;Inspecionar memória&quot; item

Ao clicar, um Memory Inspector será aberto. Nele, é possível inspecionar a memória do WebAssembly em visualizações hexadecimais e ASCII, navegar até endereços específicos e interpretar os dados em diferentes formatos:

Painel do Inspetor de memória no DevTools mostrando visualizações hexadecimais e ASCII da memória

Cenários avançados e advertências

Como criar o perfil do código WebAssembly

Quando você abre o DevTools, o código do WebAssembly é "recolhido" para um não otimizada para ativar a depuração. Essa versão é muito mais lenta, o que significa que você não pode confiar em console.time, performance.now e outros métodos para medir a velocidade do código enquanto o DevTools aberto, pois os números obtidos não representam o desempenho real de verdade.

Em vez disso, use o painel Performance do DevTools. que vai executar o código na velocidade máxima e detalhamento do tempo gasto em diferentes funções:

Painel de criação de perfil mostrando várias funções do Wasm

Como alternativa, é possível executar o aplicativo com o DevTools fechado. abra-os ao terminar para inspecionar o console.

Vamos melhorar os cenários de criação de perfil no futuro, mas, por enquanto, é importante considerar isso. Se você quiser saber mais sobre cenários de hierarquia do WebAssembly, consulte a documentação sobre o pipeline de compilação do WebAssembly.

Criar e depurar em diferentes máquinas (incluindo Docker / host)

Ao criar em um Docker, uma máquina virtual ou um servidor de build remoto, você provavelmente terá situações em que os caminhos para os arquivos de origem usados durante a criação não correspondem aos caminhos no seu sistema de arquivos em que o Chrome DevTools está em execução. Nesse caso, os arquivos aparecerão Sources, mas falha ao carregar.

Para corrigir esse problema, implementamos uma funcionalidade de mapeamento de caminho nas opções de extensão do C/C++. É possível usá-la para remapear caminhos arbitrários e ajudam o DevTools a localizar as fontes.

Por exemplo, se o projeto na máquina host estiver em um caminho C:\src\my_project, mas tiver sido criado em um contêiner do Docker em que esse caminho foi representado como /mnt/c/src/my_project, ele poderá ser remapeado durante a depuração especificando esses caminhos como prefixos:

Página de opções da extensão de depuração C/C++

O primeiro prefixo correspondente "vence". Se você conhece outras linguagens de programação depuradores, essa opção é semelhante ao comando set substitute-path no GDB ou em uma configuração target.source-map no LLDB.

Depurar builds otimizados

Como em qualquer outra linguagem, a depuração funciona melhor se as otimizações forem desativado. As otimizações podem funcionar em linha umas nas outras, reordenar ou remover partes dele completamente, e tudo isso tem pode confundir o depurador e, consequentemente, você como usuário.

Se você não se importar com uma experiência de depuração mais limitada e ainda quiser depurar um build otimizado, a maioria das otimizações funcionará o máximo possível esperado, exceto para função inline. Planejamos resolver os problemas restantes no futuro, mas, por enquanto, use -fno-inline para desativá-lo ao compilar com qualquer otimização de nível -O, por exemplo:

emcc -g temp.c -o temp.html \
     -O3 -fno-inline

Como separar as informações de depuração

As informações de depuração preservam diversos detalhes do código, definidos variáveis, funções, escopos e locais, qualquer coisa que possa ser útil para o depurador. Como resultado, muitas vezes pode ser maior do que o próprio código.

Para acelerar o carregamento e a compilação do módulo WebAssembly, você pode dividir as informações de depuração em um arquivo WebAssembly . Para fazer isso no Emscripten, transmita uma sinalização -gseparate-dwarf=… com um nome de arquivo desejado:

emcc -g temp.c -o temp.html \
     -gseparate-dwarf=temp.debug.wasm

Nesse caso, o aplicativo principal vai armazenar apenas um nome de arquivo temp.debug.wasm, e a extensão auxiliar poderá localizá-lo e carregá-lo quando você abrir o DevTools.

Quando combinado com otimizações como as descritas acima, esse recurso pode ser usado para enviar versões de produção quase otimizadas do seu e depois depure-os com um arquivo secundário local. Nesse caso, substituiremos o URL armazenado para ajudar a extensão encontre o arquivo lateral, por exemplo:

emcc -g temp.c -o temp.html \
     -O3 -fno-inline \
     -gseparate-dwarf=temp.debug.wasm \
     -s SEPARATE_DWARF_URL=file://[local path to temp.debug.wasm]

Continua depois…

Ufa! Foram muitos recursos novos!

Com todas essas novas integrações, o Chrome DevTools se torna um poderoso, o depurador não apenas para JavaScript, mas também para aplicativos C e C++, o que facilita muito o uso de apps, criados em uma variedade de tecnologias e trazê-las para uma Web compartilhada e entre plataformas.

No entanto, nossa jornada ainda não terminou. Algumas das coisas que trabalhando daqui para frente:

  • Limpar os pontos embasados na experiência de depuração.
  • Adição de suporte a formatadores de tipo personalizados.
  • Estamos trabalhando em melhorias na criação de perfis para apps do WebAssembly.
  • Adição de suporte à cobertura de código para facilitar a localização códigos não utilizados.
  • Melhoria no suporte a expressões na avaliação do console.
  • Adição do suporte a mais idiomas.
  • … e mais!

Enquanto isso, ajude-nos testando a versão Beta atual com seu próprio código e informando qualquer erro encontrado problemas para https://issues.chromium.org/issues/new?noWizard=true&amp;template=0&amp;component=1456350.

Fazer o download dos canais de visualização

Use o Chrome Canary, Dev ou Beta como seu navegador de desenvolvimento padrão. Esses canais de pré-visualização dão acesso aos recursos mais recentes do DevTools, testam APIs modernas da plataforma Web e encontram problemas no seu site antes que os usuários o façam!

Entrar em contato com a equipe do Chrome DevTools

Use as opções a seguir para discutir os novos recursos e mudanças na postagem ou qualquer outro assunto relacionado ao DevTools.

  • Envie uma sugestão ou feedback pelo site crbug.com.
  • Informe um problema do DevTools usando Mais opções   Mais > Ajuda > Relate problemas no DevTools no DevTools.
  • Tweete para @ChromeDevTools.
  • Deixe comentários nos vídeos do YouTube sobre as novidades do DevTools ou nos vídeos do YouTube com dicas sobre o DevTools.