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(“Ciao\n”);
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:
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.
- 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.
-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.
-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”):
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:
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.
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:
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.
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;
Nessun commento:
Posta un commento