Depura WebAssembly con herramientas modernas

Ingvar Stepanyan
Ingvar Stepanyan

El camino hasta ahora

Hace un año, Chrome anunció la compatibilidad inicial con la depuración nativa de WebAssembly en Chrome DevTools.

Demostramos la compatibilidad con los pasos básicos y hablamos sobre las oportunidades que nos brinda el uso de información de DWARF en lugar de mapas de origen en el futuro:

  • Cómo resolver nombres de variables
  • Tipos de impresión con formato estilístico
  • Evaluar expresiones en idiomas de origen
  • ...y mucho más

Hoy nos complace mostrar cómo cobran vida las funciones prometidas. y el progreso que lograron los equipos de Emscripten y Chrome DevTools con los este año, en particular, para las apps de C y C++.

Antes de comenzar, ten en cuenta que esta es una versión beta de la nueva experiencia. Debes usar la versión más reciente de todas las herramientas bajo tu propio riesgo. Si tienes algún problema, infórmalo en https://issues.chromium.org/issues/new?noWizard=true&template=0&component=1456350.

Empecemos con el mismo ejemplo de C simple de la ú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 compilarlo, usamos latest Emscripten. y pasa una marca -g, al igual que en la publicación original, para incluir información:

emcc -g temp.c -o temp.html

Ahora podemos entregar la página generada desde un servidor HTTP localhost (para ejemplo, con serve) ábrelo en la versión más reciente de Chrome Canary.

Esta vez, también necesitaremos una extensión de ayuda que se integre en DevTools de Chrome y la ayude a comprender toda la información de depuración codificada en el archivo WebAssembly. Instálala en esta Vínculo: goo.gle/wasm-debugging-extension

También querrás habilitar la depuración de WebAssembly en las Herramientas para desarrolladores Experimentos. Abre las Herramientas para desarrolladores de Chrome y haz clic en el ícono de ajustes () en la esquina superior derecha del panel de Herramientas para desarrolladores, ve al panel Experimentos (Experiments). y marca WebAssembly Debugging: Enable DWARF support.

Panel Experiments de la configuración de Herramientas para desarrolladores

Cuando cierres Settings, las Herramientas para desarrolladores te sugerirán que se vuelvan a cargar para aplicar la configuración, así que hagámoslo. Eso es todo para la configuración única.

Ahora, podemos volver al panel Sources, habilitar Pause on excepciones (⏸), luego marca Pausar en excepciones detectadas y vuelve a cargar la página. Deberías ver que las Herramientas para desarrolladores están en pausa en una excepción:

Captura de pantalla del panel Sources que muestra cómo habilitar la opción &quot;Detener en las excepciones detectadas&quot;

De forma predeterminada, se detiene en un código de unión generado por Emscripten, pero a la derecha, puedes ver una vista Call Stack que representa el seguimiento de pila del error y puedes navegar a la línea C original que invocó abort:

DevTools se detuvo en la función &quot;assert_less&quot; y muestra los valores de &quot;x&quot; e &quot;y&quot; en la vista de alcance.

Ahora, si miras en la vista Alcance, puedes ver los nombres originales. y valores de variables en el código C/C++, y ya no tendrás que determinar qué significan los nombres alterados como $localN y cómo se relacionan con el código fuente que escribiste.

Esto se aplica no solo a los valores primitivos, como los números enteros, sino también a los tipos compuestos, como las estructuras, las clases, los arrays, etcétera.

Compatibilidad con tipos enriquecidos

Veamos un ejemplo más complicado para mostrarlos. Esta vez, dibujaremos un fractal de Mandelbrot con el siguiente 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();
}

Puedes ver que esta aplicación aún es bastante pequeña que contiene 50 líneas de código, pero esta vez también usaré APIs externas, como la biblioteca de SDL para gráficos, así como números complejos de la C++ estándar.

Lo compilaré con la misma marca -g que está más arriba para incluirla información de depuración. Además, le pediré a Emscripten que proporcione el SDL2 y permiten una memoria de tamaño arbitrario:

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

Cuando visito la página generada en el navegador, puedo ver la hermosa forma fractal con algunos colores aleatorios:

Página de demostración

Cuando abro DevTools, una vez más, puedo ver el archivo C++ original. Esta Sin embargo, no hay errores en el código (¡Uf!), así que configuremos algún punto de interrupción al principio del código.

Cuando volvamos a cargar la página, el depurador se detendrá dentro de nuestra fuente de C++:

DevTools se detuvo en la llamada a &quot;SDL_Init&quot;.

Ya podemos ver todas nuestras variables a la derecha, pero solo width y height están inicializadas en este momento, por lo que no hay mucho que inspeccionar.

Establezcamos otro punto de interrupción dentro del bucle principal de Mandelbrot y reanudemos la ejecución se adelanta un poco.

DevTools se detuvo dentro de los bucles anidados

En este punto, nuestro palette se llenó con algunos colores aleatorios, y podemos expandir el array en sí, así como las estructuras SDL_Color individuales, e inspeccionar sus componentes para verificar que todo se vea bien (por ejemplo, que el canal "alpha" siempre esté configurado en opacidad completa). De manera similar, podemos expandir y verificar las partes reales e imaginarias del número complejo almacenado en la variable center.

