Debuggen van WebAssembly met moderne tools

Ingvar Stepanyan
Ingvar Stepanyan

De weg tot nu toe

Een jaar geleden kondigde Chrome initiële ondersteuning aan voor native WebAssembly-foutopsporing in Chrome DevTools.

We demonstreerden basisstapondersteuning en spraken over de mogelijkheden die het gebruik van DWARF- informatie biedt in plaats van bronkaarten die ons in de toekomst te wachten staan:

  • Variabelenamen oplossen
  • Mooie printtypes
  • Expressies in brontalen evalueren
  • …en nog veel meer!

Vandaag laten we graag de beloofde functies zien die tot leven komen en de vooruitgang die de Emscripten- en Chrome DevTools-teams dit jaar hebben geboekt, met name voor C- en C++-apps.

Voordat we beginnen, houd er rekening mee dat dit nog steeds een bètaversie van de nieuwe ervaring is. U moet de nieuwste versie van alle tools op eigen risico gebruiken. Als u problemen ondervindt, kunt u deze melden op https:/ /issues.chromium.org/issues/new?noWizard=true&template=0&component=1456350 .

Laten we beginnen met hetzelfde eenvoudige C-voorbeeld als de vorige keer:

#include <stdlib.h>

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

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

Om het te compileren, gebruiken we de nieuwste Emscripten en geven we, net als in het originele bericht, een vlag -g door om foutopsporingsinformatie op te nemen:

emcc -g temp.c -o temp.html

Nu kunnen we de gegenereerde pagina weergeven vanaf een localhost HTTP-server (bijvoorbeeld met serve ) en deze openen in de nieuwste Chrome Canary .

Deze keer hebben we ook een helperextensie nodig die integreert met Chrome DevTools en helpt bij het begrijpen van alle foutopsporingsinformatie die is gecodeerd in het WebAssembly-bestand. Installeer het via deze link: goo.gle/wasm-debugging-extension

U wilt ook WebAssembly-foutopsporing inschakelen in DevTools Experiments . Open Chrome DevTools, klik op het tandwielpictogram ( ) in de rechterbovenhoek van het DevTools-paneel, ga naar het paneel Experimenten en vink WebAssembly Debugging: DWARF-ondersteuning inschakelen aan .

Experimentenvenster van de DevTools-instellingen

Wanneer u Instellingen sluit, zal DevTools voorstellen zichzelf opnieuw te laden om instellingen toe te passen, dus laten we dat doen. Dat was het voor de eenmalige installatie.

Nu kunnen we teruggaan naar het Bronnenpaneel , Pauze bij uitzonderingen inschakelen (pictogram ⏸), vervolgens Pauze bij gevangen uitzonderingen aanvinken en de pagina opnieuw laden. Je zou de DevTools gepauzeerd moeten zien bij een uitzondering:

Schermafbeelding van het paneel Bronnen dat laat zien hoe u 'Pauzeer bij onderschepte uitzonderingen' inschakelt

Standaard stopt het op een door Emscripten gegenereerde lijmcode, maar aan de rechterkant ziet u een Call Stack- weergave die de stacktrace van de fout vertegenwoordigt, en kunt u naar de originele C-regel navigeren die abort heeft aangeroepen:

DevTools is gepauzeerd in de `assert_less`-functie en toont waarden van `x` en `y` in de Scope-weergave

Als u nu in de Scope- weergave kijkt, kunt u de oorspronkelijke namen en waarden van variabelen in de C/C++-code zien en hoeft u niet langer uit te zoeken wat verminkte namen als $localN betekenen en hoe deze zich verhouden tot de broncode die u gebruikt. heb geschreven.

Dit geldt niet alleen voor primitieve waarden zoals gehele getallen, maar ook voor samengestelde typen zoals structuren, klassen, arrays, enz.!

Ondersteuning voor rijke typen

Laten we eens kijken naar een ingewikkelder voorbeeld om dit te laten zien. Deze keer tekenen we een Mandelbrot-fractal met de volgende C++-code:

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

Je kunt zien dat deze applicatie nog steeds vrij klein is (het is een enkel bestand met 50 regels code), maar deze keer gebruik ik ook enkele externe API's, zoals de SDL-bibliotheek voor afbeeldingen en complexe getallen uit de C++-standaardbibliotheek.

