Maîtrisez l'outil de débogage GDB et corrigez facilement les bogues !

1. Qu'est-ce que GDB

gdb est l'abréviation de GNU debugger, qui est un outil de débogage de programmation.

  • Site officiel de GDB : https://www.gnu.org/software/gdb/
  • Langages de programmation applicables pour GDB : Ada / C / C++ / objective-c / Pascal etc.
  • Fonctionnement de GDB : débogage local et débogage distant.

La dernière version de la version actuelle est la 8.0 et GDB peut fonctionner sur les systèmes d'exploitation Linux et Windows.

1.1 Installer et démarrer GDB

  1. gdb -v vérifie si l'installation a réussi, sinon, installez-la (vous devez vous assurer que le compilateur est installé, comme gcc).

  2. démarrer gdb

    1. gdb test_file.exe pour démarrer le débogage de gdb, c'est-à-dire spécifier directement le nom du fichier exécutable à déboguer

    2. Entrez gdb directement pour démarrer et utilisez le fichier de commande test_file.exe pour spécifier le nom du fichier après avoir entré gdb

    3. Si le fichier d'exécution cible nécessite des paramètres d'entrée et de sortie (tels que les paramètres de réception argv[]), les paramètres peuvent être spécifiés de trois manières :

      1. Au démarrage de gdb, gdb --args text_file.exe
      2. Après avoir entré gdb, exécutez set args param_1
      3. Après avoir entré le débogage gdb, exécutez param_1 ou démarrez para_1

1.2 Fonctions de gdb

  • Démarrez le programme, vous pouvez exécuter le programme comme vous le souhaitez en fonction des exigences définies par l'utilisateur.
  • Permet au programme en cours de débogage de s'arrêter au point d'arrêt de débogage spécifié par l'utilisateur (le point d'arrêt peut être une expression conditionnelle).
  • Lorsque le programme s'arrête, vous pouvez vérifier ce qui s'est passé dans le programme à ce moment-là. Par exemple, vous pouvez imprimer la valeur d'une variable.
  • Modifier dynamiquement l'environnement d'exécution du programme variable.

1.3 L'utilisation de gdb

exécuter le programme

run(r)运行程序,如果要加参数,则是run arg1 arg2 ... 

afficher le code source

list(l):查看最近十行源码
list fun:查看fun函数源代码
list file:fun:查看flie文件中的fun函数源代码

Définir des points d'arrêt et regarder des points d'arrêt

break 行号/fun设置断点。
break file:行号/fun设置断点。
break if<condition>:条件成立时程序停住。
info break(缩写:i b):查看断点。
watch expr:一旦expr值发生改变,程序停住。
delete n:删除断点。

débogage en une seule étape

continue(c):运行至下一个断点。
step(s):单步跟踪,进入函数,类似于VC中的step in。
next(n):单步跟踪,不进入函数,类似于VC中的step out。
finish:运行程序,知道当前函数完成返回,并打印函数返回时的堆栈地址和返回值及参数值等信息。
until:当厌倦了在一个循环体内单步跟踪时,这个命令可以运行程序知道退出循环体。

Afficher les données d'exécution

print(p):查看运行时的变量以及表达式。
ptype:查看类型。
print array:打印数组所有元素。
print *array@len:查看动态内存。len是查看数组array的元素个数。
print x=5:改变运行时数据。

1.4 Erreurs de programme

  • Erreur de compilation : Lors de l'écriture du programme, la spécification du langage n'est pas respectée, ce qui entraîne une erreur de compilation. Par exemple : les fautes de grammaire.
  • Erreur d'exécution : le compilateur ne peut pas détecter ce type d'erreur, mais cela peut entraîner le blocage du programme lors de l'exécution. Par exemple : adresse mémoire accès illégal.
  • Erreurs logiques : la compilation et l'exécution sont correctes, mais le programme ne fait pas ce que nous attendons de lui.

Défaut de segment de débogage 1.5gdb

Qu'est-ce qu'une erreur de segmentation ? Le défaut de segmentation est une erreur causée par l'accès à une adresse illégale.

  • Accéder à la zone de données système, en particulier écrire des données à l'adresse mémoire protégée par le système. Par exemple : accéder à l'adresse dont l'adresse est 0.
  • La mémoire hors limites (tableau hors limites, incohérence de type de variable, etc.) accède à une zone mémoire qui n'appartient pas au programme en cours.

Gdb débogue les défauts de segment, vous pouvez exécuter le programme directement. Lorsque le programme plante, gdb imprimera les informations d'exécution, par exemple : après avoir reçu le signal SIGSEGV, vous pouvez utiliser la commande pour imprimer les informations de backtrace de la pile, puis modifier le programme selon le code d'erreur du btprogramme.

Débogage du fichier 1.6.core

Fichier de base 6.1

Lorsqu'un programme plante, un fichier est généralement généré, appelé corefichier. Le fichier core enregistre l'image mémoire lorsque le programme plante et ajoute des informations de débogage. Le processus de génération du fichier core est appelé core dump(核心已转储). Le système ne génère pas ce fichier par défaut.

6.2 Définir pour générer le fichier core

  • ulimit -c: Vérifiez l'état du vidage mémoire.
  • ulimit -c xxxx: définissez la taille du fichier core.
  • ulimit -c unlimited: Taille illimitée du fichier core.

6.3 fichier principal de débogage gdb

ulimit -c xxxxAprès avoir défini , exécutez à nouveau le programme et une erreur de segmentation se produit. À ce stade, un corefichier sera généré, utilisez gdb corele fichier principal de débogage et utilisez btla commande pour imprimer les informations de trace de la pile.

