Passa al contenuto principale

Debugger gdb

gdb è un debugger a linea di comando che ci permette di eseguire un programma passo passo, seguendo lo stato del processore e della memoria.

Il concetto fondamentale per un debugger è quello di breakpoint, ossia un punto del codice dove l'esecuzione dovra fermarsi. I breakpoints ci permettono di eseguire rapidamente le parti del programma che non sono di interesse e fermarsi ad osservare solo le parti che ci interessano.

Quella che segue è comunque una presentazione sintetica e semplificata. Per altre opzioni e funzionalità del debugger, vedere la documentazione ufficiale o il comando help.

Controllo dell'esecuzione

Per istruzione corrente si intende la prossima da eseguire. Quando il debugger si ferma ad un'istruzione, si ferma prima di eseguirla.

Nome completoNome scorciatoiaFormatoComportamento
frameffMostra l'istruzione corrente.
listllMostra il sorgente attorno all'istruzione corrente.
breakbb labelImposta un breakpoint alla prima istruzione dopo label.
continueccProsegue l'esecuzione del programma fino al prossimo breakpoint.
stepssEsegue l'istruzione corrente, fermandosi immediatamente dopo. Se l'istruzione corrente è una call, l'esecuzione si fermerà alla prima istruzione del sottoprogramma chiamato.
nextnnEsegue l'istruzione corrente, fermandosi all'istruzione successiva del sottoprogramma corrente. Se l'istruzione corrente è una call, l'esecuzione si fermerà dopo il ret di del sottoprogramma chiamato. Nota: aggiungere una nop dopo ogni call prima di una nuova label.
finishfinfinContinua l'esecuzione fino all'uscita dal sottoprogramma corrente (ret). L'esecuzione si fermerà alla prima istruzione dopo la call.
runrrAvvia (o riavvia) l'esecuzione del programma. Chiede conferma.
quitqqEsce dal debugger. Chiede conferma.

I seguenti comandi sono definiti ad-hoc nell'ambiente del corso, e non sono quindi tipici comandi di gdb.

Nome completoNome scorciatoiaFormatoComportamento
rrunrrrrAvvia (o riavvia) l'esecuzione del programma, senza chiedere conferma.
qquitqqqqEsce dal debugger, senza chiedere conferma.

Problemi con next

Si possono talvolta incontrare problemi con il comportamento di next, che derivano da come questa è definita e implementata. Il comando next distingue i frame come le sequenze di istruzioni che vanno da una label alla successiva. Il suo comportamento è, in realtà, di continuare l'esecuzione finché non incontra di nuovo una nuova istruzione nello stesso frame di partenza.

Questa logica può essere facilmente rotta con del codice come il seguente, dove non esiste una istruzione di punto_1 che viene incontrata dopo la call. Quel che ne consegue è che il comando next si comporta come continue.

punto_1:
...
call newline
punto_2:
...

Per ovviare a questo problema, è una buona abitudine quella di aggiungere una nop dopo ciascuna call. Tale nop, appartenendo allo stesso frame punto_1, farà regolarmente sospendere l'esecuzione.

punto_1:
...
call newline
nop
punto_2:
...

Ispezione dei registri

Nome completoNome scorciatoiaFormatoComportamento
info registersi ri rMostra lo stato di (quasi) tutti i registri. Non mostra separatamente i sotto-registri, come %ax.
info registersi ri r regMostra lo stato del registro reg specificato. reg va specificato in minuscolo senza caratteri preposti, per esempio i r eax. Si possono specificare anche sotto-registri, come %ax, e più registri separati da spazio.

gdb supporta viste alternative con il comando layout che mettono più informazioni a schermo. In particolare, layout regs mostra l'equivalente di i r e l, evidenziando gli elementi che cambiano ad ogni step di esecuzione.

Ispezione della memoria

Nome completoNome scorciatoiaFormatoComportamento
xxx/NFU addrMostra lo stato della memoria a partire dall'indirizzo addr, per le N locazione di dimensione U e interpretate con il formato F. Comando con memoria, i valori di N, F e U possono essere omessi (insieme allo /) se uguali a prima.

Il comando x sta per examine memory, ma differenza degli altri non ha una versione estesa.

