Tutoriel de débogage GDB pour les débutants

Tutoriel de débogage GDB pour les débutants

Vous pouvez déjà être versé dans des scripts de bash de débogage (voir comment déboguer les scripts de bash si vous n'êtes pas encore familier avec le débogage de bash), mais comment déboguer C ou C++? Explorons.

GDB est un utilitaire de débogage Linux de longue date et complet, qui prendrait de nombreuses années à apprendre si vous vouliez bien connaître l'outil. Cependant, même pour les débutants, l'outil peut être très puissant et utile lorsqu'il s'agit de déboguer C ou C++.

Par exemple, si vous êtes un ingénieur d'AQ et que vous souhaitez déboguer un programme C et binaire sur lequel votre équipe travaille et qu'il se bloque, vous pouvez utiliser GDB pour obtenir une arrière finalement conduit à l'accident). Ou, si vous êtes un développeur C ou C ++ et que vous venez d'introduire un bug dans votre code, vous pouvez utiliser GDB pour déboguer les variables, le code et plus! Plongeons-nous dans!

Dans ce tutoriel, vous apprendrez:

  • Comment installer et utiliser l'utilitaire GDB à partir de la ligne de commande en bash
  • Comment faire le débogage de base GDB à l'aide de la console GDB et invite
  • En savoir plus sur la sortie détaillée que GDB produit
Tutoriel de débogage GDB pour les débutants

Exigences et conventions logicielles utilisées

Exigences logicielles et conventions de ligne de commande Linux
Catégorie Exigences, conventions ou version logicielle utilisée
Système Indépendant de la distribution Linux
Logiciel Lignes de commande bash et gdb, système basé sur Linux
Autre L'utilitaire GDB peut être installé en utilisant les commandes fournies ci-dessous
Conventions # - nécessite que les commandes Linux soient exécutées avec des privilèges racine soit directement en tant qu'utilisateur racine, soit par l'utilisation de Sudo commande
$ - exige que les commandes Linux soient exécutées en tant qu'utilisateur régulier non privilégié

Configuration de GDB et d'un programme de test

Pour cet article, nous examinerons un petit test.c Programme dans la langue de développement C, qui introduit une erreur de division par zéro dans le code. Le code est un peu plus long que ce qui est nécessaire dans la vraie vie (quelques lignes feraient l'affaire, et aucune utilisation de fonction ne serait nécessaire), mais cela a été fait exprès pour souligner comment les noms de fonction peuvent être vus clairement à l'intérieur de GDB lors du débogage.

Installons d'abord les outils dont nous aurons besoin INSTALLATION SUDO APT (ou Installation sudo yum Si vous utilisez une distribution basée sur un chapeau rouge):

sudo apt install gdb build-essentiel gcc 

Le essentiel et GCC vont vous aider à compiler le test.c C Programme sur votre système.

Ensuite, définissons le test.c script comme suit (vous pouvez copier et coller ce qui suit dans votre éditeur préféré et enregistrer le fichier en tant que test.c):

int réel_calc (int a, int b) int c; c = a / b; retour 0;  int calc () int a; int b; a = 13; b = 0; réel_calc (a, b); retour 0;  int main () calc (); retour 0;  


Quelques notes sur ce script: vous pouvez voir que lorsque le principal La fonction sera démarrée (le principal La fonction est la fonction toujours principale et première appelée lorsque vous démarrez le binaire compilé, cela fait partie de la norme C), il appelle immédiatement la fonction calculer, qui à son tour appelle atual_calc Après avoir établi quelques variables un et b pour 13 et 0 respectivement.

Exécuter notre script et configurer les vidages de noyau

Laissez maintenant compiler ce script en utilisant GCC et exécuter la même chose:

$ gcc -gdb test.Test C -o.out $ ./test.Exception à point flottante (noyau déversé) 

Le -ggdb option de GCC Verrera que notre session de débogage à l'aide de GDB sera amicale; il ajoute des informations de débogage spécifiques à GDB au test.dehors binaire. Nous nommons ce fichier binaire de sortie en utilisant le -o option de GCC, Et comme entrée, nous avons notre script test.c.

Lorsque nous exécutons le script, nous recevons immédiatement un message cryptique Exception de point flottante (noyau déversé). La partie qui nous intéresse pour le moment est le noyau déversé message. Si vous ne voyez pas ce message (ou si vous voyez le message mais ne pouvez pas localiser le fichier de base), vous pouvez configurer un meilleur vidage de noyau comme suit:

si ! noyau grep -qi '.core_pattern '/ etc / sysctl.conf; puis sudo sh -c 'echo "noyau.core_pattern = core.% p.% u.% s.% e.% t ">> / etc / sysctl.conf 'sudo sysctl -p fi ulimit -c illimited 

Ici, nous nous assurons d'abord qu'il n'y a pas de motif de noyau du noyau Linux (noyau.core_pattern) Réglage fait encore dans / etc / sysctl.confli (Le fichier de configuration pour la définition des variables du système sur Ubuntu et d'autres systèmes d'exploitation) et - à condition qu'aucun modèle de noyau existant n'ait été trouvé - ajoutez un modèle de nom de fichier de base pratique (cœur.% p.% u.% s.% e.% T) au même fichier.

Le sysctl -p commande (à exécuter comme racine, d'où le Sudo) Suivant s'assure que le fichier est immédiatement rechargé sans nécessiter de redémarrage. Pour plus d'informations sur le modèle de base, vous pouvez voir le Dénomination des fichiers de vidage de base Section qui peut être accessible en utilisant le noyau de l'homme commande.

Finalement, le ulimit -c illimité La commande définit simplement le maximum de taille de fichier de base à illimité Pour cette session. Ce paramètre est pas persistant à travers les redémarrages. Pour le rendre permanent, vous pouvez faire:

Sudo Bash -C "Cat < /etc/security/limits.conf * soft core unlimited * hard core unlimited EOF 

Qui ajoutera * Soft Core Unlimited et * Hard Core Unlimited pour / etc / sécurité / limites.confli, S'assurer qu'il n'y a pas de limites pour les décharges de base.

Lorsque vous réexécutez maintenant le test.dehors fichier que vous devriez voir le noyau déversé Message et vous devriez pouvoir voir un fichier de base (avec le modèle de noyau spécifié), comme suit:

$ ls noyau.1341870.1000.8.test.dehors.Test de 1598867712.C test.dehors 

Examinons ensuite les métadonnées du fichier central:

$ File Core.1341870.1000.8.test.dehors.1598867712 Core.1341870.1000.8.test.dehors.1598867712: fichier de noyau LSB ELF 64 bits, x86-64, version 1 (SYSV), style SVR4, de './test.out ', réel uid: 1000, uid efficace: 1000, vrai gid: 1000, gid efficace: 1000, execfn:'./test.out ', plate-forme:' x86_64 ' 

Nous pouvons voir qu'il s'agit d'un fichier central 64 bits, quel ID utilisateur était utilisé, quelle était la plate-forme, et enfin quel exécutable a été utilisé. Nous pouvons également voir à partir du nom de fichier (.8.) qu'il s'agissait d'un signal 8 qui a terminé le programme. Le signal 8 est Sigfpe, une exception à point flottante. GDB nous montrera plus tard qu'il s'agit d'une exception arithmétique.

Utilisation de GDB pour analyser le dépotoir central

Ouvrez le fichier de base avec GDB et supposons pendant une seconde, nous ne savons pas ce qui s'est passé (si vous êtes un développeur chevronné, vous avez peut-être déjà vu le bogue réel dans la source!):

$ gdb ./test.dehors ./cœur.1341870.1000.8.test.dehors.1598867712 GNU GDB (Ubuntu 9.1-0ubuntu1) 9.1 Copyright (C) 2020 Free Software Foundation, Inc. Licence GPLV3 +: GNU GPL Version 3 ou version ultérieure C'est un logiciel gratuit: vous êtes libre de le modifier et de le redistribuer. Il n'y a pas de garantie, dans la mesure permise par la loi. Tapez "Afficher la copie" et "Afficher la garantie" pour plus de détails. Ce GDB a été configuré comme "X86_64-Linux-GNU". Tapez "Afficher la configuration" pour les détails de configuration. Pour les instructions de rapport de bogues, veuillez consulter: . Trouvez le manuel GDB et d'autres ressources de documentation en ligne sur: . Pour obtenir de l'aide, tapez "AIDE". Tapez "Word à propos" pour rechercher des commandes liées à "Word"… les symboles de lecture de ./test.Out… [Nouveau LWP 1341870] Core a été généré par './test.dehors'. Programme terminé avec Signal Sigfpe, exception arithmétique. # 0 0x000056468844813B dans réel_calc (a = 13, b = 0) au test.C: 3 3 C = A / B; (GDB) 


Comme vous pouvez le voir, sur la première ligne, nous avons appelé gdb avec comme première option notre binaire et comme deuxième option le fichier central. N'oubliez pas binaire et noyau. Ensuite, nous voyons GDB initialiser, et on nous présente quelques informations.

Si vous voyez un Avertissement: taille inattendue de la section.Reg-xstate / 1341870 'dans le fichier de base.ou un message similaire, vous pouvez l'ignorer pour le moment.

Nous voyons que le dépotoir central a été généré par test.dehors et ont été informés que le signal était une exception Sigfpe, arithmétique. Super; Nous savons déjà que quelque chose ne va pas avec nos mathématiques, et peut-être pas avec notre code!

Ensuite, nous voyons le cadre (veuillez réfléchir à un cadre comme un procédure en code pour le moment) sur lequel le programme s'est terminé: cadre # 0. GDB ajoute toutes sortes d'informations pratiques à cela: l'adresse mémoire, le nom de la procédure réel_calc, quelles étaient nos valeurs variables, et même sur une ligne (3) dont le fichier (test.c) Le problème s'est produit.

Ensuite, nous voyons la ligne de code (ligne 3) Encore une fois, cette fois avec le code réel (c = a / b;) De cette ligne incluse. Enfin, on nous présente une invite GDB.

Le problème est probablement très clair maintenant; Nous faisions c = a / b, ou avec des variables remplies c = 13/0. Mais l'homme ne peut pas se diviser par zéro, et un ordinateur ne peut donc pas non plus. Comme personne n'a dit à un ordinateur comment diviser par zéro, une exception s'est produite, une exception arithmétique, une exception / erreur de point flottante.

Retour de retour en arrière

Alors voyons ce que nous pouvons découvrir d'autre sur GDB. Regardons quelques commandes de base. Le poing est celui que vous êtes le plus susceptible d'utiliser le plus souvent: bt:

(gdb) bt # 0 0x000056468844813b dans réel_calc (a = 13, b = 0) au test.C: 3 # 1 0x0000564688448171 dans calc () à test.C: 12 # 2 0x000056468844818A dans main () à test.c: 17 

Cette commande est un raccourci pour retour et nous donne essentiellement une trace de l'état actuel (procédure après procédure appelée) du programme. Pensez-y comme un ordre inversé de choses qui se sont produites; cadre # 0 (la première trame) est la dernière fonction qui était exécutée par le programme lorsqu'elle s'est écrasée et le cadre # 2 était le tout premier cadre appelé lorsque le programme a été démarré.

Nous pouvons ainsi analyser ce qui s'est passé: le programme a commencé, et principal() a été automatiquement appelé. Suivant, principal() appelé calc () (et nous pouvons le confirmer dans le code source ci-dessus), et enfin calc () appelé réel_calc Et les choses ont mal tourné.

Bien, nous pouvons voir chaque ligne sur laquelle quelque chose s'est produit. Par exemple, le réel_calc () La fonction a été appelée de la ligne 12 en test.c. Notez que ce n'est pas calc () qui a été appelé de la ligne 12 mais plutôt réel_calc () ce qui a du sens; test.C a fini par s'exécuter à la ligne 12 en ce qui concerne le calc () La fonction est concernée, car c'est là que le calc () fonction appelée réel_calc ().

Astuce de l'utilisateur Power: si vous utilisez plusieurs threads, vous pouvez utiliser la commande Le thread applique tout BT Pour obtenir une contradiction pour tous les threads qui fonctionnaient au fur et à mesure que le programme s'est écrasé!

Inspection du cadre

Si nous le souhaitons, nous pouvons inspecter chaque trame, le code source correspondant (s'il est disponible), et chaque variable étape par étape:

(gdb) f 2 # 2 0x000055fa2323318a dans main () au test.c: 17 17 calc (); (gdb) Liste 12 réel_calc (a, b); 13 retour 0; 14 15 16 int main () 17 calc (); 18 Retour 0; 19 (gdb) p a pas de symbole "a" dans le contexte actuel. 

Ici, nous «sautions dans» le cadre 2 en utilisant le f 2 commande. F est une main courte pour le cadre commande. Ensuite, nous énumérons le code source en utilisant le liste commande, et enfin essayer d'imprimer (en utilisant le p Commandue raccourci) la valeur du un variable, qui échoue, comme à ce stade un n'a pas encore été défini à ce stade du code; Notez que nous travaillons à la ligne 17 dans la fonction principal(), et le contexte réel dans lequel il existait dans les limites de cette fonction / cadre.

Notez que la fonction d'affichage du code source, y compris une partie du code source affiché dans les sorties précédentes ci-dessus, n'est disponible que si le code source réel est disponible.

Ici, nous voyons immédiatement un gotcha; Si le code source est différent, le code dont le binaire a été compilé, on peut être facilement induit en erreur; La sortie peut afficher une source non applicable / modifiée. GDB fait pas Vérifiez s'il y a une correspondance de révision du code source! Il est donc d'une importance primordiale que vous utilisez exactement la même révision du code source que celle à partir de laquelle votre binaire a été compilé.

Une alternative consiste à ne pas du tout utiliser le code source et à déboguer simplement une situation particulière dans une fonction particulière, en utilisant une révision plus récente du code source. Cela se produit souvent pour les développeurs et les débogueurs avancés qui n'ont probablement pas besoin de trop d'indices sur l'endroit où le problème peut être dans une fonction donnée et avec des valeurs variables fournies.

Examinons ensuite le cadre 1:

(gdb) f 1 # 1 0x000055fa23233171 dans calc () au test.c: 12 12 réel_calc (a, b); (gdb) list 7 int calc () 8 int a; 9 int b; 10 a = 13; 11 b = 0; 12 réel_calc (a, b); 13 retour 0; 14 15 16 int main ()  

Ici, nous pouvons à nouveau voir beaucoup d'informations en cours de sortie par GDB, ce qui aidera le développeur à déboguer le problème à portée de main. Puisque nous sommes maintenant calculer (sur la ligne 12), et nous avons déjà initialisé et réglé par la suite les variables un et b pour 13 et 0 respectivement, nous pouvons maintenant imprimer leurs valeurs:

(gdb) p a 1 $ = 13 (gdb) p b 2 $ = 0 (gdb) p c sans symbole "c" dans le contexte actuel. (GDB) P A / B Division par zéro 


Notez que lorsque nous essayons d'imprimer la valeur de c, il échoue toujours comme encore c n'est pas défini jusqu'à présent (les développeurs peuvent parler de «dans ce contexte»).

Enfin, nous regardons dans le cadre # 0, Notre cadre en panne:

(gdb) f 0 # 0 0x000055fa2323313b dans réel_calc (a = 13, b = 0) au test.C: 3 3 C = A / B; (gdb) p a 3 $ = 13 (gdb) p b 4 $ = 0 (gdb) p c 5 $ = 22010 

Tous les auto-évidents, à l'exception de la valeur signalée pour c. Notez que nous avions défini la variable c, mais ne lui avait pas encore donné de valeur initiale. En tant que tel c est vraiment indéfini (et il n'a pas été rempli par l'équation c = a / b Pourtant, comme celui-ci a échoué) et la valeur résultante a probablement été lue à partir d'un espace d'adressage auquel la variable c a été attribué (et cet espace mémoire n'a pas encore été initialisé / effacé).

Conclusion

Super. Nous avons pu déboguer un dépotoir de base pour un programme C, et nous avons appuyé les bases du débogage de GDB en attendant. Si vous êtes un ingénieur QA, ou un développeur junior, et que vous avez bien compris et appris tout dans ce tutoriel, vous êtes déjà un peu en avance sur la plupart des ingénieurs d'AQ, et potentiellement d'autres développeurs autour de vous.

Et la prochaine fois que vous regardez Star Trek et Captain Janeway ou Captain Picard, je veux «vider le noyau», vous ferez un sourire plus large à coup sûr. Profitez de débogage de votre prochain noyau déversé et laissez-nous un commentaire ci-dessous avec vos aventures de débogage.

Tutoriels Linux connexes:

  • Une introduction à l'automatisation Linux, des outils et des techniques
  • Choses à installer sur Ubuntu 20.04
  • Masterring Bash Script Loops
  • Choses à faire après l'installation d'Ubuntu 20.04 Focal Fossa Linux
  • Mint 20: Mieux que Ubuntu et Microsoft Windows?
  • Ubuntu 20.04 Guide
  • Choses à installer sur Ubuntu 22.04
  • Boucles imbriquées dans les scripts bash
  • Comment doubler Kali Linux et Windows 10
  • Système linux hung? Comment s'échapper vers la ligne de commande et…