Ik ga het compileren met dezelfde vlag -g als hierboven om debug-informatie op te nemen, en ik zal Emscripten ook vragen om de SDL2-bibliotheek te leveren en geheugen van willekeurige grootte toe te staan:

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

Wanneer ik de gegenereerde pagina in de browser bezoek, zie ik de prachtige fractale vorm met enkele willekeurige kleuren:

Demopagina

Wanneer ik DevTools opnieuw open, zie ik het originele C++-bestand. Deze keer hebben we echter geen fout in de code (oef!), dus laten we in plaats daarvan een breekpunt aan het begin van onze code instellen.

Wanneer we de pagina opnieuw laden, pauzeert de debugger direct in onze C++-bron:

DevTools is gepauzeerd tijdens de aanroep `SDL_Init`

We kunnen al onze variabelen aan de rechterkant al zien, maar momenteel zijn alleen width en height geïnitialiseerd, dus er valt niet veel te inspecteren.

Laten we nog een breekpunt in onze belangrijkste Mandelbrot-lus plaatsen en de uitvoering hervatten om een ​​stukje vooruit te springen.

DevTools is binnen de geneste lussen gepauzeerd

Op dit punt is ons palette gevuld met een aantal willekeurige kleuren en kunnen we zowel de array zelf als de individuele SDL_Color -structuren uitbreiden en hun componenten inspecteren om te verifiëren dat alles er goed uitziet (dat 'alfa'-kanaal is bijvoorbeeld altijd ingesteld op volledige dekking). Op dezelfde manier kunnen we de reële en imaginaire delen van het complexe getal dat in de center variabele is opgeslagen, uitbreiden en controleren.

Als u toegang wilt krijgen tot een diep geneste eigenschap waar anders moeilijk naartoe te navigeren is via de Scope- weergave, kunt u ook de Console- evaluatie gebruiken! Houd er echter rekening mee dat complexere C++-expressies nog niet worden ondersteund.

Consolepaneel met het resultaat van `palette[10].r`

Laten we de uitvoering een paar keer hervatten en we kunnen zien hoe de binnenste x ook verandert - door opnieuw in de Scope- weergave te kijken, de naam van de variabele aan de watchlist toe te voegen, deze in de console te evalueren, of door over de variabele te bewegen in de broncode:

Tooltip over de variabele `x` in de bron, waarbij de waarde `3` wordt weergegeven

Vanaf hier kunnen we C++-instructies in- of overstappen, en observeren hoe andere variabelen ook veranderen:

Tooltips en bereikweergave met waarden van `kleur`, `punt` en andere variabelen

Oké, dus dit werkt allemaal prima als er debug-informatie beschikbaar is, maar wat als we een code willen debuggen die niet met de debug-opties is gebouwd?

Foutopsporing in Raw WebAssembly

We hebben Emscripten bijvoorbeeld gevraagd om een ​​vooraf gebouwde SDL-bibliotheek voor ons te leveren, in plaats van deze zelf vanuit de bron te compileren, dus er is - althans momenteel - geen manier voor de debugger om bijbehorende bronnen te vinden. Laten we opnieuw ingrijpen om in de SDL_RenderDrawColor te komen:

DevTools toont de demontageweergave van `mandelbrot.wasm`

We zijn terug bij de onbewerkte foutopsporingservaring van WebAssembly.

Nu ziet het er een beetje eng uit en is het niet iets waar de meeste webontwikkelaars ooit mee te maken zullen krijgen, maar af en toe wil je misschien een bibliotheek debuggen die is gebouwd zonder debug-informatie, of het nu een bibliotheek van derden is waar je geen controle over hebt , of omdat je een van die bugs tegenkomt die alleen in de productie voorkomen.

Om in deze gevallen te helpen, hebben we ook enkele verbeteringen aangebracht in de basiservaring voor foutopsporing.

Allereerst, als je eerder raw WebAssembly-foutopsporing hebt gebruikt, zul je merken dat de volledige demontage nu in één enkel bestand wordt weergegeven. Je hoeft niet meer te raden met welke functie een bronvermelding wasm-53834e3e/ wasm-53834e3e-7 mogelijk overeenkomt.

