Auf dem Chrome Dev Summit 2020 haben wir zum ersten Mal die Debugging-Unterstützung von Chrome für WebAssembly-Anwendungen im Web demonstriert. Seitdem hat das Team viel Energie darauf verwendet, die Entwicklungsumgebung für große und sogar sehr große Anwendungen zu skalieren. In diesem Beitrag zeigen wir euch die Regler, die wir in den verschiedenen Tools hinzugefügt oder aktiviert haben, und wie ihr sie verwendet.
Skalierbare Fehlerbehebung
Machen wir da weiter, wo wir in unserem Beitrag von 2020 aufgehört haben. Hier ist das Beispiel, das wir damals betrachtet haben:
#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();
}
Es ist immer noch ein ziemlich kleines Beispiel und Sie werden wahrscheinlich keine der echten Probleme sehen, die Sie in einer wirklich großen Anwendung sehen würden. Wir können Ihnen aber trotzdem zeigen, was die neuen Funktionen sind. Die Einrichtung ist schnell und einfach.
Im letzten Beitrag haben wir besprochen, wie dieses Beispiel kompiliert und debuggt wird. Wiederholen wir das noch einmal, aber werfen wir auch einen Blick auf //performance//:
$ emcc -sUSE_SDL=2 -g -O0 -o mandelbrot.html mandelbrot.cc -sALLOW_MEMORY_GROWTH
Dieser Befehl erzeugt eine 3 MB große WASM-Binärdatei. Der Großteil davon sind, wie Sie sich vorstellen können, Informationen zur Fehlerbehebung. Sie können dies mit dem llvm-objdump
-Tool [1] überprüfen:
$ 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
Diese Ausgabe zeigt alle Abschnitte in der generierten WASM-Datei. Die meisten davon sind Standard-WebAssembly-Abschnitte, aber es gibt auch mehrere benutzerdefinierte Abschnitte, deren Name mit .debug_
beginnt. Dort finden Sie die Informationen zur Fehlerbehebung. Wenn wir alle Größen zusammenzählen, sehen wir, dass die Debug-Informationen etwa 2,3 MB unserer 3 MB großen Datei ausmachen. Wenn wir auch den Befehl time
emcc
ausführen, sehen wir, dass die Ausführung auf unserem Computer etwa 1,5 Sekunden gedauert hat. Diese Zahlen sind eine gute kleine Baseline, aber sie sind so gering, dass sie wahrscheinlich niemanden interessieren würden. In realen Anwendungen kann das Debug-Binary jedoch leicht eine Größe von mehreren GB erreichen und das Erstellen kann Minuten dauern.
Binärdateien überspringen
Beim Erstellen einer WASM-Anwendung mit Emscripten wird als einer der letzten Buildschritte der Binaryen-Optimierer ausgeführt. Binaryen ist ein Compiler-Toolkit, mit dem WebAssembly-ähnliche Binärdateien sowohl optimiert als auch legalisiert werden. Das Ausführen von Binaryen im Rahmen des Builds ist ziemlich aufwendig, aber nur unter bestimmten Bedingungen erforderlich. Bei Debug-Builds können wir die Buildzeit erheblich verkürzen, wenn wir die Notwendigkeit von Binärdurchläufen vermeiden. Der am häufigsten erforderliche Binaryen-Pass dient der Legalisierung von Funktionssignaturen mit 64‑Bit-Ganzzahlwerten. Durch die Aktivierung der WebAssembly BigInt-Integration mit -sWASM_BIGINT
können wir dies vermeiden.
$ emcc -sUSE_SDL=2 -g -O0 -o mandelbrot.html mandelbrot.cc -sALLOW_MEMORY_GROWTH -sWASM_BIGINT -sERROR_ON_WASM_CHANGES_AFTER_LINK
Wir haben zur Sicherheit auch das Flag -sERROR_ON_WASM_CHANGES_AFTER_LINK
hinzugefügt. So lässt sich erkennen, wenn Binaryen ausgeführt wird und das Binärprogramm unerwartet neu geschrieben wird. So können wir dafür sorgen, dass wir auf dem schnellsten Weg bleiben.
Obwohl unser Beispiel recht klein ist, sehen wir trotzdem die Auswirkungen des Überspringens von Binaryen. Laut time
dauert die Ausführung dieses Befehls knapp eine Sekunde, also eine halbe Sekunde schneller als zuvor.
Erweiterte Optimierungen
Scannen der Eingabedatei überspringen
Normalerweise werden beim Verknüpfen eines Emscripten-Projekts alle Eingabeobjektdateien und ‑bibliotheken von emcc
gescannt. Dies geschieht, um genaue Abhängigkeiten zwischen JavaScript-Bibliotheksfunktionen und nativen Symbolen in Ihrem Programm zu implementieren. Bei größeren Projekten kann dieses zusätzliche Scannen von Eingabedateien (mit llvm-nm
) die Verknüpfungszeit erheblich verlängern.
Stattdessen können Sie -sREVERSE_DEPS=all
verwenden, um emcc
anzuweisen, alle möglichen nativen Abhängigkeiten von JavaScript-Funktionen einzubeziehen. Dies hat einen kleinen Codegrößen-Overhead, kann aber die Verknüpfungszeit verkürzen und ist für Debug-Builds nützlich.
Bei einem so kleinen Projekt wie unserem Beispiel macht das keinen großen Unterschied. Wenn Sie jedoch Hunderte oder sogar Tausende von Objektdateien in Ihrem Projekt haben, kann sich das deutlich auf die Verknüpfungszeit auswirken.
Entfernen des Abschnitts „name“
In großen Projekten, insbesondere in solchen, in denen viele C++-Vorlagen verwendet werden, kann der Bereich „name“ von WebAssembly sehr groß sein. In unserem Beispiel ist das nur ein kleiner Bruchteil der Gesamtdateigröße (siehe Ausgabe von llvm-objdump
oben), in einigen Fällen kann es aber sehr hoch sein. Wenn der Abschnitt „name“ Ihrer Anwendung sehr groß ist und die Dwarf-Debug-Informationen für Ihre Debugging-Anforderungen ausreichen, kann es vorteilhaft sein, den Abschnitt „name“ zu entfernen:
$ emstrip --no-strip-all --remove-section=name mandelbrot.wasm
Dadurch wird der WebAssembly-Abschnitt „name“ entfernt, während die DWARF-Debug-Abschnitte erhalten bleiben.
Fission debuggen
Binärdateien mit vielen Debugging-Daten belasten nicht nur die Buildzeit, sondern auch die Debugging-Zeit. Der Debugger muss die Daten laden und einen Index dafür erstellen, damit er schnell auf Abfragen wie „Was ist der Typ der lokalen Variablen x?“ reagieren kann.
Mit der Debug-Spaltung können wir die Debug-Informationen für ein Binärprogramm in zwei Teile aufteilen: einen, der im Binärprogramm verbleibt, und einen, der in einer separaten sogenannten DWARF-Objektdatei (.dwo
) enthalten ist. Sie können es aktivieren, indem Sie das Flag -gsplit-dwarf
an Emscripten übergeben:
$ 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
Unten sehen Sie die verschiedenen Befehle und die Dateien, die beim Kompilieren ohne Debug-Daten, mit Debug-Daten und schließlich mit Debug-Daten und Debug-Spaltung generiert werden.
Beim Aufteilen der DWARF-Daten befindet sich ein Teil der Debug-Daten zusammen mit dem Binärcode, während der Großteil in die mandelbrot.dwo
-Datei verschoben wird (siehe Abbildung oben).
Für mandelbrot
haben wir nur eine Quelldatei, aber im Allgemeinen sind Projekte größer und enthalten mehr als eine Datei. Bei der Debug-Spaltung wird für jede davon eine .dwo
-Datei generiert. Damit die aktuelle Betaversion des Debuggers (0.1.6.1615) diese aufgeteilten Debug-Informationen laden kann, müssen wir sie in einem sogenannten DWARF-Paket (.dwp
) bündeln. Das geht so:
$ emdwp -e mandelbrot.wasm -o mandelbrot.dwp
Das Erstellen des DWARF-Pakets aus den einzelnen Objekten hat den Vorteil, dass Sie nur eine zusätzliche Datei bereitstellen müssen. Wir arbeiten derzeit daran, in einer zukünftigen Version auch alle einzelnen Objekte zu laden.
Was ist mit DWARF 5?
Wie Sie vielleicht bemerkt haben, haben wir dem Befehl emcc
oben ein weiteres Flag hinzugefügt: -gdwarf-5
. Durch Aktivieren von Version 5 der DWARF-Symbole, die derzeit nicht standardmäßig aktiviert ist, können wir schneller mit dem Debuggen beginnen. Dabei werden bestimmte Informationen in der Haupt-Binärdatei gespeichert, die in der Standardversion 4 fehlen. Insbesondere können wir die gesamte Gruppe von Quelldateien nur anhand der Haupt-Binärdatei ermitteln. So kann der Debugger grundlegende Aktionen ausführen, z. B. den vollständigen Quellbaum anzeigen und Haltestellen setzen, ohne die vollständigen Symboldaten zu laden und zu analysieren. Dadurch wird das Debuggen mit geteilten Symbolen viel schneller. Wir verwenden die Befehlszeilen-Flags -gsplit-dwarf
und -gdwarf-5
daher immer zusammen.
Mit dem DWARF5-Debugformat erhalten wir außerdem Zugriff auf eine weitere nützliche Funktion. Dadurch wird in den Debug-Daten ein Namenindex eingeführt, der generiert wird, wenn das Flag -gpubnames
übergeben wird:
$ 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
Während einer Debugging-Sitzung werden Symbole häufig nach Namen gesucht, z.B. bei der Suche nach einer Variablen oder einem Typ. Der Nameindex beschleunigt diese Suche, da er direkt auf die Kompilierungseinheit verweist, die diesen Namen definiert. Ohne einen Namensindex wäre eine umfassende Suche in den gesamten Debug-Daten erforderlich, um die richtige Kompilierungseinheit zu finden, die das gesuchte benannte Element definiert.
Für alle Wissbegierigen: Debug-Daten ansehen
Mit llvm-dwarfdump
können Sie sich die DWARF-Daten ansehen. Probieren wir es aus:
llvm-dwarfdump mandelbrot.wasm
So erhalten wir einen Überblick über die „Compile-Einheiten“ (grob gesagt die Quelldateien), für die wir Debug-Informationen haben. In diesem Beispiel haben wir nur die Informationen zur Fehlerbehebung für mandelbrot.cc
. Die allgemeinen Informationen zeigen uns, dass wir eine Skeletteinheit haben. Das bedeutet lediglich, dass wir unvollständige Daten zu dieser Datei haben und dass es eine separate .dwo
-Datei gibt, die die verbleibenden Debug-Informationen enthält:
Sie können sich auch andere Tabellen in dieser Datei ansehen, z.B. die Zeilentabelle, in der die Zuordnung von WASM-Bytecode zu C++-Zeilen dargestellt wird (verwenden Sie dazu llvm-dwarfdump -debug-line
).
Wir können uns auch die Debug-Informationen ansehen, die in der separaten .dwo
-Datei enthalten sind:
llvm-dwarfdump mandelbrot.dwo
Zusammenfassung: Welche Vorteile bietet die Debug-Spaltung?
Die Aufteilung der Debug-Informationen hat bei großen Anwendungen mehrere Vorteile:
Schnelleres Verknüpfen: Der Linker muss nicht mehr die gesamten Debug-Informationen parsen. Linker müssen in der Regel die gesamten DWARF-Daten im Binärcode parsen. Durch das Entfernen großer Teile der Debug-Informationen in separate Dateien müssen Linker mit kleineren Binärdateien arbeiten, was zu kürzeren Verknüpfungszeiten führt (insbesondere bei großen Anwendungen).
Schnelleres Debugging: Der Debugger kann das Parsen der zusätzlichen Symbole in
.dwo
-/.dwp
-Dateien für einige Symbolsuchen überspringen. Bei einigen Suchanfragen (z. B. Anfragen zur Zeilenzuordnung von WASM-zu-C++-Dateien) müssen wir uns nicht die zusätzlichen Debug-Daten ansehen. So sparen wir Zeit, da wir die zusätzlichen Debug-Daten nicht laden und analysieren müssen.
1: Wenn Sie keine aktuelle Version von llvm-objdump
auf Ihrem System haben und emsdk
verwenden, finden Sie sie im Verzeichnis emsdk/upstream/bin
.
Vorschaukanäle herunterladen
Verwenden Sie als Standard-Entwicklungsbrowser Chrome Canary, Chrome Dev oder Chrome Beta. Diese Vorabversionen bieten Ihnen Zugriff auf die neuesten DevTools-Funktionen, ermöglichen es Ihnen, innovative Webplattform-APIs zu testen, und helfen Ihnen, Probleme auf Ihrer Website zu finden, bevor Ihre Nutzer sie bemerken.
Chrome-Entwicklertools-Team kontaktieren
Mit den folgenden Optionen können Sie über neue Funktionen, Updates oder andere Themen im Zusammenhang mit den DevTools sprechen.
- Senden Sie uns Feedback und Funktionsanfragen unter crbug.com.
- Melden Sie ein DevTools-Problem über das Dreipunkt-Menü Weitere Optionen > Hilfe > DevTools-Problem melden.
- Tweeten Sie an @ChromeDevTools.
- Hinterlassen Sie Kommentare unter den YouTube-Videos zu den Neuigkeiten in den DevTools oder den YouTube-Videos mit Tipps zu den DevTools.