Il s'avère que le principe de débogage sous-jacent de gdb est si simple

Introduction

Cet article parlera du fameux GDB . Ne ​​mentionnons pas sa riche expérience. Comme son frère GCC , il est né avec une clé en or , et son statut dans la famille GNU est inébranlable. Je crois que chaque ingénieur de développement embarqué a utilisé gdb pour déboguer des programmes. Si vous dites que vous ne l'avez pas utilisé, cela peut seulement signifier que votre expérience de développement n'est pas assez rude et que vous devez continuer à être battu par BUG.

Nous savons tous que lors de la compilation avec gcc, vous pouvez utiliser l' option -g pour incorporer plus d'informations de débogage dans le fichier exécutable, alors quelles informations de débogage sont incorporées? Comment ces informations de débogage interagissent-elles avec les instructions binaires? Lors du débogage, comment obtenir les informations de contexte dans la pile d'appels de fonction dans les informations de débogage?

En réponse aux doutes ci-dessus, Brother Dao a utilisé deux articles pour décrire en détail les problèmes les plus profonds, afin que vous puissiez les regarder en même temps.

Le premier article est le présent. Le contenu principal est d'introduire le principe de débogage sous - jacent de GDB. Voyons quel mécanisme GDB utilise pour contrôler l'ordre d'exécution du programme débogué.

Dans le deuxième article, nous choisissons un langage LUA compact et bien équipé pour analyser, de l'analyse du code source à la pile d'appels de fonction, du jeu d'instructions à la modification de la bibliothèque de débogage , le tout en une seule fois.

Il y a plus de contenu et la lecture de cet article peut prendre plus de temps. Pour votre santé, il n'est pas recommandé de lire cet article en position accroupie.

Deux, modèle de débogage GDB

Le débogage GDB comprend deux programmes: le programme gdb et le programme débogué. Selon que ces deux programmes s'exécutent sur le même ordinateur, le modèle de débogage de GDB peut être divisé en deux types:

  1. Débogage local
  2. Débogage à distance

Débogage local : le débogueur et le programme débogué s'exécutent sur le même ordinateur .

Débogage à distance : le programme de débogage s'exécute sur un ordinateur et le programme débogué s'exécute sur un autre ordinateur .

Le programme de débogage visuel n'est pas le but, c'est juste un shell utilisé pour encapsuler GDB. Nous pouvons soit utiliser la fenêtre de terminal sombre pour entrer manuellement les commandes de débogage; nous pouvons également choisir l'environnement de développement intégré (IDE), qui a intégré le débogage dans l'EDI , afin que nous puissions utiliser divers boutons au lieu de saisir manuellement les commandes de débogage.

Par rapport au débogage local, il existe un autre programme GdbServer dans le débogage à distance. Le programme cible et lui s'exécutent sur la machine cible , qui peut être un ordinateur x86 ou une carte ARM. La ligne rouge sur la figure indique la communication entre GDB et GdbServer via le réseau ou le port série. Puisqu'il s'agit de communication, un ensemble de protocoles de communication doit être requis: protocole RSP , le nom complet est: GDB Remote Serial Protocol (GDB remote communication protocol).

En ce qui concerne le format et le contenu spécifiques du protocole de communication, nous n'avons pas besoin de nous en soucier, nous avons juste besoin de savoir: ce sont toutes des chaînes , avec un caractère de début fixe ('$') et un caractère de fin ('#'), et deux seize à la fin.Le caractère ASCII de la base est utilisé comme somme de contrôle, et il suffit d'en savoir autant. Pour plus de détails, si vous pouvez jeter un coup d'œil à l'inactivité XX, en fait, ces accords, comme toutes sortes de réglementations étranges dans la société, sont tous pensés par un tas de briques dans les toilettes.

Dans le deuxième article expliquant LUA, nous implémenterons un prototype de débogage distant similaire. Le protocole de communication est aussi une chaîne de caractères. Après avoir simplifié directement le protocole HTTP, il est utilisé et il est très clair et pratique.

Trois, instructions de débogage GDB

Par souci d'exhaustivité, voici quelques commandes de débogage GDB publiées ici, juste avec des connaissances perceptives.

