Beim Chrome Dev Summit 2020 haben wir zum ersten Mal die Fehlerbehebungsunterstützung von WebAssembly-Anwendungen in Chrome im Web vorgestellt. 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 Ihnen die Drehknöpfe, die wir den verschiedenen Tools hinzugefügt (oder bearbeitet) haben, und wie Sie sie verwenden.
Skalierbare Fehlerbehebung
Machen wir im Beitrag von 2020 dort weiter, wo wir 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 handelt sich immer noch um ein relativ kleines Beispiel und Sie werden wahrscheinlich keines der tatsächlichen Probleme sehen, die bei einer sehr großen Anwendung auftreten würden, aber wir können Ihnen dennoch zeigen, was die neuen Funktionen sind. Die Einrichtung ist schnell und einfach und du kannst sie selbst ausprobieren.
Im letzten Post haben wir besprochen, wie Sie dieses Beispiel kompilieren und debuggen können. 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 ein Wasm-Binärprogramm mit 3 MB. Und der Großteil davon sind erwartungsgemäß
Informationen zur Fehlerbehebung. Sie können dies mit dem llvm-objdump
-Tool [1] prüfen. Beispiel:
$ 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, die sich in der generierten Wasm-Datei befinden. Die meisten davon sind Standard-WebAssembly-Abschnitte, aber es gibt auch mehrere benutzerdefinierte Abschnitte, deren Name mit .debug_
beginnt. Hier enthält die Binärdatei unsere Debug-Informationen. Wenn wir alle Größen addieren, sehen wir, dass die Debug-Informationen etwa 2,3 MB unserer 3 MB-Datei ausmachen. Wenn wir auch den Befehl emcc
mit time
ausführen, sehen wir, dass die Ausführung auf unserem Computer etwa 1,5 Sekunden gedauert hat. Diese Zahlen sind ein netter kleiner Anhaltspunkt, aber sie sind so klein, dass sie wahrscheinlich nicht infrage kommen. In echten Anwendungen kann das Debug-Binärprogramm jedoch problemlos eine Größe in GB erreichen und die Erstellung dauert einige Minuten.
Binaryen ü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-Binärdateien optimiert und legalisiert werden. Das Ausführen von Binaryen als Teil des Builds ist relativ teuer, ist jedoch 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 ist für die Legalisierung von Funktionssignaturen mit 64-Bit-Ganzzahlwerten erforderlich. Durch Aktivieren 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 für einen guten Messwert das Flag -sERROR_ON_WASM_CHANGES_AFTER_LINK
eingefügt. Sie kann erkennen, wenn Binaryen ausgeführt wird, und die Binärdatei unerwartet neu schreiben. So können wir sicher sein, dass wir auf dem schnellen Weg bleiben.
Auch wenn unser Beispiel recht klein ist, können wir den Effekt des Überspringens von Binaryen sehen! Laut time
wird dieser Befehl knapp unter einer Sekunde ausgeführt, 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 präzise Abhängigkeiten zwischen JavaScript-Bibliotheksfunktionen und nativen Symbolen in Ihrem Programm zu implementieren. Bei größeren Projekten kann das zusätzliche Scannen von Eingabedateien (mit llvm-nm
) die Verknüpfungszeit erheblich verlängern.
Sie können die Ausführung stattdessen mit -sREVERSE_DEPS=all
ausführen. Dadurch wird emcc
angewiesen, alle möglichen nativen Abhängigkeiten von JavaScript-Funktionen einzubeziehen. Dies hat einen geringen Mehraufwand für die Codegröße, kann jedoch die Verbindungszeiten beschleunigen und kann bei der Fehlerbehebung von Builds hilfreich sein.
Bei einem Projekt, das so klein wie in unserem Beispiel ist, macht dies keinen wirklichen Unterschied, aber wenn Sie Hunderte oder sogar Tausende von Objektdateien in Ihrem Projekt haben, können Sie die Verknüpfungszeiten erheblich verbessern.
Abschnitt „Name“ entfernen
In großen Projekten, insbesondere solchen mit häufig genutzter C++-Vorlagen, kann der Abschnitt „Name“ von WebAssembly sehr groß sein. In unserem Beispiel macht dies nur einen kleinen Bruchteil der gesamten Dateigröße aus (siehe Ausgabe von llvm-objdump
oben), kann aber in manchen Fällen auch sehr bedeutend 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 Abschnitt „Name“ von WebAssembly entfernt, während die DWARF-Debug-Bereiche erhalten bleiben.
Fehlersuche
Binärdateien mit vielen Debug-Daten wirken sich nicht nur auf die Build-Zeit aus, sondern auch auf die Debugging-Zeit. Der Debugger muss die Daten laden und einen Index dafür erstellen, damit er schnell auf Abfragen wie „Welcher Typ ist die lokale Variable 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. Zur Aktivierung kann das Flag -gsplit-dwarf
an Emscripten übergeben werden:
$ 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 durch die Kompilierung ohne Debug-Daten, mit Debug-Daten und schließlich mit Debug-Daten und Debugging-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
gibt es nur eine Quelldatei. In der Regel sind Projekte jedoch größer und enthalten mehrere Dateien. 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 auch daran, in einer zukünftigen Version auch alle einzelnen Objekte zu laden.
Was ist mit DWARF 5?
Wie Sie vielleicht bemerkt haben, haben wir oben ein weiteres Flag in den emcc
-Befehl eingefügt: -gdwarf-5
. Die Aktivierung von Version 5 der DWARF-Symbole, die derzeit nicht die Standardeinstellung ist, ist ein weiterer Trick, mit dem wir die Fehlerbehebung beschleunigen können. Damit werden bestimmte Informationen im Hauptbinärprogramm gespeichert, die in der Standardversion 4 ausgelassen wurden. Insbesondere können wir den vollständigen Satz der Quelldateien allein aus dem Hauptbinärprogramm ermitteln. Auf diese Weise kann der Debugger grundlegende Aktionen ausführen, wie den vollständigen Quellbaum anzeigen und Haltepunkte festlegen, ohne die vollständigen Symboldaten zu laden und zu parsen. Dadurch wird die Fehlerbehebung mit aufgeteilten Symbolen viel schneller, daher verwenden wir immer die Befehlszeilen-Flags -gsplit-dwarf
und -gdwarf-5
zusammen.
Mit dem DWARF5-Debug-Format erhalten wir auch Zugriff auf eine weitere nützliche Funktion. In den Debug-Daten wird ein Namensindex eingeführt, der beim Übergeben des Flags -gpubnames
generiert 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 erfolgt die Symbolsuche häufig durch die Suche nach einer Entität anhand des Namens, z.B. wenn Sie nach einer Variablen oder einem Typ suchen. Der Namensindex beschleunigt diese Suche, indem er direkt auf die Kompilierungseinheit verweist, die diesen Namen definiert. Ohne Namensindex wäre eine umfassende Suche in allen Debug-Daten erforderlich, um die richtige Kompilierungseinheit zu finden, die die gesuchte benannte Entität definiert.
Für alle Wissbegierigen: Debug-Daten ansehen
Sie können llvm-dwarfdump
verwenden, um einen Einblick in die DWARF-Daten zu erhalten. Probieren wir es aus:
llvm-dwarfdump mandelbrot.wasm
Dies gibt uns einen Überblick über die kompilierten Einheiten (grob gesprochen, die Quelldateien), für die uns Debug-Informationen vorliegen. In diesem Beispiel liegen uns nur die Informationen zur Fehlerbehebung für mandelbrot.cc
vor. 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. in der Zeilentabelle, die die Zuordnung des Wasm-Bytecodes zu C++-Zeilen zeigt (versuchen Sie es mit llvm-dwarfdump -debug-line
).
Wir können uns auch die Informationen zur Fehlerbehebung in der separaten Datei .dwo
ansehen:
llvm-dwarfdump mandelbrot.dwo
Zusammenfassung: Was ist der Vorteil der Debugging-Spalte?
Die Aufteilung der Debug-Informationen bei der Arbeit mit großen Anwendungen hat mehrere Vorteile:
Schnellere Verknüpfung: Die Verknüpfung muss nicht mehr die gesamten Debug-Informationen parsen. Verknüpfungen müssen normalerweise die gesamten DWARF-Daten im Binärprogramm parsen. Durch das Entfernen großer Teile der Debug-Informationen in separate Dateien können Linker mit kleineren Binärdateien umgehen, was zu kürzeren Verknüpfungszeiten führt (insbesondere bei großen Anwendungen).
Schnelleres Debugging: Bei der Suche nach Symbolen muss der Debugger das Parsen der zusätzlichen Symbole in den Dateien
.dwo
/.dwp
überspringen. Bei einigen Suchvorgängen (z. B. Anfragen zur Zeilenzuordnung von Wasm-zu-C++-Dateien) müssen wir uns die zusätzlichen Debug-Daten nicht ansehen. Dadurch sparen wir Zeit, da wir die zusätzlichen Debug-Daten nicht laden und parsen 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
Sie können Canary, Dev oder Beta als Standardbrowser für die Entwicklung verwenden. Über diese Vorschaukanäle erhalten Sie Zugriff auf die neuesten Entwicklertools, können innovative Webplattform-APIs testen und Probleme auf Ihrer Website erkennen, bevor Ihre Nutzer es tun.
Kontaktaufnahme mit dem Team für Chrome-Entwicklertools
Mit den folgenden Optionen kannst du die neuen Funktionen und Änderungen in dem Beitrag oder andere Aspekte der Entwicklertools besprechen.
- Senden Sie uns über crbug.com einen Vorschlag oder Feedback.
- Problem mit den Entwicklertools über Weitere Optionen melden
> Hilfe > Hier kannst du Probleme mit den Entwicklertools in den Entwicklertools melden.
- Twittern Sie unter @ChromeDevTools.
- Hinterlasse Kommentare in den YouTube-Videos mit den Neuerungen in den Entwicklertools oder in YouTube-Videos mit Tipps zu den Entwicklertools.