Si deseas acceder a una propiedad anidada de forma profunda a la que, de otro modo, es difícil navegar a través de la vista Scope, también puedes usar la evaluación de Console. Sin embargo, ten en cuenta que aún no se admiten expresiones C++ más complejas.

Panel de la consola que muestra el resultado de &quot;palette[10].r&quot;

Reanudamos la ejecución algunas veces y veamos cómo está el x interno cambiando mirando de nuevo la vista Scope y agregando el nombre de la variable a la lista de observación, evaluándola en la consola o Coloca el cursor sobre la variable en el código fuente:

Información sobre herramientas sobre la variable &quot;x&quot; en la fuente que muestra su valor &quot;3&quot;

A partir de aquí, podemos recorrer o pasar instrucciones C++ y observar cómo otras variables también están cambiando:

Información sobre herramientas y vista de alcance que muestran los valores de &quot;color&quot;, &quot;point&quot; y otras variables

Todo esto funciona muy bien cuando hay información de depuración disponible, ¿qué sucede si queremos depurar un código que no se compiló con el opciones?

Depuración de WebAssembly sin procesar

Por ejemplo, le pedimos a Emscripten que nos proporcionara una biblioteca de SDL compilada previamente, en lugar de compilarla nosotros desde la fuente, por lo que, al menos por el momento, el depurador no tiene forma de encontrar fuentes asociadas. Volvamos a entrar en SDL_RenderDrawColor:

Herramientas para desarrolladores que muestran una vista de desensamblado de `mandelbrot.wasm`

Volvemos a la experiencia de depuración sin procesar de WebAssembly.

Ahora bien, puede parecer un poco aterrador y no es algo con lo que la mayoría de los desarrolladores web deban lidiar, pero, en ocasiones, es posible que desees depurar una biblioteca compilada sin información de depuración, ya sea porque es una biblioteca de terceros sobre la que no tienes control o porque te encuentras con uno de esos errores que solo se producen en producción.

Para ayudar en esos casos, también realizamos algunas mejoras en la experiencia básica de depuración.

En primer lugar, si antes usaste la depuración de WebAssembly sin procesar, es posible que notes que todo el desmontaje ahora se muestra en un solo archivo. Ya no es necesario adivinar a qué función corresponde una entrada wasm-53834e3e/ wasm-53834e3e-7 de Sources.

Nuevo esquema de generación de nombres

También mejoramos los nombres en la vista de desensamblado. Anteriormente, solo veías índices numéricos o, en el caso de las funciones, ningún nombre.

Ahora, generamos nombres de forma similar a otras herramientas de desensamblado, ya que con las sugerencias de la sección de nombres de WebAssembly las rutas de importación y exportación y, por último, si todo lo demás falla, según el tipo y el índice del elemento, como $func123. Puedes ver cómo, en la captura de pantalla anterior, esto ya ayuda a obtener seguimientos de pila y desmontaje un poco más legibles.

Cuando no hay información de tipo disponible, puede ser difícil inspeccionar cualquier valor además de las primitivas; por ejemplo, los punteros aparecerán como números enteros normales, sin forma de saber qué se almacena detrás de ellos en la memoria.

Inspección de memoria

Anteriormente, solo podías expandir el objeto de memoria de WebAssembly, representado por env.memory en la vista Scope para buscar. bytes individuales. Esto funcionó en algunas situaciones triviales, pero no fue particularmente conveniente para expandir y no permitían reinterpretar los datos en formatos que no sean valores de bytes. Agregamos una función nueva para ayudarte con esto: un inspector de memoria lineal.

Si haces clic con el botón derecho en env.memory, ahora deberías ver un nuevo elemento opción llamada Inspeccionar memoria:

Menú contextual en “env.memory” en el panel Scope que muestra una “Inspect Memory” elemento

Una vez que hagas clic, aparecerá un Inspector de memoria, en el que puedes inspeccionar la memoria de WebAssembly en vistas hexadecimales y ASCII, navegar a direcciones específicas y, además, interpretar los datos en diferentes formatos:

Panel del Inspector de memoria en DevTools que muestra vistas hexadecimales y ASCII de la memoria

Situaciones avanzadas y advertencias

Crea perfiles del código de WebAssembly

Cuando abres Herramientas para desarrolladores, el código de WebAssembly se “reduce” a una versión no optimizada para habilitar la depuración. Esta versión es mucho más lenta, lo que significa que no puedes confiar en console.time, performance.now y otros métodos para medir la velocidad de tu código mientras DevTools está abierta, ya que las cifras que obtengas no representarán el rendimiento real en absoluto.

En su lugar, debes usar el panel de rendimiento de Herramientas para desarrolladores. que ejecutará el código a máxima velocidad y te proporcionará desglose detallado del tiempo dedicado a diferentes funciones:

Panel de perfiles que muestra varias funciones de Wasm

