Cadre de pile corrompu GDB - Comment déboguer?

113

J'ai la trace de pile suivante. Est-il possible d'en faire quelque chose d'utile pour le débogage?

Program received signal SIGSEGV, Segmentation fault.
0x00000002 in ?? ()
(gdb) bt
#0  0x00000002 in ?? ()
#1  0x00000001 in ?? ()
#2  0xbffff284 in ?? ()
Backtrace stopped: previous frame inner to this frame (corrupt stack?)
(gdb) 

Par où commencer à regarder le code lorsque nous obtenons un Segmentation fault, et que la trace de la pile n'est pas si utile?

REMARQUE: Si je poste le code, les experts SO me donneront la réponse. Je veux suivre les conseils de SO et trouver la réponse moi-même, donc je ne poste pas le code ici. Toutes mes excuses.

Sangeeth Saravanaraj
la source
Votre programme a probablement sauté dans les mauvaises herbes - pouvez-vous récupérer quelque chose du pointeur de pile?
Carl Norum
1
Une autre chose à considérer est si le pointeur de cadre est correctement défini. Construisez-vous sans optimisations ou passez-vous un drapeau comme -fno-omit-frame-pointer? En outre, pour la corruption de la mémoire, valgrindpeut être un outil plus approprié, si c'est une option pour vous.
FatalError

Réponses:

155

Ces fausses adresses (0x00000002 et autres) sont en fait des valeurs PC, pas des valeurs SP. Maintenant, lorsque vous obtenez ce type de SEGV, avec une fausse (très petite) adresse PC, 99% du temps, c'est dû à un appel via un faux pointeur de fonction. Notez que les appels virtuels en C ++ sont implémentés via des pointeurs de fonction, donc tout problème avec un appel virtuel peut se manifester de la même manière.

Une instruction d'appel indirect pousse simplement le PC après l'appel sur la pile, puis définit le PC sur la valeur cible (faux dans ce cas), donc si c'est ce qui s'est passé, vous pouvez facilement l'annuler en faisant sortir manuellement le PC de la pile. . En code x86 32 bits, il vous suffit de faire:

(gdb) set $pc = *(void **)$esp
(gdb) set $esp = $esp + 4

Avec le code x86 64 bits dont vous avez besoin

(gdb) set $pc = *(void **)$rsp
(gdb) set $rsp = $rsp + 8

Ensuite, vous devriez être capable de faire un btet de déterminer où se trouve réellement le code.

L'autre 1% du temps, l'erreur sera due à l'écrasement de la pile, généralement en débordant un tableau stocké sur la pile. Dans ce cas, vous pourrez peut-être obtenir plus de clarté sur la situation en utilisant un outil comme valgrind

Chris Dodd
la source
5
@George: gdb executable corefileouvrira gdb avec l'exécutable et le fichier de base, à quel point vous pouvez le faire bt(ou les commandes ci-dessus suivies de bt) ...
Chris Dodd
2
@mk .. ARM n'utilise pas la pile pour les adresses de retour - il utilise le registre de lien à la place. Donc, il n'a généralement pas ce problème, ou si c'est le cas, il est généralement dû à une autre corruption de pile.
Chris Dodd
2
Même dans ARM, je pense que tous les registres à usage général et LR sont stockés dans la pile avant que la fonction appelée ne commence à s'exécuter. Une fois la fonction terminée, la valeur de LR est affichée dans le PC et donc la fonction retourne. Donc, si la pile est corrompue, nous pouvons voir qu'une valeur incorrecte est PC non? Dans ce cas, l'ajustement du pointeur de pile peut conduire à une pile appropriée et aider à déboguer le problème. Qu'est-ce que tu penses? pls laissez-moi savoir vos pensées. Je vous remercie.
mk ..
1
Que signifie faux?
Danny Lo
5
ARM n'est pas x86 - son pointeur de pile est appelé sp, pas espou rsp, et son instruction d'appel stocke l'adresse de retour dans le lrregistre, pas sur la pile. Donc, pour ARM, tout ce dont vous avez vraiment besoin pour annuler l'appel est set $pc = $lr. Si $lrn'est pas valide, vous avez un problème beaucoup plus difficile à résoudre.
Chris Dodd
44

Si la situation est assez simple, la réponse de Chris Dodd est la meilleure. Il semble avoir sauté à travers un pointeur NULL.

Cependant, il est possible que le programme se soit tiré dans le pied, le genou, le cou et l'œil avant de s'écraser - il a écrasé la pile, a foiré le pointeur d'image et d'autres maux. Si tel est le cas, démêler le haschich ne vous montrera probablement pas des pommes de terre et de la viande.

