Stack frame danneggiato GDB - Come eseguire il debug?


113

Ho la seguente traccia dello stack. È possibile ricavare qualcosa di utile da questo per il debug?

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) 

Da dove iniziare a guardare il codice quando otteniamo un file Segmentation fault e la traccia dello stack non è così utile?

NOTA: se inserisco il codice, gli esperti SO mi daranno la risposta. Voglio seguire la guida di SO e trovare la risposta da solo, quindi non sto pubblicando il codice qui. Scuse.


Probabilmente il tuo programma è saltato tra le erbacce: puoi recuperare qualcosa dallo stack pointer?
Carl Norum

1
Un'altra cosa da considerare è se il puntatore del frame è impostato correttamente. Stai costruendo senza ottimizzazioni o passando una bandiera come -fno-omit-frame-pointer? Inoltre, per la corruzione della memoria, valgrindpotrebbe essere uno strumento più appropriato, se è un'opzione per te.
FatalError

Risposte:


155

Questi indirizzi fasulli (0x00000002 e simili) sono in realtà valori PC, non valori SP. Ora, quando ottieni questo tipo di SEGV, con un indirizzo PC fasullo (molto piccolo), il 99% delle volte è dovuto alla chiamata tramite un puntatore a funzione fasullo. Si noti che le chiamate virtuali in C ++ vengono implementate tramite puntatori a funzione, quindi qualsiasi problema con una chiamata virtuale può manifestarsi allo stesso modo.

Un'istruzione di chiamata indiretta spinge semplicemente il PC dopo la chiamata nello stack e quindi imposta il PC sul valore target (falso in questo caso), quindi se questo è quello che è successo, puoi facilmente annullarlo estraendo manualmente il PC dallo stack . Nel codice x86 a 32 bit devi solo fare:

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

Con il codice x86 a 64 bit è necessario

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

Quindi, dovresti essere in grado di eseguire un file bt e capire dove si trova realmente il codice.

Il restante 1% delle volte, l'errore sarà dovuto alla sovrascrittura dello stack, di solito per overflow di un array memorizzato nello stack. In questo caso, potresti essere in grado di ottenere maggiore chiarezza sulla situazione utilizzando uno strumento come valgrind


5
@George: gdb executable corefileaprirà gdb con l'eseguibile e il file core, a quel punto puoi fare bt(oi comandi sopra seguiti da bt) ...
Chris Dodd

2
@mk .. ARM non usa lo stack per gli indirizzi di ritorno - usa invece il registro di collegamento. Quindi generalmente non ha questo problema, o se lo fa, di solito è dovuto a qualche altro danneggiamento dello stack.
Chris Dodd

2
Anche in ARM, penso, tutti i registri di uso generale e LR sono memorizzati nello stack prima che la funzione chiamata inizi l'esecuzione. Al termine della funzione, il valore di LR viene inserito nel PC e quindi la funzione ritorna. Quindi se lo stack è danneggiato, possiamo vedere che un valore sbagliato è il PC giusto? In questo caso, la regolazione del puntatore dello stack porterà allo stack appropriato e aiuterà a eseguire il debug del problema. Cosa ne pensi? per favore fammi sapere i tuoi pensieri. Grazie.
mk ..

1
Cosa significa falso?
Danny Lo

5
ARM non è x86: il suo puntatore allo stack è chiamato sp, non espo rsp, e la sua istruzione di chiamata memorizza l'indirizzo di ritorno nel lrregistro, non nello stack. Quindi per ARM, tutto ciò di cui hai veramente bisogno per annullare la chiamata è set $pc = $lr. Se $lrnon è valido, hai un problema molto più difficile da rilassare.
Chris Dodd

44

Se la situazione è abbastanza semplice, la risposta di Chris Dodd è la migliore. Sembra che sia saltato attraverso un puntatore NULL.

Tuttavia, è possibile che il programma si sia sparato al piede, al ginocchio, al collo e all'occhio prima di schiantarsi: ha sovrascritto la pila, incasinato il puntatore del fotogramma e altri mali. Se è così, è improbabile che sbrogliare l'hashish ti mostri patate e carne.

