Valgrind : un outil méconnu et resté dans l'ombre, ou un outil de débogage sérieux ?

Last updated

Il y a quelques mois, nous nous sommes intéressés aux « sanitizers » C++, ces petits outils très pratiques qui nous aident à garder notre code propre et exempt de bogues liés à la mémoire.

Aujourd’hui, nous allons faire un petit retour en arrière et parler d’un outil un peu ancien, mais toujours très puissant, appelé Valgrind. Il est utilisé par les développeurs depuis un certain temps déjà, car il permet de détecter un large éventail d’erreurs, telles que les lectures de mémoire non initialisée, les débordements de tampon du tas, les fuites de mémoire, les blocages, etc.

Après la sortie par Clang de sa gamme de « sanitizers », Valgrind a été relégué au second plan, mais comme vous le verrez dans cet article de blog, il existe certains cas où Valgrind surpasse largement les « sanitizers ». 

Une véritable bête sous le capot

L'un des principaux avantages de Valgrind par rapport aux outils de vérification de sécurité réside dans le fait qu'il ne nécessite pas d'instrumenter le logiciel avant de le tester. Cela signifie qu'il peut être utilisé pour déboguer n'importe quel type de logiciel « tel quel », sans avoir besoin d'accéder à son code source.

Cela ne serait pas possible sans la « bête » qui se cache sous le capot de Valgrind. En bref, une fois Valgrind initialisé, il prend le contrôle de votre programme et l'exécute sur un processeur simulé fourni par le noyau de Valgrind. Il ajoute ensuite son propre code d'instrumentation en fonction du type d'erreurs que vous souhaitez détecter. 

Contrairement aux « sanitizers », Valgrind utilise l’instrumentation binaire dynamique (DPI) et la compilation « just-in-time » (JIT) pour intégrer le code d’instrumentation au code de notre logiciel, c’est-à-dire pour intercepter les appels aux fonctions d’allocation afin de stocker des informations supplémentaires. 

Here’s how it works. Each allocated block maps to a shadow block where the call stack at the time of the malloc function call is stored. Once the free function is called, Valgrind tries to find the shadow block corresponding to the address passed to free. If the block is not found, Valgrind emits an error message. Otherwise, the block is added to the queue and gets marked as inaccessible. This way, it is possible to detect invalid access to freed memory. However, please note that blocks can be removed from the queue once the system runs out of free space.

Vous souhaitez en savoir plus ?
Contactez-nous dès aujourd'hui pour échanger avec un expert Microblink en matière de fraude et d'identité

Flexibilité offerte par une architecture en couches

