Déboguer WebAssembly avec des outils modernes

Ingvar Stepanyan
Ingvar Stepanyan

La route jusqu'à présent

Il y a un an, Chrome a annoncé la compatibilité initiale du débogage WebAssembly natif dans les outils pour les développeurs Chrome.

Nous avons montré une assistance de base pour le pass et parlé de l'utilisation des opportunités d'utilisation des informations DWARF plutôt que des cartes sources à l'avenir:

  • Résoudre les noms de variables
  • Types d'impression élégante
  • Évaluer des expressions dans les langages sources
  • ...et bien plus !

Aujourd'hui, nous sommes heureux de vous présenter les fonctionnalités annoncées, ainsi que les progrès accomplis par les équipes Emscripten et Chrome DevTools au cours de cette année, en particulier pour les applications C et C++.

Avant de commencer, veuillez noter qu'il s'agit encore d'une version bêta de la nouvelle interface. Vous devez utiliser la dernière version de tous les outils à vos propres risques. Si vous rencontrez des problèmes, veuillez les signaler sur https://bugs.chromium.org/p/chromium/issues/entry?template=DevTools+issue.

Commençons par le même exemple simple en C que la dernière fois:

#include <stdlib.h>

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

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

Pour le compiler, nous utilisons la dernière version d'Embscripten et transmettons un indicateur -g, comme dans l'article d'origine, pour inclure les informations de débogage:

emcc -g temp.c -o temp.html

Nous pouvons maintenant diffuser la page générée à partir d'un serveur HTTP localhost (par exemple, avec serve) et l'ouvrir dans la dernière version de Chrome Canary.

Cette fois, nous aurons également besoin d'une extension d'assistance qui s'intègre aux outils pour les développeurs Chrome et qui l'aide à comprendre toutes les informations de débogage encodées dans le fichier WebAssembly. Veuillez l'installer en cliquant sur ce lien: goo.gle/wasm-debugging-extension.

Vous devez également activer le débogage WebAssembly dans la section Tests des outils de développement. Ouvrez les Outils pour les développeurs Chrome, cliquez sur l'icône en forme de roue dentée () dans l'angle supérieur droit du volet "Outils de développement", accédez au panneau Tests, puis cochez Débogage WebAssembly: Activer la prise en charge de DWARF.

Volet &quot;Tests&quot; des paramètres des outils de développement

Lorsque vous fermez Settings (Paramètres), les outils de développement suggèrent de s'actualiser pour appliquer les paramètres. C'est ce que nous allons faire. C'est tout pour la configuration unique.

Revenez au panneau Sources, activez l'option Pause on exceptions (Mettre en pause sur les exceptions) (icône ⏸), puis cochez Pause on Catch exceptions (Mettre en pause sur les exceptions détectées), puis actualisez la page. Vous devriez constater que les outils de développement sont suspendus au niveau d'une exception:

Capture d&#39;écran du panneau &quot;Sources&quot; montrant comment activer l&#39;option &quot;Suspendre les exceptions interceptées&quot;

Par défaut, elle s'arrête sur un code glue généré par Emscripten. Toutefois, à droite, vous pouvez voir une vue Call Stack (Pile d'appel) représentant la trace de la pile de l'erreur et accéder à la ligne C d'origine qui a appelé abort:

Les outils de développement sont suspendus dans la fonction &quot;assert_less&quot; et affichent les valeurs de &quot;x&quot; et &quot;y&quot; dans la vue du champ d&#39;application

