Fehlerbehebung bei WebAssembly mit modernen Tools

Ingvar Stepanyan
Ingvar Stepanyan

Bisherige Entwicklung

Vor einem Jahr wurde in Chrome die erste Unterstützung angekündigt für natives WebAssembly-Debugging in den Chrome-Entwicklertools.

Wir haben die Unterstützung für grundlegende Stufen demonstriert und über die Möglichkeiten gesprochen, die sich durch die Verwendung von DWARF-Informationen anstelle von Quellkarten in Zukunft ergeben:

  • Variablennamen auflösen
  • Quelltextformatierungen
  • Ausdrücke in den Ausgangssprachen auswerten
  • ...und vieles mehr!

Heute möchten wir zeigen, wie die versprochenen Funktionen und die Fortschritte der Emscripten- und Chrome DevTools-Teams insbesondere für C- und C++-Apps.

Bevor wir beginnen, möchten wir Sie darauf hinweisen, dass dies noch eine Betaversion der neuen Version ist. Die Verwendung der neuesten Version aller Tools erfolgt auf eigenes Risiko. Sollten Probleme auftreten, melden Sie sie bitte unter https://issues.chromium.org/issues/new?noWizard=true&template=0&component=1456350.

Beginnen wir mit demselben einfachen C-Beispiel wie beim letzten Mal:

#include <stdlib.h>

void assert_less(int x, int y) {
  if (x >= y) {
    abort();
  }
}

int main() {
  assert_less(10, 20);
  assert_less(30, 20);
}

Zur Kompilierung verwenden wir die neueste Emscripten-Version. und übergeben Sie wie im ursprünglichen Beitrag ein -g-Flag, um Informationen:

emcc -g temp.c -o temp.html

Jetzt können wir die generierte Seite von einem lokalen HTTP-Server aus bereitstellen (z. B. mit serve) und sie in der neuesten Version von Chrome Canary öffnen.

Dieses Mal benötigen wir auch eine Hilfserweiterung, die in die Chrome DevTools eingebunden ist und dabei hilft, alle in der WebAssembly-Datei codierten Informationen zur Fehlerbehebung zu verstehen. Sie können sie unter goo.gle/wasm-debugging-extension installieren.

Außerdem müssen Sie das WebAssembly-Debugging in den DevTools unter Experiments aktivieren. Öffnen Sie die Chrome-Entwicklertools, klicken Sie in der Symbolleiste auf das Zahnradsymbol () Gehen Sie rechts oben im Bereich „Entwicklertools“ zum Bereich Tests. und wählen Sie WebAssembly Debugging: Enable DWARF support (WebAssembly-Debugging: DWARF-Unterstützung aktivieren) aus.

Bereich „Tests“ in den Entwicklertools-Einstellungen

Wenn Sie die Einstellungen schließen, werden Sie von den Entwicklertools aufgefordert, sie neu zu laden, um die Einstellungen anzuwenden. Tun wir das. Das war's für die einmalige einrichten.

Jetzt können wir zum Bereich Quellen zurückkehren, Bei Ausnahmen pausieren (Symbol ⏸) aktivieren, dann Bei erkannten Ausnahmen pausieren auswählen und die Seite neu laden. Die Entwicklertools sollten bei einer Ausnahme pausiert sein:

Screenshot des Bereichs „Quellen“, in dem gezeigt wird, wie „Bei erkannten Ausnahmen anhalten“ aktiviert wird

Standardmäßig wird bei einem von Emscripten generierten Glue-Code angehalten. Rechts sehen Sie jedoch eine Aufrufabfolge, die den Stacktrace des Fehlers darstellt. Sie können auch zur ursprünglichen C-Zeile wechseln, die abort aufgerufen hat:

Entwicklertools wurden in der Funktion „assert_less“ pausiert und zeigen die Werte „x“ und „y“ in der Ansicht „Umfang“ an

In der Ansicht Umfang sehen Sie jetzt die ursprünglichen Namen, und Werte von Variablen im C/C++-Code und müssen nicht mehr findest du heraus, was fehlerhafte Namen wie $localN bedeuten und wie sie mit dem Quellcodes, den Sie geschrieben haben.

Dies gilt nicht nur für primitive Werte wie Ganzzahlen, sondern auch für Typen wie Strukturen, Klassen, Arrays usw. anwenden.

Rich-Type-Unterstützung

Sehen wir uns dazu ein etwas komplizierteres Beispiel an. Dieses Mal zeichnen wir mit dem folgenden C++-Code ein Mandelbrot-Fraktal:

#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();
}