Deux, commandes communes GDB

  • Ce qui suit utilise test_file.c comme nom de l'exemple de programme source, test_file.exe comme nom de l'exemple de fichier exécutable et param_1 comme nom de l'exemple de paramètre.
  • (gdb) signifie s'exécuter en mode débogage gdb
  • Généralement, il existe deux méthodes couramment utilisées, à savoir le débogage des points d'arrêt et le débogage en une seule étape .
  • list(l) : liste le code source
  • quit(q) : quitter le mode de débogage de gdb
  • Après avoir entré gdb, entrez help pour afficher les instructions pour toutes les commandes

2.1 Afficher le code source

liste [nom de la fonction] [nombre de lignes]

2.2 Débogage des points d'arrêt

(1) Définissez un point d'arrêt :

  • a. break + [numéro de ligne du code source][nom de la fonction du code source][adresse mémoire]
  • b. break... if condition... peut être n'importe lequel des paramètres ci-dessus, et condition est une condition. Par exemple, dans le corps de la boucle, vous pouvez définir break ... if i = 100 pour définir le nombre de boucles

supprimer le point d'arrêt

(gdb) emplacement clair : l'emplacement du paramètre est généralement le numéro de ligne d'une certaine ligne de code ou un nom de fonction spécifique. Lorsque le paramètre d'emplacement est le nom de fonction d'une fonction, cela signifie supprimer tous les points d'arrêt à l'entrée de la fonction.

(gdb) delete [breakpoints] [num] : Le paramètre breakpoints est facultatif, et le paramètre num est le numéro du point d'arrêt spécifié, qui peut être supprimé pour supprimer un certain point d'arrêt, pas tous.

désactiver le point d'arrêt

