Depura WebAssembly con herramientas modernas

Ingvar Stepanyan
Ingvar Stepanyan

El camino recorrido hasta ahora

Hace un año, Chrome anunció la compatibilidad inicial para la depuración nativa de WebAssembly en las Herramientas para desarrolladores de Chrome.

Demostramos apoyo básico para dar pasos y hablamos sobre oportunidades el uso de información DWARF en lugar de los mapas de origen estarán disponibles para nosotros en el futuro:

  • Cómo resolver nombres de variables
  • Tipos de impresión con formato
  • 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.

Comencemos con el mismo ejemplo simple de C que usamos 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 de localhost (por ejemplo, con serve) y abrirla 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, haz clic en el ícono de ajustes () en la esquina superior derecha del panel de DevTools, ve al panel Experiments y marca WebAssembly Debugging: Enable DWARF support.

Panel Experiments de la configuración de DevTools

Cuando cierres Configuración, DevTools te sugerirá que se vuelva 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 adhesión generado por Emscripten, pero en la a la derecha, podrás ver una vista de Call Stack que representa el seguimiento de pila de el error, y 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 mostrarlas. 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 sigue siendo bastante pequeña (es un solo archivo que contiene 50 líneas de código), pero esta vez también uso algunas APIs externas, como la biblioteca SDL para gráficos, así como números complejos de la biblioteca estándar de C++.

La compilaré con la misma marca -g que antes para incluir información de depuración y también le pediré a Emscripten que proporcione la biblioteca SDL2 y permita 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. Sin embargo, esta vez, no tenemos un error en el código (uf), así que establezcamos algunos puntos de interrupción al comienzo de nuestro código.

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

Las Herramientas para desarrolladores se pausaron en la llamada `SDL_Init`

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

Establezcamos otro punto de interrupción dentro de nuestro bucle principal de Mandelbrot y reanudemos la ejecución para avanzar un poco.

Herramientas para desarrolladores pausadas 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). Del mismo modo, podemos expandir y verificar los valores reales partes imaginarias del número complejo almacenadas 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:

Cuadro de información sobre la variable `x` en la fuente que muestra su valor `3`

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 valores de &quot;color&quot;, &quot;punto&quot; y otras variables

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

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. Regresemos a 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, hemos realizado algunas mejoras en la versión 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 desmontaje. 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. También agregamos una nueva función para ayudar 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 &quot;env.memory&quot; en el panel de alcance que muestra un elemento &quot;Inspect Memory&quot;

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 y advertencias avanzadas

Crea perfiles del código de WebAssembly

Cuando abres Herramientas para desarrolladores, el código de WebAssembly “disminuye de nivel” a una 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. abiertos, ya que los números que obtenga no representarán el rendimiento real en absoluto.

En su lugar, debes usar el panel Rendimiento de DevTools, que ejecutará el código a la velocidad máxima y te proporcionará un desglose detallado del tiempo dedicado a las diferentes funciones:

Panel de perfiles que muestra varias funciones de Wasm

Como alternativa, puedes ejecutar tu aplicación con DevTools cerrado y abrirlo una vez que termines para inspeccionar Console.

Mejoraremos las situaciones de generación de perfiles en el futuro, pero, por ahora, ten en cuenta esta advertencia. Si quieres obtener más información sobre WebAssembly situaciones de niveles, consulta nuestros documentos sobre 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 Sources, pero no se pudo cargar.

Para solucionar este problema, implementamos una funcionalidad de asignación de rutas en las opciones de extensión C/C++. Puedes usarlo para reasignar rutas arbitrarias y ayudar a las Herramientas para desarrolladores a encontrar 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 se inhabilitan las optimizaciones. 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 aún quieres depurar una compilación optimizada, la mayoría de las optimizaciones funcionarán como se espera, excepto la inserción de funciones. Planeamos abordar las restantes problemas en el futuro, pero, por ahora, usa -fno-inline para inhabilítala cuando realices compilaciones con cualquier optimización de nivel -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, definidos tipos, variables, funciones, alcances y ubicaciones: cualquier cosa que pueda serán útiles para el depurador. Por eso, suele ser más grande código en sí mismo.

Para acelerar la carga y la compilación del módulo de WebAssembly, puedes dividir esta información de depuración en un archivo WebAssembly separado . Para hacerlo en Emscripten, pasa una marca -gseparate-dwarf=… con el nombre de archivo deseado:

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

En este caso, la aplicación principal solo almacenará el nombre de archivo temp.debug.wasm y la extensión auxiliar podrá localizar y y cargarlo cuando abras Herramientas para desarrolladores.

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, tendremos que anular la URL almacenada para ayudar a la extensión a encontrar 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 integraciones nuevas, Chrome DevTools se convierte en un depurador viable y potente, no solo para JavaScript, sino también para apps de C y C++, lo que facilita más que nunca tomar apps compiladas en una variedad de tecnologías y llevarlas a una Web compartida y multiplataforma.

Sin embargo, nuestro viaje aún no termina. Estas son algunas de las funciones en las que trabajaremos a partir de ahora:

  • Limpiar los rugosidades de la experiencia de depuración
  • Se agregó compatibilidad con formateadores de tipos personalizados.
  • Se están realizando mejoras en 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, prueban API de plataformas web de vanguardia y detectan problemas en tu sitio antes que los usuarios.

Comunícate con el equipo de Chrome DevTools

Usa las siguientes opciones para analizar las nuevas funciones y los cambios en la publicación, o cualquier otro tema relacionado con DevTools.