Wie Sie sehen, ist diese Anwendung noch ziemlich klein – es ist eine einzelne Datei mit 50 Codezeilen. Dieses Mal verwende ich jedoch auch einige externe APIs, z. B. die SDL-Bibliothek für Grafiken sowie komplexe Zahlen aus der C++-Standardbibliothek.

Ich werde sie mit demselben -g-Flag wie oben kompilieren, und bitte Emscripten, die SDL2-Datei und Arbeitsspeicher mit beliebiger Größe zulassen:

emcc -g mandelbrot.cc -o mandelbrot.html \
     -s USE_SDL=2 \
     -s ALLOW_MEMORY_GROWTH=1

Wenn ich die generierte Seite im Browser aufrufe, sehe ich die schöne Fraktalform mit einigen zufälligen Farben:

Demoseite

Wenn ich die Entwicklertools wieder öffne, sehe ich die ursprüngliche C++-Datei. Dieses Allerdings haben wir keinen Fehler im Code (Puh!), also legen wir einen Haltepunkt am Anfang des Codes.

Wenn wir die Seite noch einmal neu laden, wird der Debugger direkt in unserer C++-Quelldatei angehalten:

Entwicklertools beim Aufruf „SDL_Init“ pausiert

Auf der rechten Seite sind bereits alle Variablen zu sehen, aber nur width und height sind derzeit initialisiert, es kann also nicht viel prüfen.

Setzen wir einen weiteren Haltepunkt in unserer Mandelbrot-Hauptschleife und setzen um ein bisschen weiterzuspringen.

DevTools in den verschachtelten Schleifen pausiert

Wir haben palette mit zufälligen Farben gefüllt, Wir können sowohl das Array selbst als auch die einzelnen Elemente erweitern, SDL_Color strukturiert und ihre Komponenten auf alles in Ordnung ist (z. B. ist der Kanal "Alpha" immer volle Deckkraft). Ebenso können wir den reellen und imaginären Teil der komplexen Zahl, die in der Variablen center gespeichert ist, ausweiten und prüfen.

Wenn Sie auf eine tief verschachtelte Eigenschaft zugreifen möchten, die ansonsten schwer zu Rufen Sie über die Ansicht Scope (Umfang) auf. Sie können die Console verwenden. Bewertung ab. Beachten Sie jedoch, dass komplexere C++-Ausdrücke keine unterstützt.

Konsolenbereich mit dem Ergebnis von „palette[10].r“

Wir setzen die Ausführung einige Male fort, um zu sehen, wie die innere x indem Sie erneut in der Ansicht Umfang der Beobachtungsliste hinzufügen, in der Konsole auswerten Bewegen Sie den Mauszeiger auf die Variable im Quellcode:

Kurzinfo zur Variablen „x“ in der Quelle mit ihrem Wert „3“

Hier können wir C++-Anweisungen Schritt für Schritt ausführen und beobachten, wie sich auch andere Variablen ändern:

Kurzinfos und Bereichsansicht mit Werten für „color“, „point“ und andere Variablen

Okay, das funktioniert also hervorragend, wenn Debug-Informationen verfügbar sind. Was aber, wenn wir Code debuggen möchten, der nicht mit den Debugging-Optionen erstellt wurde?

Rohes WebAssembly-Debugging

Wir haben Emscripten beispielsweise gebeten, eine vorkonfigurierte SDL-Bibliothek für uns bereitzustellen, anstatt sie selbst aus der Quelle zu kompilieren. Daher kann der Debugger zumindest derzeit keine zugehörigen Quellen finden. Lassen Sie uns noch einmal einsteigen, um in den SDL_RenderDrawColor einzusteigen:

DevTools mit der Ansicht „Disassembly“ von „mandelbrot.wasm“

Wir sind wieder beim reinen WebAssembly-Debugging.

Es sieht jetzt etwas beängstigend aus und ist für die meisten Webentwickler nicht relevant. mit denen Sie sich befassen müssen, aber gelegentlich möchten Sie Fehler ohne Informationen zur Fehlerbehebung erstellt wurde – Drittanbieter-Bibliothek, die Sie nicht steuern können, oder weil Sie auf einen dieser Fehler stößt, der nur in der Produktion auftritt.

Um dies zu unterstützen, haben wir einige Verbesserungen an der grundlegenden auch für die Fehlerbehebung.

Wenn Sie zuvor unformatiertes WebAssembly-Debugging verwendet haben, die gesamte Demontage in einer einzigen Datei Sie sollten genauer wissen, welcher Funktion ein Sources-Eintrag wasm-53834e3e/ wasm-53834e3e-7 zugeordnet werden kann.