Désormais, si vous consultez la vue Scope (Champ d'application), vous pouvez voir les noms et valeurs d'origine des variables dans le code C/C++. Vous n'avez plus besoin de comprendre la signification des noms tronqués tels que $localN ni leur lien avec le code source que vous avez écrit.

Cela s'applique non seulement aux valeurs primitives telles que les entiers, mais également aux types composés tels que les structures, les classes, les tableaux, etc.

Prise en charge avancée des caractères

Voyons un exemple plus compliqué. Cette fois, nous allons dessiner une fractale de Mandelbrot avec le code C++ suivant:

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

Comme vous pouvez le constater, cette application est encore relativement petite. Il s'agit d'un fichier unique contenant 50 lignes de code, mais cette fois, j'utilise également des API externes, telles que la bibliothèque SDL pour les graphiques, ainsi que les nombres complexes de la bibliothèque standard C++.

Je vais le compiler avec le même indicateur -g que ci-dessus pour inclure des informations de débogage. Je vais également demander à Emscripten de fournir la bibliothèque SDL2 et d'autoriser la mémoire de taille arbitraire:

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

Lorsque j'accède à la page générée dans le navigateur, je peux voir la magnifique forme fractale avec des couleurs aléatoires:

Page de démonstration

Lorsque j'ouvre à nouveau les outils de développement, je peux voir à nouveau le fichier C++ d'origine. Cette fois-ci, le code ne comporte pas d'erreur. Définissons donc un point d'arrêt au début du code.

Lorsque nous actualisons à nouveau la page, le débogueur s'interrompt directement dans notre source C++:

Outils de développement suspendus au niveau de l&#39;appel &quot;SDL_Init&quot;

Nous pouvons déjà voir toutes nos variables à droite, mais seules width et height sont initialisées pour le moment. Il n'y a donc pas grand-chose à inspecter.

Définissons un autre point d'arrêt dans notre boucle Mandelbrot principale et reprenons l'exécution pour avancer un peu.

Les outils de développement sont mis en pause dans les boucles imbriquées

À ce stade, le palette a été rempli de couleurs aléatoires. Nous pouvons développer le tableau lui-même ainsi que les structures SDL_Color individuelles, et inspecter leurs composants pour vérifier que tout est correct (par exemple, que le canal "alpha" est toujours défini sur une opacité totale). De même, nous pouvons développer et vérifier les parties réelles et imaginaires du nombre complexe stocké dans la variable center.

Si vous souhaitez accéder à une propriété profondément imbriquée à laquelle il est difficile d'accéder via la vue Champ d'application, vous pouvez également utiliser l'évaluation de la console. Toutefois, notez que les expressions C++ plus complexes ne sont pas encore acceptées.

Panneau de la console affichant le résultat de &quot;palette[10].r&quot;

Reprenons l'exécution plusieurs fois pour voir comment la x interne change également. Pour ce faire, consultez à nouveau la vue Champ d'application, ajoutez le nom de la variable à la liste de surveillance, évaluez-la dans la console ou pointez sur la variable dans le code source:

Info-bulle sur la variable &quot;x&quot; dans la source affichant sa valeur &quot;3&quot;

À partir de là, nous pouvons passer par des instructions C++ intégrées et observer comment les autres variables changent également:

Info-bulles et vue du champ d&#39;application affichant les valeurs de &quot;couleur&quot;, &quot;point&quot; et d&#39;autres variables

Tout cela fonctionne très bien lorsqu'une information de débogage est disponible, mais que se passe-t-il si nous voulons déboguer un code qui n'a pas été créé avec les options de débogage ?

Débogage WebAssembly brut

Par exemple, nous avons demandé à Emscripten de nous fournir une bibliothèque SDL prédéfinie, au lieu de la compiler nous-mêmes à partir de la source. Par conséquent, au moins actuellement, le débogueur n'a aucun moyen de trouver les sources associées. Passons à nouveau à SDL_RenderDrawColor:

Outils de développement affichant la vue de démontage de &quot;mandelbrot.wasm&quot;

Nous sommes de retour à l'expérience de débogage brut de WebAssembly.

Cela semble un peu effrayant et n'est pas forcément à gérer pour la plupart des développeurs Web, mais il peut arriver que vous souhaitiez déboguer une bibliothèque créée sans informations de débogage, qu'il s'agisse d'une bibliothèque tierce sur laquelle vous n'avez aucun contrôle ou que vous rencontrez l'un de ces bugs qui n'affecte que la production.

Pour vous aider dans ces cas, nous avons également amélioré l'expérience de débogage de base.

Tout d'abord, si vous avez déjà utilisé le débogage brut WebAssembly, vous remarquerez peut-être que l'ensemble du démontage est maintenant affiché dans un seul fichier. Plus besoin de deviner à quelle fonction une entrée Sources wasm-53834e3e/ wasm-53834e3e-7 correspond éventuellement.

Nouveau schéma de génération de noms

Nous avons également amélioré les noms dans la vue de démontage. Auparavant, vous ne voyiez que des index numériques ou, dans le cas des fonctions, aucun nom.

À présent, nous générons des noms de la même manière que d'autres outils de démontage à l'aide des indications de la section de nom WebAssembly et des chemins d'importation/exportation. Enfin, si tout le reste échoue, nous les générons en fonction du type et de l'index de l'élément, comme $func123. Comme vous pouvez le voir dans la capture d'écran ci-dessus, cela permet déjà d'obtenir des traces de pile et un démontage légèrement plus lisibles.

Lorsqu'aucune information de type n'est disponible, il peut être difficile d'inspecter les valeurs autres que les primitives. Par exemple, les pointeurs s'affichent sous forme d'entiers standards, sans aucun moyen de savoir ce qui est stocké en mémoire derrière eux.

Inspection de la mémoire

Auparavant, vous ne pouviez développer que l'objet mémoire WebAssembly, représenté par env.memory dans la vue Scope (Champ d'application) pour rechercher des octets individuels. Cela fonctionnait dans certains scénarios anodins, mais n'était pas particulièrement pratique à développer et ne permettait pas de réinterpréter les données dans des formats autres que les valeurs d'octet. Pour cela, nous avons ajouté une fonctionnalité: un inspecteur de mémoire linéaire.