De plus, toutes les instructions ne sont pas répertoriées ici. Les instructions répertoriées sont toutes couramment utilisées et plus faciles à comprendre. Lors de l'explication de LUA, nous choisirons certaines des instructions pour une comparaison détaillée, y compris le mécanisme de mise en œuvre sous-jacent.

Chaque commande de débogage comporte de nombreuses options de commande. Par exemple, les points d'arrêt incluent: définir des points d'arrêt, supprimer des points d'arrêt, des points d'arrêt conditionnels, les désactiver temporairement et les activer . L'objectif de cet article est de comprendre le mécanisme de débogage sous-jacent de gdb, de sorte que l'utilisation de ces instructions au niveau de la couche application n'est plus répertoriée. Il existe de nombreuses ressources sur le réseau.

Quatrièmement, la relation entre GDB et le programme débogué

Pour faciliter la description, écrivez d'abord le programme C le plus simple :

#include <stdio.h>

int main(int argc, char *argv[])
{
    int a = 1;
    int b = 2;
    int c = a + b;
    printf("c = %d \n", c);
    return 0;
}

Commande de compilation:

$ gcc -g test.c -o test

Nous débogageons le test du programme exécutable et entrons la commande:

$ gdb ./test

La sortie est la suivante:

Dans la dernière ligne, vous pouvez voir que le curseur clignote. C'est le programme gdb qui attend que nous lui émettions des commandes de débogage.

Lorsque la fenêtre de terminal sombre ci-dessus exécutait gdb ./test, de nombreuses choses compliquées se sont produites dans le système d'exploitation:

Le système démarre d'abord le processus gdb . Ce processus appelle la fonction système fork () pour créer un processus enfant . Ce processus enfant fait deux choses:

  1. Appelez la fonction système ptrace (PTRACE_TRACEME, [autres paramètres]);
  2. Le test du programme exécutable est chargé et exécuté via execc, puis le programme de test commence à s'exécuter dans ce sous-processus.

Un point supplémentaire: il est parfois appelé programme et parfois processus dans le texte. «Programme» décrit un concept statique , c'est-à-dire un ensemble de données se trouvant sur le disque dur, et «processus» décrit un processus dynamique . Une fois le programme lu et chargé dans la mémoire, il y a un bloc de contrôle de tâche (un structure de données) est spécialement utilisé pour gérer ce processus.

Après avoir posé les bases pendant une longue période, c'est finalement au tour du protagoniste de faire ses débuts, c'est-à-dire la fonction d'appel système ptrace (les paramètres seront expliqués plus loin). C'est avec son aide que gdb dispose de puissantes capacités de débogage. Le prototype de fonction est:

#include <sys/ptrace.h>
long ptrace(enum __ptrace_request request, pid_t pid, void *addr, void *data);

Jetons un coup d'œil à l'introduction de cette fonction chez l'homme:

Tracer est le programme de débogage, qui peut être compris comme le programme gdb; tracee est le programme débogué, qui correspond au test du programme cible sur la figure. Les étrangers aiment généralement utiliser -er et -ee pour exprimer la relation active et passive. Par exemple, l'employé est l'employeur (le patron) et l'employé est le travailleur embauché (battant le travailleur).

La fonction système ptrace est un appel système pour le suivi de processus fourni par le noyau Linux. Grâce à lui, un processus (gdb) peut lire et écrire l'espace d'instructions, l'espace de données, la pile et les valeurs de registre d'un autre processus (test). Et le processus gdb prend en charge tous les signaux du processus de test, ce qui signifie que tous les signaux envoyés par le système au processus de test sont reçus par le processus gdb. De cette manière, l'exécution du processus de test est contrôlée par gdb pour atteindre l'objectif du débogage.

En d'autres termes, s'il n'y a pas de débogage gdb, il y a une interaction directe entre le système d'exploitation et le processus cible; si gdb est utilisé pour déboguer le programme, alors le signal envoyé par le système d'exploitation au processus cible sera intercepté par gdb et gdb détermineront en fonction des attributs du signal: Lors de la poursuite de l'exécution du programme cible, s'il faut transférer le signal actuellement intercepté vers le programme cible, de cette manière, le programme cible exécutera les actions correspondantes sous la commande du signal envoyé par gdb.