Nieuw naamgeneratieschema

We hebben ook de namen in de demontageweergave verbeterd. Voorheen zag je alleen numerieke indexen, of, in het geval van functies, helemaal geen naam.

Nu genereren we namen op dezelfde manier als andere demontagetools, door hints uit de WebAssembly-naamsectie te gebruiken, paden te importeren/exporteren en, ten slotte, als al het andere faalt, deze te genereren op basis van het type en de index van het item, zoals $func123 . Je kunt zien hoe dit, in de bovenstaande schermafbeelding, al helpt om iets beter leesbare stacktraces en demontage te krijgen.

Als er geen type-informatie beschikbaar is, kan het moeilijk zijn om andere waarden dan de primitieven te inspecteren. Pointers worden bijvoorbeeld weergegeven als gewone gehele getallen, zonder dat je weet wat er achter hen in het geheugen is opgeslagen.

Geheugeninspectie

Voorheen kon u alleen het WebAssembly-geheugenobject, vertegenwoordigd door env.memory in de Scope -weergave, uitbreiden om individuele bytes op te zoeken. Dit werkte in een aantal triviale scenario's, maar was niet bijzonder handig om uit te breiden en maakte het niet mogelijk om gegevens in andere formaten dan bytewaarden te herinterpreteren. We hebben ook een nieuwe functie toegevoegd om hierbij te helpen: een lineaire geheugeninspecteur.

Als u met de rechtermuisknop op env.memory klikt, ziet u nu een nieuwe optie genaamd Geheugen inspecteren :

Contextmenu op `env.memory` in het Scope-paneel met een item 'Inspect Memory'

Eenmaal erop geklikt, verschijnt er een Memory Inspector , waarin u het WebAssembly-geheugen in hexadecimale en ASCII-weergaven kunt inspecteren, naar specifieke adressen kunt navigeren en de gegevens in verschillende formaten kunt interpreteren:

Deelvenster Geheugeninspecteur in DevTools met een hex- en ASCII-weergave van het geheugen

Geavanceerde scenario's en kanttekeningen

Profilering van WebAssembly-code

Wanneer u DevTools opent, wordt de WebAssembly-code "verlaagd" naar een niet-geoptimaliseerde versie om foutopsporing mogelijk te maken. Deze versie is een stuk langzamer, wat betekent dat je niet kunt vertrouwen op console.time , performance.now en andere methoden om de snelheid van je code te meten terwijl DevTools open zijn, omdat de cijfers die je krijgt niet de echte wereld vertegenwoordigen prestatie überhaupt.

In plaats daarvan moet u het DevTools Performance-paneel gebruiken, dat de code op volle snelheid uitvoert en u een gedetailleerd overzicht geeft van de tijd die u aan verschillende functies besteedt:

Profileringspaneel met verschillende Wasm-functies

Als alternatief kunt u uw applicatie uitvoeren terwijl DevTools gesloten is, en deze openen zodra u klaar bent om de console te inspecteren.

We zullen de profileringsscenario's in de toekomst verbeteren, maar voor nu is het een voorbehoud waar we rekening mee moeten houden. Als u meer wilt weten over WebAssembly-tieringscenario's, bekijk dan onze documenten over de WebAssembly-compilatiepijplijn .

Bouwen en debuggen op verschillende machines (inclusief Docker/host)

Wanneer u een Docker, virtuele machine of op een externe buildserver inbouwt, zult u waarschijnlijk situaties tegenkomen waarin de paden naar de bronbestanden die tijdens de build worden gebruikt, niet overeenkomen met de paden op uw eigen bestandssysteem waarop de Chrome DevTools worden uitgevoerd. In dit geval verschijnen de bestanden in het Bronnenpaneel , maar kunnen ze niet worden geladen.

Om dit probleem op te lossen, hebben we een padtoewijzingsfunctionaliteit geïmplementeerd in de C/C++-extensieopties. U kunt het gebruiken om willekeurige paden opnieuw toe te wijzen en de DevTools te helpen bronnen te lokaliseren.

Als het project op uw hostcomputer zich bijvoorbeeld onder het pad C:\src\my_project bevindt, maar is gebouwd in een Docker-container waar dat pad werd weergegeven als /mnt/c/src/my_project , kunt u het tijdens het debuggen opnieuw toewijzen door deze paden als voorvoegsels op te geven:

