Šta je GDB i kako se koristi?
Svako ko e ikada, bar malo, bavio programiranjem zna da prvi kod koji napišete nikada ne radi. Ako vam se desi da odmah proradi ili pišete nešto sitno ili mislite da vam radi a zapravo ne radi. Kada napišemo našu prvu verziju bilo kog programa, potrebno je da uočimo greške i da ih ispravimo. Proces traženja greške se naziva debagovanje i obavlja se alatom koji se naziva dibager (debuger).
Jedan od takvih alata je i GDB. On je konzolni alat (pokreće se iz komandne linije) i služi traženju grešaka u vašem programu. Ako ste već radili u komandnoj liniji (npr kompajlirali ručno pomoću GCC-a ili koristili neki konzolni editor kao što su Nano i Vim), onda vam je princip rada ovakvih programa poznat. Ako niste, možete se upoznati detaljnije sa ovim načinom rada ovde gde je opisan rad na Linuksovoj komandnoj liniji.
U ovom članku neće detaljno biti opisan rad u komandnoj liniji i biće pretpostavljeno da znate da napišete jednostavan program u programskom jeziku C kao i da taj napisani kod znate da kompajlirate upotrebom GCC kompajlera.
Prikazaćemo rad GDB-a kroz primere i objasniti neke njegove mogućnosti kako bismo lakše uočili greške u našem programu.
Pre nego što koristimo GDB, potrebno je da naš program kompajliramo sa potrebnim flagovima za GDB. To radimo upotrebom -g opcije prosleđene GCC-u na sledeći način:
gcc program.c -o izlazni_fajl -g
Sada možemo pokrenuti GDB na sledeći način:
gdb izlazni_fajl
Da bismo bolje razumeli kako radi GDB, uzmimo za primer program koji računa faktorijel rekurzivno ali koji ima grešku u sebi:
#include <stdio.h>
int fact(int n)
{
if (n==0) return 0;
int res = n * fact(n-1);
return res;
}
int main()
{
int f = fact(5);
printf("%d\n", f);
return 0;
}
Ovde odmah vidimo grešku jer je trivijalna ali hajde da to pokušamo da uvidimo i upotrebom GDB-a.
Dakle, prvo kompajliramo naš program sa -g opcijom prosleđenom GCC-u a zatim pokrenemo GDB na gore-opisani način. Trebalo bi da dobijete nešto slično ovome:
GNU gdb (GDB) 7.12.1
Copyright (C) 2017 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law. Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-pc-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from a.out...done.
(gdb)
Sada možemo početi izvršavanje našeg programa liniju po liniju i to radimo tako što kucamo komandu start:
(gdb) start
i dobijamo nešto slično ovome:
Temporary breakpoint 1 at 0x40052f: file program.c, line 13.
Starting program: /me/home/stefan/a.out
Temporary breakpoint 1, main () at program.c:13
13 int f = fact(5);
Kao što vidimo, linija koju nam GDB izbaci je linija koja tek treba da se izvrši. Dakle, linija koja će se tek izvršiti je:
13 int f = fact(5);
Da bismo je izvršili, kucamo komandu next ili skraćeno n:
(gdb) next
14 printf("%d\n", f);
(gdb)
Šta se sada desilo? Pa kako se naša linija izvršila, promenljivoj f je dodeljen rezultat funkcije fact. Da bismo odštampali vrednost promenljive f, koristimo komandu print ili skraćeno p:
(gdb) p f
$1 = 0
(gdb)
Kao što vidimo, vrednost promenljive f je 0 što se ne poklapa sa očekivanom vrednošću jer znamo da faktorijel od 5 nije nula. Dakle, greška se desila negde u liniji koju smo izvršili, tačnije, greška se dešava u funkciji koju smo napisali. Da bismo “ušli” u našu funkciju, umesto komande next treba koristiti komandu step (skraćeno s) i time izvršavamo ono unutar funkcije. Pošto smo tu liniju već izvršili, moramo pokrenuti naš program ponovo. To radimo tako što izađemo iz GDB-a komandom quit i pokrenemo GDB na isti način kao ranije:
(gdb) quit
A debugging session is active.
Inferior 1 [process 11459] will be killed.
Quit anyway? (y or n) y
pokretanjem ponovo GDB-a i pokretanjem programa sa komandom start, dolazimo ponovo do sledeće situacije:
(gdb) start
Temporary breakpoint 1 at 0x40052f: file program.c, line 13.
Starting program: /me/home/stefan/a.out
Temporary breakpoint 1, main () at program.c:13
13 int f = fact(5);
(gdb) step
fact (n=5) at program.c:6
6 if (n==0) return 0;
(gdb)
Sada se izvršava linija po liniju u datoj funkciji. Kako je ova funkcija rekurzivna, ovo radimo dok ne uvidimo gde je greška:
(gdb) p n
$1 = 5
(gdb) n
7 int res = n * fact(n-1);
(gdb) s
fact (n=4) at program.c:6
6 if (n==0) return 0;
(gdb) n
7 int res = n * fact(n-1);
(gdb) s
fact (n=3) at program.c:6
6 if (n==0) return 0;
(gdb) n
7 int res = n * fact(n-1);
(gdb) s
fact (n=2) at program.c:6
6 if (n==0) return 0;
(gdb) p n
$2 = 2
(gdb) n
7 int res = n * fact(n-1);
(gdb) s
fact (n=1) at program.c:6
6 if (n==0) return 0;
(gdb) p n
$3 = 1
(gdb) n
7 int res = n * fact(n-1);
(gdb) s
fact (n=0) at program.c:6
6 if (n==0) return 0;
(gdb) p n
$4 = 0
(gdb) n
9 }
(gdb) n
8 return res;
(gdb) p res
$5 = 0
(gdb)
Posle debagovanja uočavamo da ako je n jednako nuli, da funkcija vraća nulu pa samim tim množenje sa nulom je uvek nula.
Razmotrimo sada slučaj kada želimo da program debagujemo samo u jednom njegovom delu, bez izvršavanja liniju po liniju od samog početka. Za ovo nam služi breakpoint. Dakle, kada pokrenemo GDB, umesto start kucamo break n gde je n broj linije gde želimo da se izvršavanje zaustavi. Kada to uradimo pokrenemo izvršavanje sa run.
Na primer, razmotrimo sledeći program:
#include <stdio.h>
int main()
{
int i;
int n;
scanf(" %d", &n);
int fib[100];
fib[0] = 0;
fib[1] = 1;
for (i=2; i<n; i++)
{
fib[i] = fib[i-1] + fib[i-2];
}
printf("%d\n", fib[n]);
return 0;
}
Ovaj program iterativno računa n-ti Fibonačijev broj. Kada otvorimo GDB i kada uradimo break na liniji gde treba ispisati rešenje, i kada tu ispišemo vrednosti fib[5] i fib[4], uočavamo sledeće:
(gdb) b 17
Breakpoint 1 at 0x4005dc: file program.c, line 17.
(gdb) run
Starting program: /me/home/stefan/a.out
5
Breakpoint 1, main () at program.c:17
17 printf("%d\n", fib[n]);
(gdb) p fib[5]
$1 = 32767
(gdb) p fib[4]
$2 = 3
(gdb)
Dakle, fib[4] je u redu dok je fib[5] neka čudna vrednost koja se ne poklapa sa očekivanom, iz ovoga zaključujemo da nam računanje Fibonačijevog broja radi za 0 1 2 3 i 4 ali ne radi za 5. Kada probamo neki drugi ulaz, na primer 7, uočavamo da nam računanje radi za 0 1 2 3 4 5 i 6 ali da ne radi za 7. Greška je u uslovu zaustavljanja for ciklusa pa to ispravimo da je uslov i<=n i time otklanjamo grešku.
Iz ovog primera vidimo da GDB lepo radi i kada koristimo funkcije poput scanf i printf i da upotrebom scanf funkcije, normalno unosimo željeni ulaz i kad pritisnemo Enter, GDB normalno nastavlja sa radom.
Ovim je demonstrirano neko osnovno korišćenje GDB-a. Za više informacija, postoji dobra dokumentacija a možete krenuti tako što u konzolu kucate sledeće:
man gdb
Za one koji preferiraju više grafičko okruženje, postoji grafički wrapper za GDB koji se zove DDD. Nisam se upuštao u upotrebu dotičnog jer meni lično GDB radi posao ali vidim da ga asistenti na faksu dosta koriste tako da ne verujem da je preterano loš.