sreda, 24 aprila, 2024
Kako da...?

GDB i načini uklanjanja grešaka

Autor: Veljko Simić

Tokom kodiranja, programer može da napravi dve vrste grešaka: sintaksne i semantičke. U velikom programu greške su neizbežne, sintaksne greške prijavljuje prevodilac i lakše su za otklanjanje od semantičkih (logičkih) grešaka, zbog kojih programi mogu da daju pogrešne rezultate, da se upletu u beskonačne petlje ili da „puknu” zbog nedozvoljenih operacija nad memorijom. Pronalaženje i eleminisanje logičkih grešaka može da se odradi na dva načina:

  1. Iščitavanjem kôda nekoliko stotina puta i lupanjem glavom o zid.
  2. Korišćenjem nekog programa za otkrivanje grešaka (debugger).

Ako je vaš konačan odgovor „pod 2”, ovaj tekst je pisan baš za vas. U nastavku teksta biće opisana instalacija i jednostavan primer rada GNU-ove alatke GDB.

Instalacija programa GDB

Ukoliko je na vašem sistemu dostupan GNU prevodilac za jezik C (GCC), vrlo verovatno da je i GDB već instaliran. To ćete utvrditi narednom komandom, koja bi trebalo da ispiše podatak o verziji i informacije o autorskim pravima GDB-a.

gdb -version

Ako je GDB instaliran ispisuje se poruka slična sledećoj:

GNU gdb (GDB) 7.6.2 Copyright (C) 2013 Free Software Foundation, Inc. License GPLv3+: GNU GPL version 3 or later  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-unknown-linux-gnu".

Ukoliko GDB nije instaliran, možete preuzeti izvorni kôd sa web-a (http://www.gnu.org/software/gdb/download/) i instalirati ga, mada Juniksoliki sistemi omogućavaju pogodnije rešenje – instaliranje binarnog paketa. Ako koristite Windows, preporučujemo vam da instalirate softver Cygwin. Cygwin obezbeđuje standardno Unix okruženje na Windows platformama, gde spadaju GCC, GDB i druge GNU alatke (http://www.cygwin.com/)

Primer rada GDB-a

Da bismo vam objasnili rad GDB-a, iskoristićemo verovatno svima dobro poznatu funkciju zamene vrednosti dveju promenljivih „swap”.

#include  void swap (int *p1, int *p2); int main (){ int a=10, b=20; printf ("Stare vrednosti: a=%d, b=%d.\n",a,b); swap(&a,&b); printf ("Nove vrednosti: a=%d, b=%d.\n",a,b); return 0; } void swap (int *p1, int *p2){ int *tmp=p1; p1=p2; p2=tmp; }

Kao što vidite, program ne radi ono što bismo mi želeli, već ispisuje sledeći rezultat:

Vrednosti promenljivih su: 10 20 Vrednosti zamenjenih promenljivih su: 10 20

Otkrivanje grešaka počinjemo pokretanjem programa GDB iz komandnog okruženja, navodeći ime izvršne datoteke kao argument komandne linije za GDB.

gdb ./a.out
GNU gdb (GDB) 7.6.2 Copyright (C) 2013 Free Software Foundation, Inc. ... Reading symbols from /~/a.out...done.''

GDB ispisuje (gdb) na početku novog reda tražeći da unesete komandu za otkrivanje grešaka. Možete početi komandom list, ili samo njenim prvim slovom, l, da biste prikazali nekoliko redova izvršnog kôda programa u kome otkrivate greške. Program podrazumeva 10 redova izvorne datoteke.

 (gdb) l 1	#include  2 3	void swap (int *a, int *b){ 4		int *tmp=a; 5		a=b; 6		b=tmp; 7	} 8 9	int main () { 10		int a=10, b=20; (gdb)

Pre nego što naložite programu GDB da izvrši program, morate mu navesti željenu tačku prekida. Kada program dođe do tačke prekida, on se zaustavlja i omogućava vam da pogledate trenutno stanje programa na tom mestu, kao i nastavak izvršavanja programa red po red, prateći promenu stanja programa.

Tačku prekida definišete pomoću komande break, ili b, skraćeno zadavajući joj broj linije izvornog kôda u kojoj želite da zaustavite program.

(gdb) b 8 Breakpoint 1, main () at swap.c:8 8		swap(&a,&b);

Komanda run, ili r pokreće program:

(gdb) run Starting program: /home/wex/./a.out warning: Could not load shared library symbols for linux-vdso.so.1. Do you need "set solib-search-path" or "set sysroot"? Stare vrednosti: a=10, b=20. Breakpoint 1, main () at swap.c:8 8		swap(&a,&b);

Kada naiđe na tačku prekida, program za otkrivanje grešaka prekida ispitivanje programa i prikazuje red sa naredbom koja se sledeća izvršava. Pošto pretpostavljamo da je greška u funkciji swap(), želimo da je izvršimo korak po korak. Program GDB to omogućava pomoću komandi next, ili n, i step, ili s. Iako obe naredbe izvršavaju program red po red, između njih postoji razlika. Naredba next izvršava naredni red, uključujući sve pozive funkcija, i ponovo zaustavlja program u sledećem redu. Komanda step izvršava skok na pozvanu funkciju i prekida program na prvoj naredbi u telu funkcije. U našem primeru, komanda step vodi do prve naredbe u funkciji swap().

(gdb) s swap (p1=0x7fffffffe99c, p2=0x7fffffffe998) at swap.c:14 14		int *tmp=p1;

GDB omogućava i prikaz vrednosti promenljivih pomoću funkcije print, ili p.

(gdb) p *p1 $1 = 10 (gdb) p *p2 $2 = 20

Kao što vidimo, početne vrednosti su u redu. Komandom next ćemo izvršavati red po red funkcije.

(gdb) n 15		p1=p2; (gdb) n 16		p2=tmp; (gdb) n 17	}

Sada ćemo ponovo proveriti vrednosti promenljivih

(gdb) p *p1 $3 = 20 (gdb) p *p2 $4 = 10

Napomena: Komandom print možemo da vidimo samo vrednosti lokalnih promenljivih.

Sada p1 ima vrednost 20, a p2 ima vrednost 10, što se čini ispravnim. Program možete da nastavite da pregledate pomoću još dve komande print:

(gdb) p p1 $5 = (int *) 0x7fffffffe998 (gdb) p p2 $6 = (int *) 0x7fffffffe99c

Obratite pažnju kada smo izvršili komandu step na vrednosti p1 i p2. (p1=0x7fffffffe99c, p2=0x7fffffffe998) Kao što možete da primetite, zamenjene su vrednosti pokazivača p1 i p2, a ne sadržaji memorijskih lokacija p1 i p2. To je bila greška u funkciji swap(). Funkciju moramo izmeniti da zamenjuje celobrojne vrednosti p1 i p2, umesto vrednosti pokazivača u promenljivama p1 i p2. Ispravna verzija izgleda ovako:

void swap (int *p1, int *p2){ int tmp=*p1; *p1=*p2; *p2=tmp; }

Komanda continue, ili c, omogućava izvršavanje programa do naredne tačke prekida ili do svog kraja.

(gdb) c Continuing. Nove vrednosti: a=10, b=20. [Inferior 1 (process 1253) exited normally]

Izlazak iz programa GDB se vrši komandom quit, ili q.

(gdb)quit $

Poslovica „Na tuđim greškama se uči” je pusta laž; čovek najbolje uči na svojim greškama. Zato pravite greške i ispravljajte ih, ali neka to budu isključivo programske greške.