Si vous effectuez un clic droit sur env.memory, vous devriez maintenant voir une nouvelle option appelée Inspect souvenir (Inspecter la mémoire) :

Menu contextuel &quot;env.memory&quot; dans le volet &quot;Scope&quot; (Champ d&#39;application) affichant l&#39;élément &quot;Inspect Memory&quot; (Inspecter la mémoire)

Une fois que vous avez cliqué dessus, un outil d'inspection de mémoire s'affiche. Vous pouvez y inspecter la mémoire WebAssembly dans des vues hexadécimales et ASCII, accéder à des adresses spécifiques et interpréter les données dans différents formats:

Volet de l&#39;outil d&#39;inspection de mémoire dans les outils de développement affichant des vues hexadécimales et ASCII de la mémoire

Scénarios avancés et mises en garde

Profiler du code WebAssembly

Lorsque vous ouvrez les outils de développement, le code WebAssembly est "classé" vers une version non optimisée pour permettre le débogage. Cette version est beaucoup plus lente, ce qui signifie que vous ne pouvez pas compter sur console.time, performance.now et d'autres méthodes pour mesurer la vitesse de votre code lorsque les outils de développement sont ouverts, car les chiffres que vous obtiendrez ne représenteront pas du tout les performances réelles.

Utilisez plutôt le panneau des performances des outils de développement, qui exécutera le code à pleine vitesse et fournira une répartition détaillée du temps passé dans les différentes fonctions:

Panneau de profilage montrant différentes fonctions Wasm

Vous pouvez également exécuter votre application avec les outils de développement fermés, puis les ouvrir une fois l'opération terminée pour inspecter la console.

Nous améliorerons les scénarios de profilage à l'avenir, mais pour le moment, il s'agit d'une mise en garde importante. Si vous souhaitez en savoir plus sur les scénarios de hiérarchisation WebAssembly, consultez nos documents sur le pipeline de compilation WebAssembly.

Créer et déboguer sur différentes machines (y compris Docker et / ou hôte)

Lors de la compilation dans Docker, une machine virtuelle ou un serveur de compilation distant, vous risquez de rencontrer dans des situations où les chemins d'accès aux fichiers sources utilisés lors de la compilation ne correspondent pas à ceux de votre propre système de fichiers où les Outils pour les développeurs Chrome s'exécutent. Dans ce cas, les fichiers s'affichent dans le panneau Sources, mais ne se chargent pas.

Pour résoudre ce problème, nous avons implémenté une fonctionnalité de mappage de chemin d'accès dans les options d'extension C/C++. Vous pouvez l'utiliser pour remapper les chemins d'accès arbitraires et aider les outils de développement à localiser les sources.

Par exemple, si le projet sur votre machine hôte se trouve sous un chemin d'accès C:\src\my_project, mais a été créé dans un conteneur Docker où ce chemin était représenté par /mnt/c/src/my_project, vous pouvez le remapper lors du débogage en spécifiant ces chemins d'accès en tant que préfixes:

Page d&#39;options de l&#39;extension de débogage C/C++

Le premier préfixe correspondant "victoire". Si vous connaissez d'autres débogueurs C++, cette option est semblable à la commande set substitute-path dans GDB ou au paramètre target.source-map dans LLDB.

Déboguer les builds optimisés

Comme pour tout autre langage, le débogage fonctionne mieux si les optimisations sont désactivées. Les optimisations peuvent intégrer des fonctions dans une autre, réorganiser le code ou supprimer des parties du code. Tout cela peut perturber le débogueur et, par conséquent, vous, en tant qu'utilisateur.

Si une expérience de débogage plus limitée ne vous dérange pas et que vous souhaitez toujours déboguer une version optimisée, la plupart des optimisations fonctionneront comme prévu, à l'exception de l'intégration de fonctions. Nous prévoyons de résoudre les problèmes restants à l'avenir, mais pour l'instant, veuillez utiliser -fno-inline pour le désactiver lors de la compilation avec des optimisations de niveau -O, par exemple :

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

Séparer les informations de débogage

Les informations de débogage conservent de nombreux détails sur votre code, les types définis, les variables, les fonctions, les champs d'application et les emplacements, ainsi que tout ce qui peut être utile au débogueur. Par conséquent, il peut souvent être plus volumineux que le code lui-même.

Pour accélérer le chargement et la compilation du module WebAssembly, vous pouvez diviser ces informations de débogage dans un fichier WebAssembly distinct. Pour ce faire, dans Emscripten, transmettez un indicateur -gseparate-dwarf=… avec le nom de fichier souhaité:

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

Dans ce cas, l'application principale ne stockera que le nom de fichier temp.debug.wasm, et l'extension d'assistance pourra la localiser et la charger lorsque vous ouvrirez les outils de développement.

Lorsqu'elle est combinée à des optimisations telles que décrites ci-dessus, cette fonctionnalité peut même être utilisée pour envoyer des versions de production presque optimisées de votre application, puis les déboguer ultérieurement avec un fichier secondaire local. Dans ce cas, nous devons également remplacer l'URL stockée pour aider l'extension à trouver le fichier secondaire, par exemple:

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]

