Uso del debugger:

Per individuare efficacemente gli errori nei programmi c'è un tool, chiamato debugger, che permette di controllare lo stato delle variabili durante l'esecuzione dei programmi anche dopo un errore di tipo “Segmentation fault”, bloccarne l'esecuzione, con o senza condizioni, visualizzare lo stack delle chiamate a funzioni e molte altre cose interessanti.

Per ora ci limiteremo a fare ciò che è più utile per analizzare i nostri problemi.

Prima di tutto procuriamoci un programma che crea un bell'errore di Segmentation fault:

#include <iostream>
using namespace std;
int main() {
   int *x;
   cout << *x;
}

Ora proviamo ad eseguirlo:

alessandro@BloodyMary:~$ g++ errore.cpp
alessandro@BloodyMary:~$ ./a.out
Segmentation fault

Come è evidente dal listato del programma il problema sta nel fatto che abbiamo dereferenziato un puntatore che punta ad un indirizzo di memoria non noto.

Passiamo al debug:

Per verificare che il problema è stato causato da questo genere di errore ricompiliamo il programma con i simboli di debug:

g++ --debug errore.cpp

Con questo comando otteniamo un eseguibile, a.out, che include dei simboli che danno al debugger informazioni sul codice, necessarie poi per analizzare l'eseguibile in fase di esecuzione.

Ora invochiamo gdb per controllare il nostro errore:

alessandro@BloodyMary:~$ gdb a.out
GNU gdb 6.6.50.20070726-cvs
Copyright (C) 2007 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB. Type "show warranty" for details.
This GDB was configured as "x86_64-suse-linux"...
Using host libthread_db library "/lib64/libthread_db.so.1".
(gdb)
Dovreste trovarvi davanti un prompt del genere. Ora date il comando run:
(gdb) run
Starting program: /home/alessandro/a.out

Program received signal SIGSEGV, Segmentation fault.
0x00000000004009bc in main () at errore.cpp:6
6 cout << *x;

Come vedete il programma genera un Segmentation fault alla riga 6 del codice, e la stampa.

Ora vogliamo vedere il valore di x, quindi diamo il comando “print x”:

(gdb) print x
$1 = (int *) 0x0

Come vedete print ritorna informazioni sia sul tipo che sul valore del puntatore (Attenzione, il suo valore in questo caso è 0, ma non assumete che sia sempre lo stesso su tutti i compilatori o tutte le piattaforme!).

Per uscire dal debugger date il comando “q”:

(gdb) q
The program is running. Exit anyway? (y or n) y
alessandro@BloodyMary:~$

Visualizzare lo stack delle chiamate di funzione:

Procuriamoci ora un programma un po' più complicato per provare un'altra funzionalità del debugger, la visualizzazione dello stack delle chiamate:

#include <iostream>

using namespace std;
void f(int x, int *y=0) {
  if (x)
    f(x-1, y);
  else
    cout << *y;
}

int main() {
  f(10);
}

Il principio di questo programma è lo stesso del primo, ovvero stampa un indirizzo non accessibile della memoria assegnata al programma, ma il suo comportamento è un po' più subdolo... vediamolo in azione:

alessandro@BloodyMary:~$ g++ --debug errore.cpp
alessandro@BloodyMary:~$ ./a.out
Segmentation fault

Come vedete il problema è sempre quello di prima, e in genere è questo nella stragrande maggioranza dei casi, ma noi vogliamo essere certi di quale chiamata ha generato l'errore.

Chiediamo aiuto al debugger per comprendere il nostro problema:

alessandro@BloodyMary:~$ gdb a.out
[...]
(gdb) run
Starting program: /home/alessandro/a.out

Program received signal SIGSEGV, Segmentation fault.
0x00000000004009da in f (x=0, y=0x0) at errore.cpp:8
8 cout << *y;

Ora la funzione backrtace (abbreviata bt) ci da lo stato dello stack al momento della terminazione forzata del programma:

(gdb) backtrace
#0 0x00000000004009da in f (x=0, y=0x0) at errore.cpp:8
#1 0x00000000004009d4 in f (x=1, y=0x0) at errore.cpp:6
#2 0x00000000004009d4 in f (x=2, y=0x0) at errore.cpp:6
#3 0x00000000004009d4 in f (x=3, y=0x0) at errore.cpp:6
#4 0x00000000004009d4 in f (x=4, y=0x0) at errore.cpp:6
#5 0x00000000004009d4 in f (x=5, y=0x0) at errore.cpp:6
#6 0x00000000004009d4 in f (x=6, y=0x0) at errore.cpp:6
#7 0x00000000004009d4 in f (x=7, y=0x0) at errore.cpp:6
#8 0x00000000004009d4 in f (x=8, y=0x0) at errore.cpp:6
#9 0x00000000004009d4 in f (x=9, y=0x0) at errore.cpp:6
#10 0x00000000004009d4 in f (x=10, y=0x0) at errore.cpp:6
#11 0x00000000004009fb in main () at errore.cpp:12

Per vedere qual è il valore di x e di y all'interno dell'ultima chiamata di f diamo il comando “print f::x” al debugger:

(gdb) print f::x
$1 = 0

Notare come si accede alle variabili interne alle funzioni, ovvero con l'operatore di scoping sullo spazio dei nomi delle funzioni.

Se invece vogliamo dereferenziare il puntatore facciamo così:

(gdb) print *f::x
Cannot access memory at address 0x0

Ecco svelato l'arcano: l'area di memoria è inaccessibile...

Per il momento questo vi dovrebbe bastare, buon debuging a tutti! ;-)