Debug di WebAssembly con strumenti moderni

Ingvar Stepanyan
Ingvar Stepanyan

La strada percorsa finora

Un anno fa, Chrome ha annunciato il supporto iniziale per il debug nativo di WebAssembly in Chrome DevTools.

Abbiamo dimostrato di avere un supporto di base durante il passo e parlato delle opportunità l'utilizzo delle informazioni DWARF anziché le mappe di origine ci apriranno in futuro:

  • Risoluzione dei nomi delle variabili
  • Tipi di stampa avvincenti
  • Valutazione delle espressioni nelle lingue di origine
  • ...e molto altro ancora.

Oggi siamo felici di presentare le funzionalità promesse che sono diventate realtà e i progressi fatti dai team di Emscripten e Chrome DevTools quest'anno, in particolare per le app C e C++.

Prima di iniziare, tieni presente che si tratta ancora di una versione beta della nuova esperienza, devi utilizzare la versione più recente di tutti gli strumenti a tuo rischio e pericolo e, se riscontri problemi, segnalali a https://issues.chromium.org/issues/new?noWizard=true&template=0&component=1456350.

Iniziamo con lo stesso semplice esempio dell'ultima volta:

#include <stdlib.h>

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

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

Per compilarlo, utilizziamo la versione più recente di Emscripten e passiamo un flag -g, proprio come nel post originale, per includere le informazioni di debugging:

emcc -g temp.c -o temp.html

Ora possiamo pubblicare la pagina generata da un server HTTP localhost (ad esempio, con serve) e nella versione più recente di Chrome Canary.

Questa volta avremo bisogno anche di un'estensione helper che si integri con Chrome DevTools e aiuta a dare un senso a tutte le informazioni di debug. codificati nel file WebAssembly. Per installarla, vai a questo link: goo.gle/wasm-debugging-extension

Ti consigliamo inoltre di attivare il debug di WebAssembly in DevTools Esperimenti. Apri Chrome DevTools, fai clic sull'icona a forma di ingranaggio () nell'angolo in alto a destra del riquadro di DevTools, vai al riquadro Sperimentali e seleziona Debug WebAssembly: abilita il supporto DWARF.

Riquadro Esperimenti delle impostazioni di DevTools

Quando chiudi le Impostazioni, DevTools ti suggerirà di ricaricarsi automaticamente. per applicare le impostazioni. È tutto per l'evento una tantum configurazione.

Ora possiamo tornare al riquadro Origini e attivare Metti in pausa su eccezioni (icona ⏸), poi seleziona Metti in pausa in caso di eccezioni rilevate e ricarica la pagina. Dovresti vedere i DevTools in pausa per un'eccezione:

Screenshot del riquadro Origini che mostra come attivare &quot;Metti in pausa in caso di eccezioni rilevate&quot;

Per impostazione predefinita, si interrompe su un codice glue generato da Emscripten, ma sulla a destra puoi vedere una visualizzazione Stack di chiamate che rappresenta l'analisi dello stack l'errore e possiamo passare alla riga C originale che ha richiamato abort:

DevTools è in pausa nella funzione &quot;assert_less&quot; e mostra i valori di &quot;x&quot; e &quot;y&quot; nella visualizzazione Ambito

Nella visualizzazione Ambito puoi vedere i nomi originali e i valori delle variabili nel codice C/C++ e non è più necessario cosa significano nomi alterati come $localN e come si relazionano che hai scritto.

Questo vale non solo per i valori primitivi come i numeri interi, ma anche per come strutture, classi, array e così via.

Supporto dei tipi avanzati

Diamo un'occhiata a un esempio più complicato per spiegarli. Questo volta, disegneremo un frattale di Mandelbrot con seguente codice C++:

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

Puoi vedere che questa applicazione è ancora abbastanza piccola: è un singolo file contenente 50 righe di codice, ma questa volta sto utilizzando anche alcune API esterne, come la libreria SDL per la grafica e i numeri complessi della libreria standard C++.

