grim7reaper

A Code Craftsman

Outils de débogage pour la programmation concurrente — Valgrind

Valgrind est un logiciel sous licence GPLv2. Il a été développé à la base comme un outil pour déboguer les erreurs de gestion de la mémoire sous GNU/Linux. Par la suite, il a évolué pour devenir un véritable framework pour le développement d’outils d’analyse dynamique. Aujourd’hui, toute une suite d’outils est disponible et elle permet de :

  • détecter les problèmes dans la gestion de la mémoire (Memcheck) ;
  • faire du profilage (Callgrind, Cachegrind, Massif) ;
  • détecter les erreurs dans les programmes multithread (Helgrind, DRD), ce sont ces outils qui vont nous intéresser dans le cadre de cette série d’articles.

Approche

Valgrind fonctionne en instrumentant le code. Cela signifie qu’il ajoute dynamiquement du code à exécuter en plus du code du programme en cours d’analyse afin de pouvoir le surveiller. Il y a deux façons d’instrumenter le code1 :

  • Copy and Annotate : le code du programme est copié quasiment à l’identique (seules quelques instructions pour changer le flot d’exécution du programme sont insérées). Chaque instruction est annotée avec une description de ses effets, ces annotations sont ensuite utilisées par le logiciel d’analyse pour réaliser l’instrumentation.
  • Disassemble and Resynthesize : au lieu d’exécuter directement le code du programme, on le convertit en une représentation intermédiaire. Ce code intermédiaire est ensuite instrumenté (en ajoutant des instructions, elles aussi sous forme intermédiaire) puis l’ensemble est reconverti en code machine pour être exécuté par le processeur.

La seconde méthode est celle utilisée par Valgrind. Un de ses avantages est que le compilateur à la volée utilisé pour transformer le code intermédiaire en code machine peut donc également optimiser le code d’instrumentation.

Architecture

Valgrind est composé de trois grandes parties :

  • le cœur (Coregrind) qui contient tout un ensemble de services : un ordonnanceur de threads, l’implémentation d’un sous-ensemble de la libc, un allocateur de mémoire, un gestionnaire de signaux, … La plupart de ces services sont dépendants du système sous-jacent.
  • la libVEX qui est responsable de la traduction du code machine en représentation intermédiaire et réciproquement. Cette partie dépend bien sûr aussi de l’architecture.
  • les outils (Memcheck, DRD, …) qui réalisent l’instrumentation du code et qui analysent l’exécution du programme. Étant donné que ces outils se basent sur Coregrind et la libVEX ils sont normalement indépendant de l’architecture.

Instrumentation

La représentation intermédiaire utilisé par Valgrind est appelé VEX. C’est une représentation qui possède un typage, ne dépend pas de l’architecture utilisée (les instructions spécifiques à une architecture sont implémentées en tant que fonctions C) et qui est de la forme SSA (représentation où chaque variable est assignée une seule fois) qui est une représentation très utilisée dans les compilateurs car elle permet de transformer et optimiser le code (élimination de code mort par exemple) plus simplement.

Le processus d’instrumentation est composé de huit étapes :

Architecture et fonctionnement global de Valgrind

  1. désassemblage (le code machine du programme est converti en VEX) ;
  2. optimisation du code VEX ;
  3. instrumentation du code VEX par l’outil ;
  4. optimisation du code instrumenté ;
  5. construction de l’arbre à partir du code instrumenté (préparation pour l’étape suivante) ;
  6. conversion de l’arbre en liste d’instructions ;
  7. allocation des registres ;
  8. assemblage (la liste d’instructions est convertie en code machine).

Enfin, le code instrumenté est exécuté par le processeur. Selon les instrumentations réalisées, le code pourra émettre des évènements (accès mémoire à l’adresse X, allocation/libération de mémoire, appels système, …) à l’intention de l’outil.

Dans le prochain épisode…

Maintenant que l’on sait comment s’architecture Valgrind, on va pouvoir se pencher plus en détails sur le fonctionnement des outils qui nous intéresse dans le cadre de la programmation concurrente : Helgrind et DRD.


  1. Nethercote, N., and Seward, J. Valgrind: a framework for heavyweight dynamic binary instrumentation. In Proceedings of the 2007 ACM SIGPLAN conference on Programming language design and implementation (New York,NY, USA, 2007), PLDI ’07, ACM, pp. 89–100.