La soluzione più efficiente sarà eseguire il programma nel debugger e passare alle funzioni finché il programma non si arresta in modo anomalo. Una volta identificata una funzione che si è arrestata in modo anomalo, ricominciare da capo ed entrare in quella funzione e determinare quale funzione chiama causa l'arresto. Ripeti finché non trovi la singola riga di codice incriminata. Il 75% delle volte la correzione sarà quindi ovvia.

Nell'altro 25% delle situazioni, la cosiddetta linea di codice incriminata è una falsa pista. Risponderà alle condizioni (non valide) impostate molte righe prima, forse migliaia di righe prima. In tal caso, il miglior corso scelto dipende da molti fattori: principalmente dalla tua comprensione del codice e dall'esperienza con esso:

  • Forse l'impostazione di un punto di controllo del debugger o l'inserimento di diagnostiche printfsu variabili critiche porterà al necessario A ha!
  • Forse la modifica delle condizioni di test con input diversi fornirà maggiori informazioni rispetto al debug.
  • Forse un secondo paio di occhi ti costringerà a controllare le tue ipotesi o raccogliere prove trascurate.
  • A volte, tutto ciò che serve è andare a cena e pensare alle prove raccolte.

In bocca al lupo!


13
Se un secondo paio di occhi non è disponibile, le paperelle di gomma sono ben collaudate come alternative.
Matt

2
Anche la cancellazione della fine di un buffer può farlo. Potrebbe non bloccarsi nel punto in cui annulli la fine del buffer, ma quando esci dalla funzione, muore.
phyatt

Può essere utile: GDB: "Avanti" automatico
user202729

28

Supponendo che il puntatore allo stack sia valido ...

Potrebbe essere impossibile sapere esattamente dove si trova il SEGV dal backtrace - penso che i primi due stack frame siano completamente sovrascritti. 0xbffff284 sembra un indirizzo valido, ma i due successivi non lo sono. Per uno sguardo più da vicino allo stack, puoi provare quanto segue:

gdb $ x / 32ga $ rsp

o una variante (sostituire il 32 con un altro numero). Questo stamperà un certo numero di parole (32) a partire dallo stack pointer di dimensioni giganti (g), formattate come indirizzi (a). Digita "help x" per maggiori informazioni sul formato.

Strumentare il codice con alcuni "printf" sentinella potrebbe non essere una cattiva idea, in questo caso.


Incredibilmente utile, grazie - avevo uno stack che tornava indietro di soli tre fotogrammi e poi premevo "Backtrace interrotto: frame precedente identico a questo frame (stack danneggiato?)"; Ho già fatto qualcosa di esattamente simile nel codice in un gestore di eccezioni della CPU, ma non riuscivo a ricordare altro che info symbolcome farlo in gdb.
Leander

22
FWIW su dispositivi ARM a 32 bit: x/256wa $sp =)
Leander

2
@leander Potresti dirmi cos'è X / 256wa? Ne ho bisogno per ARM a 64 bit. In generale sarà utile se puoi spiegare di cosa si tratta.
mk ..

5
Secondo la risposta, "x" = esamina la posizione di memoria; stampa un numero di parole "w" = (in questo caso 256) e le interpreta come indirizzi "a" =. Ci sono maggiori informazioni nel manuale GDB su sourceware.org/gdb/current/onlinedocs/gdb/Memory.html#Memory .
Leander

7

Guarda alcuni dei tuoi altri registri per vedere se uno di loro ha il puntatore dello stack memorizzato nella cache. Da lì, potresti essere in grado di recuperare una pila. Inoltre, se questo è incorporato, molto spesso lo stack è definito in un indirizzo molto particolare. Usandolo, a volte puoi anche ottenere uno stack decente. Tutto ciò presuppone che quando sei saltato nell'iperspazio, il tuo programma non ha vomitato per tutta la memoria lungo la strada ...


3

Se si tratta di una sovrascrittura dello stack, i valori potrebbero corrispondere a qualcosa di riconoscibile dal programma.

Ad esempio, mi sono appena ritrovato a guardare la pila

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

ed 0x342dè 13357, che si è rivelato essere un node-id quando ho estratto i log dell'applicazione per questo. Ciò ha immediatamente aiutato a restringere i siti candidati in cui potrebbe essersi verificata la sovrascrittura dello stack.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.