Lo compilerò con lo stesso flag -g di cui sopra per includere informazioni di debug e chiederò a Emscripten di fornire lo standard SDL2 libreria e consentire memoria di dimensioni arbitrarie:

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

Quando visito la pagina generata nel browser, vedo la bellissima forma frattale con alcuni colori casuali:

Pagina demo

Quando apro DevTools, di nuovo vedo il file C++ originale. Questo Tuttavia, non abbiamo un errore nel codice (whew!), quindi impostiamo qualche punto di interruzione all'inizio del codice.

Quando ricarichiamo la pagina, il debugger viene messo in pausa direttamente all'interno della Sorgente C++:

DevTools è in pausa sulla chiamata &quot;SDL_Init&quot;

Possiamo già vedere tutte le variabili a destra, ma solo width e height sono inizializzati al momento, quindi non c'è molto da possono essere ispezionati.

Impostiamo un altro punto di interruzione all'interno del ciclo principale di Mandelbrot e riprendiamo per andare un po' avanti.

DevTools è in pausa all&#39;interno dei loop nidificati

A questo punto il nostro palette è stato riempito con alcuni colori casuali, e possiamo espandere sia l'array stesso che la singola SDL_Color strutture e controllarne i componenti per verificare che sembra tutto a posto (ad esempio, il canale "alpha" è sempre impostato fino alla massima opacità). Allo stesso modo, possiamo espandere e verificare parti immaginarie del numero complesso memorizzate nella variabile center.

Se vuoi accedere a una proprietà con un alto grado di nidificazione, altrimenti difficile accedi tramite la vista Ambito, puoi usare la console valutazione. Tuttavia, tieni presente che le espressioni C++ più complesse non sono ancora supportate.

Riquadro della console che mostra il risultato di &quot;palette[10].r&quot;

Riprenderemo l'esecuzione alcune volte per capire com'è l'elemento x interno modificando la vista Ambito, aggiungendo il nome della variabile nella lista di controllo, valutandola nella console o passando il mouse sopra la variabile nel codice sorgente:

Descrizione comando per la variabile &quot;x&quot; nell&#39;origine che mostra il suo valore &quot;3&quot;

Da qui, possiamo passare o eseguire il passaggio successivo delle istruzioni C++ e osservare come stanno cambiando anche altre variabili:

Descrizioni comando e vista Ambito che mostrano i valori di &quot;colore&quot;, &quot;punto&quot; e altre variabili

Bene, tutto questo funziona perfettamente quando sono disponibili informazioni di debug, ma E se volessimo eseguire il debug di un codice che non è stato creato con le opzioni disponibili?

Debug di WebAssembly non elaborato

Ad esempio, abbiamo chiesto a Emscripten di fornire una libreria SDL predefinita anziché compilarlo direttamente dalla fonte, almeno attualmente il debugger non può trovare origini associate. Entriamo di nuovo in SDL_RenderDrawColor:

Strumenti DevTools che mostrano la visualizzazione disassemblata di &quot;mandelbrot.wasm&quot;

Torniamo all'esperienza di debug di WebAssembly non elaborata.

Ora sembra un po' spaventoso e non è qualcosa che la maggior parte degli sviluppatori web ma a volte potresti voler eseguire il debug libreria creata senza informazioni di debug, indipendentemente dal fatto che si tratti di Una libreria di terze parti che non ha alcun controllo o perché ha incontrato uno di quei bug che si verificano solo in produzione.

Per venire incontro in questi casi, abbiamo apportato alcuni miglioramenti alle di debug.

Prima di tutto, se hai utilizzato prima il debug WebAssembly non elaborato, potresti l'intero processo di smontaggio viene ora mostrato in un unico file a indovinare a quale funzione corrisponde una voce Sources wasm-53834e3e/ wasm-53834e3e-7.

Schema di generazione del nuovo nome

Abbiamo migliorato i nomi anche nella visualizzazione disassemblata. In precedenza vedevi solo indici numerici o, in caso di funzioni, nessun nome.

