GDB il debugger

GDBDebugger

Salve a tutti! Una cosa che capita spesso a ogni programmatore è che si commetta qualche errore, magari di assegnamento o inizializzazione, e che il compilatore ignora, lasciando il compito all'informatico di trovarlo, e come ben sappiamo ci potrebbero volere anche ore.. se non di più! Ma per fortuna ci sono i debugger, potenti strumenti messi a disposizione del programmatore per l'analisi del software! Oggi ne andremo a vedere uno ossia il GNU Debugger o semplicemente gdb.

COS'È GDB
Come abbiamo detto è un debugger, in grado di analizzare programmi scritti in linguaggio C, ( anche ADA, c++ e Fortran) e ovviamente programmini scritti in Assembly.
È open source sviluppato dal progetto GNU, ed è disponibile, oltre che per Linux, anche per Windows e sistemi Unix.

INSTALLAZIONE E UTILIZZO
Ma vediamo ora come installarlo sul nostro sistema Linux.

Apriamo una shell e digitiamo:

 

sudo apt-get install gdb

 

Bene, ora che lo abbiamo installato proviamolo! Quindi scriviamo un semplicissimo programma in C per il nostro testing, il solito Hello world che potete copiare qui di seguito:

 


#include <stdio.h>

int main(){
      printf(“Ciaon”);
      return 0;
}

 

Direi che non è necessario spiegare il codice, salviamo il sorgente in un file con ad esempio il seguente nome ciao.c, e procediamo con la compilazione dando il seguente comando:

 

gcc -g ciao.c -o ciao

 

Nella compilazione abbiamo inserito l'opzione -g, semplicemente inserisce dei simboli all'interno dell'eseguibile, che serviranno per l'analisi con il debugger, intuitivamente l'opzione -o è l'output del compilatore ossia il programma eseguibile.

Ora iniziamo l'analisi dando il seguente comando:

 

gdb -q ciao

 

l'opzione -q ci serve solo per non far mostrare le scritte di introduzione del debugger, ora siamo all'interno del programma:

 

gdb

Iniziamo ora a vedere alcuni comandi utili.

- list: scrivendo “list” ci verrà mostrato il sorgente dell'eseguibile, questo comando sfrutta i simboli aggiunti con l'opzione -g, infatti durante la compilazione senza questo non si potrebbe osservare il sorgente, quindi diventa molto utile durante l'analisi e soprattutto per evitare, (forse), probabili esaurimenti nervosi.

 

list


- break: se scriviamo break nome_funzione, gdb inserirà un breakpoint all'inizio di tale funzione. A cosa serve un breakpoint? Semplicemente durante l'analisi, l'esecuzione andrà in pausa sul break impostato. Ad esempio nel nostro programmino possiamo scrivere break main, e durante l'esecuzione gdb si metterà in pausa all'inizio del main, permettendoci di analizzare i registri o quello che vogliamo.
Volendo possiamo scrivere anche break numero dove numero è la linea di codice in cui vogliamo il breakpoint.

 

break

-disassemble: Con disassemble, andiamo a disassemblare una funzione del programma, ad esempio se vogliamo vedere il codice macchina della nostra funzione main, basta dare il seguente comando disassemble main, insomma ci mostra le ossa del nostro programmino.

 

disassemblemain
-start (o run): Con questo comando iniziamo l'esecuzione del programma che vogliamo esaminare, (se necessità di parametri durante l'esecuzione si può scrivere anche così run parametro):

 

start

Come potete notare nell'immagine qui sopra, il programma si è messo in pausa al breakpoint selezionato da noi prima: Temporary breakpoint 1 at 0x400531.

-disass: durante l'esecuzione, più precisamente in un breakpoint, possiamo dare il comando disass, che ci mostrerà il codice macchina della funzione attualmente in esecuzione:

 

disass

Come potete notare dall'immagine qui sopra, c'è un simbolo => di fianco all'indirizzo 0x400531, questo indirizzo è semplicemente l'inizio del main (ossia il breakpoint impostato da noi). L'istruzione disass è analoga a disassemble main.


