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 prise en charge initiale pour le débogage natif de WebAssembly dans les outils pour les développeurs Chrome.

Nous avons présenté la prise en charge de la progression de base et évoqué les opportunités offertes par l'utilisation d'informations DWARF au lieu de cartes sources à l'avenir :

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

Aujourd'hui, nous sommes ravis de présenter les fonctionnalités promises et les progrès réalisés 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 et, si vous rencontrez des problèmes, veuillez les signaler à https://issues.chromium.org/issues/new?noWizard=true&template=0&component=1456350.

Commençons par le même exemple C simple 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'Emscripten et transmettons un indicateur -g, comme dans le post d'origine, pour inclure des 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 de développement Chrome et l'aide à comprendre toutes les informations de débogage encodées dans le fichier WebAssembly. Veuillez l'installer en accédant à ce lien : goo.gle/wasm-debugging-extension.

Vous devez également activer le débogage WebAssembly dans les outils de développement Tests : Ouvrez les outils pour les développeurs Chrome, cliquez sur l'icône en forme de roue dentée  en haut à droite du volet des outils pour les développeurs, accédez au panneau Tests et cochez WebAssembly Debugging: Enable DWARF support (Débogage WebAssembly : activer la prise en charge de DWARF).

Volet &quot;Tests&quot; des paramètres DevTools

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

Nous pouvons maintenant revenir au panneau Sources, activer Mettre en pause en cas d'exception (icône ⏸), puis cocher Mettre en pause en cas d'exception détectée et actualiser la page. Les outils de développement devraient être suspendus sur une exception :

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

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

Outils de développement mis en veille dans la fonction &quot;assert_less&quot; et affichage des valeurs de &quot;x&quot; et &quot;y&quot; dans la vue &quot;Scope&quot;

Maintenant, si vous regardez dans la vue Scope, vous pouvez voir les noms d'origine et les valeurs des variables dans le code C/C++, ce que signifient les noms déchiffrés comme $localN et comment ils sont liés au le code source que vous avez écrit.

Cela s'applique non seulement aux valeurs primitives telles que les entiers, mais aussi aux valeurs comme les structures, les classes, les tableaux, etc.

Compatibilité avec les types enrichis

Prenons un exemple plus complexe pour illustrer ces cas de figure. Cette fois, nous allons dessiner un fractal 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();
}

Vous pouvez constater que cette application est encore assez petite. Il s'agit d'un seul fichier contenant 50 lignes de code. Toutefois, cette fois, j'utilise également des API externes, comme la bibliothèque SDL pour les graphiques, ainsi que les nombres complexes de la bibliothèque standard C++.

Je vais la compiler avec le même indicateur -g que ci-dessus pour inclure informations de débogage. Je demande aussi à Emscripten de fournir le fichier SDL2 bibliothèque et autorisent une 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 vois avec des couleurs aléatoires:

Page de démonstration

Lorsque j'ouvre DevTools, je peux à nouveau voir le fichier C++ d'origine. Cette fois, cependant, il n'y a pas d'erreur dans le code (ouf !). Nous allons donc définir un point d'arrêt au début de notre code.

Lorsque nous actualisons à nouveau la page, le débogueur se met en pause dans notre source C++ :

Les outils de développement ont été mis en pause sur 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 principale Mandelbrot, puis reprenons l'exécution pour avancer un peu.

Outils de développement mis en pause dans les boucles imbriquées

À ce stade, notre 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é complète). De même, nous pouvons développer et vérifier les parties réelle et imaginaire du nombre complexe stocké dans la variable center.

Si vous souhaitez accéder à une propriété profondément imbriquée qui est autrement difficile à atteindre via la vue Champ d'application, vous pouvez également utiliser l'évaluation de la console. Notez toutefois que les expressions C++ plus complexes ne sont pas encore prises en charge.

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 est ou en consultant à nouveau la vue Portée, en ajoutant le nom de la variable à la liste de surveillance, en l'évaluant dans la console ou en en pointant 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 entrer ou passer les instructions C++ et observer comment d'autres variables changent également:

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

Tout fonctionne parfaitement 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é compilé avec les options de débogage ?

Débogage WebAssembly brut

Par exemple, nous avons demandé à Emscripten de nous fournir une bibliothèque SDL précompilée au lieu de la compiler nous-mêmes à partir de la source. Par conséquent, le débogueur ne peut pas trouver les sources associées, du moins pour le moment. Revenons à l'étape suivante pour accéder à SDL_RenderDrawColor:

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

Nous revenons à l'expérience de débogage WebAssembly brute.

Cela semble un peu effrayant, et la plupart des développeurs Web à résoudre, mais il peut arriver que vous souhaitiez déboguer est créée sans informations de débogage, que ce soit parce qu'il s'agit d'une bibliothèque tierce sur laquelle vous n'avez aucun contrôle, ou parce que rencontrer l’un de ces bugs qui ne se produit qu’en production.

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

Tout d'abord, si vous avez déjà utilisé le débogage WebAssembly brut, vous remarquerez peut-être que l'intégralité du désassemblage est désormais affichée dans un seul fichier. Vous n'avez plus à deviner à quelle fonction une entrée wasm-53834e3e/ wasm-53834e3e-7 Sources peut correspondre.

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

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

Nous générons maintenant des noms de la même manière que d'autres outils de démontage, à l'aide des indications de la section des noms WebAssembly les chemins d'importation/exportation et, enfin, si tout le reste échoue, générer en fonction du type et de l'index de l'élément, par exemple $func123. Vous pouvez comme le montre la capture d'écran ci-dessus, cela permet déjà d'obtenir des traces de pile et un démontage plus lisibles.

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

