Como debugar Webassemly?

Debugar usando Print é um método muito popular de debug. Esse artigo mostra como implementar uma pequena, mas útil, estrutura de debug baseado em Print. Se você perdeu o artigo anterior que fala sobre debugar com o Webassembly, veja ele aqui.

Porque debug baseado em Print é tão popular?

Vantagens

  • Sem barreira de entrada, é gratuíto, e não necessita aprender uma ferramenta.
  • Permite debugar código que está rodando na máquina do usuário. Como se sabe, os bugs quase nunca aparecem na máquina do desenvolvedor.
  • É irônico, mas este método de debugar é a ferramenta que mostra para nós como o nosso código realmente se comporta. Não é raro ouvir: Humm, eu não esperava que o código passasse por aqui…
  • Não é necessário nenhum arquivo extra para debug, como arquivo de símbolos.

Desvantagens

  • Ele pode deixar o código fonte bagunçado, tornando-o difícil de seguir, isso assumindo que os Print statements são deixados no código, o que é usual.
  • O tamanho do código pode crescer fora de controle através dos anos, a menos que um esquema de debug/produção seja usado, com o build de produção não contendo os print statements.

Debugar com Print Pode ser Complicado

O kernel do Linux usa o debug por Print método, a funçao que implementa o print se chama printk(), k para kernel. Em Mar/2018. o módulo que implementa o printk contém mais que 3000 linhas de código C, e sua primeira versão é de 1991!

Como Ele se Parece?

O demo a seguir mostra o básico desta implementação. Há um botão que conta quantas vezes o botão foi clicado, o código que conta esta dentro de um módulo Webassembly. Há uma caixa de entrada que permite que entremos um nível de debug. Nesse exemplo, entrando um nível de debug 7 na caixa de entrada fará com que o console do Browser imprima mensagens de debug com nível até 7. Nos vamos debugar a contagem que acontece dentro do módulo Webassembly colocando algumas mensagens antes/depois que o contador é incrementado. Note que a mensagem de debug mostra algumas propriedades de sua localização: nome do arquivo C, função e linha.

Debug by printing in the browser

Nossa implementação do Debug por Print(Debug de Pobre)

A seguir está o conteúdo dos arquivos debug-wasm.h and debug-wasm.c. Para detalhes, veja os comentários no código.

  • Há uma definição principal que habilita ou desabilita o debug em todo o código. Quando DEBUG_WASM_ENABLED é definido como 0, nenhum código de debug faz parte do módulo Webassembly. Desta forma você pode remover todo o código de debug para a produção.
  • De forma parecida com a implementação printk() do Linux, ele define níveis de debug que podem ser associados com cada mensagem, desta forma é possível habilitar mensagens de debug com algum nível de debug específico, por exemplo, invocando Dbg_Set_Level() com DEBUG_WASM_ERR instruirá o módulo de debug para imprimir mensagens com níveis menores ou iguais a DEBUG_WASM_ERR.
  • Funções para escrever e ler os níveis de debug: Dbg_Set_Level() and Dbg_Get_Level()
  • A macro a ser colocada no seu código C/C++ para imprimir a mensagem de debug: DBG_LOG()

wasm-debug.h:


#include <stdarg.h>

// This define controls the debug message system at compilation time:
//  0-Disables the debug message system. If optimization is used(-Os,-Oz,...),
//      then the debug code will be optimized out because it becomes dead code.
// any other number enables it and leave the debug code ready for use
#define DEBUG_WASM_ENABLED  (1)

// Debug levels to be associated with each debug message,
// based on printk implemented in the linux kernel
// Only takes effect if DEBUG_WASM_ENABLED is not 0
typedef enum DEBUG_WASM_LEVELS
{
    DEBUG_WASM_EMERG =      0,
    DEBUG_WASM_ALERT =      1,
    DEBUG_WASM_CRIT =       2,
    DEBUG_WASM_ERR =        3,
    DEBUG_WASM_WARN =       4,
    DEBUG_WASM_NOTICE =     5,
    DEBUG_WASM_INFO =       6,
    DEBUG_WASM_DEBUG =      7
} DEBUG_WASM_LEVELS;

// Prototypes for set/get debug level, to be called from Javascript
void Dbg_Set_Level(const DEBUG_WASM_LEVELS level);
DEBUG_WASM_LEVELS Dbg_Get_Level(void);

// Main debug print function:
//  level-This debug message level
//  fmt-Message format
//  ...-Variable number of parameters with the message to be used
void Dbg_Printf(const DEBUG_WASM_LEVELS level, const char *fmt, ...);

// Main macro to be used in the wasm module.
//  It is a macro because it can then get information about the location where
//  it is placed, like file, line, function, etc...
//
#define DBG_LOG(level, fmt, ...) \
do \
{ \
    if (DEBUG_WASM_ENABLED) \
    { \
        Dbg_Printf(level,"%s:%d:%s(): " fmt, __FILE__, __LINE__, __func__, __VA_ARGS__); \
    } \
} while (0)

wasm-debug.c:


#include <emscripten.h>
#include "debug-wasm.h"

// Static variable that holds the current debug level for the entire wasm module
// By default only print messages with levels EMERG and lower
static DEBUG_WASM_LEVELS debugWasmLevel=DEBUG_WASM_EMERG;