Ora generiamo i nomi in modo simile ad altri strumenti di smontaggio, utilizzando i suggerimenti della sezione dei nomi di WebAssembly, i percorsi di importazione/esportazione e, infine, se non funzionano, li generiamo in base al tipo e all'indice dell'elemento, ad esempio $func123. Puoi vediamo come, nello screenshot qui sopra, questo è già utile per ottenere analisi dello stack e operazioni di smontaggio più leggibili.

Quando non sono disponibili informazioni sui tipi, potrebbe essere difficile da esaminare a qualsiasi valore oltre alle primitive: ad esempio, i puntatori verranno visualizzati come numeri interi regolari, senza modo di sapere cosa si trova dietro di essi la memoria.

Ispezione della memoria

In precedenza, potevi espandere solo l'oggetto di memoria WebAssembly, rappresentato da env.memory nella visualizzazione Ambito per cercare, singoli byte. Questa soluzione ha funzionato in alcune situazioni basilari, ma non particolarmente conveniente da espandere e non permetteva di reinterpretare i dati in formati diversi dai valori in byte. Abbiamo aggiunto una nuova funzionalità per aiutarti anche con questo: un controllo della memoria lineare.

Se fai clic con il tasto destro del mouse su env.memory, ora dovresti vedere una nuova chiamata Ispeziona memoria:

Menu contestuale in &quot;env.memory&quot; nel riquadro Ambito che mostra la sezione &quot;Ispeziona memoria&quot; voce

Dopo aver fatto clic, viene visualizzato un Controllo memoria, in che puoi esaminare nella memoria WebAssembly nelle visualizzazioni esadecimale e ASCII. raggiungere indirizzi specifici e interpretare i dati formati diversi:

Riquadro Controllo memoria in DevTools che mostra le visualizzazioni esadecimale e ASCII della memoria

Scenari avanzati e avvertenze

Profilazione del codice WebAssembly

Quando apri DevTools, il codice WebAssembly viene "ordinato a livelli" a un non ottimizzata per attivare il debug. Questa versione è molto più lenta, quindi non puoi fare affidamento su console.time, performance.now e altri metodi per misurare la velocità del codice, mentre DevTools aperta, in quanto i numeri che ottieni non rappresentano il rendimento reale del tutto.

Devi invece utilizzare il riquadro Rendimento di DevTools che esegue il codice alla massima velocità e fornisce una un'analisi dettagliata del tempo trascorso nelle varie funzioni:

Riquadro di profilazione che mostra varie funzioni Wasm

In alternativa, puoi eseguire l'applicazione con DevTools chiuso e al termine dell'operazione, aprili per ispezionare la console.

Miglioreremo gli scenari di profilazione in futuro, ma per il momento è un caveat da tenere presente. Se vuoi scoprire di più su WebAssembly consulta la nostra documentazione sulla pipeline di compilazione WebAssembly.

Creazione e debug su macchine diverse (inclusi Docker / host)

Quando crei un Docker, una macchina virtuale o un server di build remoto, probabilmente imbatterai in situazioni in cui i percorsi dei file di origine utilizzati durante la creazione non corrispondono ai percorsi sul tuo file system in cui i Chrome DevTools sono in esecuzione. In questo caso, i file verranno visualizzati Origini ma non viene caricato.

Per risolvere il problema, abbiamo implementato una funzionalità di mappatura dei percorsi le opzioni dell'estensione C/C++. Puoi utilizzarlo per rimappare percorsi arbitrari e aiutare DevTools a individuare le origini.

Ad esempio, se il progetto sulla macchina host si trova in un percorso C:\src\my_project, ma è stata creata all'interno di un container Docker in cui il percorso era rappresentato come /mnt/c/src/my_project, puoi rimappare durante il debug specificando i percorsi come prefissi:

Pagina Opzioni dell&#39;estensione di debug C/C++

Il primo prefisso corrispondente è "vince". Se hai familiarità con altri strumenti C++ debugger, questa opzione è simile al comando set substitute-path in GDB o un'impostazione target.source-map in LLDB.

Debug di build ottimizzate

Come con qualsiasi altra lingua, il debug funziona al meglio se le ottimizzazioni vengono disattivata. Le ottimizzazioni possono incorporare le funzioni una nell'altra, riordinare o rimuovere del tutto parti del codice e tutto questo ha un con la possibilità di confondere il debugger e, di conseguenza, l'utente.

Se preferisci un'esperienza di debug più limitata e vuoi comunque eseguire il debug di una build ottimizzata, la maggior parte delle ottimizzazioni funzionerà previsto, tranne che per l'incorporamento della funzione. Prevediamo di risolvere i problemi rimanenti in futuro, ma per il momento utilizza -fno-inline per disattivarlo durante la compilazione con eventuali ottimizzazioni a livello di -O, ad esempio:

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

Separazione delle informazioni di debug

Le informazioni di debug conservano molti dettagli sul codice, tipi, variabili, funzioni, ambiti e località, qualunque cosa possa utile al debugger. Di conseguenza, spesso può essere maggiore del il codice stesso.

Per velocizzare il caricamento e la compilazione del modulo WebAssembly, potresti vuoi suddividere queste informazioni di debug in un'istanza WebAssembly separata . Per farlo in Emscripten, passa un flag -gseparate-dwarf=… con il nome del file che preferisci:

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

In questo caso, l'applicazione principale memorizzerà solo il nome file temp.debug.wasm e l'estensione helper sarà in grado di individuare e caricalo all'apertura di DevTools.

Se combinata con le ottimizzazioni descritte in precedenza, questa funzione può anche per produrre build di produzione quasi ottimizzate del tuo ed eseguirne il debug con un file lato locale. In questo caso, dovremo inoltre sostituire l'URL memorizzato per aiutare l'estensione per trovare il file laterale, ad esempio:

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]

Continua...

Wow, erano molte nuove funzionalità!

Con tutte queste nuove integrazioni, Chrome DevTools diventa un'app, potente, debugger non solo per JavaScript, ma anche per le app C e C++, rendendo più facile che mai prendere le app, integrate in una tecnologie e di portarle su un Web condiviso e multipiattaforma.

Tuttavia, il nostro viaggio non è ancora finito. Ecco alcune delle cose che lavorando da qui in poi:

  • Eliminazione dei punti critici nell'esperienza di debug.
  • Aggiunta del supporto per i formatter dei tipi personalizzati.
  • Stiamo lavorando per migliorare le Profilazione per le app WebAssembly.
  • È stato aggiunto il supporto della copertura del codice per facilitare la ricerca codice inutilizzato.
  • Miglioramento del supporto delle espressioni nella valutazione della console.
  • Aggiunta del supporto per altre lingue.
  • …e altro ancora.

Nel frattempo, aiutaci provando l'attuale versione beta sul tuo codice e segnalando eventuali problemi a https://issues.chromium.org/issues/new?noWizard=true&amp;template=0&amp;component=1456350.

Scaricare i canali in anteprima

Prendi in considerazione l'utilizzo di Chrome Canary, Dev o Beta come browser di sviluppo predefinito. Questi canali di anteprima ti consentono di accedere alle funzionalità più recenti di DevTools, testare le API delle piattaforme web all'avanguardia e individuare i problemi sul tuo sito prima che lo facciano gli utenti.

Contattare il team di Chrome DevTools

Utilizza le seguenti opzioni per discutere delle nuove funzionalità e modifiche nel post o di qualsiasi altra informazione relativa a DevTools.

  • Inviaci un suggerimento o un feedback tramite crbug.com.
  • Segnala un problema di DevTools utilizzando Altre opzioni   Altro > Guida > Segnala un problema di DevTools in DevTools.
  • Invia un tweet all'indirizzo @ChromeDevTools.
  • Lascia commenti sulle novità nei video di YouTube di DevTools o nei video di YouTube dei suggerimenti di DevTools.