-i r (o info registers): durante l'analisi di un programma, può tornare utile esaminare i registri, se vogliamo vedere com'è impostato ognuno di esso, basta dare il comando i r che è un'abbreviazione di info registers, ci mostrerà con quale valore attualmente è impostato in ognuno di essi.

 

ir

Con i r possiamo esaminare anche un singolo registro.

-x (o examine): con questo comando possiamo andare a vedere cosa c'è all'interno di un singolo registro, proviamo quindi a esaminarne uno, ad esempio rax, (il vostro registro ax potrebbe iniziare con un nome diverso, per esempio eax, la differenza sta nel fatto che il mio computer ha un processore a 64 bit, quindi i registri a 64 bit sono indicati con una r iniziale, mentre quelli a 32 bit con una e iniziale, ma la sintassi del codice assembly e dei vari registri esula da questa guida, quindi non approfondiremo questo discorso).
Scriviamo quindi x $rax per esaminare rax.
In questo modo il suo contenuto verrà mostrato in esadecimale, ma possiamo vederlo anche in ottale, in decimale senza segno e in binario (per i masochisti), quindi per vederlo ad esempio in ottale scriviamo x/o $rax, come nell'immagine qui sotto:

 

examine


Se noi scriviamo x/4x verranno mostrate “4 zone di memoria” consecutive, di 4 byte ciascuna (in esadecimale perché abbiamo messo la x), se vogliamo cambiare la dimensione, quindi al posto di 4 byte vogliamo vederne 1 per volta, allora possiamo scrivere x/4xb dove la b ci indicherà appunto di mostrarci 1 solo byte. Volendo, al posto di b, possiamo mettere h per 2 byte, w per 4 byte o g per 8 byte.

 

4xb
Ci sono anche altre opzioni che accetta il comando examine, come ad esempio i che ci mostra l'istruzione assembly, ad esempio x/i $rax, ci mostrerà l'istruzione assembly che contiene questo registro (se c'è ovviamente). Abbiamo anche opzioni come c o s che ci mostrano rispettivamente un carattere ascii o una stringa. Oltre a scrivere $rax, possiamo anche scrivere un indirizzo di memoria come ad esempio x/x 0x400531.

Quindi usando il comando x possiamo esaminare parti importanti di un programma come ad esempio lo stack, esaminando il registro opportuno.

-nexti: Ci permettere di esaminare l'istruzione successiva al breakpoint in cui ci troviamo, nel nostro caso quella successiva al breakpoint del main.

-print: questa istruzione la possiamo usare, ad esempio, come calcolatrice esadecimale, o per sapere la differenza tra due numeri esadecimali in decimale, per esempio digitando print 0xFF – 0x0F ci mostrerà che la differenza è 240. Noterete che il risultato verrà mostrato così: $1 = 240, in modo che noi successivamente possiamo usare questo valore, per esempio ristampandolo con print $1.

-cont (o continue): questo semplice comando ci permette di continuare l'esecuzione del nostro programma, ma a differenza di nexti, che andava all'istruzione successiva, esso continua fino al breakpoint successivo, o fino alla fine dell'esecuzione se non ce ne sono.

-bt (o backtrace): con questo comando, ci verranno mostrati tutti i frame delle funzioni attive nel nostro stack.

-q (o quit): come al solito c'è il comando quit per terminare il programma.

CONCLUSIONI
Bene, vi abbiamo mostrato come esaminare dei singoli registri e porzioni di memoria, cosa che secondo me è importante e utile saper fare, (anche divertente dai xD), il programma gdb contiene tantissimi altri comandi al suo interno, per vederli basta che scriviate help all e verrà mostrata la lista completa, bene ora avete uno strumento in più da testare sui vostri programmini! Al prossimo post!

 

Fonti:
- Wikipedia gdb;
- Immagini di Joel Garia;

Dottore in Informatica. Da sempre appassionato di Linux, reti informatiche, sicurezza e, in modo amatoriale, all'elettronica. Il mio intento è quello di trasmettere le mie conoscenze ad altri appassionati.

Lascia un commento