Cinq, comment GDB débogue le processus de service qui a été exécuté

Y a-t-il un petit partenaire qui soulèvera une telle question: Le test de programme débogué ci-dessus est exécuté depuis le début, pouvez-vous utiliser gdb pour déboguer un processus de service qui est déjà en cours d'exécution? La réponse est oui. Il s'agit du premier paramètre de la fonction système ptrace. Ce paramètre est une valeur énumérée, dont deux sont importantes: PTRACE_TRACEME et PTRACE_ATTACH < .

Dans l'explication ci-dessus, le paramètre utilisé par le processus enfant pour appeler la fonction système ptrace est PTRACE_TRACEME . Faites attention au texte orange: le processus enfant appelle ptrace, ce qui équivaut au processus enfant disant au système d'exploitation: le processus gdb est mon père. Si vous voulez m'envoyer un signal, merci de l'envoyer directement au processus gdb!

Si vous souhaitez déboguer un processus B déjà exécuté , vous devez appeler ptrace ( PTRACE_ATTACH , [autres paramètres]) dans le processus parent de gdb. À ce stade , le processus gdb s'attachera (liera) au processus exécuté B , gdb adopte processus B comme son propre processus enfant , et le comportement du processus enfant B équivaut à une opération PTRACE_TRACEME. À ce moment, le processus gdb enverra le signal SIGSTO au processus enfant B. Après que le processus enfant B reçoive le signal SIGSTOP, il suspendra l'exécution et entrera dans l'état TASK_STOPED, indiquant qu'il est prêt à être débogué.

Par conséquent, que vous débogiez un nouveau programme ou un programme de service qui est déjà en cours d'exécution, via l'appel système ptrace, le résultat final est: le programme gdb est le processus parent, le programme débogué est le processus enfant et tous les signaux du processus enfant Tous sont pris en charge par le processus parent gdb, et le processus parent gdb peut visualiser et modifier les informations internes du processus des enfants, y compris des piles, des registres, et ainsi de suite .

En ce qui concerne la liaison, plusieurs restrictions doivent être comprises: l'auto-liaison n'est pas autorisée, les liaisons multiples vers le même processus ne sont pas autorisées et le processus n ° 1 n'est pas autorisé à être lié.

Six, en regardant comment GDB implémente les instructions de point d'arrêt

Le principe est terminé, nous définissons ici une instruction de débogage de point d'arrêt (break) , pour jeter un coup d'œil au mécanisme de débogage interne de gdb.
Prenez toujours le code ci-dessus comme exemple et republiez le code ici:

#include <stdio.h>

int main(int argc, char *argv[])
{
    int a = 1;
    int b = 2;
    int c = a + b;
    printf("c = %d \n", c);
    return 0;
}

Jetons un coup d'œil à ce à quoi ressemble le code de désassemblage compilé, l'instruction de compilation:

gcc -S test.c; test de chat S)

Seule une partie du code de démontage est affichée ici, tant que le principe sous-jacent peut être expliqué, notre objectif sera atteint.

Comme mentionné ci-dessus, après avoir exécuté gdb ./test, gdb va bifurquer un processus enfant. Ce processus enfant appelle d'abord ptrace puis exécute le programme de test, afin que l'environnement de débogage soit prêt.

Nous associons le code source et le code d'assemblage pour une compréhension facile:

Entrez la commande de point d'arrêt "break 5" dans la fenêtre de débogage, et gdb fait deux choses à ce stade:

  1. La 10e ligne de code d'assemblage correspondant à la 5e ligne de code source est stockée dans la liste liée des points d'arrêt .
  2. Dans la 10e ligne du code d'assemblage, insérez l'instruction d'interruption INT3, ce qui signifie que la 10e ligne du code d'assemblage est remplacée par INT3 .