Il parametro N si specifica come un numero intero, il valore di default (all'avvio di gdb) è 1.

Il parametro F può essere

  • x per esadecimale
  • d per decimale
  • c per ASCII
  • t per binario
  • s per stringa delimitata da 0x00

Il valore di default (all'avvio di gdb) è x.

Il parametro U può essere

  • b per byte
  • h per word (2 byte)
  • w per long (4 byte)

Il valore di default (all'avvio di gdb) è h.

L'argomento addr può essere espresso in diversi modi, sia usando label che registri o espressioni basate su aritmetica dei puntatori. Per esempio:

  • letterale esadecimale: x 0x56559066
  • label: x &label
  • registro puntatore: x $esi
  • registro puntatore e registro indice: x (char*)$esi + $ecx

Notare che nell'ultimo caso, dato che ci si basa su aritmetica dei puntatori, il tipo all'interno del cast determina la scala, ossia la dimensione di ciascuna delle $ecx locazioni del vettore da saltare. Si può usare (char*) per 1 byte, (short*) per 2 byte, (int*) per 4 byte.

Un alternativa a questo è lo scomporre, anche solo temporaneamente, le istruzioni con indirizzamento complesso. Per esempio, si può sostituire movb (%esi, %ecx), %al con lea (%esi, %ecx), %ebx seguita da movb (%ebx), %al, così che si possa eseguire semplicemente x $ebx nel debugger.

Gestione dei breakpoints

Oltre a crearli, i breakpoint possono anche essere rimossi o (dis)abilitati. Questi comandi si basano sulla conoscenza dell'id di un breakpoint: questo viene stampato quando un breakpoint viene creato o raggiunto durante l'esecuzione,oppure si possono ristampare tutti usando info b.

Nome completoNome scorciatoiaFormatoComportamento
info breakpointsinfo binfo b [id]Stampa informazioni sul breakpoint id, o tutti se l'argomento è omesso.
disable breakpointsdisdis [id]Disabilita il breakpoint id, o tutti se l'argomento è omesso.
enable breakpointsenen [id]Abilita il breakpoint id, o tutti se l'argomento è omesso.
delete breakpointsdd [id]Rimuove il breakpoint id, o tutti se l'argomento è omesso.

Conditional Breakpoints

In alcuni casi, la complessità del programma, l'uso intensivo di sottoprogrammi o lunghi loop possono rendere molto lungo trovare il punto giusto dell'esecuzione. A questo scopo, è possibile definire dei breakpoint condizionali, per far sì che l'esecuzione si interrompa a tale breakpoint solo se la condiziona è verificata.

Nome completoNome scorciatoiaFormatoComportamento
conditioncondcond id condImposta la condizione cond per il breakpoint id.

La sintassi per una condizione è in "stile C", come il comando x. Alcuni esempi di questa sintassi:

  • cond 2 $al==5 per far sì che l'esecuzione si fermi al breakpoint 2 solo se il registro al contiene il valore 5,
  • cond 2 (short *)$edi==-5 per far sì che l'esecuzione si fermi al breakpoint 2 solo se il registro edi contiene l'indirizzo di una word di valore -5,
  • cond 2 (int *)&count!=0 per far sì che l'esecuzione si fermi al breakpoint 2 solo se la locazione di 4 byte a partire da count contiene un valore diverso da 0,
warning

Fare attenzione alle conversioni automatiche di rappresentazione: quando si usa la rappresentazione decimale, gdb interpreta automaticamente i valori come interi. Una condizione come cond 2 $al==128, per quanto accettata dal debugger, sarà sempre falsa perché la codifica 0x80 è interpretata in decimale come l'intero -128, mai come il naturale 128. È quindi una buona idea usare la notazione esadecimale in casi del genere, cioè quanto il bit più significativo è 1.

informazioni

Una feature disponibile in molti IDE è quello di creare dipendenze tra breakpoint, cioè abilitare un breakpoint solo se è stato prima colpito un altro. Questo però è fin troppo ostico da fare in gdb.

Watchpoints

I watchpoint sono come dei breapoint ma per dati (registri e memoria), non per il codice. Si creano indicando l'espressione del dato da controllare. Si gestiscono con gli stessi comandi per i breakpoint.

Nome completoNome scorciatoiaFormatoComportamento
watchpointwatchwatch exprImposta un watchpoint per l'espressione expr.
info watchpointsinfo watinfo wat [id]Stampa informazioni sul watchpoint id, o tutti se l'argomento è omesso.
disable breakpointsdisdis [id]Disabilita il breakpoint o watchpoint id, o tutti se l'argomento è omesso.
enable breakpointsenen [id]Abilita il breakpoint o watchpoint id, o tutti se l'argomento è omesso.
delete breakpointsdd [id]Rimuove il breakpoint o watchpoint id, o tutti se l'argomento è omesso.

Un watchpoint richiede la specifica di un registro o locazione nella stessa notazione "stile C" del comando x, e interrompe l'esecuzione quando tale valore cambia. Per esempio, watch $eax crea un watchpoint che interrompe l'esecuzione ogni volta che eax cambia valore.