Neues Schema zur Namensgenerierung

Außerdem wurden die Namen in der Demontageansicht verbessert. Bisher wurden diese nur numerische Indizes oder, im Fall von Funktionen, überhaupt keinen Namen.

Jetzt generieren wir Namen ähnlich wie bei anderen Deaktivierungstools. Dazu verwenden wir Hinweise aus dem WebAssembly-Namensabschnitt, Import-/Exportpfade und, wenn alles andere fehlschlägt, generieren wir sie basierend auf dem Typ und dem Index des Elements wie $func123. Sie können Wie im Screenshot oben gezeigt, Stacktraces und deren Demontage besser lesbar sind.

Wenn keine Typinformationen verfügbar sind, ist der Zugriff möglicherweise schwierig. alle Werte außer den Primitiven, z. B. werden Zeiger angezeigt, als reguläre Ganzzahlen dargestellt, ohne zu wissen, was sich hinter ihnen zu speichern.

Arbeitsspeicherprüfung

Bisher konnten Sie das WebAssembly-Speicherobjekt, das in der Ansicht Umfang durch env.memory dargestellt wird, nur maximieren, um einzelne Bytes abzurufen. Das funktionierte in einigen einfachen Szenarien, war aber nicht besonders praktisch zu erweitern und erlaubte nicht, Daten in anderen Formaten als Bytewerten neu zu interpretieren. Dazu haben wir eine neue Funktion hinzugefügt: einen linearen Speicherprüfer.

Wenn Sie mit der rechten Maustaste auf das env.memory klicken, sollte jetzt eine neue mit der Option Arbeitsspeicher prüfen:

Kontextmenü in „env.memory“ im Bereich „Scope“ (Bereich) mit dem Eintrag „Inspect Memory“ Element

Daraufhin wird der Memory Inspector Sie können den WebAssembly-Arbeitsspeicher in Hexadezimal- und ASCII-Ansichten zu bestimmten Adressen navigieren und die Daten in verschiedene Formate verwenden:

Der Bereich „Memory Inspector“ in den Entwicklertools mit Hex- und ASCII-Ansichten des Arbeitsspeichers

Erweiterte Szenarien und Einschränkungen

Profilerstellung für WebAssembly-Code

Wenn Sie die Entwicklertools öffnen, wird WebAssembly-Code in eine nicht optimierte Version heruntergestuft, um das Debuggen zu ermöglichen. Diese Version ist viel langsamer, Das bedeutet, dass Sie sich nicht auf console.time, performance.now und andere Methoden zur Messung der Codegeschwindigkeit, während die Entwicklertools da die angezeigten Zahlen nicht die tatsächliche Leistung überhaupt nicht.

Verwenden Sie stattdessen in den Entwicklertools das Steuerfeld „Leistung“. Dadurch wird der Code mit voller Geschwindigkeit ausgeführt und Sie erhalten eine Eine detaillierte Aufschlüsselung der für die verschiedenen Funktionen aufgewendeten Zeit:

Profilbereich mit verschiedenen Wasm-Funktionen

Alternativ können Sie Ihre Anwendung mit geschlossenen Entwicklertools ausführen und Wenn Sie fertig sind, öffnen Sie sie, um die Console zu überprüfen.

Wir werden die Profiling-Szenarien in Zukunft verbessern, aber derzeit ist dies ein wichtiger Hinweis. Weitere Informationen zu WebAssembly-Tiering-Szenarien finden Sie in unserer Dokumentation zur WebAssembly-Kompilierungspipeline.

Builds und Debugging auf verschiedenen Maschinen (einschließlich Docker/Host)

Wenn Sie in einer Docker-Umgebung, auf einer virtuellen Maschine oder auf einem Remote-Build-Server erstellen, kommt es wahrscheinlich vor, dass die Pfade zu den Quelldateien, die während des Builds verwendet werden, nicht mit den Pfaden in Ihrem eigenen Dateisystem übereinstimmen, in dem die Chrome DevTools ausgeführt werden. In diesem Fall werden Dateien im Bereich Quellen angezeigt, aber nicht geladen.

Um dieses Problem zu beheben, haben wir eine Pfadzuordnungsfunktion Erweiterungsoptionen für C/C++. Sie können damit beliebige Pfade neu zuordnen und damit die Entwicklertools Quellen leichter finden können.

