El camino recorrido 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
- Cómo evaluar expresiones en idiomas de origen
- …y mucho más.
Hoy, nos complace mostrar las funciones prometidas que cobran vida y el progreso que los equipos de Emscripten y Chrome DevTools han logrado a lo largo de este año, en particular, para las apps de C y C++.
Antes de comenzar, ten en cuenta que esta sigue siendo 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 la versión más reciente de Emscripten y pasamos una marca -g
, como en la publicación original, para incluir información de depuració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. Para instalarla, ve a este vínculo: goo.gle/wasm-debugging-extension
También querrás habilitar la depuración de WebAssembly en Experiments de DevTools. 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.
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 exceptions (ícono ⏸), luego marcar Pause on caught exceptions y volver a cargar la página. Deberías ver DevTools detenido en una excepción:
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
:
Ahora, si miras en la vista Alcance, puedes ver los nombres originales
y los valores de las variables en el código C/C++, y ya no tienes que averiguar
qué significan nombres dañados 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 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:
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 detendrá dentro de nuestra fuente de C++:
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 de nuestro bucle principal de Mandelbrot y reanudemos la ejecución para avanzar un poco.
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 Alcance, 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.
Resumamos la ejecución varias veces y podremos ver cómo también cambia el x
interno. Para ello, podemos volver a mirar la vista Scope, agregar el nombre de la variable a la lista de vigilancia, evaluarla en la consola o colocar el cursor sobre la variable en el código fuente:
Desde aquí, podemos ingresar o omitir instrucciones de C++ y observar cómo también cambian 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.
Volvamos a entrar en SDL_RenderDrawColor
:
Volvemos a la experiencia de depuración sin procesar de WebAssembly.
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. No es necesario adivinar a qué función puede corresponder 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 manera similar a otras herramientas de desmontaje. Para ello, usamos sugerencias de la sección de nombres de WebAssembly, las rutas de importación/exportación y, por último, si todo lo demás falla, los generamos en función del 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 era particularmente conveniente para expandirse y no permitía reinterpretar los datos en formatos distintos de los 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
, deberías ver una nueva opción llamada Inspeccionar memoria:
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:
Situaciones y advertencias avanzadas
Genera 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 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:
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 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.
Compila y depura en diferentes máquinas (incluidos Docker o el 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:
El primer prefijo que coincida "gana". Si conoces otros depuradores de C++, esta opción es similar al comando set substitute-path
en GDB o a 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 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
Separación de la información de depuración
La información de depuración conserva muchos detalles sobre tu código, los tipos definidos, las variables, las funciones, los alcances y las 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 que se describieron anteriormente, esta función incluso se puede usar para enviar compilaciones de producción casi optimizadas de tu aplicación 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á…
¡Uf! Eso fue mucha información sobre las 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:
- Se corrigieron los problemas de la experiencia de depuración.
- Se agregó compatibilidad con los 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.
- Se agregó 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 tu navegador de desarrollo predeterminado. Estos canales de versión preliminar te brindan acceso a las funciones más recientes de DevTools, te permiten probar las APIs de plataformas web de vanguardia y te ayudan a encontrar problemas en tu sitio antes que tus usuarios.
Comunícate con el equipo de Chrome DevTools
Usa las siguientes opciones para hablar sobre las funciones nuevas, las actualizaciones o cualquier otro tema relacionado con DevTools.
- Envíanos tus comentarios y solicitudes de funciones a crbug.com.
- Informa un problema de DevTools con Más opciones > Ayuda > Informar un problema de DevTools en DevTools.
- Twittea a @ChromeDevTools.
- Deja comentarios en los videos de YouTube sobre las novedades de DevTools o en los videos de YouTube sobre sugerencias de DevTools.