Inspection de la mémoire

Auparavant, vous ne pouviez développer que l'objet de mémoire WebAssembly, représenté par env.memory dans la vue Scope (Champ d'application) pour rechercher octets individuels. Cela fonctionnait dans certains scénarios triviaux, 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. Nous avons ajouté une nouvelle fonctionnalité pour vous aider avec ceci aussi: un inspecteur de mémoire linéaire.

Si vous effectuez un clic droit sur env.memory, une nouvelle option appelée Inspecter la mémoire devrait s'afficher :

Menu contextuel sur &quot;env.memory&quot; dans le volet &quot;Champ d&#39;application&quot; affichant un élément &quot;Inspecter la mémoire&quot;

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

Volet de l&#39;outil d&#39;inspection de la mémoire dans DevTools 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 "hiérarchisé" à un version non optimisée pour activer le débogage. Cette version est beaucoup plus lente, ce qui signifie que vous ne pouvez pas vous fier à console.time, performance.now et à d'autres méthodes de mesure de la vitesse de votre code lorsque DevTools est ouvert, car les chiffres que vous obtenez ne représentent pas du tout les performances réelles.

Utilisez plutôt le panneau "Performances" de DevTools, qui exécute le code à pleine vitesse et vous fournit une répartition détaillée du temps passé dans différentes fonctions :

Panneau de profilage affichant différentes fonctions Wasm

Vous pouvez également exécuter votre application avec les outils pour les développeurs fermés, puis les ouvrir une fois terminé pour inspecter la console.

Nous améliorerons les scénarios de profilage à l'avenir, mais pour le moment, il s'agit mise en garde à prendre en compte. Pour en savoir plus sur les scénarios de hiérarchisation WebAssembly, consultez notre documentation sur le pipeline de compilation WebAssembly.

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

Lorsque vous effectuez une compilation dans un Docker, une machine virtuelle ou sur un serveur de compilation distant, vous rencontrerez probablement des situations où les chemins d'accès aux fichiers sources utilisés lors de la compilation ne correspondent pas aux chemins de votre propre système de fichiers où les outils de développement Chrome s'exécutent. Dans ce cas, les fichiers apparaissent Sources, mais son chargement échoue.

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 des chemins arbitraires et pour aider les outils de développement à localiser les sources.

Par exemple, si le projet de votre machine hôte se trouve sous un chemin d'accès C:\src\my_project, mais a été créée 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 &quot;Options&quot; de l&#39;extension de débogage C/C++

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

Déboguer des builds optimisés

Comme pour les autres langages, le débogage fonctionne mieux si les optimisations sont désactivées. Les optimisations peuvent intégrer des fonctions les unes aux autres, réorganiser le code ou supprimer complètement des parties du code. Tout cela peut prêter à confusion pour le débogueur et, par conséquent, pour vous en tant qu'utilisateur.

Si vous n'êtes pas gêné par une expérience de débogage plus limitée et que vous souhaitez toujours déboguer un build optimisé, la plupart des optimisations fonctionneront comme prévu, à l'exception de l'intégration de fonction. 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 portées et les emplacements, tout ce qui peut être utile au débogueur. Il est donc souvent 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 composant WebAssembly distinct . Pour le 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 stocke qu'un nom de fichier temp.debug.wasm, et l'extension d'assistance peut le localiser et le charger lorsque vous ouvrez DevTools.

Associée aux optimisations décrites ci-dessus, cette fonctionnalité peut même les utiliser pour envoyer des versions de production presque optimisées application, puis les déboguer plus tard avec un fichier secondaire local. Dans ce cas, nous devons également remplacer l'URL stockée pour que l'extension Recherchez 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...

Ouf, c'est beaucoup de nouvelles fonctionnalités !

Avec toutes ces nouvelles intégrations, les outils pour les développeurs Chrome deviennent un débogueur viable et puissant, non seulement pour JavaScript, mais aussi pour les applications C et C++. Il est ainsi plus facile que jamais de prendre des applications, conçues dans diverses technologies, et de les transférer vers un Web partagé et multiplate-forme.

Cependant, notre voyage n'est pas encore terminé. Voici quelques-unes des choses sur lesquelles nous allons travailler à l'avenir :

  • Nettoyer les ébauches de l'expérience de débogage.
  • Ajout de la prise en charge des formateurs de type personnalisés.
  • Amélioration du profilage pour les applications WebAssembly.
  • Ajout de la prise en charge de la couverture de code pour faciliter la recherche code inutilisé.
  • Amélioration de la compatibilité avec les expressions dans l'évaluation de la console.
  • Prise en charge de plus de langues.
  • Et bien d'autres…

En attendant, veuillez nous aider en essayant la version bêta actuelle sur votre propre code et en signalant les problèmes détectés sur https://issues.chromium.org/issues/new?noWizard=true&template=0&component=1456350.

Télécharger les canaux de prévisualisation

Vous pouvez utiliser la version Canary, Dev ou Bêta de Chrome comme navigateur de développement par défaut. Ces canaux de prévisualisation vous donnent accès aux dernières fonctionnalités de DevTools, vous permettent de tester les API de plates-formes Web de pointe et vous aident à détecter les problèmes sur votre site avant vos utilisateurs.

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

Utilisez les options suivantes pour discuter des nouvelles fonctionnalités, des mises à jour ou de tout autre élément lié aux outils de développement.