Depura WebAssembly con herramientas modernas

Ingvar Stepanyan
Ingvar Stepanyan

El camino 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:

  • Resuelve 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 todavía 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 y, si tienes algún problema, infórmalo a https://bugs.chromium.org/p/chromium/issues/entry?template=DevTools+issue.

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 con Chrome. las Herramientas para desarrolladores y le ayuda a comprender toda la información de depuración codificadas 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 por una sola vez configuración.

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 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:

Las Herramientas para desarrolladores se pausaron en la función `assert_less` y se mostraron los valores de `x` y `y` en la vista Scope

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 valores primitivos como números enteros, como estructuras, clases, arrays, etc.

Compatibilidad con tipos enriquecidos

Veamos un ejemplo más complicado para mostrarlas. Esta 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 el anterior para incluir 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 Herramientas para desarrolladores, una vez más, puedo ver el archivo C++ original. Esta Sin embargo, no hay errores en el código (¡Uf!), así que algún punto de interrupción al principio del 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 del bucle principal de Mandelbrot y reanudemos ejecución se adelanta 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 tanto la matriz en sí, como SDL_Color e inspecciona sus componentes para verificar lo siguiente: todo se ve bien (por ejemplo, ese canal "alfa" siempre está configurado a la opacidad completa). Del mismo modo, podemos expandir y verificar los valores reales partes imaginarias del número complejo almacenadas en la variable center.

Si quieres acceder a una propiedad profundamente anidada que es difícil de navega a través de la vista Alcance, puedes usar la consola y la evaluación de los resultados. Sin embargo, ten en cuenta que las expresiones C++ más complejas no son todavía es compatible.

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 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 proporcione una biblioteca SDL compilada previamente para en lugar de compilarlos nosotros mismos desde la fuente, por lo que, al menos, actualmente, no hay forma de que el depurador encuentre 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 de WebAssembly sin procesar.

Se ve un poco aterrador y no es algo que la mayoría de los desarrolladores web necesitas manejar, pero es posible que, en ocasiones, quieras depurar un compilada sin información de depuración, ya sea porque es un de terceros sobre la que no tienes control o porque uno de esos errores, que solo ocurre 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, Observa que todo el desensamblado ahora se muestra en un solo archivo más para adivinar a qué función corresponde una entrada de Sources wasm-53834e3e/ wasm-53834e3e-7

Nuevo esquema de generación de nombres

También mejoramos los nombres en la vista de desensamblado. Anteriormente, verías solo índices numéricos o, en el caso de 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 Observa cómo, en la captura de pantalla anterior, esto ya ayuda a obtener seguimientos de pila y desensamblado más legibles.

Cuando no hay información disponible sobre el tipo, puede ser difícil inspeccionar cualquier valor además de las primitivas. Por ejemplo, se mostrarán punteros como números enteros regulares, sin forma de saber qué se almacena detrás de ellos 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

Cuando hagas clic, se abrirá un Inspector de memoria. en la que puede inspeccionar la memoria de WebAssembly en vistas hexadecimales y ASCII, navegar a direcciones específicas e interpretar los datos de formatos diferentes:

Panel del Inspector de memoria en Herramientas para desarrolladores 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 “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 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 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 compilas en Docker, en una máquina virtual o en un servidor de compilación remoto, es probable que te encuentres con situaciones en las que las rutas a los archivos fuente usadas durante la compilación no coinciden con las rutas de tu propio sistema de archivos donde se estén ejecutando 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 anfitrión se encuentra en una ruta de acceso 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 reasignar 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 coincidente “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 a otra, reordenar código o quitar partes de él por completo, y todo esto tiene un la posibilidad de 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 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, 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í:

  • Limpiar los extremos 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.
  • Agregamos compatibilidad con la cobertura de código para que sea más fácil de encontrar código sin usar.
  • Se mejoró la compatibilidad con 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 e informando los hallazgos problemas a https://bugs.chromium.org/p/chromium/issues/entry?template=DevTools+issue.

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 plataforma web de vanguardia y detectan problemas en tu sitio antes que los usuarios.

Comunicarse con el equipo de Herramientas para desarrolladores de Chrome

Usa las siguientes opciones para hablar sobre las nuevas funciones y los cambios en la publicación, o cualquier otra cosa relacionada con Herramientas para desarrolladores.

  • Para enviarnos sugerencias o comentarios, accede a crbug.com.
  • Informa un problema en Herramientas para desarrolladores con Más opciones   Más > Ayuda > Informa problemas de Herramientas para desarrolladores en Herramientas para desarrolladores.
  • Twittea a @ChromeDevTools.
  • Deja comentarios en nuestros videos de YouTube de Herramientas para desarrolladores o en videos de YouTube de las Sugerencias de las Herramientas para desarrolladores.