En la Cumbre de desarrolladores de Chrome de 2020, presentamos por primera vez la compatibilidad con la depuración de Chrome para aplicaciones de WebAssembly en la Web. Desde entonces, el equipo ha invertido mucho energía en hacer que la experiencia de los desarrolladores se adapte a aplicaciones grandes y hasta grandes. En esta publicación, te mostraremos los controles que agregamos (o hicimos funcionar) en las diferentes herramientas y cómo usarlos.
Depuración escalable
Retomemos desde donde lo dejamos en nuestra publicación de 2020. Este es el ejemplo que estábamos analizando en ese momento:
#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();
}
Aún es un ejemplo bastante pequeño y es probable que no veas ninguno de los problemas reales que verías en una aplicación muy grande, pero aún podemos mostrarte cuáles son las funciones nuevas. La configuración es rápida y fácil, y pruébala por tu cuenta.
En la última publicación, discutimos cómo compilar y depurar este ejemplo. Volvamos a hacerlo, pero analicemos también //performance//:
$ emcc -sUSE_SDL=2 -g -O0 -o mandelbrot.html mandelbrot.cc -sALLOW_MEMORY_GROWTH
Este comando produce un objeto binario wasm de 3 MB. Y la mayor parte de eso, como puedes esperar, es información de depuración. Puedes verificarlo con la herramienta de llvm-objdump
[1], por ejemplo:
$ llvm-objdump -h mandelbrot.wasm
mandelbrot.wasm: file format wasm
Sections:
Idx Name Size VMA Type
0 TYPE 0000026f 00000000
1 IMPORT 00001f03 00000000
2 FUNCTION 0000043e 00000000
3 TABLE 00000007 00000000
4 MEMORY 00000007 00000000
5 GLOBAL 00000021 00000000
6 EXPORT 0000014a 00000000
7 ELEM 00000457 00000000
8 CODE 0009308a 00000000 TEXT
9 DATA 0000e4cc 00000000 DATA
10 name 00007e58 00000000
11 .debug_info 000bb1c9 00000000
12 .debug_loc 0009b407 00000000
13 .debug_ranges 0000ad90 00000000
14 .debug_abbrev 000136e8 00000000
15 .debug_line 000bb3ab 00000000
16 .debug_str 000209bd 00000000
En este resultado, se muestran todas las secciones que se encuentran en el archivo wasm generado. La mayoría de ellas son secciones estándar de WebAssembly, pero también hay varias secciones personalizadas cuyo nombre comienza con .debug_
. Ahí es donde el objeto binario contiene nuestra información de depuración. Si sumamos todos los tamaños, veremos que la información de depuración representa aproximadamente 2.3 MB de nuestro archivo de 3 MB. Si también time
el comando emcc
, veremos que en nuestra máquina tardó aproximadamente 1.5 s en ejecutarse. Estos números forman una pequeña referencia, pero son tan pequeños que probablemente nadie las vigila. Sin embargo, en aplicaciones reales, el objeto binario de depuración puede alcanzar fácilmente un tamaño en GB y tardar minutos en compilarse.
Omitir objeto binario
Cuando compilas una aplicación Wasm con Emscripten, uno de los últimos pasos de la compilación es ejecutar el optimizador Binaryen. Binaryen es un kit de herramientas de compilación que optimiza y legaliza los objetos binarios de WebAssembly. La ejecución de Binaryen como parte de la compilación es bastante costosa, pero solo es necesaria en ciertas condiciones. Para compilaciones de depuración, podemos acelerar significativamente el tiempo de compilación si evitamos la necesidad de pases de Binaryen. El pase de Binaryen más común requerido es para legalizar firmas de funciones que involucran valores enteros de 64 bits. Si habilitas la integración de WebAssembly BigInt con -sWASM_BIGINT
, podemos evitar esto.
$ emcc -sUSE_SDL=2 -g -O0 -o mandelbrot.html mandelbrot.cc -sALLOW_MEMORY_GROWTH -sWASM_BIGINT -sERROR_ON_WASM_CHANGES_AFTER_LINK
Se arroja la marca -sERROR_ON_WASM_CHANGES_AFTER_LINK
por si acaso. Ayuda a detectar cuándo Binaryen se ejecuta y vuelve a escribir el objeto binario de forma inesperada. De esta manera, podemos asegurarnos de mantenernos en el camino rápido.
Aunque nuestro ejemplo es relativamente pequeño, podemos ver el efecto de omitir Binaryen. Según time
, este comando se ejecuta un poco menos de 1 s, así que medio segundo más rápido que antes.
Ajustes avanzados
Cómo omitir el análisis del archivo de entrada
Por lo general, cuando se vincula un proyecto de Emscripten, emcc
analizará todos los archivos y las bibliotecas de objetos de entrada. Lo hace para implementar dependencias precisas entre las funciones de la biblioteca JavaScript y los símbolos nativos de tu programa. Para proyectos más grandes, este análisis adicional de los archivos de entrada (mediante llvm-nm
) puede aumentar considerablemente el tiempo de vinculación.
En su lugar, es posible ejecutarlo con -sREVERSE_DEPS=all
, que le indica a emcc
que incluya todas las dependencias nativas posibles de las funciones de JavaScript. Esto tiene una sobrecarga de tamaño de código pequeña, pero puede acelerar los tiempos de vinculación y puede ser útil para compilaciones de depuración.
Para un proyecto tan pequeño como nuestro ejemplo, esto no hace ninguna diferencia real, pero si tiene cientos o incluso miles de archivos de objetos en su proyecto, puede mejorar significativamente los tiempos de vinculación.
Quitar la sección "name"
En proyectos grandes, especialmente en aquellos con mucho uso de plantillas C++, la sección “name” de WebAssembly puede ser muy grande. En nuestro ejemplo, es solo una pequeña fracción del tamaño del archivo total (consulta el resultado de llvm-objdump
más arriba), pero, en algunos casos, puede ser muy importante. Si la sección “name” de tu aplicación es muy grande y la información de depuración de enano es suficiente para tus necesidades de depuración, puede ser beneficioso eliminar la sección “name”:
$ emstrip --no-strip-all --remove-section=name mandelbrot.wasm
Esto quitará la sección “name” de WebAssembly y conservará las secciones de depuración de DWARF.
Depurar fisión
Los objetos binarios con muchos datos de depuración no solo ejercen presión en el tiempo de compilación, sino también en el de depuración. El depurador debe cargar los datos y crear un índice para ellos, de modo que pueda responder consultas rápidamente, como "¿Cuál es el tipo de variable local x?".
La fisión de depuración nos permite dividir la información de depuración de un objeto binario en dos partes: una que permanece en el objeto binario y otra que está contenida en un archivo de objeto DWARF (.dwo
) separado. Se puede habilitar pasando la marca -gsplit-dwarf
a Emscripten:
$ emcc -sUSE_SDL=2 -g -gsplit-dwarf -gdwarf-5 -O0 -o mandelbrot.html mandelbrot.cc -sALLOW_MEMORY_GROWTH -sWASM_BIGINT -sERROR_ON_WASM_CHANGES_AFTER_LINK
A continuación, mostramos los diferentes comandos y los archivos que se generan mediante la compilación sin datos de depuración, con datos de depuración y, por último, con los datos de depuración y la fisión de depuración.
Cuando se dividen los datos DWARF, una parte de los datos de depuración reside junto con el objeto binario, mientras que la parte grande se coloca en el archivo mandelbrot.dwo
(como se ilustra más arriba).
Para mandelbrot
, solo tenemos un archivo de origen, pero, en general, los proyectos son más grandes que este y, además, incluyen más de un archivo. La fisión de depuración genera un archivo .dwo
para cada uno de ellos. Para que la versión beta actual del depurador (0.1.6.1615) pueda cargar esta información de depuración dividida, debemos agrupar todo en un paquete llamado DWARF (.dwp
) de la siguiente manera:
$ emdwp -e mandelbrot.wasm -o mandelbrot.dwp
Compilar el paquete DWARF a partir de los objetos individuales tiene la ventaja de que solo necesitas entregar un archivo adicional. Estamos trabajando para cargar también todos los objetos individuales en una versión futura.
¿Qué sucede con DWARF 5?
Es posible que hayas notado que colocamos otra marca en el comando emcc
anterior, -gdwarf-5
. Habilitar la versión 5 de los símbolos DWARF, que actualmente no es la versión predeterminada, es otro truco para ayudarnos a comenzar la depuración más rápido. Con él, se almacena cierta información en el objeto binario principal que omitió la versión 4 predeterminada. Específicamente, podemos determinar el conjunto completo de archivos de origen solo a partir del objeto binario principal. Esto permite que el depurador realice acciones básicas, como mostrar el árbol de fuentes completo y establecer puntos de interrupción sin cargar ni analizar los datos de símbolos completos. Esto hace que la depuración con símbolos de división sea mucho más rápida, por lo que siempre usaremos las marcas de línea de comandos -gsplit-dwarf
y -gdwarf-5
juntas.
Con el formato de depuración DWARF5 también tenemos acceso a otra función útil. Introduce un índice de nombre en los datos de depuración que se generará cuando se pase la marca -gpubnames
:
$ emcc -sUSE_SDL=2 -g -gdwarf-5 -gsplit-dwarf -gpubnames -O0 -o mandelbrot.html mandelbrot.cc -sALLOW_MEMORY_GROWTH -sWASM_BIGINT -sERROR_ON_WASM_CHANGES_AFTER_LINK
Durante una sesión de depuración, las búsquedas de símbolos suelen realizarse cuando se busca una entidad por nombre, p.ej., cuando se busca una variable o un tipo. El índice de nombres acelera esta búsqueda apuntando directamente a la unidad de compilación que define ese nombre. Sin un índice de nombres, se necesitaría una búsqueda exhaustiva de todos los datos de depuración para encontrar la unidad de compilación correcta que defina la entidad denominada que buscamos.
Para los curiosos: Consultar los datos de depuración
Puedes usar llvm-dwarfdump
para echar un vistazo a los datos DWARF. Probemos esto:
llvm-dwarfdump mandelbrot.wasm
Esto nos da una descripción general de las “unidades de compilación” (en términos generales, los archivos de origen) de las que tenemos información de depuración. En este ejemplo, solo tenemos la información de depuración de mandelbrot.cc
. La información general nos permitirá saber que tenemos una unidad básica, lo que solo significa que tenemos datos incompletos en este archivo, y que hay un archivo .dwo
independiente que contiene la información de depuración restante:
También puedes consultar otras tablas en este archivo, p.ej., en la tabla de líneas que muestra la asignación del código de bytes de wasm a líneas de C++ (intenta usar llvm-dwarfdump -debug-line
).
También podemos observar la información de depuración que se incluye en el archivo .dwo
separado:
llvm-dwarfdump mandelbrot.dwo
Resumen: ¿Cuál es la ventaja de usar la fisión de depuración?
Existen varias ventajas a la hora de dividir la información de depuración si se trabaja con aplicaciones grandes:
Vinculación más rápida: El vinculador ya no necesita analizar toda la información de depuración. Por lo general, los vinculadores necesitan analizar todos los datos DWARF que se encuentran en el objeto binario. Al eliminar grandes partes de la información de depuración en archivos separados, los vinculadores trabajan con objetos binarios más pequeños, lo que da como resultado tiempos de vinculación más rápidos (sobre todo en aplicaciones grandes).
Depuración más rápida: El depurador puede omitir el análisis de símbolos adicionales en los archivos
.dwo
o.dwp
para algunas búsquedas de símbolos. Para algunas búsquedas (como las solicitudes en la asignación de líneas de archivos wasm a C++), no necesitamos analizar los datos de depuración adicionales. Esto nos ahorra tiempo, ya que no necesitamos cargar y analizar los datos de depuración adicionales.
1: Si no tienes una versión reciente de llvm-objdump
en tu sistema y usas emsdk
, puedes encontrarla en el directorio emsdk/upstream/bin
.
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
> 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.