**désactiver [points d'arrêt] [num…] : le paramètre **points d'arrêt est facultatif ; num... signifie qu'il peut y avoir plusieurs paramètres, et chaque paramètre est le numéro du point d'arrêt à désactiver. Si vous spécifiez num..., la commande disable désactivera le point d'arrêt avec le numéro spécifié ; sinon, si vous ne définissez pas num..., disable désactivera tous les points d'arrêt du programme en cours.

activer le point d'arrêt

  1. enable [breakpoints] [num…] active plusieurs points d'arrêt spécifiés avec num… paramètres, si num… n'est pas défini, cela signifie activer tous les points d'arrêt désactivés
  2. enable [breakpoints] once num… Active temporairement plusieurs points d'arrêt numérotés par num…, mais le point d'arrêt ne peut être utilisé qu'une seule fois, puis il reviendra automatiquement à l'état désactivé
  3. enable [breakpoints] count num… Activer temporairement plusieurs points d'arrêt numérotés par num…, les points d'arrêt peuvent être utilisés compter les fois, puis entrer dans l'état désactivé
  4. enable [breakpoints] delete num... Active plusieurs points d'arrêt numérotés num..., mais les points d'arrêt ne peuvent être utilisés qu'une seule fois, puis ils seront définitivement supprimés.

break(b) : C'est un point d'arrêt commun, et il existe deux formes de points d'arrêt

(gdb) break location // b location, location représente l'emplacement du point d'arrêt

image

(gdb) break … if cond // b … if cond, ce qui signifie que si la condition de cond est vraie, le point d'arrêt sera à « … »

En utilisant la commande condition pour définir des expressions conditionnelles pour différents types de points d'arrêt, uniquement lorsque les expressions conditionnelles sont vraies (la valeur est True), le point d'arrêt correspondant sera déclenché et le programme sera suspendu.

tbreak : La commande tbreak peut être considérée comme une autre version de la commande break. L'utilisation et les fonctions des commandes tbreak et break sont très similaires. La seule différence est que le point d'arrêt atteint par la commande tbreak ne sera utilisé qu'une seule fois, même après le programme est suspendu, le point d'arrêt disparaît automatiquement.

rbreak : Différente des commandes break et tbreak, la commande rbreak agit sur les fonctions dans les programmes C et C++, et elle s'arrêtera au début de la fonction spécifiée.

  • (gdb) tbreak regex

    • regex représente une expression régulière qui se casse au début de la fonction correspondante
  • Le point d'arrêt défini par la commande tbreak a le même effet que celui de la commande break, il existera toujours et ne disparaîtra pas automatiquement.

watch : cette commande atteint un point d'arrêt d'observation, qui peut surveiller la valeur d'une variable ou d'une expression. Ce n'est que lorsque la valeur de la variable surveillée (expression) change que le programme s'arrête.

  • (gdb) regarder cond

    • cond représente la variable ou l'expression à surveiller

Commande rwatch : tant qu'il y a une opération pour lire la valeur de la variable cible (expression) dans le programme, le programme s'arrête ;

awatch commande : tant qu'il y a une opération pour lire la valeur de la variable cible (expression) ou changer la valeur dans le programme, le programme s'arrête.

catch : La fonction de capture d'un point d'arrêt est de surveiller l'occurrence d'un certain événement dans le programme, comme lorsqu'une certaine exception se produit dans le programme, lorsqu'une bibliothèque dynamique est chargée, etc. Une fois l'heure cible atteinte, le programme s'arrête exécution.

(2) Observez le point d'arrêt :

  • a. watch + [variable] [expression] Arrête le programme lorsque la valeur de la variable ou de l'expression change.
  • b. rwatch + [variable] [expression] Lorsque la variable ou l'expression est lue, arrête le programme.
  • c, awatch + [variable] [expression] Lorsque la variable ou l'expression est lue ou écrite, arrête le programme.

(3) Définissez le point de capture :

catch + événement Lorsque l'événement se produit, arrête le programme.

événement peut être le suivant :

  • a, throw Une exception levée par C++. (lancer est un mot-clé)
  • b.catch Une exception interceptée par C++. (catch est un mot clé)
  • c. Lorsque exec appelle le système, appelez exec. (exec est un mot-clé, actuellement cette fonction n'est disponible que sous HP-UX)
  • d. Lorsque fork appelle le système pour appeler fork. (fork est un mot-clé, actuellement cette fonction n'est disponible que sous HP-UX)
  • e. Lorsque vfork appelle le système pour appeler vfork. (vfork est un mot-clé, actuellement cette fonction n'est disponible que sous HP-UX)
  • f, load ou load Lors du chargement d'une bibliothèque partagée (bibliothèque de liens dynamiques). (load est un mot-clé, actuellement cette fonction n'est disponible que sous HP-UX)
  • g, décharger ou décharger lors du déchargement d'une bibliothèque partagée (bibliothèque de liens dynamiques). (unload est un mot-clé, actuellement cette fonction n'est disponible que sous HP-UX)

(4) Signal de capture :

poignée + [argu] + signaux

Signaux : il s'agit d'un signal défini par Linux/Unix. SIGINT signifie un signal de caractère d'interruption, c'est-à-dire le signal de Ctrl+C, SIGBUS signifie un signal de panne matérielle ; SIGCHLD signifie un signal pour changer l'état d'un processus enfant ; SIGKILL signifie un signal pour terminer le fonctionnement du programme, et ainsi de suite.

argument:

  • nostop Lorsque le programme en cours de débogage reçoit un signal, GDB n'arrêtera pas l'exécution du programme, mais affichera un message pour vous indiquer qu'il a reçu un tel signal.
  • stop GDB arrête votre programme lorsque le programme en cours de débogage reçoit un signal.
  • print GDB affiche un message lorsque le programme en cours de débogage reçoit un signal.
  • noprint Lorsque le programme en cours de débogage reçoit un signal, GDB ne vous dira pas que le signal a été reçu.
  • pass ou noignore GDB ne traite pas les signaux lorsque le programme en cours de débogage les reçoit. Cela signifie que GDB transmettra ce signal au programme en cours de débogage pour traitement.
  • nopass ou ignore Lorsque le programme débogué reçoit un signal, GDB ne laissera pas le programme débogué gérer le signal.

(5) Interruption du fil :

break [linespec] thread [threadno] [if …]

linespec Le numéro de ligne du code source où le point d'arrêt est défini. Par exemple : test.c:12 signifie que le fichier définit un point d'arrêt pour la ligne 12 dans test.c.

threadno L'ID du thread. Il est alloué par GDB et vous pouvez afficher les informations sur les threads du programme en cours d'exécution en saisissant des threads d'informations.

si… Définir une condition d'arrêt.

Afficher les informations :

(1) Afficher les données :

variable d'impression afficher les variables

print *array@len Afficher le tableau (array est le pointeur de tableau, len est la longueur de données requise)

Le format de sortie peut être défini en ajoutant des paramètres :

/ 按十六进制格式显示变量。
/d 按十进制格式显示变量。
/u 按十六进制格式显示无符号整型。
/o 按八进制格式显示变量。
/t 按二进制格式显示变量。 
/a 按十六进制格式显示变量。
/c 按字符格式显示变量。
/f 按浮点数格式显示变量。

(2) Afficher la mémoire

examiner /nfu + adresse mémoire (variable de pointeur)

  • n indique la longueur de la mémoire d'affichage
  • f indique le format de sortie (voir ci-dessus)
  • u indique le nombre d'octets spécifiés (b simple octet ; h double octet ; w quatre octets ; g huit octets ; la valeur par défaut est quatre octets)
  如:x /10cw pFilePath  (pFilePath为一个字符串指针,指针占4字节)
     x 为examine命令的简写。

(3) Afficher les informations de la pile

trace arrière [-n][n]

  • n indique que seules les informations de pile de la couche n sur le dessus de la pile sont imprimées.
  • -n signifie imprimer uniquement les informations de pile de n couches au-dessus du bas de la pile.
  • Sans paramètres, cela signifie imprimer toutes les informations de la pile.

2.3 Débogage en une seule étape

courir®

continuer©

suivant(n)

  • Format de commande : (gdb) next count : count indique le nombre de lignes de code à exécuter en une seule étape, et la valeur par défaut est 1 ligne
  • Sa principale caractéristique est que lorsqu'une instruction contenant une fonction appelante est rencontrée, quel que soit le nombre de lignes de code contenues dans la fonction, l'instruction suivante sera exécutée en une seule étape. C'est-à-dire que pour la fonction appelée, la commande suivante ne la traitera que comme une ligne de code

pas)

  • (gdb) step count : Le paramètre count indique le nombre de lignes exécutées en même temps, et la valeur par défaut est 1 ligne.
  • Normalement, la commande pas à pas et la commande suivante ont la même fonction et exécutent toutes deux le programme pas à pas. La différence est que lorsque la ligne de code exécutée par la commande step contient une fonction, elle entrera dans la fonction et arrêtera l'exécution à la première ligne de code de la fonction.

jusqu'à (u)

  • (gdb) until : La commande until sans paramètres peut faire en sorte que le débogueur GDB parcoure rapidement le corps de la boucle actuelle et s'exécute jusqu'à ce que le corps de la boucle s'arrête. Notez que la commande until ne joue en aucun cas ce rôle, seulement lorsqu'elle est exécutée jusqu'à la fin du corps de la boucle (la dernière ligne de code), la commande until aura cet effet ; sinon, la commande until a le même fonctionne comme la commande suivante, uniquement le programme d'exécution en une seule étape

(gdb) until location : Le paramètre location est le numéro de ligne d'une certaine ligne de code

Afficher la valeur de la variable

imprimer§

  • p num_1 : Le paramètre num_1 permet de faire référence à la variable ou expression cible à visualiser ou modifier
  • Sa fonction est de sortir ou de modifier la valeur de la variable ou de l'expression spécifiée dans le processus de débogage GDB du programme

afficher

  • (gdb) expression d'affichage
  • (gdb) affichage/fmt expr
  • expr indique la variable ou l'expression cible à visualiser ; le paramètre fmt est utilisé pour spécifier le format de la variable ou de l'expression de sortie

image

  • (gdb) undisplay num…
  • (gdb) supprimer le numéro d'affichage…
  • Le paramètre num... indique le numéro de la variable ou de l'expression cible, et le nombre de nombres peut être multiple
  • (gdb) désactiver l'affichage du numéro…
  • Désactiver l'affichage automatique des variables ou expressions actives dans la liste
  • (gdb) active l'affichage du numéro…
  • Vous pouvez également activer une variable ou une expression actuellement désactivée
  • Comme la commande d'impression, la commande d'affichage est également utilisée pendant la phase de débogage pour afficher la valeur d'une variable ou d'une expression
  • La différence entre eux est que lors de l'utilisation de la commande d'affichage pour afficher la valeur d'une variable ou d'une expression, chaque fois que le programme fait une pause (comme l'exécution en une seule étape), le débogueur GDB l'imprimera automatiquement pour nous, tandis que la commande d'impression ne le fera pas.

GDB handle Commande : traitement du signal

→(gdb) handle signal mode Parmi eux, le paramètre signal indique le signal cible à définir, qui est généralement le nom complet (SIGINT) ou l'abréviation (la partie après suppression de 'SIG', comme INT) d'un certain signal ; si vous voulez spécifier tous les signaux peuvent être représentés par tous.

Le paramètre mode est utilisé pour spécifier la façon dont GDB gère les informations cibles, et sa valeur peut être la suivante :

  • ostop : Lorsque le signal se produit, GDB ne suspendra pas le programme, il peut continuer à s'exécuter, mais affichera un message d'invite nous indiquant que le signal s'est produit ;
  • stop : Lorsque le signal se produit, GDB suspend l'exécution du programme.
  • noprint : GDB n'imprimera aucune information d'invite lorsque le signal se produira ;
  • print : lorsqu'un signal se produit, GDB imprime les informations d'invite nécessaires ;
  • nopass (ou ignore) : pendant que GDB capture le signal cible, le programme n'est pas autorisé à traiter le signal par lui-même ;
  • pass (ou noignore) : bien que le débogage GDB capture le signal cible, il permet également au programme de gérer automatiquement le signal.

En mode gdb, vous pouvez afficher les informations de différents signaux via des signaux d'information ou des signaux d'information <signal_name> (par exemple, des signaux d'information SIGINT).

Commandes frame et backtrace GDB : afficher les informations de la pile

(gdb) frame spec Cette commande peut sélectionner le cadre de pile spécifié par le paramètre spec comme cadre de pile actuel. Il existe trois méthodes couramment utilisées pour spécifier la valeur du paramètre spec :

  1. Spécifié par le numéro du cadre de pile. 0 est le numéro de cadre de pile correspondant à la fonction actuellement appelée, et la fonction correspondant au cadre de pile avec le plus grand nombre est généralement la fonction main() ;
  2. A l'aide de la spécification d'adresse du cadre de pile. L'adresse du cadre de pile peut être vue dans les informations imprimées par la commande de cadre d'information (sera discuté plus tard);
  3. Spécifié par le nom de fonction de la fonction. Notez que s'il s'agit d'une fonction récursive similaire qui correspond à plusieurs cadres de pile, le cadre de pile avec le plus petit nombre est spécifié par cette méthode.

(gdb) cadre d'information Nous pouvons voir les informations stockées dans le cadre de pile actuel

Cette commande imprimera les informations suivantes du cadre de pile actuel dans l'ordre :

  • Le numéro du cadre de pile actuel et l'adresse du cadre de pile ;
  • L'adresse de stockage de la fonction correspondant au cadre de pile actuel et l'adresse du stockage du code lorsque la fonction est appelée
  • L'appelant de la fonction courante, l'adresse du cadre de pile correspondant ;
  • Le langage de programmation utilisé pour écrire ce cadre de pile ;
  • L'adresse de stockage et la valeur du paramètre de fonction ;
  • L'adresse de stockage de la variable locale dans la fonction ;
  • Les variables de registre stockées dans le cadre de pile, telles que le registre d'instructions (représenté par rip dans l'environnement 64 bits et eip dans l'environnement 32 bits), le registre de pointeur de base de pile (représenté par rbp dans l'environnement 64 bits, et ebp dans l'environnement 32 bits), etc.

De plus, vous pouvez également utiliser info args la commande pour afficher la valeur de chaque paramètre de la fonction actuelle ; utilisez info locals la commande pour afficher la valeur de chaque variable locale dans la fonction actuelle.

(gdb) backtrace [-full] [n] est utilisé pour imprimer les informations de tous les cadres de pile dans l'environnement de débogage actuel

Parmi eux, les paramètres entourés de [] sont facultatifs et leurs significations sont :

  • n : une valeur entière, lorsqu'il s'agit d'un entier positif, cela signifie imprimer les informations des n cadres de pile les plus internes ; lorsque n est un entier négatif, cela signifie imprimer les informations des n cadres de pile les plus externes ;
  • -full : affiche les valeurs des variables locales lors de l'impression des informations sur le cadre de la pile.

Édition GDB et recherche de code source

Commande d'édition GDB : modifier les fichiers

  • (gdb) modifier [emplacement]

  • (gdb) modifier [nom du fichier] : [emplacement]

    • location représente l'emplacement dans le programme. Cette commande indique d'activer l'emplacement spécifié du fichier, puis de le modifier.
    • Si vous rencontrez une erreur "bash: /bin/ex: No such file or directory", parce que l'éditeur par défaut de GDB est ex, vous devez spécifier l'éditeur, comme export EDITOR=/usr/bin/vim ou export EDITOR =/usr/bin/vi

Commande de recherche GDB : rechercher des fichiers

  • recherche

  • recherche inversée

    • Le format de commande du premier élément signifie une recherche vers l'avant depuis le début de la ligne actuelle, et le second élément signifie une recherche vers l'arrière depuis le début de la ligne actuelle. Parmi elles, regexp est une expression régulière. Une expression régulière décrit un modèle de correspondance de chaîne, qui peut être utilisé pour vérifier si une chaîne contient une certaine sous-chaîne, remplacer la sous-chaîne correspondante ou extraire une certaine condition d'une sous-chaîne. De nombreux langages de programmation prennent en charge l'utilisation d'expressions régulières.

Troisièmement, l'utilisation du débogueur GDB

De manière générale, GDB vous aide principalement à remplir les quatre fonctions suivantes :

1. Démarrez votre programme et vous pouvez exécuter le programme comme vous le souhaitez en fonction de vos besoins personnalisés.
2. Autorisez le programme en cours de débogage à s'arrêter au point d'arrêt spécifié. (Le point d'arrêt peut être une expression conditionnelle)
3. Lorsque le programme est arrêté, vous pouvez vérifier ce qui s'est passé dans votre programme à ce moment.
4. Modifiez dynamiquement l'environnement d'exécution de votre programme.

D'après ce qui précède, GDB n'est pas différent des outils de débogage généraux. Il complète essentiellement ces fonctions. Cependant, dans les détails, vous constaterez que GDB est un outil de débogage puissant. Vous êtes peut-être plus habitué aux outils de débogage graphiques, mais il existe parfois, l'outil de débogage de la ligne de commande a des fonctions que l'outil graphique ne peut pas compléter. Voyons-les un par un.

Un exemple de débogage :

源程序:tst.c

1 #include <stdio.h>
2
3 int func(int n)
4 {
5 int sum=0,i;
6 for(i=0; i<n; i++)
7 {
8 sum+=i;
9 }
10 return sum;
11 }
12
13
14 main()
15 {
16 int i;
17 long result = 0;
18 for(i=1; i<=100; i++)
19 {
20 result += i;
21 }
22
23 printf("result[1-100] = %d /n", result );
24 printf("result[1-250] = %d /n", func(250) );
25 }

Compiler et générer des fichiers exécutables : (sous Linux)

hchen/test> cc -g tst.c -o tst

Déboguer avec GDB :

hchen/test> gdb tst <---------- 启动GDB
GNU gdb 5.1.1
Copyright 2002 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB. Type "show warranty" for details.
This GDB was configured as "i386-SUSE-linux"...
(gdb) l <-------------------- l命令相当于list,从第一行开始例出原码。
1 #include <stdio.h>
2
3 int func(int n)
4 {
5 int sum=0,i;
6 for(i=0; i<n; i++)
7 {
8 sum+=i;
9 }
10 return sum;
(gdb) <-------------------- 直接回车表示,重复上一次命令
11 }
12
13
14 main()
15 {
16 int i;
17 long result = 0;
18 for(i=1; i<=100; i++)
19 {
20 result += i;
(gdb) break 16 <-------------------- 设置断点,在源程序第16行处。
Breakpoint 1 at 0x8048496: file tst.c, line 16.
(gdb) break func <-------------------- 设置断点,在函数func()入口处。
Breakpoint 2 at 0x8048456: file tst.c, line 5.
(gdb) info break <-------------------- 查看断点信息。
Num Type Disp Enb Address What
1 breakpoint keep y 0x08048496 in main at tst.c:16
2 breakpoint keep y 0x08048456 in func at tst.c:5
(gdb) r <--------------------- 运行程序,run命令简写
Starting program: /home/hchen/test/tst

Breakpoint 1, main () at tst.c:17 <---------- 在断点处停住。
17 long result = 0;
(gdb) n <--------------------- 单条语句执行,next命令简写。
18 for(i=1; i<=100; i++)
(gdb) n
20 result += i;
(gdb) n
18 for(i=1; i<=100; i++)
(gdb) n
20 result += i;
(gdb) c <--------------------- 继续运行程序,continue命令简写。
Continuing.
result[1-100] = 5050 <----------程序输出。

Breakpoint 2, func (n=250) at tst.c:5
5 int sum=0,i;
(gdb) n
6 for(i=1; i<=n; i++)
(gdb) p i <--------------------- 打印变量i的值,print命令简写。
$1 = 134513808
(gdb) n
8 sum+=i;
(gdb) n
6 for(i=1; i<=n; i++)
(gdb) p sum
$2 = 1
(gdb) n
8 sum+=i;
(gdb) p i
$3 = 2
(gdb) n
6 for(i=1; i<=n; i++)
(gdb) p sum
$4 = 3
(gdb) bt <--------------------- 查看函数堆栈。
#0 func (n=250) at tst.c:5
#1 0x080484e4 in main () at tst.c:24
#2 0x400409ed in __libc_start_main () from /lib/libc.so.6
(gdb) finish <--------------------- 退出函数。
Run till exit from #0 func (n=250) at tst.c:5
0x080484e4 in main () at tst.c:24
24 printf("result[1-250] = %d /n", func(250) );
Value returned is $6 = 31375
(gdb) c <--------------------- 继续运行。
Continuing.
result[1-250] = 31375 <----------程序输出。

Program exited with code 027. <--------程序退出,调试结束。
(gdb) q <--------------------- 退出gdb。
hchen/test>

Eh bien, avec les connaissances perceptuelles ci-dessus, apprenons à connaître gdb systématiquement.

Commandes gdb de base :

GDB常用命令	格式	含义	简写
list	List [开始,结束]	列出文件的代码清单	l
prit	Print 变量名	打印变量内容	p
break	Break [行号或函数名]	设置断点	b
continue	Continue [开始,结束]	继续运行	c
info	Info 变量名	列出信息	i
next	Next	下一行	n
step	Step	进入函数(步入)	S
display	Display 变量名	显示参数	 
file	File 文件名(可以是绝对路径和相对路径)	加载文件	 
run	Run args	运行程序	r

Quatre, combat réel GDB

Voici un exemple pratique utilisant la commande ci-dessus :

[[email protected] bufbomb]# gdb bufbomb 
GNU gdb (GDB) Red Hat Enterprise Linux (7.2-75.el6)
Copyright (C) 2010 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-RedHat-linux-gnu".
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>...
Reading symbols from /root/Temp/bufbomb/bufbomb...done.
(gdb) b getbuf
Breakpoint 1 at 0x8048ad6
(gdb) run -t cdai
Starting program: /root/Temp/bufbomb/bufbomb -t cdai
Team: cdai
Cookie: 0x5e5ee04e

Breakpoint 1, 0x08048ad6 in getbuf ()
Missing separate debuginfos, use: debuginfo-install glibc-2.12-1.149.el6_6.4.i686

(gdb) bt
#0  0x08048ad6 in getbuf ()
#1  0x08048db2 in test ()
#2  0x08049085 in launch ()
#3  0x08049257 in main ()
(gdb) info frame 0
Stack frame at 0xffffb540:
 eip = 0x8048ad6 in getbuf; saved eip 0x8048db2
 called by frame at 0xffffb560
 Arglist at 0xffffb538, args: 
 Locals at 0xffffb538, Previous frame's sp is 0xffffb540
 Saved registers:
  ebp at 0xffffb538, eip at 0xffffb53c
(gdb) info registers
eax            0xc      12
ecx            0xffffb548       -19128
edx            0xc8c340 13157184
ebx            0x0      0
esp            0xffffb510       0xffffb510
ebp            0xffffb538       0xffffb538
esi            0x804b018        134524952
edi            0xffffffff       -1
eip            0x8048ad6        0x8048ad6 <getbuf+6>
eflags         0x282    [ SF IF ]
cs             0x23     35
ss             0x2b     43
ds             0x2b     43
es             0x2b     43
fs             0x0      0
gs             0x63     99
(gdb) x/10x $sp
0xffffb510:     0xf7ffc6b0      0x00000001      0x00000001      0xffffb564
0xffffb520:     0x08048448      0x0804a12c      0xffffb548      0x00c8aff4
0xffffb530:     0x0804b018      0xffffffff

(gdb) si
0x08048ad9 in getbuf ()
(gdb) si
0x08048adc in getbuf ()
(gdb) si
0x080489c0 in Gets ()
(gdb) n
Single stepping until exit from function Gets,
which has no line number information.
Type string:123
0x08048ae1 in getbuf ()
(gdb) si
0x08048ae2 in getbuf ()
(gdb) c
Continuing.
Dud: getbuf returned 0x1
Better luck next time

Program exited normally.
(gdb) quit

4.1 Débogage inverse

La fonction Reversal Debugging a été ajoutée après GDB 7.0. Plus précisément, par exemple, j'ai défini des points d'arrêt sur getbuf() et main(), et lorsque le programme est lancé, il s'arrêtera au point d'arrêt de la fonction main(). À ce stade, après avoir tapé record, passez au point d'arrêt suivant getbuf(), et GDB enregistrera les informations d'exécution de main() à getbuf(). Utilisez maintenant rn pour déboguer en sens inverse de getbuf() à main(). Comme dans "X-Men : Days of Future Past", c'est incroyable !

Cette méthode convient pour trouver le code qui a causé le bogue à l'envers du bogue, et l'aspect pratique varie selon la situation. Bien sûr, il a aussi des limites. Lorsque des conditions externes telles que la sortie d'E / S du programme changent, GDB ne peut pas "inverser".

[[email protected] bufbomb]# gdb bufbomb 
GNU gdb (GDB) Red Hat Enterprise Linux (7.2-75.el6)
Copyright (C) 2010 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-redhat-linux-gnu".
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>...
Reading symbols from /root/Temp/bufbomb/bufbomb...done.

(gdb) b getbuf
Breakpoint 1 at 0x8048ad6
(gdb) b main
Breakpoint 2 at 0x80490c6

(gdb) run -t cdai
The program being debugged has been started already.
Start it from the beginning? (y or n) y

Starting program: /root/Temp/bufbomb/bufbomb -t cdai

Breakpoint 2, 0x080490c6 in main ()
(gdb) record
(gdb) c
Continuing.
Team: cdai
Cookie: 0x5e5ee04e

Breakpoint 1, 0x08048ad6 in getbuf ()

(gdb) rn
Single stepping until exit from function getbuf,
which has no line number information.
0x08048dad in test ()
(gdb) rn
Single stepping until exit from function test,
which has no line number information.
0x08049080 in launch ()
(gdb) rn
Single stepping until exit from function launch,
which has no line number information.
0x08049252 in main ()

4.2 VSCode+GDB+Qemu déboguant le noyau Linux ARM64

Le noyau Linux est un système très compliqué, et il est difficile pour les débutants de démarrer. S'il existe un environnement de débogage pratique, l'efficacité de l'apprentissage peut être améliorée d'au moins 5 à 10 fois.

Afin d'apprendre le noyau Linux, il y a généralement ces deux besoins :

  1. Peut se débarrasser du matériel, compiler et exécuter facilement Linux
  2. Vous pouvez utiliser des outils graphiques pour déboguer Linux

L'auteur utilise VSCode+GDB+Qemu pour répondre à ces deux exigences :

  • qemu est utilisé comme machine virtuelle pour démarrer Linux.
  • VSCode + GDB est utilisé comme outil de débogage pour le DEBUG graphique.

L'effet final est à peu près le suivant :

interface d'exécution de qemu :

image

interface de débogage vscode :

image

Ce qui suit présentera étape par étape comment créer l'environnement ci-dessus. Toutes les opérations de cet article sont effectuées sur une machine virtuelle Vmware Ubuntu16.

Installer la chaîne d'outils de compilation

Étant donné qu'Ubuntu est une architecture X86, pour compiler les fichiers arm64, vous devez installer une chaîne d'outils de compilation croisée

sudo apt-get install gcc-aarch64-linux-gnu
sudo apt-get install libncurses5-dev  build-essential git bison flex libssl-dev

créer un système de fichiers racine

Le démarrage de Linux doit coopérer avec le système de fichiers racine. Ici, nous utilisons busybox pour créer un système de fichiers racine simple

compiler la boîte occupée

wget  https://busybox.net/downloads/busybox-1.33.1.tar.bz2
tar -xjf busybox-1.33.1.tar.bz2
cd busybox-1.33.1

Activer l'option de compilation de bibliothèque statique

make menuconfig
Settings --->
 [*] Build static binary (no shared libs)

Spécifier les outils de compilation

export ARCH=arm64
export CROSS_COMPILE=aarch64-linux-gnu-

compiler

make
make install

La compilation est terminée et le répertoire _install est généré sous le répertoire busybox

système de fichiers personnalisé

Pour que le processus d'initialisation démarre normalement, une configuration supplémentaire est requise

Ajoutez les répertoires etc, dev et lib au répertoire racine

# bryant @ ubuntu in ~/Downloads/busybox-1.33.1/_install [1:02:17]
$ mkdir etc dev lib
# bryant @ ubuntu in ~/Downloads/busybox-1.33.1/_install [1:02:17]
$ ls
bin  dev  etc  lib  linuxrc  sbin  usr

Créez des fichiers séparément dans etc :

# bryant @ ubuntu in ~/Downloads/busybox-1.33.1/_install/etc [1:06:13]
$ cat profile
#!/bin/sh
export HOSTNAME=bryant
export USER=root
export HOME=/home
export PS1="[$USER@$HOSTNAME \W]\# "
PATH=/bin:/sbin:/usr/bin:/usr/sbin
LD_LIBRARY_PATH=/lib:/usr/lib:$LD_LIBRARY_PATH
export PATH LD_LIBRARY_PATH

# bryant @ ubuntu in ~/Downloads/busybox-1.33.1/_install/etc [1:06:16]
$ cat inittab
::sysinit:/etc/init.d/rcS
::respawn:-/bin/sh
::askfirst:-/bin/sh
::ctrlaltdel:/bin/umount -a -r

# bryant @ ubuntu in ~/Downloads/busybox-1.33.1/_install/etc [1:06:19]
$ cat fstab
#device  mount-point    type     options   dump   fsck order
proc /proc proc defaults 0 0
tmpfs /tmp tmpfs defaults 0 0
sysfs /sys sysfs defaults 0 0
tmpfs /dev tmpfs defaults 0 0
debugfs /sys/kernel/debug debugfs defaults 0 0
kmod_mount /mnt 9p trans=virtio 0 0

# bryant @ ubuntu in ~/Downloads/busybox-1.33.1/_install/etc [1:06:26]
$ ls init.d
rcS

# bryant @ ubuntu in ~/Downloads/busybox-1.33.1/_install/etc [1:06:30]
$ cat init.d/rcS
mkdir -p /sys
mkdir -p /tmp
mkdir -p /proc
mkdir -p /mnt
/bin/mount -a
mkdir -p /dev/pts
mount -t devpts devpts /dev/pts
echo /sbin/mdev > /proc/sys/kernel/hotplug
mdev -s

Voici une petite explication de ces fichiers :

  1. Une fois que busybox est démarré en tant que linuxrc, il lira /etc/profile, qui définit certaines variables d'environnement et propriétés du shell
  2. Montez le système de fichiers en fonction des informations de montage fournies par /etc/fstab
  3. busybox lira sysinit depuis /etc/inittab et l'exécutera, où sysinit pointe vers /etc/init.d/rcS
  4. Dans /etc/init.d/rcS, la commande mdev -s est très importante, elle analysera le répertoire /sys, trouvera les périphériques de caractères et les périphériques de blocage, et mknod sous /dev

répertoire de développement :

# bryant @ ubuntu in ~/Downloads/busybox-1.33.1/_install/dev [1:17:36]
$ sudo mknod console c 5 1

Cette étape est très importante, sans le fichier console, la sortie du mode utilisateur ne peut pas être imprimée sur le port série

Répertoire lib : copiez la bibliothèque lib et prenez en charge les applications compilées dynamiquement pour exécuter :

# bryant @ ubuntu in ~/Downloads/busybox-1.33.1/_install/lib [1:18:43]
$ cp /usr/aarch64-linux-gnu/lib/*.so*  -a .

Compiler le noyau

Configurer le noyau

Le code source du noyau Linux peut être téléchargé directement depuis github.

Générer .config selon le fichier arch/arm64/configs/defconfig

make defconfig ARCH=arm64

Ajoutez la configuration suivante au fichier .config

CONFIG_DEBUG_INFO=y 
CONFIG_INITRAMFS_SOURCE="./root"
CONFIG_INITRAMFS_ROOT_UID=0
CONFIG_INITRAMFS_ROOT_GID=0

CONFIG_DEBUG_INFO est pour le débogage

CONFIG_INITRAMFS_SOURCE est de spécifier l'emplacement du disque virtuel du noyau, de sorte que le disque virtuel sera directement compilé dans l'image du noyau après avoir spécifié.

Nous transférons le système de fichiers racine précédemment créé au répertoire racine :

# bryant @ ubuntu in ~/Downloads/linux-arm64 on git:main x [1:26:56]
$ cp -r ../busybox-1.33.1/_install root

exécuter compiler

make ARCH=arm64 Image -j8  CROSS_COMPILE=aarch64-linux-gnu-

Spécifier la cible comme Image ici ne compilera que le noyau, pas les modules, ce qui augmentera la vitesse de compilation

démarrer qemu

télécharger qemu

A noter qu'il est préférable de compiler qemu depuis le code source, la version de qemu installée directement avec apt-get peut être trop basse, ce qui rend impossible le démarrage du noyau arm64. L'auteur utilise la version 4.2.1 de qemu

apt-get install build-essential zlib1g-dev pkg-config libglib2.0-dev binutils-dev libboost-all-dev autoconf libtool libssl-dev libpixman-1-dev libpython-dev python-pip python-capstone virtualenv
wget https://download.qemu.org/qemu-4.2.1.tar.xz
tar xvJf qemu-4.2.1.tar.xz
cd qemu-4.2.1
./configure --target-list=x86_64-softmmu,x86_64-linux-user,arm-softmmu,arm-linux-user,aarch64-softmmu,aarch64-linux-user --enable-kvm
make 
sudo make install

Une fois la compilation terminée, qemu se trouve dans le répertoire /usr/local/bin

$ /usr/local/bin/qemu-system-aarch64 --version
QEMU emulator version 4.2.1
Copyright (c) 2003-2019 Fabrice Bellard and the QEMU Project developers

démarrer le noyau Linux

/usr/local/bin/qemu-system-aarch64 -m 512M -smp 4 -cpu cortex-a57 -machine virt -kernel

Voici quelques explications sur les paramètres :

  • -m 512MLa mémoire est de 512M
  • -smp 44 Nucléaire
  • -cpu cortex-a57le processeur est cortex-a57
  • -kernelfichier image du noyau
  • -appendLe paramètre cmdline passé au noyau. Parmi eux, rdinit spécifie le processus d'initialisation ; nokaslr interdit la randomisation de l'adresse de démarrage du noyau, ce qui est très important, sinon le débogage GDB peut avoir des problèmes ; console=ttyAMA0 spécifie le port série, sans cette étape, vous ne pouvez pas voir la sortie de linux ;
  • -nographicdésactiver la sortie graphique
  • -sÉcoutez le port gdb et le programme gdb peut être connecté via le port 1234.

Voici une explication du fonctionnement de console=ttyAMA0.

La visualisation du code source linux montre que ttyAMA0 correspond à AMBA_PL011ce pilote :

config SERIAL_AMBA_PL011_CONSOLE
    bool "Support for console on AMBA serial port"
    depends on SERIAL_AMBA_PL011=y
    select SERIAL_CORE_CONSOLE
    select SERIAL_EARLYCON
    help
      Say Y here if you wish to use an AMBA PrimeCell UART as the system
      console (the system console is the device which receives all kernel
      messages and warnings and which allows logins in single user mode).

      Even if you say Y here, the currently visible framebuffer console
      (/dev/tty0) will still be used as the system console by default, but
      you can alter that using a kernel command line option such as
      "console=ttyAMA0". (Try "man bootparam" or see the documentation of
      your boot loader (lilo or loadlin) about how to pass options to the
      kernel at boot time.)

AMBA_PL011 est un périphérique de port série standard de bras, et la sortie de qemu est le port série simulé.

Dans le fichier de code source de qemu, vous pouvez également voir les fichiers associés de PL011 :

# bryant @ ubuntu in ~/Downloads/qemu-4.2.1 [1:46:54]
$ find . -name "*pl011*"
./hw/char/pl011.c

Après avoir démarré Linux avec succès, les impressions du port série sont les suivantes :

[    3.401567] usbcore: registered new interface driver usbhid
[    3.404445] usbhid: USB HID core driver
[    3.425030] NET: Registered protocol family 17
[    3.429743] 9pnet: Installing 9P2000 support
[    3.435439] Key type dns_resolver registered
[    3.440299] registered taskstats version 1
[    3.443685] Loading compiled-in X.509 certificates
[    3.461041] input: gpio-keys as /devices/platform/gpio-keys/input/input0
[    3.473163] ALSA device list:
[    3.474432]   No soundcards found.
[    3.485283] uart-pl011 9000000.pl011: no DMA platform data
[    3.541376] Freeing unused kernel memory: 10752K
[    3.545897] Run /linuxrc as init process
[    3.548390]   with arguments:
[    3.550279]     /linuxrc
[    3.551073]     nokaslr
[    3.552216]   with environment:
[    3.554396]     HOME=/
[    3.555898]     TERM=linux
[    3.985835] 9pnet_virtio: no channels available for device kmod_mount
mount: mounting kmod_mount on /mnt failed: No such file or directory
/etc/init.d/rcS: line 8: can't create /proc/sys/kernel/hotplug: nonexistent directory

Please press Enter to activate this console.
[root@bryant ]#
[root@bryant ]#

VSCode + GDB

La fonction GDB est intégrée dans vscode, nous pouvons l'utiliser pour déboguer graphiquement le noyau linux

Nous ajoutons d'abord le fichier de configuration gdb de vscode (.vscode/launch.json) :

{
    // Use IntelliSense to learn about possible attributes.
    // Hover to view descriptions of existing attributes.
    // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
    "version": "0.2.0",
    "configurations": [
        {
            "name": "kernel debug",
            "type": "cppdbg",
            "request": "launch",
            "program": "${workspaceFolder}/vmlinux",
            "cwd": "${workspaceFolder}",
            "MIMode": "gdb",
            "miDebuggerPath":"/usr/bin/gdb-multiarch",
            "miDebuggerServerAddress": "localhost:1234"
        }
    ]
}

Voici quelques explications pour plusieurs paramètres clés :

  • program: fichier de symboles pour le débogage
  • miDebuggerPath: Le chemin de gdb. Il convient de noter ici que puisque nous sommes le noyau arm64, nous devons utiliser gdb-multiarch pour déboguer
  • miDebuggerServerAddress: Adresse homologue, qemu utilisera le port 1234 par défaut

Une fois la configuration terminée, vous pouvez directement démarrer GDB et vous connecter au noyau Linux

image

Dans vscode, vous pouvez définir des points d'arrêt pour le débogage en une seule étape

image

image


Déclaration de droit d'auteur : cet article est un article original écrit par le blogueur Zhihu "Jouer avec le noyau Linux". Il suit l'accord de droit d'auteur CC 4.0 BY-SA. Pour la réimpression, veuillez joindre le lien source original et cette déclaration.
Lien d'origine : https://zhuanlan.zhihu.com/p/639365490

Je suppose que tu aimes

Origine blog.csdn.net/m0_50662680/article/details/131484284
conseillé
Classement