GNU Debugger (gdb) è uno strumento prezioso per ispezionare i processi in esecuzione e risolvere i problemi durante lo sviluppo di programmi.
Puoi impostare punti di interruzione in posizioni specifiche (per nome della funzione, numero di riga e così via), abilitare e disabilitare tali punti di interruzione, visualizzare e modificare i valori delle variabili ed eseguire tutte le operazioni standard che ti aspetteresti da qualsiasi debugger. Ma ha molte altre funzionalità che potresti non aver sperimentato. Eccone cinque da provare.
Punti di interruzione condizionali
L'impostazione di un punto di interruzione è una delle prime cose che imparerai a fare con GNU Debugger. Il programma si interrompe quando raggiunge un punto di interruzione e puoi eseguire i comandi gdb per ispezionarlo o modificare le variabili prima di consentire al programma di continuare.
Ad esempio, potresti sapere che una funzione chiamata spesso si arresta in modo anomalo a volte, ma solo quando ottiene un determinato valore di parametro. È possibile impostare un punto di interruzione all'inizio di quella funzione ed eseguire il programma. I parametri della funzione vengono visualizzati ogni volta che raggiunge il punto di interruzione e se il valore del parametro che provoca l'arresto anomalo non viene fornito, è possibile continuare fino a quando la funzione non viene richiamata. Quando il parametro problematico provoca un arresto anomalo, puoi scorrere il codice per vedere cosa c'è che non va.
(gdb) break sometimes_crashes
Breakpoint 1 at 0x40110e: file prog.c, line 5.
(gdb) run
[...]
Breakpoint 1, sometimes_crashes (f=0x7fffffffd1bc) at prog.c:5
5 fprintf(stderr,
(gdb) continue
Breakpoint 1, sometimes_crashes (f=0x7fffffffd1bc) at prog.c:5
5 fprintf(stderr,
(gdb) continue
Per renderlo più ripetibile, puoi contare quante volte la funzione viene chiamata prima della chiamata specifica che ti interessa e impostare un contatore su quel punto di interruzione (ad esempio, "continua 30" per ignorare le 29 volte successive che raggiunge il punto di interruzione).
Ma dove i punti di interruzione diventano davvero potenti è nella loro capacità di valutare le espressioni in fase di esecuzione, che consente di automatizzare questo tipo di test. Immettere:punti di interruzione condizionali.
break [LOCATION] if CONDITION
(gdb) break sometimes_crashes if !f
Breakpoint 1 at 0x401132: file prog.c, line 5.
(gdb) run
[...]
Breakpoint 1, sometimes_crashes (f=0x0) at prog.c:5
5 fprintf(stderr,
(gdb)
Invece di fare in modo che gdb chieda cosa fare ogni volta che viene chiamata la funzione, un punto di interruzione condizionale consente di fare in modo che gdb si fermi in quella posizione solo quando una particolare espressione viene valutata come vera. Se l'esecuzione raggiunge la posizione del punto di interruzione condizionale, ma l'espressione viene valutata come false, il
Più risorse Linux
- Comandi Linux cheat sheet
- Cheat sheet sui comandi avanzati di Linux
- Corso online gratuito:Panoramica tecnica RHEL
- Cheat sheet della rete Linux
- Cheat sheet di SELinux
- Cheat sheet dei comandi comuni di Linux
- Cosa sono i container Linux?
- I nostri ultimi articoli su Linux
il debugger consente automaticamente al programma di continuare senza chiedere all'utente cosa fare.
Comandi breakpoint
Una caratteristica ancora più sofisticata dei punti di interruzione in GNU Debugger è la capacità di scrivere una risposta al raggiungimento di un punto di interruzione. I comandi di punto di interruzione ti consentono di scrivere un elenco di comandi GNU Debugger da eseguire ogni volta che raggiunge un punto di interruzione.
Possiamo usarlo per aggirare il bug che già conosciamo in sometimes_crash funzione e farlo tornare da quella funzione in modo innocuo quando fornisce un puntatore nullo.
Possiamo usare silenzioso come prima riga per ottenere un maggiore controllo sull'output. Senza questo, lo stack frame verrà visualizzato ogni volta che viene raggiunto il punto di interruzione, anche prima dell'esecuzione dei nostri comandi di punto di interruzione.
(gdb) break sometimes_crashes
Breakpoint 1 at 0x401132: file prog.c, line 5.
(gdb) commands 1
Type commands for breakpoint(s) 1, one per line.
End with a line saying just "end".
>silent
>if !f
>frame
>printf "Skipping call\n"
>return 0
>continue
>end
>printf "Continuing\n"
>continue
>end
(gdb) run
Starting program: /home/twaugh/Documents/GDB/prog
warning: Loadable section ".note.gnu.property" outside of ELF segments
Continuing
Continuing
Continuing
#0 sometimes_crashes (f=0x0) at prog.c:5
5 fprintf(stderr,
Skipping call
[Inferior 1 (process 9373) exited normally]
(gdb)
Scarica la memoria binaria
GNU Debugger ha un supporto integrato per esaminare la memoria usando x comando in vari formati, inclusi ottale, esadecimale e così via. Ma mi piace vedere due formati affiancati:byte esadecimali a sinistra e caratteri ASCII rappresentati da quegli stessi byte a destra.
Quando voglio visualizzare il contenuto di un file byte per byte, utilizzo spesso hexdump -C (hexdump deriva dal pacchetto util-linux). Ecco la x di gdb comando che visualizza byte esadecimali:
(gdb) x/33xb mydata
0x404040 <mydata>: 0x02 0x01 0x00 0x02 0x00 0x00 0x00 0x01
0x404048 <mydata+8>: 0x01 0x47 0x00 0x12 0x61 0x74 0x74 0x72
0x404050 <mydata+16>: 0x69 0x62 0x75 0x74 0x65 0x73 0x2d 0x63
0x404058 <mydata+24>: 0x68 0x61 0x72 0x73 0x65 0x75 0x00 0x05
0x404060 <mydata+32>: 0x00
E se potessi insegnare a gdb a visualizzare la memoria proprio come fa hexdump? Puoi, e in effetti, puoi utilizzare questo metodo per qualsiasi formato tu preferisca.
Combinando il dump comando per memorizzare i byte in un file, la shell comando per eseguire hexdump sul file e define comando, possiamo creare il nostro nuovo hexdump comando per utilizzare hexdump per visualizzare il contenuto della memoria.
(gdb) define hexdump
Type commands for definition of "hexdump".
End with a line saying just "end".
>dump binary memory /tmp/dump.bin $arg0 $arg0+$arg1
>shell hexdump -C /tmp/dump.bin
>end
Questi comandi possono anche essere inseriti in ~/.gdbinit file per definire il comando hexdump in modo permanente. Eccolo in azione:
(gdb) hexdump mydata sizeof(mydata)
00000000 02 01 00 02 00 00 00 01 01 47 00 12 61 74 74 72 |.........G..attr|
00000010 69 62 75 74 65 73 2d 63 68 61 72 73 65 75 00 05 |ibutes-charseu..|
00000020 00 |.|
00000021
Smontaggio in linea
A volte vuoi capire di più su cosa è successo che ha portato a un arresto anomalo e il codice sorgente non è sufficiente. Vuoi vedere cosa sta succedendo a livello di istruzione della CPU.
Lo smontaggio comando consente di visualizzare le istruzioni della CPU che implementano una funzione. Ma a volte l'output può essere difficile da seguire. Di solito, voglio vedere quali istruzioni corrispondono a una determinata sezione del codice sorgente nella funzione. Per ottenere ciò, usa /s modificatore per includere le righe del codice sorgente con il disassembly.
(gdb) disassemble/s main
Dump of assembler code for function main:
prog.c:
11 {
0x0000000000401158 <+0>: push %rbp
0x0000000000401159 <+1>: mov %rsp,%rbp
0x000000000040115c <+4>: sub $0x10,%rsp
12 int n = 0;
0x0000000000401160 <+8>: movl $0x0,-0x4(%rbp)
13 sometimes_crashes(&n);
0x0000000000401167 <+15>: lea -0x4(%rbp),%rax
0x000000000040116b <+19>: mov %rax,%rdi
0x000000000040116e <+22>: callq 0x401126 <sometimes_crashes>
[...snipped...]
Questo, insieme ai registri informativi per vedere i valori correnti di tutti i registri della CPU e comandi come stepi per eseguire un'istruzione alla volta, ti consente di avere una comprensione molto più dettagliata del programma.
Debug inverso
A volte vorresti poter tornare indietro nel tempo. Immagina di aver raggiunto un punto di osservazione su una variabile. Un punto di osservazione è come un punto di interruzione, ma invece di essere impostato in una posizione nel programma, viene impostato su un'espressione (usando il orologio comando). Ogni volta che il valore dell'espressione cambia, l'esecuzione si interrompe e il debugger assume il controllo.
Quindi immagina di aver raggiunto questo punto di osservazione e la memoria utilizzata da una variabile ha cambiato valore. Questo può essere causato da qualcosa che è accaduto molto prima; ad esempio, la memoria è stata liberata e ora viene riutilizzata. Ma quando e perché è stato liberato?
Il GNU Debugger può risolvere anche questo problema perché puoi eseguire il tuo programma al contrario!
Raggiunge questo obiettivo registrando attentamente lo stato del programma ad ogni passaggio in modo che possa ripristinare gli stati precedentemente registrati, dando l'illusione che il tempo scorra all'indietro.
Per abilitare questa registrazione dello stato, utilizza target record-full comando. Quindi puoi usare comandi dal suono impossibile, come:
- passo indietro , che torna alla riga di origine precedente
- indietro-successivo , che riavvolge alla riga di origine precedente, tornando indietro sulle chiamate di funzione
- retromarcia , che torna indietro fino al punto in cui stava per essere chiamata la funzione corrente
- indietro-continua , che torna allo stato precedente nel programma che (ora) attiverebbe un punto di interruzione (o qualsiasi altra cosa che ne provochi l'arresto)
Ecco un esempio di debug inverso in azione:
(gdb) b main
Breakpoint 1 at 0x401160: file prog.c, line 12.
(gdb) r
Starting program: /home/twaugh/Documents/GDB/prog
[...]
Breakpoint 1, main () at prog.c:12
12 int n = 0;
(gdb) target record-full
(gdb) c
Continuing.
Program received signal SIGSEGV, Segmentation fault.
0x0000000000401154 in sometimes_crashes (f=0x0) at prog.c:7
7 return *f;
(gdb) reverse-finish
Run back to call of #0 0x0000000000401154 in sometimes_crashes (f=0x0)
at prog.c:7
0x0000000000401190 in main () at prog.c:16
16 sometimes_crashes(0);
Queste sono solo alcune delle cose utili che GNU Debugger può fare. Ci sono molti altri da scoprire. Quale caratteristica nascosta, poco conosciuta o semplicemente sorprendente di gdb è la tua preferita? Per favore condividilo nei commenti.