Como alternativa, puedes ejecutar tu aplicación con Herramientas para desarrolladores cerrada y ábrelo cuando termines para inspeccionar la consola.

Mejoraremos los casos de generación de perfiles en el futuro, pero, por ahora, es un con lo que debes tener en cuenta. Si deseas obtener más información sobre las situaciones de nivelación de WebAssembly, consulta nuestra documentación sobre la canalización de compilación de WebAssembly.

Compilación y depuración en diferentes máquinas (incluidos Docker y host)

Cuando compiles en Docker, una máquina virtual o un servidor de compilación remoto, es probable que te encuentres con situaciones en las que las rutas de acceso a los archivos de origen que se usan durante la compilación no coincidan con las rutas de acceso de tu propio sistema de archivos en el que se ejecutan las herramientas para desarrolladores de Chrome. En este caso, los archivos aparecerán en el panel Sources, pero no se cargarán.

Para solucionar este problema, implementamos una funcionalidad de asignación de ruta en las opciones de extensión de C/C++. Puedes usarlo para volver a asignar rutas arbitrarias y ayudar a las herramientas para desarrolladores a ubicar fuentes.

Por ejemplo, si el proyecto en tu máquina host se encuentra en una ruta C:\src\my_project, pero se compiló dentro de un contenedor de Docker en el que esa ruta se representó como /mnt/c/src/my_project, puedes volver a asignarla durante la depuración especificando esas rutas como prefijos:

Página de opciones de la extensión de depuración de C/C++

El primer prefijo que coincida "gana". Si conoces otros tipos de lenguaje depuradores, esta opción es similar al comando set substitute-path en GDB o una configuración target.source-map en LLDB.

Cómo depurar compilaciones optimizadas

Al igual que con cualquier otro lenguaje, la depuración funciona mejor si las optimizaciones son inhabilitado. Las optimizaciones pueden intercalar funciones una dentro de otra, reordenar el código o quitar partes del código por completo, y todo esto puede confundir al depurador y, en consecuencia, a ti como usuario.

Si no te importa tener una experiencia de depuración más limitada y depurar una compilación optimizada, la mayoría de las optimizaciones funcionarán como esperado, excepto por la incorporación de funciones. Planeamos abordar los problemas restantes en el futuro, pero, por ahora, usa -fno-inline para inhabilitarlo cuando compiles con cualquier optimización a nivel de -O, p. ej.:

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

Separa la información de depuración

La información de depuración conserva muchos detalles sobre tu código, tipos definidos, variables, funciones, alcances y ubicaciones, todo lo que podría ser útil para el depurador. Como resultado, a menudo puede ser más grande que el código en sí.

Para acelerar la carga y la compilación del módulo de WebAssembly, te recomendamos que dividas esta información de depuración en un archivo WebAssembly independiente. Para hacerlo en Emscripten, pasa una marca -gseparate-dwarf=… con el nombre de archivo que desees:

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

En este caso, la aplicación principal solo almacenará un nombre de archivo temp.debug.wasm, y la extensión de ayuda podrá encontrarlo y cargarlo cuando abras DevTools.

Cuando se combina con optimizaciones como las descritas anteriormente, esta función puede usarse para enviar compilaciones de producción casi optimizadas de tu y, luego, depurarlas con un archivo lateral local. En este caso, además, necesitaremos anular la URL almacenada para que la extensión busca el archivo lateral, por ejemplo:

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]

Continuará…

¡Vaya! Esas son muchas funciones nuevas.

Con todas esas nuevas integraciones, las Herramientas para desarrolladores de Chrome potente y depurador no solo para JavaScript, sino también para apps de C y C++, por lo que es más fácil que nunca tomar apps, ya que están integradas en una variedad y llevarlas a una Web compartida multiplataforma.

Sin embargo, nuestro recorrido aún no ha terminado. Algunas de las funciones trabajando a partir de aquí:

  • Se corrigieron los problemas de la experiencia de depuración.
  • Se agregó compatibilidad con formateadores de tipos personalizados.
  • Estamos trabajando para mejorar la generación de perfiles para apps de WebAssembly.
  • Se agregó compatibilidad con la cobertura de código para facilitar la búsqueda de código no utilizado.
  • Se mejoró la compatibilidad con las expresiones en la evaluación de la consola.
  • Agregamos compatibilidad con más idiomas.
  • …y mucho más

Mientras tanto, ayúdanos probando la versión beta actual en tu propio código y, luego, informa cualquier problema que encuentres en https://issues.chromium.org/issues/new?noWizard=true&template=0&component=1456350.

Descarga los canales de vista previa

Considera usar Chrome Canary, Dev o Beta como navegadores de desarrollo predeterminados. Estos canales de vista previa te brindan acceso a las funciones más recientes de Herramientas para desarrolladores, te permiten probar API de plataformas web de vanguardia y te ayudan a encontrar problemas en tu sitio antes que los usuarios.

Comunícate con el equipo de Herramientas para desarrolladores de Chrome

Usa las siguientes opciones para hablar sobre las nuevas funciones, actualizaciones o cualquier otro aspecto relacionado con Herramientas para desarrolladores.