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 investiert, um die Entwicklererfahrung für große und sogar große Anwendungen skalieren zu können. 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 uns damals angesehen 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 und du kannst sie selbst ausprobieren.
Im letzten Beitrag haben wir besprochen, wie dieses Beispiel kompiliert und debuggt wird. Sehen wir uns als Nächstes die Leistung unter //performance// an:
$ 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 zu erwarten Debug-Informationen. 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-Binärprogramm 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 in einem der letzten Build-Schritte das Optimierungstool Binaryen 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 Build-Zeit erheblich beschleunigen, wenn wir keine Binaryen-Pässe mehr benötigen. 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. Sie kann erkennen, wenn Binaryen ausgeführt wird, und die Binärdatei unerwartet neu schreiben. So können wir dafür sorgen, dass wir auf dem schnellsten Weg bleiben.
Auch wenn unser Beispiel recht klein ist, können wir den Effekt des Überspringens von Binaryen sehen! Laut time
dauert die Ausführung dieses Befehls knapp eine Sekunde, also eine halbe Sekunde schneller als zuvor.
Erweiterte Optimierungen
Scannen der Eingabedatei überspringen
Beim Verknüpfen eines Emscripten-Projekts scannt emcc
normalerweise alle Eingabeobjektdateien und -bibliotheken. 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, können sich die Verknüpfungszeiten erheblich verbessern.
Entfernen des Abschnitts „name“
In großen Projekten, insbesondere solchen mit häufig genutzter C++-Vorlagen, kann der Abschnitt „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 "name"-Abschnitt Ihrer Anwendung sehr groß ist und die Zwerg-Debug-Informationen für Ihre Debugging-Anforderungen ausreichen, kann es von Vorteil 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 Debug-Daten belasten nicht nur die Buildzeit, sondern auch die Zeit für das Debuggen. 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 Fehlersuche können wir die Debug-Informationen für eine Binärdatei in zwei Teile aufteilen: einen Teil, 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ärprogramm, während der große Teil in die Datei mandelbrot.dwo
verschoben wird (wie oben dargestellt).
Für mandelbrot
haben wir nur eine Quelldatei, aber im Allgemeinen sind Projekte größer und enthalten mehr als eine Datei. Bei der Debugging-Suche wird für jeden eine .dwo
-Datei generiert. Damit die aktuelle Betaversion des Debuggers (0.1.6.1615) diese Informationen zur aufgeteilten Fehlerbehebung laden kann, müssen wir alle diese Informationen in einem sogenannten DWARF-Paket (.dwp
) bündeln:
$ 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
. Aus den allgemeinen Informationen geht hervor, dass wir eine Basiseinheit haben, was nur bedeutet, dass die Daten unvollständig sind und dass es eine separate .dwo
-Datei mit den verbleibenden Debug-Informationen gibt:
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 arbeiten Linker mit kleineren Binärdateien, 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 Zugriff auf die neuesten DevTools-Funktionen, ermöglichen den Test moderner Webplattform-APIs 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.
- Wenn du ein Problem mit den Entwicklertools melden möchtest, klicke in den Entwicklertools auf Weitere Optionen > Hilfe > Entwicklertools-Problem melden.
- Twittern Sie unter @ChromeDevTools.
- Hinterlassen Sie Kommentare unter den YouTube-Videos zu den Neuigkeiten in den DevTools oder den YouTube-Videos mit Tipps zu den DevTools.