GNU Debugger questo sconosciuto

Posted by – November 12, 2004

Quando si scrivono programmi in C, si cerca ovviamente di non fare errori di sintassi, segnalati dal compilatore (il quale non compilerà in presenza di “sintax errors”), e un discreto programmatore riesce ad evitarli (altrimenti non sarebbe nemmeno un programmatore!). Ci sono però casi in cui la sintassi è corretta ma gli errori vengono notati solo a run-time.

Questi sono gli errori più pericolosi perchè non evidenziati dal compilatore ma comunque esistenti. Gli errori a run-time, come si può capire sono difficili da trovare e quindi da risolvere. Ogni programma che si rispetti necessita una fase di “debug”, cioè una fase di testing molto approfondito sul codice e soprattutto sulle aree di memoria che quel codice modifica (quindi stack, heap e cosi via).

Il tool per eccellenza per fare un buon debug è assolutamente il gdb, scritto (in parte) da Richard Stallman.

Descrivo i principali comandi del gdb (il debugger per eccellenza dei programmatori linux) per fare un corretto debug dei programmi (tutti) che lo richiedono. Inizio col presentare due categorie distinte di programmi: quelli con argomenti e quelli senza.

I programmi che accettano argomenti di solito applicano una determinata azione sugli argomenti passati. Per esempio un programma che calcola il fattoriale di un numero potrebbe accettare quel numero come argomento.
Es.

./fattoriale 5
120

Ovviamente i programmi che non accettano argomenti sono più semplificati durante la chiamata del programma stesso ma richiederanno sicuramente ciò di cui necessitano durante l’esecuzione dello stesso. Es.

./fattoriale_senza_argomenti
Inserire il numero:
5

120

Questa banale introduzione agli argomenti del main viene considerata anche dal gdb. In gdb tutti i programmi, con o senza argomenti, vengono eseguiti senza argomenti. L’esempio che riporto fa riferimento a un programma con argomenti, poichè la soluzione opposta è ancora più semplice.

Il programma usato nell’esempio lo chiameremo mioprogramma il quale accetta un parametro durante la sua reale esecuzione. Prima di iniziare il debug dell’eseguibile (ovviamente) è bene compilare con gcc con una opzione particolare. Cioè con -g la quale include informazioni extra nei file oggetto (i .o, in pratica) e nell’eseguibile stesso (il programma finale).

Il debugger userà proprio queste informazioni per raggiungere la riga di codice corrispondente all’indirizzo di memoria corrente. Per ulteriori informazioni sull’opzione -g del gcc rimando al man oppure all’info sicuramente installato sulla vostra linux box.
Iniziamo il debug.

$gdb mioprogramma

A questo punto il gdb segnalerà un bellissimo Segmentation Fault che disorienta molti programmatori. Il segnale corrispondente è il SIGSEGV che ci informa che il programma è andato in crash. La cosa non deve preoccupare perchè nel linguaggio dei comuni mortali questo vuol dire che il programma si aspettava un argomento che non avete passato.

Il comando where vi darà la possibilità di visionare lo stack relativo. E se ne avete la necessità anche quella di passare al livello n-esimo dello stack (sapete che lo stack = pila quindi fatto da diversi livelli, vero ?) con il comando up n dove n è il numero del livello da visionare, appunto.

La più importante azione che si può fare in debug time è il settaggio dei breakpoints cioè le linee di codice sulle quali volete che il programma si fermi (non definitivamente) per poi riprendere da dove si è fermato quando digitate un tasto. I programmatori che vengono dall’ambiente Windows, di solito utilizzano gli IDE (Integrated Development Environment) che permettono di fissare i breakpoint in modo visuale, per esempio evidenziando la riga di codice su cui mandare in freeze il programma.

Ma il concetto è lo stesso. Il comando per fissare i breakpoints è il break Gli argomenti accettati da break sono molteplici.
Se vogliamo fissare un breakpoint sul file main.c alla riga 5 allora dovremmo digitare

(gdb)break sorgente.c:5

Se vogliamo fissare un breakpoint su una intera funzione (davvero molto utile quando si vuole fare il debug di una nuova feature inserita), per esempio la funzione main, allora digitiamo

(gdb)break main

Il comando delete ovviamente serve per cancellare eventuali breakpoints dati in modo errato oppure non più utili ai fini del debug. Una volta settati i breakpoints iniziamo l’esecuzione del programma all’interno del gdb stesso. Il comando in questione è run. A run dobbiamo passare tutti gli argomenti di cui il nostro eseguibile necessita. Quindi

run param1 param2 … paramN

Ipotizziamo di aver fissato 3 breakpoints. Bene, il debugger bloccherà il programma sul primo breakpoint. Una volta bloccato, possiamo visualizzare lo stack corrente, oppure fare una qualsiasi altra operazione visto che abbiamo il prompt di gdb assolutamente a nostra disposizione.
Intanto il nostro programma è in freeze, è congelato nell’attesa di un comando che lo faccia saltare al prossimo breakpoint. Facciamolo con next

Ci sono casi in cui il numero dei breakpoints è elevato. Scrivere next ogni volta che vogliamo fare un salto al break successivo può diventare noioso. Il comando step ci viene in aiuto permettendo una esecuzione passo-passo.

Infine il comando list stampa sullo standard output (si vabbè il monitor!) le righe di codice nell’intorno del break . Molto utile anche se piuttosto scomodo.

Come faccio allora a visionare la riga di codice corrispondente allo stato di esecuzione corrente ? Niente di più semplice, a patto che si editi il tutto con emacs, il re degli editors per qualsiasi linguaggio. Facendo partire il debugger gdb direttamente da emacs, questo evidenzierà com una ‘=>’ la riga di codice corrispondente allo stato di esecuzione corrente.

Ovviamente i comandi del gdb sono molto più numerosi, ma una prima infarinatura non può che descriverne i principali. Spero che questo mini-mini HOWTO sia stato di aiuto a coloro che, forse, non sapevano nemmeno cosa fosse un debugger.

0 Comments on GNU Debugger questo sconosciuto

Log in to respond