Débogage local et distant avec GDB

En tant que débogueur, GDB est un véritable couteau suisse. Et tout comme l’exploration de toutes les utilisations non évidentes de ces couteaux, votre réponse initiale à la portée de l’ensemble de fonctionnalités de GDB sera probablement une question de confusion, de confusion ultérieure et de rires occasionnels. C’est une réaction compréhensible dans le cas du couteau suisse car il est peu probable que l’on soit au milieu d’une campagne militaire ou piégé dans la nature. De même, il faut une session de débogage délicate pour vraiment apprendre à apprécier l’ensemble des fonctionnalités de GDB.

Si vous avez déjà utilisé GDB pour déboguer du code, il a probablement été enveloppé dans la couverture de confort d’un IDE. C’est bien sûr une façon d’utiliser GDB, mais limite les fonctionnalités disponibles à ce que l’EDI expose. Heureusement, l’interface de ligne de commande (CLI) de GDB n’a pas de telles limitations. L’apprentissage des commandes CLI GDB présente également l’avantage de pouvoir effectuer cette session de débogage à distance critique même sur le terrain via une session SSH via le modem satellite 9600 bauds à l’intérieur de votre couteau suisse, Cyber ​​Edition.

Ai-je poussé cette analogie trop loin? Probablement. Mais apprendre tout le potentiel de GDB vaut bien votre temps alors aujourd’hui, plongeons-nous pour affûter nos outils numériques.

Godmode pour l’exécution de code

Exemple de session GDB.

Le concept derrière un débogueur est assez simple: trop souvent, quelque chose empêche le code que vous avez écrit de fonctionner, ou vous voulez examiner de plus près certains états de l’application pendant son exécution. Un moyen simple de le faire est d’imprimer les valeurs des variables sur un terminal ou un port série, mais le moyen beaucoup plus puissant est d’utiliser un débogueur comme GDB pour travailler de manière interactive avec le code lors de son exécution.

Cela signifie suspendre l’exécution, avancer et reculer dans les différentes lignes de code, inspecter les cadres de pile et les parties de la mémoire, modifier le contenu de la mémoire et des variables spécifiques, etc. Parallèlement à la définition des points d’arrêt et à la surveillance des variables, pratiquement n’importe quelle partie de l’exécution de l’application peut être surveillée et influencée. Cela inclut quand les choses vont vers le sud et que l’exécution se termine avec une condition d’erreur, permettant de rappeler une trace de pile.

Bien que GDB puisse être utilisé avec le binaire de n’importe quelle application, il est infiniment plus utile lorsque les symboles de débogage sont également fournis au débogueur. Ces symboles de débogage sont des chaînes de texte comprenant le code source, ainsi que d’autres informations. Ils sont inclus dans le binaire par le compilateur lorsqu’il est invité à le faire. Pour les compilateurs basés sur GCC et LLVM, cela se fait généralement en utilisant le -g drapeau.

Héros local

L’exécution d’une session de débogage locale est un bon moyen de se familiariser avec l’utilisation de l’interface de ligne de commande de GDB. Assurez-vous d’avoir une référence de commande pratique à portée de main à tout moment lorsque vous utilisez GDB. Cela vous permettra de vous familiariser facilement avec les commandes les plus impliquées.

Les essentiels à connaître sont break (b), pour définir un point d’arrêt, des informations pour obtenir des informations locales, des threads, etc. backtrace (bt) et continue (c), next (n) et step (s) pour imprimer une trace arrière, poursuivre l’exécution et se déplacer dans le code par incréments, respectivement. Après avoir chargé l’exécutable avec GDB, le programme démarre avec run (r), qui peut être fourni avec n’importe quel argument de ligne de commande à l’exécutable.

Écrivons un programme très simple que nous pouvons utiliser pour la pratique du débogage:


/* hello.c - Hello World */

#include<stdio.h>

int main(void) {
        char hello[] = "Hello World";
        printf("%sn", hello);
        return 0;
}