Optiespagina van de C/C++-foutopsporingsextensie

Het eerste overeenkomende voorvoegsel "wint". Als u bekend bent met andere C++-foutopsporingsprogramma's, is deze optie vergelijkbaar met de opdracht set substitute-path in GDB of een target.source-map instelling in LLDB.

Fouten opsporen in geoptimaliseerde builds

Net als bij andere talen werkt foutopsporing het beste als optimalisaties zijn uitgeschakeld. Optimalisaties kunnen functies in elkaar plaatsen, code opnieuw ordenen of delen van de code helemaal verwijderen - en dit alles heeft de kans om de debugger in verwarring te brengen en, bijgevolg, jou als gebruiker.

Als u een beperktere foutopsporingservaring niet erg vindt en toch fouten in een geoptimaliseerde build wilt debuggen, werken de meeste optimalisaties zoals verwacht, behalve de functie-inlining. We zijn van plan de resterende problemen in de toekomst aan te pakken, maar gebruik voor nu -fno-inline om het uit te schakelen bij het compileren met optimalisaties op -O -niveau, bijvoorbeeld:

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

Het scheiden van de foutopsporingsinformatie

Debug-informatie bewaart veel details over uw code, gedefinieerde typen, variabelen, functies, bereiken en locaties, alles wat nuttig kan zijn voor de debugger. Als gevolg hiervan kan deze vaak groter zijn dan de code zelf.

Om het laden en compileren van de WebAssembly-module te versnellen, wilt u deze foutopsporingsinformatie wellicht opsplitsen in een afzonderlijk WebAssembly-bestand. Om dat in Emscripten te doen, geeft u een vlag -gseparate-dwarf=… door met de gewenste bestandsnaam:

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

In dit geval slaat de hoofdtoepassing alleen de bestandsnaam temp.debug.wasm op en kan de helperextensie deze lokaliseren en laden wanneer u DevTools opent.

In combinatie met optimalisaties zoals hierboven beschreven, kan deze functie zelfs worden gebruikt om bijna geoptimaliseerde productiebuilds van uw applicatie te verzenden, en deze later te debuggen met een lokaal zijbestand. In dit geval moeten we bovendien de opgeslagen URL overschrijven om de extensie te helpen het zijbestand te vinden, bijvoorbeeld:

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]

Wordt vervolgd…

Wauw, dat waren een heleboel nieuwe functies!

Met al deze nieuwe integraties wordt Chrome DevTools een levensvatbare, krachtige debugger, niet alleen voor JavaScript, maar ook voor C- en C++-apps, waardoor het eenvoudiger dan ooit wordt om apps, die in verschillende technologieën zijn ingebouwd, naar een gedeelde, platformonafhankelijk web.

Onze reis is echter nog niet voorbij. Een aantal zaken waar we vanaf nu aan gaan werken:

  • Het opruimen van de ruwe randen in de foutopsporingservaring.
  • Ondersteuning toegevoegd voor aangepaste typeformatters.
  • Werken aan verbeteringen aan de profilering voor WebAssembly apps.
  • Ondersteuning toegevoegd voor codedekking om het gemakkelijker te maken ongebruikte code te vinden.
  • Verbetering van de ondersteuning voor expressies bij console-evaluatie.
  • Ondersteuning voor meer talen toegevoegd.
  • …en meer!

Help ons intussen door de huidige bètaversie van uw eigen code uit te proberen en eventuele gevonden problemen te melden op https://issues.chromium.org/issues/new?noWizard=true&template=0&component=1456350 .

Download de voorbeeldkanalen

Overweeg om Chrome Canary , Dev of Beta te gebruiken als uw standaard ontwikkelingsbrowser. Met deze voorbeeldkanalen krijgt u toegang tot de nieuwste DevTools-functies, kunt u geavanceerde webplatform-API's testen en kunt u problemen op uw site opsporen voordat uw gebruikers dat doen!

Neem contact op met het Chrome DevTools-team

Gebruik de volgende opties om de nieuwe functies, updates of iets anders gerelateerd aan DevTools te bespreken.