Déboguer WebAssembly avec des outils modernes

Ingvar Stepanyan
Ingvar Stepanyan

Le chemin parcouru 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 encore !

Aujourd'hui, nous avons le plaisir de vous présenter les fonctionnalités annoncées et les progrès réalisés par les équipes d'Emscripten et des outils pour les développeurs Chrome cette année, en particulier pour les applications C et C++.

Avant de commencer, veuillez garder à l'esprit qu'il s'agit toujours d'une version bêta de la nouvelle expérience. Vous devez utiliser la dernière version de tous les outils à vos risques et périls. Si vous rencontrez des problèmes, veuillez les signaler sur 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 transmettre un indicateur -g, comme dans le post d'origine, pour inclure le débogage informations:

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 avons également besoin d'une extension d'assistance qui s'intègre à Chrome. Les outils de développement vous aident à interpréter toutes les informations de débogage encodée 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, puis cliquez sur l'icône en forme de roue dentée () En haut à droite du volet "DevTools", accédez au panneau Experiments (Tests). et cochez WebAssembly Debugging: Enable DWARF support (Débogage WebAssembly : Activer la prise en charge DWARF).

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

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.

Revenez maintenant au panneau Sources et activez Mettre en veille sur les exceptions (icône ⏸), puis cochez la case Mettre en veille sur les exceptions interceptées et actualiser la page. Les outils de développement doivent être mis en veille 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;

Si vous regardez dans la vue Champ d'application, vous pouvez voir les noms et les valeurs d'origine des variables dans le code C/C++, et vous n'avez plus à déterminer la signification des noms tronqués comme $localN et leur relation avec 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 de nombreux types

Examinons un exemple plus complexe pour les illustrer. 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 les outils de développement, je peux à nouveau voir le fichier C++ d'origine. Ce mais comme il n'y a pas d'erreur dans le code (ouf !), un point d'arrêt au début de notre code à la place.

Lorsque nous actualisons la page, le débogueur se met en pause directement dans notre Source C++:

Outils de développement interrompus dans l&#39;appel &quot;SDL_Init&quot;

Toutes nos variables s'affichent déjà à droite, mais seulement width et height sont initialisés 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.

DevTools mis en pause dans les boucles imbriquées

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

Si vous souhaitez accéder à une propriété profondément imbriquée qui est autrement difficile à via la vue Champ d'application, vous pouvez utiliser la console l'évaluation ! 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 l'x interne change également. Pour ce faire, vous pouvez consulter à nouveau la vue Champ d'application, ajouter le nom de la variable à la liste de surveillance, l'évaluer dans la console ou pointer 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 exécuter des étapes ou ignorer des instructions C++, et observer comment les 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 très bien lorsqu'une information de débogage est disponible, que faire si nous voulons déboguer un code qui n'a pas été créé avec l'API 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 à la SDL_RenderDrawColor :

DevTools affichant la vue d&#39;assemblage de &quot;mandelbrot.wasm&quot;

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

Cela peut sembler un peu effrayant et la plupart des développeurs Web n'auront jamais à s'en occuper, mais il est possible que vous souhaitiez déboguer une bibliothèque compilé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 vous rencontrez l'un de ces bugs qui ne se produit que en production.

Pour vous aider, nous avons apporté des améliorations et de débogage.

Tout d'abord, si vous avez déjà utilisé le débogage WebAssembly brut, que le démontage complet est maintenant affiché dans un seul fichier devinez plus facilement à quelle fonction correspond une entrée Sources wasm-53834e3e/ wasm-53834e3e-7.

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

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

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. Dans la capture d'écran ci-dessus, vous pouvez voir comment cela permet déjà d'obtenir des traces de pile et un désassemblage légèrement 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 Champ d'application, pour rechercher des octets individuels. Cela fonctionnait dans certains scénarios insignifiants, mais n'était pas particulièrement pratique à développer et ne permet pas de réinterpréter les données dans des formats autres que les valeurs en octets. 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, vous devriez maintenant voir appelée Inspect Memorystore (Inspecter la mémoire) :

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

Un outil d'inspection de la mémoire s'affiche alors, dans lequel vous pouvez inspecter la mémoire WebAssembly dans les vues hexadécimale 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 "dégradé" 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 de mesure de la vitesse de votre code. Les outils de développement car les chiffres obtenus ne reflètent pas les performances réelles du tout.

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

Panneau de profilage affichant différentes fonctions Wasm

Vous pouvez aussi exécuter votre application avec les outils de développement fermés une fois que vous avez terminé, ouvrez-les pour inspecter la console.

Nous allons améliorer les scénarios de profilage à l'avenir, mais pour le moment, il s'agit d'un point à prendre en compte. Pour en savoir plus sur WebAssembly de hiérarchisation, consultez nos documents sur le pipeline de compilation WebAssembly.

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

Lorsque vous compilez 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 d'accès de votre propre système de fichiers où les outils pour les développeurs Chrome sont en cours d'exécution. 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 aider les outils de développement à localiser des sources.

Par exemple, si le projet sur votre machine hôte se trouve sous un chemin C:\src\my_project, mais qu'il 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 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 les builds optimisés

Comme pour tout autre langage, le débogage fonctionne mieux si les optimisations sont est désactivé. 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 l'expérience de débogage plus limitée ne vous dérange pas et que vous souhaitez tout de même déboguer un build optimisé, la plupart des optimisations fonctionnent à l'exception de l'intégration de fonctions. Nous prévoyons d'aborder les problèmes à l'avenir. Pour le moment, utilisez -fno-inline pour la désactiver lors de la compilation avec toute optimisation 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. 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 en 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 stocke qu'un nom de fichier temp.debug.wasm, ainsi que l'extension d'assistance, pourront localiser et et la charger à l'ouverture des outils de développement.

Combinée à des optimisations comme décrit ci-dessus, cette fonctionnalité peut même être utilisée pour publier des builds de production presque optimisés de votre application, puis les déboguer 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 puissant non seulement pour JavaScript, mais aussi pour les applications C et C++. Il n'a jamais été aussi facile d'utiliser des applications grâce à une variété et les intégrer à un Web partagé et multiplate-forme.

Toutefois, notre parcours n'est pas encore terminé. Certaines des choses que nous allons à partir de là:

  • Nettoyer les ébauches de l'expérience de débogage.
  • Ajout de la prise en charge des formateurs de type personnalisés.
  • Nous nous efforçons d'améliorer 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

Envisagez d'utiliser Chrome Canary, Dev ou Bêta 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 plates-formes Web de pointe et de 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 et des modifications dans l'article, ou de tout autre sujet lié aux outils de développement.