Déboguer WebAssembly plus rapidement

Philip Pfaffe
Kim-Anh Tran
Kim-Anh Tran
Eric Leese
Sam Clegg

Lors du Chrome Dev Summit 2020, nous avons présenté pour la première fois la compatibilité de débogage de Chrome pour les applications WebAssembly sur le Web. Depuis, l'équipe a investi beaucoup d'énergie pour que l'expérience des développeurs soit adaptée à des applications volumineuses, voire très volumineuses. Dans ce post, nous allons vous montrer les commandes que nous avons ajoutées (ou utilisées) dans les différents outils et comment les utiliser !

Débogage évolutif

Reprenons là où nous nous étions arrêtés dans notre post de 2020. Voici l'exemple que nous regardions à l'époque:

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

Il s'agit encore d'un exemple assez petit et vous ne verrez probablement aucun des vrais problèmes que vous rencontrerez dans une très grande application, mais nous pouvons tout de même vous montrer les nouvelles fonctionnalités. L'outil est simple et rapide à configurer, et à essayer par vous-même !

Dans le dernier message, nous vous avons expliqué comment compiler et déboguer cet exemple. Faisons-le à nouveau, mais regardons aussi //performance//:

$ emcc -sUSE_SDL=2 -g -O0 -o mandelbrot.html mandelbrot.cc -sALLOW_MEMORY_GROWTH

Cette commande produit un binaire Wasm de 3 Mo. Comme vous pouvez vous y attendre, l'essentiel concerne les informations de débogage. Vous pouvez le vérifier à l'aide de l'outil llvm-objdump [1], par exemple:

$ llvm-objdump -h mandelbrot.wasm

mandelbrot.wasm:        file format wasm

Sections:
Idx Name          Size     VMA      Type
  0 TYPE          0000026f 00000000
  1 IMPORT        00001f03 00000000
  2 FUNCTION      0000043e 00000000
  3 TABLE         00000007 00000000
  4 MEMORY        00000007 00000000
  5 GLOBAL        00000021 00000000
  6 EXPORT        0000014a 00000000
  7 ELEM          00000457 00000000
  8 CODE          0009308a 00000000 TEXT
  9 DATA          0000e4cc 00000000 DATA
 10 name          00007e58 00000000
 11 .debug_info   000bb1c9 00000000
 12 .debug_loc    0009b407 00000000
 13 .debug_ranges 0000ad90 00000000
 14 .debug_abbrev 000136e8 00000000
 15 .debug_line   000bb3ab 00000000
 16 .debug_str    000209bd 00000000

Ce résultat nous montre toutes les sections qui se trouvent dans le fichier Wasm généré. La plupart d'entre elles sont des sections WebAssembly standards, mais il existe également plusieurs sections personnalisées dont le nom commence par .debug_. C'est là que le binaire contient nos informations de débogage. Si nous additionnons toutes les tailles, nous constatons que les informations de débogage représentent environ 2,3 Mo de notre fichier de 3 Mo. Si nous ajoutons également time à la commande emcc, nous constatons que l'exécution sur notre machine a pris environ 1,5 seconde. Ces chiffres constituent une jolie référence, mais ils sont tellement petits que personne ne les examinerait. Dans les applications réelles, cependant, le binaire de débogage peut facilement atteindre une taille en Go et prendre quelques minutes pour compiler.

Ignorer Binaryen

Lors de la création d'une application Wasm à l'aide d'Emscripten, l'une de ses dernières étapes de compilation consiste à exécuter l'optimiseur Binaryen. Binaryen est un kit d'outils de compilation qui optimise et légalise les binaires WebAssembly (comme les binaires). L'exécution de Binaryen dans le cadre de la compilation est assez coûteuse, mais elle n'est requise que dans certaines conditions. Pour les versions de débogage, nous pouvons accélérer considérablement la durée de compilation si nous évitons d'avoir recours aux passes Binaryen. La passe Binaryen la plus courante concerne la légalisation des signatures de fonction impliquant des valeurs entières de 64 bits. L'intégration de WebAssembly BigInt à l'aide de -sWASM_BIGINT permet d'éviter cela.

$ emcc -sUSE_SDL=2 -g -O0 -o mandelbrot.html mandelbrot.cc -sALLOW_MEMORY_GROWTH -sWASM_BIGINT -sERROR_ON_WASM_CHANGES_AFTER_LINK