La solution la plus efficace sera d'exécuter le programme sous le débogueur et de parcourir les fonctions jusqu'à ce que le programme se bloque. Une fois qu'une fonction en panne est identifiée, recommencez et entrez dans cette fonction et déterminez quelle fonction elle appelle provoque le crash. Répétez jusqu'à ce que vous trouviez la seule ligne de code incriminée. 75% du temps, le correctif sera alors évident.

Dans les 25% restants, la ligne de code dite offensante est un hareng rouge. Il réagira à des conditions (invalides) définies avant de nombreuses lignes, peut-être des milliers de lignes auparavant. Si tel est le cas, le meilleur cours choisi dépend de nombreux facteurs: principalement votre compréhension du code et votre expérience avec celui-ci:

  • Peut-être que la définition d'un point de contrôle du débogueur ou l'insertion de diagnostics printfsur des variables critiques conduira au A ha!
  • Peut-être que changer les conditions de test avec des entrées différentes fournira plus d'informations que le débogage.
  • Peut-être qu'une deuxième paire d'yeux vous obligera à vérifier vos hypothèses ou à rassembler des preuves négligées.
  • Parfois, il suffit d'aller dîner et de réfléchir aux preuves recueillies.

Bonne chance!

marcher
la source
13
Si une deuxième paire d'yeux n'est pas disponible, les canards en caoutchouc sont des alternatives bien éprouvées.
Matt
2
Ecrire la fin d'un tampon peut aussi le faire. Il peut ne pas planter là où vous écrivez la fin du tampon, mais lorsque vous sortez de la fonction, il meurt.
phyatt
Peut être utile: GDB: Automatic 'Next'ing
user202729
28

En supposant que le pointeur de pile est valide ...

Il peut être impossible de savoir exactement où se produit le SEGV à partir de la trace arrière - je pense que les deux premiers cadres de pile sont complètement écrasés. 0xbffff284 semble être une adresse valide, mais les deux suivants ne le sont pas. Pour examiner de plus près la pile, vous pouvez essayer ce qui suit:

gdb $ x / 32ga $ rsp

ou une variante (remplacez le 32 par un autre numéro). Cela affichera un certain nombre de mots (32) à partir du pointeur de pile de taille géante (g), formatés en adresses (a). Tapez 'help x' pour plus d'informations sur le format.

Instrumenter votre code avec des 'printf' sentinelles n'est peut-être pas une mauvaise idée, dans ce cas.

manabear
la source
Incroyablement utile, merci - j'avais une pile qui ne remontait que de trois images et qui appuyait ensuite sur "Backtrace stoppé: image précédente identique à cette image (pile corrompue?)"; J'ai déjà fait quelque chose d'exactement comme ça dans le code dans un gestionnaire d'exceptions CPU, mais je ne me souvenais pas d'autre que de la info symbolfaçon de le faire dans gdb.
maigre
22
FWIW sur les périphériques ARM 32 bits: x/256wa $sp =)
maigre
2
@leander Pouvez-vous me dire ce qu'est X / 256wa? J'en ai besoin pour ARM 64 bits. En général, il sera utile que vous puissiez expliquer ce que c'est.
mk ..
5
Selon la réponse, «x» = examiner l'emplacement de la mémoire; il imprime un certain nombre de «w» = mots (dans ce cas, 256), et les interprète comme «a» = adresses. Il y a plus d'informations dans le manuel GDB à sourceware.org/gdb/current/onlinedocs/gdb/Memory.html#Memory .
maigre
7

Regardez certains de vos autres registres pour voir si l'un d'eux a le pointeur de pile mis en cache. À partir de là, vous pourrez peut-être récupérer une pile. De plus, si cela est intégré, la pile est souvent définie à une adresse très particulière. En utilisant cela, vous pouvez également parfois obtenir une pile décente. Tout cela suppose que lorsque vous avez sauté dans l'hyperespace, votre programme n'a pas vomi de la mémoire en cours de route ...

Michael Dorgan
la source
3

S'il s'agit d'un écrasement de pile, les valeurs peuvent bien correspondre à quelque chose de reconnaissable par le programme.

Par exemple, je me suis retrouvé à regarder la pile

(gdb) bt
#0  0x0000000000000000 in ?? ()
#1  0x000000000000342d in ?? ()
#2  0x0000000000000000 in ?? ()

et 0x342dest 13357, qui s'est avéré être un identifiant de nœud lorsque j'ai saisi les journaux d'application pour cela. Cela a immédiatement aidé à réduire les sites candidats où l'écrasement de la pile aurait pu se produire.

Craig Ringer
la source