Comme le montre la figure ci-dessous, Valgrind se compose de deux couches : le noyau Valgrind et le plugin de l'outil, qui peut correspondre à n'importe quel outil de la suite Valgrind, notamment : 

  • Memcheck – suit les allocations de mémoire et signale les fuites de mémoire
  • Helgrind – détecte les problèmes liés au multithreading (par exemple, les blocages, les conflits d'accès, etc.)
  • Cachegrind – sert de profileur de cache et de prédiction de branche
  • Massif – analyse l'utilisation de la mémoire du tas

N'oubliez pas que Valgrind est un logiciel libre et que vous pouvez créer votre propre outil si vous le souhaitez. 

Ces deux couches ont chacune leur rôle. La partie centrale charge le logiciel à tester dans le processus et désassemble son code. Une fois cette opération terminée, des fragments de code sont transmis au plugin de l'outil, qui ajoute l'instrumentation au code, puis le réassemble. 

Une méthode plus simple pour déboguer

Comme nous l’avons mentionné dans notre précédent article de blog, les « sanitizers » C++ nécessitent que votre code soit recompilé. Cela peut s’avérer gênant lorsque vous souhaitez tester votre code à la fois avec AddressSanitizer et MemorySanitizer. Comme ces deux outils ne peuvent pas fonctionner ensemble, vous devriez effectuer plusieurs séries de tests pour détecter les lectures de mémoire non initialisée et d’autres bogues liés aux adresses. En revanche, Valgrind peut exécuter pratiquement n’importe quel logiciel tel quel. La seule condition est qu’il prenne en charge toutes les instructions utilisées par notre logiciel.

Imaginons que vous souhaitiez déboguer une bibliothèque dont le code source vous est inaccessible. L'utilisation de « sanitizers » ne donnerait aucun résultat car, contrairement à Valgrind, ceux-ci fonctionnent au niveau du compilateur. Avec Valgrind, vous pouvez gérer ces cas par défaut, mais gardez à l'esprit que vous risquez d'obtenir des messages d'erreur qui ne vous diront pas grand-chose, puisque vous n'avez aucun contrôle sur ce code. Vous êtes toutefois libre de filtrer ces messages en les inscrivant dans un fichier de suppression qui est lu au démarrage de Valgrind. 

Outre un processus de débogage plus fluide, Valgrind constitue une excellente alternative sur les plateformes où les « sanitizers » ne sont pas pris en charge. Apple Clang, par exemple, n’est pas fourni avec LeakSanitizer, ce qui fait de Valgrind votre meilleure alternative, à moins que vous ne souhaitiez changer de compilateur. 

Les performances constituent le principal inconvénient

Examinons une version légèrement modifiée de l'exemple présenté dans notre premier article consacré aux « sanitizers » en C++ : 

char const * src{ "Hello world!" };
auto const dst{ std::make_unique< char[] >( std::strlen( src ) ) };

for ( auto i{ 0ul }; i < 1000000; ++i )
{
std::strcpy( dst.get(), src );
}

std::puts( dst.get() );

A for loop has been added to emphasize the performance difference between AddressSanitizer and Valgrind. In both cases, the code is compiled using the following command (running ASan also requires us to add -fsanitize=address):

clang++ example.cpp -g -o example.out

Un rapide coup d'œil au graphique ci-dessous révèle un écart de performances considérable entre Valgrind et ASan, ce qui explique pourquoi de nombreux développeurs peuvent se montrer réticents à utiliser cet outil dès le départ.

Comparaison côte à côte 

Afin de mieux comprendre quels types de bogues Valgrind et les « sanitizers » sont capables de détecter, examinons le tableau suivant : 

* MSan détecte les
lectures de mémoire non initialisée** UBSan détecte les comportements indéfini

As you can see, Valgrind won’t help you catch overflows in stack and global variables. This is because it only has access to the heap allocations performed by malloc function. Also, don’t count on it detecting any undefined behavior in your code — though you’ll get warned about trying to access memory that resulted from undefined behavior.  

Cela dit, ASan n’est pas non plus parfait. Il ne détecte ni les lectures de mémoire non initialisée ni les comportements indéfini, contrairement à MSan et UBSan. 

Valgrind ou les « sanitizers » : lequel choisir ?

La question reste donc posée : faut-il utiliser Valgrind ou les « sanitizers » constituent-ils une meilleure alternative ? Il n'y a pas de réponse unique à cette question, car ces deux outils fonctionnent de manière très différente. Idéalement, il faudrait utiliser les deux, en fonction de votre environnement et des erreurs que vous souhaitez détecter.

Même si les outils de vérification de code imposent aujourd’hui une charge bien moindre au processeur et détectent un plus large éventail d’erreurs, ils présentent tout de même des inconvénients. Ils fonctionnent au niveau du compilateur, ce qui implique que vous disposiez du code source. Ils nécessitent également de recompiler votre code à chaque exécution de test, prennent plus de temps à intégrer et ne sont pas pris en charge sur certaines plateformes.  

Si vous travaillez sur un projet de grande envergure et que vous ne souhaitez pas avoir à vous soucier de la recompilation de votre code, il peut être plus judicieux d’opter pour Valgrind. Nous l'avons trouvé particulièrement utile pour déboguer des bibliothèques à code fermé et détecter plus facilement des problèmes tels que les bogues de type « use-after-free ». Cela dit, comme les « sanitizers » ne cessent de s'améliorer, nous avons tendance à les privilégier par rapport à Valgrind.

Au final, quels que soient les outils que vous utilisez, l'essentiel est de rendre votre code aussi sûr que possible. Dans cette optique, ne manquez pas nos prochains articles intéressants sur le débogage en C++.

Découvrez nos solutions

L’exploration de nos solutions est à portée de clic. Essayez nos produits ou discutez avec l’un de nos experts pour approfondir notre offre.

Rapport
Analyse de la montée en puissance de la fraude à l'identité alimentée par l'IA

L'IA n'a pas seulement accéléré la fraude. Elle en a fait un véritable système. Nous avons analysé des millions d'interactions liées à l'identité afin de cartographier l'évolution des attaques d'usurpation d'identité selon les régions, les types d'attaques et les niveaux de sophistication — et d'identifier ce que les organisations doivent repenser pour rester dans la course.

Consultez les données