À suivre...

Waouh, ça fait beaucoup de nouvelles fonctionnalités !

Grâce à toutes ces nouvelles intégrations, les outils pour les développeurs Chrome deviennent un débogueur viable et performant non seulement pour JavaScript, mais aussi pour les applications C et C++. Il n'a jamais été aussi simple d'utiliser des applications intégrant diverses technologies pour les transférer sur un Web multiplate-forme partagé.

Mais notre parcours n'est pas encore terminé. Voici certains des points sur lesquels nous allons travailler à partir de maintenant:

  • Éliminer les aspects techniques de l'expérience de débogage.
  • Prise en charge des outils de mise en forme des types personnalisés.
  • Nous travaillons sur l'amélioration du profilage pour les applications WebAssembly.
  • Ajout de la prise en charge de la couverture de code pour faciliter la recherche de code inutilisé
  • Amélioration de la prise en charge des expressions dans l'évaluation de la console.
  • Davantage de langues sont maintenant disponibles.
  • Et bien d'autres…

En attendant, aidez-nous en essayant la version bêta actuelle sur votre propre code et en signalant les problèmes détectés sur https://bugs.chromium.org/p/chromium/issues/entry?template=DevTools+issue.

Télécharger les canaux de prévisualisation

Nous vous conseillons d'utiliser Chrome Canary, Dev ou Beta comme navigateur de développement par défaut. Ces versions preview vous permettent d'accéder aux dernières fonctionnalités des outils de développement, de tester des API de pointe de plates-formes Web et de détecter les problèmes sur votre site avant qu'ils ne le fassent.

Contacter l'équipe des outils pour les développeurs Chrome

Utilisez les options suivantes pour discuter des nouvelles fonctionnalités et des modifications dans l'article, ou de toute autre question concernant les outils de développement.

  • Envoyez-nous une suggestion ou des commentaires via crbug.com.
  • Signalez un problème dans les outils de développement via Plus d'options   More > Aide > Signaler un problème dans les outils de développement dans les Outils de développement.
  • Envoyez un tweet à @ChromeDevTools.
  • Dites-nous en plus sur les nouveautés concernant les vidéos YouTube dans les outils de développement ou sur les vidéos YouTube de nos conseils relatifs aux outils de développement.