Ensuite, continuez à entrer la commande d'exécution "run" dans la fenêtre de débogage ( exécutez jusqu'à ce qu'elle atteigne un point d'arrêt et faites une pause ). Lorsque le pointeur PC (un pointeur interne qui pointe vers la ligne de code à exécuter) dans le code d'assemblage est exécuté à la ligne 10, il est trouvé. Il s'agit d'une instruction INT3, le système d'exploitation envoie donc un signal SIGTRAP au processus de test.

À ce moment, la 10e ligne de code d'assemblage a été exécutée et le pointeur du PC pointe vers la 11e ligne.

Comme mentionné ci-dessus, tout signal envoyé par le système d'exploitation à tester est pris en charge par gdb, ce qui signifie que gdb recevra le signal SIGTRAP en premier, et gdb trouve que le code d'assemblage actuel exécute la ligne 10, il va donc à la liste des points d'arrêt Lors de la recherche, on constate que la 10e ligne de code est stockée dans la liste chaînée, indiquant qu'un point d'arrêt est défini sur la 10e ligne. Donc gdb a fait deux autres opérations:

  1. Remplacez la 10e ligne «INT3» dans le code d'assemblage par le code d'origine dans la liste des points d'arrêt.
  2. Ramenez le pointeur du PC en arrière, c'est-à-dire qu'il pointe sur la ligne 10.

Ensuite, gdb continue d'attendre les instructions de débogage de l'utilisateur.

À ce moment, cela équivaut à la prochaine instruction exécutée soit la 10e ligne du code d'assemblage , qui est la 5e ligne du code source . Du point de vue de notre débogueur, le programme en cours de débogage est mis en pause au point d'arrêt sur la ligne 5. À ce stade, nous pouvons continuer à entrer d'autres commandes de débogage à déboguer, telles que: afficher les valeurs des variables, afficher les informations de la pile, modifier les valeurs des variables locales , etc. Attendez.

Seven, jetant un œil à la façon dont GDB implémente l'instruction en une seule étape ensuite

Prenez le code source et le code d'assemblage à titre d'exemple, en supposant que le programme s'arrête à la 6ème ligne du code source, c'est-à-dire à la 11ème ligne du code d'assemblage:

Entrez la commande d'exécution en une seule étape suivante dans la fenêtre de débogage . Notre objectif est d' exécuter une ligne de code , qui est, pour terminer l' exécution de la 6e ligne du code source, puis arrêter à la ligne 7 . Lorsque gdb recevra la prochaine exécution, il calculera la 7ème ligne du code source, qui devrait correspondre à la 14ème ligne du code d'assemblage , donc gdb contrôle le pointeur PC dans le code d'assemblage pour s'exécuter jusqu'à la fin de la 13ème ligne , c'est-à-dire que le PC pointe vers les 14 premières lignes lorsqu'il s'est arrêté, puis continue d'attendre les commandes de débogage d'entrée utilisateur.

8. Résumé

Grâce aux deux instructions de débogage de break et next, nous avons compris comment les instructions de débogage sont gérées dans gdb. Bien sûr, il y a beaucoup plus d'instructions de débogage dans gdb, y compris l' acquisition plus compliquée des informations de pile, la modification des valeurs de variables, etc. Les amis intéressés peuvent continuer à suivre en profondeur.

Quand j'écrirai la bibliothèque de débogage dans le langage LUA plus tard, j'aborderai ce problème plus en profondeur et en détail.Après tout, le langage LUA est plus petit et plus simple. Je vais également montrer la partie code de la façon de définir le pointeur PC dans le code LUA, afin que nous ayons une meilleure compréhension et une meilleure compréhension de l'implémentation interne d'un langage de programmation, et que nous puissions également enregistrer une vidéo, afin que nous puissions mieux expliquer les détails internes du langage LUA.


Si cet article peut vous apporter un peu d'aide, n'hésitez pas à commenter, transmettre et partager avec vos amis.

Je vais continuer à résumer l'expérience de combat réelle dans le processus de développement de projets embarqués dans la ville de l'Internet des objets public-public-number IOT , je crois que vous ne serez pas déçu!

Je suppose que tu aimes

Origine blog.csdn.net/u012296253/article/details/111150497
conseillé
Classement