Nous avons lancé l'indicateur -sERROR_ON_WASM_CHANGES_AFTER_LINK pour faire bonne mesure. Il permet de détecter quand Binaryen est en cours d'exécution et de réécrire le binaire de manière inattendue. De cette façon, nous pouvons nous assurer que nous restons sur la voie rapide.

Même si notre exemple est assez petit, nous pouvons toujours voir l'effet de l'omission de Binaryen ! D'après time, cette commande s'exécute un peu moins de 1 s, soit une demi-seconde plus rapide qu'auparavant.

Ajustements avancés

Ignorer l'analyse des fichiers d'entrée

Normalement, lorsque vous associez un projet Emscripten, emcc analyse tous les fichiers et bibliothèques d'objets d'entrée. Cela permet d'implémenter des dépendances précises entre les fonctions de la bibliothèque JavaScript et les symboles natifs de votre programme. Pour les projets plus volumineux, cette analyse supplémentaire des fichiers d'entrée (à l'aide de llvm-nm) peut augmenter considérablement le temps de liaison.

Il est possible de l'exécuter à la place avec -sREVERSE_DEPS=all, qui indique à emcc d'inclure toutes les dépendances natives possibles des fonctions JavaScript. Cela entraîne une légère surcharge du code, mais peut accélérer les temps de liaison et peut être utile pour les versions de débogage.

Pour un projet aussi petit que notre exemple, cela ne fait pas vraiment de différence, mais si votre projet contient des centaines, voire des milliers de fichiers d'objets, cela peut considérablement améliorer les temps de liaison.

Suppression de la section "Nom"

Dans les projets de grande envergure, en particulier ceux qui utilisent beaucoup de modèles C++, la section "name" de WebAssembly peut être très volumineuse. Dans notre exemple, il ne s'agit que d'une infime partie de la taille totale du fichier (voir le résultat de llvm-objdump ci-dessus), mais elle peut parfois être très importante. Si la section "name" (nom) de votre application est très volumineuse et que les informations de débogage du nain sont suffisantes pour vos besoins de débogage, il peut être intéressant de supprimer la section "name" (nom) :

$ emstrip --no-strip-all --remove-section=name mandelbrot.wasm

La section "name" de WebAssembly sera supprimée tout en conservant les sections de débogage DWARF.

Déboguer la fission

Les binaires contenant de nombreuses données de débogage n'entraînent pas seulement une pression sur le temps de compilation, mais également sur le temps de débogage. Le débogueur doit charger les données et créer un index pour celles-ci, afin de pouvoir répondre rapidement aux requêtes telles que "Quel est le type de la variable locale x ?".

La fission de débogage nous permet de diviser les informations de débogage d'un binaire en deux parties: une, qui reste dans le binaire, et une autre, contenue dans un fichier d'objet DWARF distinct (.dwo). Vous pouvez l'activer en transmettant l'indicateur -gsplit-dwarf à Emscripten:

$ emcc -sUSE_SDL=2 -g -gsplit-dwarf -gdwarf-5 -O0 -o mandelbrot.html mandelbrot.cc  -sALLOW_MEMORY_GROWTH -sWASM_BIGINT -sERROR_ON_WASM_CHANGES_AFTER_LINK

Nous affichons ci-dessous les différentes commandes et les fichiers générés par la compilation sans données de débogage, avec les données de débogage, et enfin avec les données de débogage et les données de débogage.

les différentes commandes et 
quels fichiers sont générés

Lors de la division des données DWARF, une partie des données de débogage se trouve avec le binaire, tandis qu'une grande partie est placée dans le fichier mandelbrot.dwo (comme illustré ci-dessus).

Pour mandelbrot, nous n'avons qu'un seul fichier source, mais les projets sont généralement plus volumineux et incluent plusieurs fichiers. La commande de débogage génère un fichier .dwo pour chacun d'entre eux. Pour que la version bêta actuelle du débogueur (0.1.6.1615) puisse charger ces informations de débogage fractionné, nous devons toutes les regrouper dans un package DWARF (.dwp) semblable à celui-ci:

$ emdwp -e mandelbrot.wasm -o mandelbrot.dwp

regrouper des fichiers dwo dans un package DWARF

Créer le package DWARF à partir des objets individuels présente l'avantage de ne nécessiter qu'un seul fichier supplémentaire ! Nous travaillons actuellement au chargement de tous les objets individuels dans une prochaine version.

En quoi consiste DWARF 5 ?

Vous l'avez peut-être remarqué, nous avons intégré une autre option dans la commande emcc ci-dessus, -gdwarf-5. L'activation de la version 5 des symboles DWARF, qui n'est actuellement pas la version par défaut, constitue une autre astuce pour accélérer le débogage. Avec lui, certaines informations sont stockées dans le binaire principal que la version par défaut 4 a laissée de côté. Plus précisément, nous pouvons déterminer l'ensemble complet des fichiers sources uniquement à partir du binaire principal. Cela permet au débogueur d'effectuer des actions de base telles que l'affichage de l'arborescence source complète et la définition de points d'arrêt sans charger ni analyser l'intégralité des données de symboles. Le débogage avec des symboles de fractionnement est ainsi beaucoup plus rapide. Nous utilisons donc toujours les indicateurs de ligne de commande -gsplit-dwarf et -gdwarf-5 ensemble.

Le format de débogage DWARF5 nous donne également accès à une autre fonctionnalité utile. Elle introduit un index de nom dans les données de débogage qui seront générées lors de la transmission de l'option -gpubnames:

$ emcc -sUSE_SDL=2 -g -gdwarf-5 -gsplit-dwarf -gpubnames -O0 -o mandelbrot.html mandelbrot.cc -sALLOW_MEMORY_GROWTH -sWASM_BIGINT -sERROR_ON_WASM_CHANGES_AFTER_LINK

Lors d'une session de débogage, les recherches de symboles se produisent souvent en recherchant une entité par son nom, par exemple en recherchant une variable ou un type. L'index de noms accélère cette recherche en pointant directement vers l'unité de compilation qui définit ce nom. Sans index de noms, une recherche exhaustive de l'ensemble des données de débogage serait nécessaire pour trouver l'unité de compilation appropriée qui définit l'entité nommée que nous recherchons.

Pour les curieux: examiner les données de débogage

Vous pouvez utiliser llvm-dwarfdump pour avoir un aperçu des données DWARF. Essayons ceci:

llvm-dwarfdump mandelbrot.wasm

Cela nous donne un aperçu des unités de compilation (autrement dit, les fichiers sources) pour lesquelles nous disposons d'informations de débogage. Dans cet exemple, nous ne disposons que des informations de débogage pour mandelbrot.cc. Les informations générales nous permettront de savoir que nous disposons d'un squelette, ce qui signifie simplement que ce fichier contient des données incomplètes et qu'il contient un fichier .dwo distinct contenant les dernières informations de débogage:

mandelbrot.wasm et infos de débogage

Vous pouvez également consulter les autres tables de ce fichier, par exemple au niveau de la table des lignes, qui montre le mappage du bytecode Wasm aux lignes C++ (essayez d'utiliser llvm-dwarfdump -debug-line).

Nous pouvons également consulter les informations de débogage contenues dans le fichier .dwo distinct:

llvm-dwarfdump mandelbrot.dwo

mandelbrot.wasm et infos de débogage

Résumé: Quel est l'avantage d'utiliser la fonctionnalité de débogage ?

La division des informations de débogage présente plusieurs avantages dans le cas d'applications volumineuses:

  1. Association plus rapide: l'éditeur de liens n'a plus besoin d'analyser toutes les informations de débogage. Les linkers doivent généralement analyser l'intégralité des données DWARF qui se trouvent dans le binaire. En extrayant de grandes parties des informations de débogage dans des fichiers distincts, les outils de création de liens traitent les fichiers binaires plus petits, ce qui réduit les délais d'association (en particulier pour les applications volumineuses).

  2. Débogage plus rapide: le débogueur peut ignorer l'analyse des symboles supplémentaires dans les fichiers .dwo/.dwp pour certaines recherches de symboles. Pour certaines recherches (comme les requêtes sur le mappage de lignes des fichiers Wasm vers C++), nous n'avons pas besoin d'examiner les données de débogage supplémentaires. Cela nous fait gagner du temps, car nous n'avons plus besoin de charger ni d'analyser les données de débogage supplémentaires.

1: Si vous ne disposez pas d'une version récente de llvm-objdump sur votre système et que vous utilisez emsdk, vous le trouverez dans le répertoire emsdk/upstream/bin.

Télécharger les canaux de prévisualisation

Vous pouvez 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.

  • Envoyez-nous une suggestion ou un commentaire via crbug.com.
  • Signalez un problème lié aux outils de développement en cliquant sur Autres options   Plus > Aide > Signalez un problème dans les outils de développement.
  • Tweetez à l'adresse @ChromeDevTools.
  • Faites-nous part de vos commentaires sur les vidéos YouTube sur les nouveautés des outils de développement ou sur les vidéos YouTube de conseils pour les outils de développement.