Nous utiliserons le -g3 flag lors de la compilation pour inclure des symboles de débogage. Passons maintenant en revue cet exemple Hello World basé sur C:

gcc -o hello -g3 hello.c
gdb ./hello

[..]
(gdb) b main
Breakpoint 1 at 0x1169: file hello.c, line 5.
(gdb) run
Starting program: /home/hackaday/hello
Breakpoint 1, main() at hello.c:5
5 int main(void) {
(gdb) n
6 char hello[] = "Hello World";
(gdb) n
7 printf("%sn", hello);
(gdb)

Nous avons d’abord établi un point de rupture au main()fonction, alors run pour démarrer le programme. Après le point d’arrêt, à chaque exécution de next (ou simplement en appuyant sur Entrée sur une entrée vide pour répéter la commande précédente), nous passerons à la ligne suivante de l’application, sans passer aux appels de fonction. C’est ce que step est pour. Si nous utilisons printf() dans le code, par exemple, en utilisant step nous amènerait à examiner chaque ligne de cette fonction et sa mise en œuvre également. La question de savoir si cela est souhaitable dépend de ses besoins.

Enfin, nous pouvons examiner les variables et la mémoire en utilisant print (p) pour l’impression des variables et x pour imprimer des octets à une adresse mémoire. Par exemple:

(gdb) print hello
$1 = "Hello World"

La plupart des commandes sont assez simples et sûres. Interdire l’utilisation des GDB set commande, à l’aide de laquelle on peut non seulement modifier les paramètres de GDB, mais également modifier le contenu de la mémoire. Utilisez celui-ci avec prudence.

Jacking dans la télécommande

L’exécution d’une session GDB distante est à peu près la même chose qu’une session locale, avec la complication évidente d’avoir à établir la session sur un système distant. Ledit système distant peut être n’importe quoi, depuis un serveur, un bureau ou un autre système exécutant un système d’exploitation à part entière, jusqu’à un microcontrôleur (MCU) fonctionnant directement sur le métal nu.

La principale condition requise pour que GDB établisse une session de débogage sur un système distant est qu’il y ait un serveur GDB (gdbserver) à laquelle l’outil GDB peut se connecter. Ce serveur GDB agit alors comme un pont entre GDB et la session de débogage active. Cette connexion peut être établie via TCP ou une ligne série. Cela en fait une approche hautement portable qui fonctionne à la fois pour les serveurs distants ou les systèmes de bureau, ainsi que pour les cartes industrielles avec une liaison RS-232C.

Il est encore plus intéressant d’utiliser l’approche du serveur GDB pour créer un pont vers la fonctionnalité de débogage en circuit fournie par les plates-formes de microcontrôleur telles que les systèmes STM32 Cortex-M de ST. Cette même approche fonctionnera avec quelques modifications pour les plates-formes SAM et AVR basées sur ARM de Microchip.

OpenOCD comme serveur GDB

Quiconque a développé un MCU connaît probablement OpenOCD. Cet outil est inestimable dans la programmation d’une grande variété de MCU, mais est également livré avec un serveur GDB intégré. À titre d’exemple, imaginez vouloir établir une session GDB sur un MCU STM32, sur une carte de développement commune comme celle du STM32F4-Discovery.

La première étape consiste à démarrer le serveur GDB d’OpenOCD:

openocd -f board/stm32f4discovery.cfg

Ensuite, nous pouvons nous connecter à ce serveur via l’interface de bouclage, tout en fournissant également à GDB (à partir de la chaîne d’outils arm-none-eabi) le chemin vers le binaire ELF contenant le firmware:

arm-none-eabi-gdb --eval-command="target remote localhost:3333" "hello_world.elf"

GDB va maintenant se connecter au serveur GDB, avec OpenOCD en utilisant la fonction de débogage en circuit de la carte STM32F4-Discovery de l’interface ST-Link / V2 intégrée. Toutes les traductions de protocoles sont désormais effectuées par OpenOCD, permettant toutes les fonctionnalités habituelles de GDB, même si le code que nous débogageons s’exécute sur le MCU sur la carte de développement.

Comme le MCU aura déjà démarré le firmware que nous souhaitons déboguer, nous devrons encore effectuer une étape supplémentaire, qui consiste à réinitialiser le MCU pour obtenir une session GDB que nous pouvons utiliser:

(gdb) mon reset halt

Le MCU aura maintenant été réinitialisé et dans un état arrêté jusqu’à ce que nous fassions quelque chose. Nous allons maintenant ajouter un nouveau point d’arrêt temporaire et continuer:

(gdb) tbreak main
(gdb) c

Après la poursuite de l’exécution, ce point d’arrêt temporaire nous place juste au début de notre fonction principale, à partir de laquelle nous pouvons configurer des points d’arrêt et plus si nécessaire. Par exemple, nous pouvons vérifier la valeur d’un registre spécifique du périphérique GPIOA sur cette carte basée sur STM32F4. Disons que nous voulons voir si les états d’entrée et de sortie ont été définis correctement dans le registre GPIO_MODER:

(gdb) x/4tb 0x40020000
$1 = 0100 0000 0000 0000

La syntaxe spéciale du x La commande imprime une seule adresse 32 bits, sous forme de blocs d’octets uniques. L’emplacement du périphérique GPIOA se trouve dans la fiche technique du STM32F407, le Manuel de référence (RM) répertoriant les décalages pour des registres spécifiques au sein de l’E / S mappée en mémoire pour ce type de périphérique. Dans ce cas, le registre MODER est à l’offset 0x00, avec GPIOA à l’adresse 0x40020000. L’ordre des octets est imprimé de gauche à droite, ce qui signifie que le premier octet se trouve sur le côté gauche.

Dans ce cas, nous pouvons voir que MODER1 (pour la broche 1) est réglé sur «01», ce qui signifie un mode de sortie à usage général.

Il est temps d’arrêter de deviner

Il arrive souvent que je me retrouve ou que d’autres se retrouvent coincés à couler sur des lignes de code, spéculant laquelle de ces lignes pourrait être la cause des symptômes étranges. Qu’il suffise de dire que cela n’est ni amusant ni productif. Avec des outils comme Valgrind, les débogueurs comme GDB sont parfaits pour obtenir des réponses aux questions, même aux questions que vous ne saviez pas que vous vouliez poser. C’est particulièrement utile avec quelque chose comme le développement embarqué, où le retour immédiat du firmware nouvellement flashé peut être… absent ou pas tout à fait comme prévu.

Il est avantageux d’établir une routine de test stricte pour isoler les cas de test et de frapper le micrologiciel problématique in-situ avec un plan de test ciblé, en utilisant des outils comme GDB. Créez une liste de contrôle des éléments à vérifier en premier lorsque quelque chose ne fonctionne pas, puis progressez à partir de là.

Aussi non déterministe que le débogage puisse parfois sembler – et avec Heisenbugs s’efforçant certainement de le faire apparaître de cette façon – en fin de compte, il y a une bonne et solide raison à chaque problème. Vous avez juste besoin de savoir ce qu’il faut regarder de quelle manière. Se familiariser avec un outil puissant comme GDB y est définitivement un atout majeur.

François Zipponi
Je suis François Zipponi, éditorialiste pour le site 10-raisons.fr. J'ai commencé ma carrière de journaliste en 2004, et j'ai travaillé pour plusieurs médias français, dont le Monde et Libération. En 2016, j'ai rejoint 10-raisons.fr, un site innovant proposant des articles sous la forme « 10 raisons de... ». En tant qu'éditorialiste, je me suis engagé à fournir un contenu original et pertinent, abordant des sujets variés tels que la politique, l'économie, les sciences, l'histoire, etc. Je m'efforce de toujours traiter les sujets de façon objective et impartiale. Mes articles sont régulièrement partagés sur les réseaux sociaux et j'interviens dans des conférences et des tables rondes autour des thèmes abordés sur 10-raisons.fr.