Wenn sich das Projekt auf Ihrem Hostcomputer beispielsweise unter dem Pfad C:\src\my_project befindet, aber in einem Docker-Container erstellt wurde, in dem dieser Pfad als /mnt/c/src/my_project dargestellt wurde, können Sie ihn beim Debuggen neu zuordnen, indem Sie diese Pfade als Präfixe angeben:

Optionen-Seite der C/C++-Debugging-Erweiterung

Das erste übereinstimmende Präfix „gewinnt“. Wenn Sie mit anderen C++- Debugger erstellt haben, ähnelt diese Option dem Befehl set substitute-path in GDB oder eine target.source-map-Einstellung in LLDB.

Optimierte Builds beheben

Wie bei allen anderen Sprachen funktioniert das Debugging am besten, wenn die Optimierungen deaktiviert. Durch Optimierungen können Funktionen inline angeordnet oder neu angeordnet werden. oder Teile des Codes ganz entfernen. den Debugger und folglich Sie als Nutzer zu verwirren.

Wenn Ihnen eine eingeschränkte Debugging-Funktion nichts ausmacht, einen optimierten Build zu beheben, funktionieren die meisten Optimierungen mit Ausnahme der Inline-Funktion für die Funktion. Wir haben vor, die verbleibenden in Zukunft zu beheben. Verwenden Sie für den Moment -fno-inline, bei der Kompilierung mit Optimierungen auf -O-Ebene deaktivieren, z.B.:

emcc -g temp.c -o temp.html \
     -O3 -fno-inline

Debug-Informationen trennen

In den Debug-Informationen werden viele Details zu Ihrem Code, zu definierten Typen, Variablen, Funktionen, Bereichen und Speicherorten gespeichert – alles, was für den Debugger nützlich sein könnte. Daher kann sie häufig größer sein als die Code selbst.

Um das Laden und Kompilieren des WebAssembly-Moduls zu beschleunigen, diese Debug-Informationen in eine separate WebAssembly- -Datei. Dazu übergeben Sie in Emscripten ein -gseparate-dwarf=…-Flag mit einen gewünschten Dateinamen:

emcc -g temp.c -o temp.html \
     -gseparate-dwarf=temp.debug.wasm

In diesem Fall speichert die Hauptanwendung nur einen Dateinamen temp.debug.wasm. Die Hilfserweiterung kann ihn finden und laden, wenn Sie die DevTools öffnen.

In Kombination mit den oben beschriebenen Optimierungen kann diese Funktion können sogar fast optimierte Produktions-Builds und später mit einer lokalen Side-Datei debuggen. In diesem Fall Außerdem müssen wir die gespeicherte URL überschreiben, damit die Erweiterung die Nebendatei suchen, zum Beispiel:

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]

Wird fortgesetzt...

Wow, das waren viele neue Funktionen!

Mit all diesen neuen Integrationen werden die Chrome-Entwicklertools zu einem leistungsstarken Debugger, der nicht nur für JavaScript, sondern auch für C- und C++-Apps geeignet ist. So können Apps, die mit einer Vielzahl von Technologien erstellt wurden, einfacher denn je in ein gemeinsames, plattformübergreifendes Web übertragen werden.

Unsere Reise ist jedoch noch nicht vorbei. Einige der Dinge, von hier an arbeiten:

  • Die Grenzen der Debugging-Funktion säubern.
  • Benutzerdefinierte Typformatierer werden jetzt unterstützt.
  • Wir arbeiten an Verbesserungen des Profilings für WebAssembly-Apps.
  • Codeabdeckung wird unterstützt, um die Suche zu erleichtern nicht verwendetem Code.
  • Verbesserte Unterstützung von Ausdrücken bei der Konsolenerstellung.
  • Weitere Sprachen werden unterstützt.
  • und weitere!

In der Zwischenzeit können Sie die aktuelle Betaversion mit Ihrem eigenen Code testen und alle gefundenen Probleme unter https://issues.chromium.org/issues/new?noWizard=true&template=0&component=1456350 melden.

Vorschaukanäle herunterladen

Sie können Canary, Dev oder Beta als Standardbrowser für die Entwicklung verwenden. Diese Vorschaukanäle bieten Ihnen Zugriff auf die neuesten DevTools-Funktionen, die Möglichkeit, innovative Webplattform-APIs zu testen, und die Möglichkeit, Probleme auf Ihrer Website zu finden, bevor Ihre Nutzer sie sehen.

Chrome-Entwicklertools-Team kontaktieren

Mit den folgenden Optionen können Sie über die neuen Funktionen und Änderungen im Beitrag oder über andere Themen im Zusammenhang mit den Entwicklertools diskutieren.