//
// Sets a new debug level
//
EMSCRIPTEN_KEEPALIVE void Dbg_Set_Level(const DEBUG_WASM_LEVELS level)
{
    debugWasmLevel=level;
}

//
// Gets current debug level
//
EMSCRIPTEN_KEEPALIVE DEBUG_WASM_LEVELS Dbg_Get_Level(void)
{
    return debugWasmLevel;
}

//
// Main debug print function:
//  Note EMSCRIPTEN implements stderr to print into the Browser's console
//
void Dbg_Printf(const DEBUG_WASM_LEVELS level, const char *fmt, ...)
{
    // Only logs the message if the wasm debug level is equal or higher than
    // this message level
    if( level <= debugWasmLevel )
    {
        // Gets the variable argument list and feeds to vfprintf
        // EMSCRIPTEN logs writes to stderr to the console, exactly what we need
        va_list args;
        va_start(args, fmt);
        vfprintf(stderr, fmt, args);
        va_end(args);
    }
}

Colocando Mensagens de Debug Dentro do Seu Código Webassembly

Use a macro DBG_LOG. A parte mais difícil é escolher onde no seu código colocá-la e qual nível de debug utilizar. No exemplo abaixo eu coloco as mensagens antes e depois que a variável clicks é incrementada, eu uso o nível DEBUG pois não quero que estas mensagens apareçam no Browser do usuário todo o tempo, somente quando estiver debugando. No entanto, é razoável deixar algumas mensagens com nível de debug critical sempre habilitadas, pois gostaríamos de saber se e quando elas acontecem.

click-count.c


#include <emscripten.h>
#include "debug-wasm.h"

//
// This function will change the HTML value of a node(or element) that has id=countButton
//
// Important things to note:
//
// EMSCRIPTEN_KEEPALIVE
//  It forces this function to be present in the webassembly module.
//  There is no reference to this function in the module, the optimization would
//  wipe it out as it appears to be dead code.
//  This function is called from Javascript though
//
// EM_ASM_
//  EMSCRIPTEN macro that does all the interface with Javascript by us.
//  It creates a function inside the generated javascript file and call it from
//  here.
//  Generated function from the Javascript file:
//  var ASM_CONSTS = [function($0) { document.getElementById("countButton").value='Webassembly click count: '+$0 }];
// 
void EMSCRIPTEN_KEEPALIVE IncrementClickCountOnValue()
{
    static int clicks=0;
    
    // Log the content of variable clicks before it gets incremented.
    // Log message will only be shown if:
    //  -DEBUG_CODE_COMPILED_IN is not 0 (defined in debug-wasm.h) AND:
    //  -debugWasmLevel >= DEBUG_WASM_DEBUG
    DBG_LOG(DEBUG_WASM_DEBUG, "Variable clicks before=%d\n", clicks);
    
    // Here is where the variable clicks gets incremented
    EM_ASM_( {document.getElementById("countButton").value='Webassembly click count: '+$0}, ++clicks );
    
    // Log the content of variable clicks after it gets incremented
    DBG_LOG(DEBUG_WASM_DEBUG, "Variable clicks after=%d\n", clicks);
}

Aplicação de Demo

Implementa o que é mostrado na imagem gif no começo deste artigo, um botão, caixa de entrada, etc…:


<html>
<head>
  <meta charset="UTF-8">
  <script type="text/javascript" src="debug.js"></script>
  <script>
  
    // EMSCRIPTEN "-s MODULARIZED=1" option makes it easy to load a webassembly module,
    // it generates a .js file with a Module() function that does the job.
    // The .js file also puts some glue code that allows changing the DOM from within
    // the Webassembly code (through JavaScript)
    Module()
    .then(function(instance){
        var exports = instance['asm']; // the .js file puts the exports in the 'asm' field
        // Add button listener, calls Webassembly's IncrementClickCountOnValue() when clicked
        var button = document.getElementById('countButton');
        button.addEventListener('click', function() {
            exports._IncrementClickCountOnValue();
        }, false);
        // Add button listener, change Webassembly's debug level
        var input = document.getElementById('debugLevelInput');
        input.addEventListener('change', function() {
            exports._Dbg_Set_Level(input.value);
        }, false);
      }
  );
  </script>
</head>
<body>
  <input type="button" id="countButton" value="click here to see wasm changing this message"/>
  </br> Enter New Debug Level and Hit Enter: <input type="text" id="debugLevelInput" name="txt" value="">
</body>
</html>

Compilando e Rodando

Dois arquivos C combinados em um comando de compilação.


emcc click-count.c debug-wasm.c -O1 -s MODULARIZE=1 -s WASM=1 -o debug.js

Inicie o servidor EMSCRIPTEN:


emrun --no_browser --port 8080 .

Aponte o seu Browser para: http://localhost:8080/dom.html

Desafio

Olhe a macro DBG_LOG com cuidado. Voce notou que há um do {} while(0) em volta dos comandos de print? Porque isso está lá? A ser respondido num arquivo futuro.

Conclusão

O ambiente do Browser é bem apropriado para o método de debug por Print pois ele provê o console onde as mensagens podem ser loggadas, o método é gratuito e fácil de implementar. Não há razão para não tê-lo implementado.


Leave a message below. Webassembly is evolving rapidly, please let me know if this post got outdated.

Enjoyed this post?

Don't miss new posts: Share it with your friends:

Você pode gostar...

Deixe uma resposta

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *