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:

  • Risolvere i 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 l'ultimo Emscripten e passare un flag -g, proprio come nel post originale, per includere il debug informazioni:

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 di supporto che si integri con Chrome DevTools e aiuta a dare un senso a tutte le informazioni di debug. codificati nel file WebAssembly. Installala seguendo questo link: goo.gle/wasm-debugging-extension

Inoltre, devi attivare il debug di WebAssembly in Esperimenti di DevTools. Apri Chrome DevTools, fai clic sull'icona a forma di ingranaggio () nella Nell'angolo in alto a destra del riquadro DevTools, vai al riquadro Esperimenti e seleziona WebAssembly Debugging: 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, attivare Metti in pausa su eccezioni (icona ⏸), quindi selezionare Metti in pausa su eccezioni rilevate e reloading la pagina. Dovresti vedere i DevTools in pausa per un'eccezione:

Screenshot del riquadro Origini che mostra come attivare l&#39;opzione &quot;Metti in pausa sulle 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

Ora, se guardi nella visualizzazione Ambito, puoi vedere i nomi e i valori originali delle variabili nel codice C/C++ e non devi più capire cosa significano nomi alterati come $localN e come si riferiscono al codice sorgente che hai scritto.

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

Supporto dei tipi avanzati

Vediamo un esempio più complicato per mostrarli. 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ò anche a Emscripten di fornire la libreria SDL2 e di consentire una 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, riesco di nuovo a vedere il file C++ originale. Questa volta, però, non c'è un errore nel codice (meno male!), quindi impostiamo un breakpoint all'inizio del codice.

Quando ricarichiamo di nuovo la pagina, il debugger si mette in pausa proprio all'interno del codice sorgente C++:

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

Possiamo già vedere tutte le nostre variabili a destra, ma al momento solo width e height sono inizializzate, quindi non c'è molto da controllare.

Impostiamo un altro punto di interruzione all'interno del nostro loop Mandelbrot principale e riprendiamo l'esecuzione 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à). Analogamente, possiamo espandere e controllare le parti reale e immaginaria del numero complesso memorizzato 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;

Riprendiamo l'esecuzione alcune volte e vediamo come cambia anche x interno: controllando di nuovo la visualizzazione Ambito, aggiungendo il nome della variabile alla 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 eseguire il passaggio all'istruzione C++ o ignorarla e osservare anche come cambiano 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 fornirci una libreria SDL precompilata, anziché compilarla noi stessi dal codice sorgente, quindi, almeno al momento, non c'è modo per il debugger di trovare le origini associate. Riproviamo ad accedere a SDL_RenderDrawColor:

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

Torniamo all'esperienza di debug di WebAssembly non elaborata.

Sembra un po' spaventoso e non è qualcosa che la maggior parte degli sviluppatori web dovrà mai gestire, ma a volte potresti voler eseguire il debug di una libreria creata senza informazioni di debug, ad esempio perché si tratta di una libreria di terze parti su cui non hai alcun controllo o perché hai riscontrato uno di quei bug che si verificano solo in produzione.

Per aiutarti in questi casi, abbiamo apportato alcuni miglioramenti anche all'esperienza di debugging di base.

Innanzitutto, se in precedenza hai utilizzato il debug di WebAssembly non elaborato, potresti notare che l'intero disassemblage ora viene mostrato in un unico file. Non dovrai più indovinare a quale funzione potrebbe corrispondere una voce wasm-53834e3e/ wasm-53834e3e-7 Origini.

Schema di generazione del nuovo nome

Abbiamo migliorato anche i nomi nella visualizzazione di smontaggio. In precedenza, venivano visualizzati solo gli 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 sul tipo, potrebbe essere difficile ispezionare qualsiasi valore oltre a quelli primitivi. Ad esempio, i puntatori verranno visualizzati come interi regolari, senza alcun modo di sapere cosa è memorizzato al loro interno nella memoria.

Ispezione della memoria

In precedenza, per cercare singoli byte potevi espandere solo l'oggetto memoria WebAssembly, rappresentato da env.memory nella visualizzazione Ambito. Questa soluzione ha funzionato in alcuni scenari banali, 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 visualizzare una nuova opzione denominata Esamina memoria:

Menu contestuale su &quot;env.memory&quot; nel riquadro Ambito che mostra un elemento &quot;Esamina memoria&quot;

Dopo aver fatto clic, viene visualizzato un Memory Inspector, in cui puoi ispezionare la memoria WebAssembly nelle visualizzazioni esadecimali e ASCII, accedere ad indirizzi specifici e interpretare i dati in diversi formati:

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 "ridotto" a una versione non ottimizzata per abilitare il debug. Questa versione è molto più lenta, il che significa che non puoi fare affidamento su console.time, performance.now e su altri metodi per misurare la velocità del codice quando DevTools è aperto, poiché i numeri ottenuti non rappresentano affatto le prestazioni reali.

Ti consigliamo invece di utilizzare il pannello Rendimento di DevTools, che eseguirà il codice a piena velocità e ti fornirà un'analisi dettagliata del tempo trascorso nelle diverse funzioni:

Riquadro di profilazione che mostra varie funzioni Wasm

In alternativa, puoi eseguire l'applicazione con DevTools chiuso e aprirlo al termine 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 esegui la compilazione in Docker, in una macchina virtuale o su un server di compilazione remoto, è probabile che si verifichino situazioni in cui i percorsi dei file di origine utilizzati durante la compilazione non corrispondono ai percorsi sul tuo file system su cui vengono eseguiti gli strumenti di sviluppo di Chrome. 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 tua macchina host si trova in un percorsoC:\src\my_project, ma è stato creato all'interno di un contenitore Docker in cui quel percorso era rappresentato come /mnt/c/src/my_project, puoi rimappare nuovamente il percorso durante il debug specificando questi percorsi come prefissi:

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

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

Debug di build ottimizzate

Come per qualsiasi altra lingua, il debug funziona al meglio se le ottimizzazioni sono disabilitate. Le ottimizzazioni potrebbero inserire in linea le funzioni una nell'altra, riordinare il codice o rimuoverne parti del tutto e tutto ciò potrebbe confondere il debugger e, di conseguenza, te come utente.

Se non ti dispiace un'esperienza di debug più limitata e vuoi comunque eseguire il debugging di una build ottimizzata, la maggior parte delle ottimizzazioni funzionerà come previsto, ad eccezione dell'inserimento in linea delle funzioni. Prevediamo di risolvere i problemi rimanenti in futuro, ma per il momento utilizza -fno-inline per disattivarlo durante la compilazione con qualsiasi ottimizzazione 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 posizioni definiti, ovvero tutto ciò che potrebbe essere utile al debugger. Di conseguenza, spesso può essere più grande del 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 file desiderato:

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

In questo caso, l'applicazione principale memorizza solo un nome filetemp.debug.wasm e l'estensione di supporto sarà in grado di trovarlo e caricarlo quando apri DevTools.

Se combinata con le ottimizzazioni descritte in precedenza, questa funzionalità può anche per produrre build di produzione quasi ottimizzate del tuo ed eseguirne il debug con un file lato locale. In questo caso, dobbiamo anche sostituire l'URL memorizzato per aiutare l'estensione a trovare il file secondario, 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, quante 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 funzionalità su cui lavoreremo da qui in poi:

  • Eliminazione delle imperfezioni 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 per la copertura del codice per trovare più facilmente il 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

Valuta la possibilità di utilizzare 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 API di piattaforme web all'avanguardia e trovare i problemi sul tuo sito prima che lo facciano i tuoi utenti.

Contattare il team di Chrome DevTools

Utilizza le seguenti opzioni per discutere delle nuove funzionalità e delle modifiche nel post o di qualsiasi altro argomento relativo a DevTools.