GDB Opis GDB-a ⇀ Uvod ⇀ Instalacija ⇀ Pozivanje ⇀ Izlaz iz GDB-a ⇀ Odabir fajlova ⇀ Tačke prekida (eng. Breakpoints) ⇀ GDB komande Osnovna upotreba GDB-a ⇀ GDB i razvojna okruženja ⇀ GDB TUI (Text User Interface) ⇀ Debagovanje u TUI režimu ⇀ GDB i Emacs ⇀ Primer GDB u višeprocesnom okruženju ⇀ Uvod ⇀ Debagovanje procesa ⇀ Višeprocesno debagovanje ⇀ Primer-fork ⇀ Primer-exec GDB u višenitnom okruženju ⇀ Uvod ⇀ Primer





GDB

Nekada se dešava da provedemo sate i sate gledajući u kod i pokušavajući da pronađemo grešku, odnosno razlog zbog koga se naš program ne ponaša očekivano. Za ubrzanje procesa detekcije greške koriste se specijalni programi koji se nazivaju debageri. U ovom poglavlju upoznaćemo se sa osnovnim konceptima i upotrebom GDB debagera.

Opis GDB-a

Uvod

Često se prilikom pisanja programa susrećemo sa greškama u kodu. Postoje tri osnovne vrste grešaka:

   greške prilikom prevođenja programa (leksičke i sintaksičke greške - interpreter ili kompajler šalje upozorenja u slučaju ovakvih grešaka),
   greške prilikom izvršavanja programa (fatalne greške koje dovode do prekida programa u izvršavanju)
   i logičke greške (greške koje dovode do neočekivanog ponašanja programa).

Softveri koji se koriste kako bi se greške u izvršavanju i logičke greške lakše detektovale nazivaju se debageri. Debager je program koji se koristi za testiranje i proveru rada drugih programa (ciljni programi). Glavna uloga debagera je da pokreće ciljni program pod određenim uslovima i na taj način omogući programeru da u svakom trenutku može pratiti stanje programa u toku njegovog izvršavanja npr. promene u računarskim resursima (najčešće memorijske adrese koje koristi ciljni program).

GNU debager ili GDB je originalno razvijao Richard Stallman 1986. godine kao deo njegovog GNU sistema. GDB je slobodan softver zaštićen od strane General Public License (GPL). GPL omogucava kopiranje programa, modifikovanje kopije kao i njeno distribuiranje. GDB je standardan debager za GNU operativni sistem. Prenosiv je, a takođe radi i na mnogim sistemima sličnim Unix-u i upotrebljiv je za veliki broj programskih jezika kao što su Ada, C, C++, Objective-C, Go i drugi. Kod za GDB napisan je u C-u. Poslednja stabilna verzija ovog alata je 11.1 (u odnosu na vreme pisanja rada). Osnovne 4 funkcionalnosti koje GDB pruža kako bi se otklonile greške u izvršavanju i logičke greške u kodu:

1. pokretanje programa uz specijalne opcije
2. zaustavljanje programa na odredjenoj liniji koda
3. pregled resursa koje program koristi prilikom njegovog zaustavljanja
4. promena delova koda, tako da se nakon ispravljanja greške može nastaviti sa debagovanjem i otklanjanjem preostalih.

Preterano korišćenje debagera je loša navika. Umesto toga, mnogo je bolje greške otkrivati pažljivim pregledom samog koda. Ipak, u situacijama kada je upotreba debagera neizbežna, GDB je, bilo da se koristi iz komandne linije, ili iz nekog drugog okruženja (o njima će biti reči u narednim poglavljima), moćan alat za obavljanje tog zadatka.

Instalacija

GDB se isporučuje kao sastavni deo većine GNU/Linux distribucija. Ukoliko GDB ne postoji na sistemu, može se instalirati na koristeći neki od sledeća dva načina:

  Prvi način se koristi za Debian GNU/Linux distribucije poput Ubuntu-a, Mint-a i drugih. sudo apt-get update
sudo apt-get install gdb

  Drugi način:

Korak 1: Preuzimanje izvornog koda. Izvorni kod svih verzija se može preuzeti na linku http://ftp.gnu.org/gnu/gdb/.

wget “http://ftp.gnu.org/gnu/gdb/gdb-7.11.tar.gz”
Korak 2: Raspakivanje preuzete datoteke.

tar -xvzf gdb-7.11.tar.gz
Korak 3: Provera konfiguracije i kompajliranje.

cd gdb-7.11
gdb-7.11$ ./configure
gdb-7.11$ make

Ovaj korak će potrajati neko vreme. Kada se završi, možete proveriti da li je GDB binarna datoteka na lokaciji gdb-7.11/gdb/gdb.
Korak 4: Instalacija GDB-a.

make install
Podrazumevano instalira GDB binarne datoteke na lokaciji /usr/local/bin i biblioteke na /usr/local/lib.
Nakon instalacije, možete proveriti GDB verziju.

gdb --version

Pozivanje

Opcijom gdb pozivamo debager. Jednom kada se pokrene, GDB čita komande iz GDB konzole sve dok se eksplicitno ne zatraži izlaz opcijom quit ili ctrl+d (Slika 1). GDB terminal je sličan običnom terminalu, samo što naravno prepoznaje samo komande koje se odnose na debagovanje. Nije neophodno izaći iz GDB konzole da bi se izvršila neka UNIX naredba, dovoljno je koristiti komandu shell. Komandom make se može pokrenuti make, dakle ne mora se kucati komanda shell make.

GDB terminal, baš kao i klasični terminal, podržava automatsko kompletiranje imena komande pritiskom na Tab taster, što takođe treba obavezno koristiti u cilju izbegavanja kucanja punih imena komandi.
Slika 1

pozivanje

GDB se moze pokretati sa raznim argumentima i opcijama u cilju podešavanja okruženja za debagovanje na samom početku. U nastavku će biti prikazane neke od mogućih opcija prilikom pokretanja debagera.
Najčešći način pokretanja GDB-a je sa jednim argumentom koji predstavlja ime izvršnog programa:

gdb program
Fatalne greške u programu dovode do prekida izvršavanja programa uz odgovarajuću poruku o grešci. Nakon što se desi fatalna greška u programu operativni sistem pokreće mehanizam oporavka od greške koji najčešće rezultuje fajlom u kojem su zapisani sadržaji registara, status procesa i podaci procesa u trenutku greške. Taj fajl se može koristiti za analizu i otkrivanje uzroka greške i naziva se jezgro fajl (eng. core file/core dump file). Kao argument pored izvršnog fajla može se proslediti i jezgro fajl:

gdb program core
Moguće je proslediti bilo kakve argumente GDB-u nakon opcije --args koja zaustavlja procesiranje narednih opcija.

gdb --args gcc -02 -c foo.c
Prilikom pokretanja GDB-a može se videti uvodni tekst (Slika 1). Pokretanje bez prikazivanja uvodnog materijala može se izvršiti sledećom opcijom:

gdb -silent
Nadalje, ponašanje GDB-a određuju opcije unešene u komandnoj liniji. GDB može prikazati dostupne opcije.
Naredba

gdb –help
prikazuje sve dostupne opcije i ukratko ih opisuje (‘gdb -h’ je ekvivalentna naredba).
Sve opcije i prosleđeni argumenti izvršavaju se sekvencijalnim redom. Redosled se može promeniti korišćenjem opcije ‘-x’.

Izlaz iz GDB-a

Kao što je već napomenuto, izlaz iz GDB-a omogućava naredba quit ili naredba koja označava kraj fajla tj. ctrl+d.

Komanda ctrl+c se ne koristi za izlaz ali je korisno upotrebiti za prekidanje bilo koje akcije debagera. Bezbedno je koristiti ovu naredbu zato što GDB ne dozvoljava njeno izvršenje dok se ne obezbede uslovi kada je to bezbedno. Npr. česta greška u programima su beskonačne petlje koje, ukoliko dodatno u okviru same petlje imamo i curenje memorije mogu dovesti do fatalnih posledica. Jedini način zaustavljanja debagera ukoliko uđe u beskonačnu petlju je upravo upotreba naredbe ctrl+c. Nakon prekida, biće označena problematična linija, pa daljim debagovanjem liniju po liniju programer bi trebao da uoči grešku.

Odabir fajlova

Prilikom pokretanja, GDB čita sve argumente osim onih koji se odnose na izvršivi fajl i jezgro fajl. Ako GDB verzija ne podržava rad sa jezgro fajlom, što je slučaj sa velikim brojem ugrađenih uređaja, u tom slučaju će izdati upozorenje i ignorisaće drugi argument.

Većina opcija ima duže i kraće forme tj. GDB podržava skraćen zapis opcija. Zapis se može skraćivati dokle god je opcija koju opisuje jednoznačno određena. Argumenti koji predstavljaju opciju mogu imati prefiks ‘--’ ili ‘-’. Navedimo neke primere u nastavku:

Čitanje tabličnih simbola iz fajla:
  -symbols file
  -s file

Koristi fajl file za izvršavanje i za ispitivanje samih podataka u kombinaciji sa jezgro fajlom:
  -exec file
  -e file

Čita tabelu simbola iz fajla file i koristi je kao izvršivi fajl:
  -se file

Koristi fajl file kao jezgro fajl:
  -core file
  -c file

Izvršava naredbe iz fajla file:
  -command file
  -x file

Izvršava jednu GDB komandu.
  -eval-command command
  -ex command

Ukoliko želimo da navedemo više komandi, dovoljno je ponoviti naredbu željeni broj puta:
  gdb -ex c1 -ex c2 … -ex cn , gde su ci, i = 1, 2, …, n komande.

Dodaje putanju do direktorijuma iz koga može čitati fajlove:
  -directory direktory_name
  -d directory

Tačke prekida (eng. Breakpoints)

Tačke prekida služe za označavanje broja linije, naziva funkcije ili adrese na kojoj želimo da se program zaustavi prilikom debagovanja. Izvršavanje se zaustavlja ispred linije u kojoj je postavljena prekidna tačka, tj. instrukcija koja se nalazi u toj liniji se ne izvršava. Na nekim sistemima, tačke prekida se mogu postaviti na deljene biblioteke pre početka izvršavanja. To je glavno ograničenje za HP-UX sisteme: neophodno je sačekati početak izvršavanja da bi se postavile tačke na rutinama deljene biblioteke koje nisu pozvane direktno iz programa (npr. rutina koja je argument funkcije pthread_create koja kreira novu nit u okviru tekućeg procesa - više o nitima i procesima biće narednim poglavljima). Tačka prekida se postavlja naredbom break ili nekim njenim varijacijama koje će biti u nastavku opisane. Specijalna promenljiva debagera ‘$bpnum’ čuva broj tačaka prekida koje su poslednje dodate. Naredba show conv(convenience) prikazuje vrednosti svih specijalnih promenljivih debagera, pa samim tim i ‘$bpnum’. Naredba continue prelazi sa jedne tačke prekida na prvu sledeću.
Deljene ili dinamičke biblioteke su jedna vrsta eksternih biblioteka. Prilikom pokretanja programa dinamičke biblioteke se učitavaju u memoriju računara zajedno sa programom kako bi se razrešili pozivi nekih funkcija izvršive datoteke. Dakle, linkovanje se vrši dinamički tj. u fazi izvršavanja.
Primeri postavljanja tačaka prekida:

break location

Postavlja tačku prekida na zadatu lokaciju, koja može biti naziv funkcije, broj linije ili adresa neke instrukcije. Postavljena tačka će zaustaviti program pre izvršenja označene linije.
Kada se koristi izvorni kod i naredbi break se kao argument prosledi naziv funkcije to može dovesti do problema. Naime, neki programski jezici poput C++ pružaju podršku opterećivanju metoda tj. više funkcija može imati isti naziv ali različite parametre. Jasno je da mesto postavljanja tačke prekida u ovom slučaju nije jednoznačno određeno.
Takođe je moguće postaviti tačku prekida koja bi prekinula program samo ukoliko se nalazi u konkretnoj niti ili zadatku.

break

Kada se pozove bez argumenata, break postavlja tačku prekida na sledeću instrukciju koja će se izvršiti u tekućem stek okviru.
Često se za postavljanje prekidne tačke kao argumenti break komande koriste + ili – praćeni nekim brojem. U tom slučaju se prekidna tačka postavlja onoliko linija koda iza odnosno ispred tekuće pozicije u izvršavanju programa koliko iznosi dati broj.

break … if cond

Postavlja tačku prekida pod uslovom cond; izračunava izraz cond svaki put kada pokušava da postavi tačku prekida, i postavlja je jedino ukoliko je vrednost izraza različita od nule, odnosno ukoliko je izraz tačan. ‘…’ predstavlja neki od gore navedenih argumenata.

tbreak args

Postavlja tačku prekida na koju možemo samo jednom da se zaustavimo. Dakle, nakon prvog zaustavljanja na postavljenoj tački prekida dolazi do njenog brisanja.

hbreak args

Za postavljanje ovakve tačke prekida neophodna nam je određena podrška hardvera, što automatski ukazuje na ograničenu upotrebu ove naredbe. Glavna svrha naredbe je EPROM/ROM debagovanje gde tačka prekida može da se postavi na instrukciji bez promene same instrukcije. Može se koristiti na većini računara koji podržavaju x86 arhitekturu. Ovi računari će postaviti prepreku kada program pokuša da pristupi podatku ili adresi instrukcije koja pripada registru. Jedna od mana hardverskih tačaka prekida registara je ta što je njihov broj ograničen. Npr. na DSU (Debug Support Unit) istovremeno mogu biti postavljene samo dve tačke prekida. Ukoliko se pokuša dodavanje novih, GDB će odbiti ovaj zahtev. Takođe, moguće je i ograničavanje broja tačaka prekida na pojedinim sistemima.

EPROM i ROM su memorije koju su dostupne samo za čitanje, čuvaju podatke i kada računar ostane bez napajanja. Sadržaj EPROM-a može biti izbrisan pri izlaganju uv-zracima
x86 je arhitektura skupa instrukcija za računarske procesore, prvenstveno razvijana od strane Intel-a
rbreak regex

Postavlja tačku prekida na sve funkcije čiji naziv odgovara navedenom regularnom izrazu. Jednom kada se postave ovakve tačke prekida, tretiraju se kao one postavljene break naredbom. Mogu se obrisati, isključiti (ne brišu se, ali debager kada naiđe na njih ignoriše ih) ili im se može dodati dodatni uslov kao bilo kojoj drugoj tački prekida.

Ukoliko želimo da postavimo tačku prekida na svaku funkciju u programu, to možemo uraditi tako što kao regex prosledimo samo tačku.

Tačka u regularnim izrazima predstavlja bilo koji karakter.

Specijalni oblici tačaka prekida su tačke nadgledanja (eng. watchpoints) i tačke hvatanja (eng. catchpoints).
Tačka nadgledanja zaustavlja program kada se vrednost određenog izraza promeni. Izraz može biti vrednost promenljive, ili može uključivati jednu ili više promenljivih u kombinaciji sa nekim od operatora npr. ‘x * y’. Nekad se naziva i tačka zaustavljanja podataka. Neophodno je koristiti različite izraze za postavljanje tačaka nadgledanja. Uprkos tome, nad tačkama nadgledanja moguće je primenjivati iste operacije kao i nad tačkama prekida. Moguće ih je uključivati, isključivati, brisati itd…
Tačka hvatanja zaustavlja program kada se desi određeni događaj kao što je npr. izbacivanje C++ izuzetaka ili učitavanje biblioteke. Kao i kod tačaka nadgledanja, moguće je koristiti različite komande za njihovo postavljanje, ali i primenjivati operacije nad njima kao i nad bilo kojom tačkom prekida.
GDB pridružuje broj svakoj tački prekida, nadgledanja i hvatanja kada se kreiraju. Ti brojevi su uzastopni i numerisanje kreće od jedan. U okviru komandi za upravljanje određenom tačkom dovoljno je navesti redni broj tačke koja nas zanima. Neke GDB komande mogu operisati i sa nizom određenih tačaka. Ako želimo da operišemo sa tačkama od 5 do 10, dovoljno je kao argument navesti ‘5-10’. Komandom ‘info break’ dobijamo neke korisne informacije vezane za tačke prekida u našem programu, kao što su njihovi redni brojevi i adrese.

GDB komande

U nastavku navodimo neke GDB komande koje se mogu pozivati kada je GDB pozvan nad izvršnom datotekom.

break -kao što je već napomenuto, postavljanje tačke prekida
clear [arg] -briše tačku prekida u zavisnosti od argumenta:
1. ako je navedeni argument broj linije, briše se tačka prekida na toj liniji
2. ako je navedeni argument naziv funkcije, brišu se sve tačke prekida u okviru te funkcije
continue -nastavak izvršavanja programa do sledeće tačke prekida
display var -prikazuje vrednost promenljive var svaki put kada se program zaustavi prilikom debagovanja (bilo da je u pitanju tačka prekida ili komanda ‘next’)
frame -prikazuje liniju do koje se stiglo debagovanjem
help [arg] -prikazuje GDB dokumentaciju
-opcioni argument navodimo ukoliko želimo opis konkretne komande.
info args/locals/catch -ako je argument args ispisuju se sve promenljive tekućeg stek okvira
-ako je argument locals ispisuju se sve lokalne promenljive tekuće funkcije
-ako je argument catch ispisuju se uhvaćene greške (izuzeci)
info registers -prikazuje sadržaj registara
info threads -prikazuje niti koje se koriste
kill -prekida program u bilo kom trenutku
list -prikazuje izvorni kod
next -prelazak na sledeću liniju koda
print arg -ispisuje vrednost promenljve arg
-ukoliko prosledimo ime niza, ispisaće se adresa početka; moguće je dobiti i ispis svih elemenata niza malom modifikacijom argumenta
'*naziv-niza@duzina-niza'
-ukoliko nas zanima adresa neke promenljive ‘&naziv_prom’
run -pokretanje debagovanja programa
step -ukoliko se u tekućoj liniji poziva funkcija, komanda step ima značenje da preusmeri debagovanje na telo date funkcije
-ako se u tekućoj liniji ne nalazi funkcija, komanda step ima isti efekat kao i komanda next
where -prikazuje tekuću liniju i naziv funkcije u kojoj se nalazimo

Postoje i komande koje se mogu pozivati u GDB terminalu kao što su: help, tui, version i druge.

Osnovna upotreba GDB-a

GDB i razvojna okruženja

Kao što smo već videli GDB je konzolni alat. U nastavku će biti opisano tekstualno korisničko okruženje TUI koje olakšava proces debagovanja. Kao modifikacija tektualnih korisničkih okruženja nastaju grafička korisnička okruženja. Modifikacija u smislu jednostavnije upotrebe i vizuelizacije za samog korisnika. Trenutno najpoznatiji GUI za rad sa GDB-om je gdbgui.

Danas je sve veća upotreba integrisanih razvojnih okruženja (eng. IDE), koja predstavljaju više od alata za debagovanje. IDE se uglavnom sastoje iz editora za pisanje izvornog koda, alata za automatizaciju i debagera. Pomenuta okruženja pružaju potpunu automatizaciju procesa debagovanja. Jedni od najpoznatijih IDE okruženja koja koriste GDB su GNU Emacs, CLion, Eclipse CDT, NetBeans, QtCreator i drugi. Iako postoje modernija okruženja za debagovanje korišćenjem GDB-a, prikazaćemo podršku koju Emacs pruža kada je u pitanju proces debagovanja. Emacs je tradicionalan editor koji se vezuje za GNU/Linux operativni sistem, što je glavni razlog navedene odluke.

GDB TUI (Text User Interface)

GDB tekstualni korisnički interfejs je konzola koja koristi kursor biblioteke za prikazivanje izvornog koda, asemblerskog izlaza, registara procesora i GDB komandi u različitim prozorima. TUI režim je podržan jedino na platformama gde su dostupne određene kursor biblioteke.
Kursor (eng. curses) biblioteke pružaju veliki skup funkcija koje omogućavaju manipulisanje terminalom pružajući tekstualni korisnički interfejs.
TUI režim se može dobiti komandom ‘gdbtui’ ili ‘gdb -tui’. Takođe, TUI je moguće pokrenuti iz GDB terminala komandom ‘tui enable’.
Izlaz iz TUI režima se može postići komandom ‘quit’ ili ‘tui disable’.
TUI režim može prikazivati nekoliko tekstualnih prozora:

● komandni

Ovaj prozor je namenjen GDB komandama sa GDB terminalom i izlazom. Ulaz se unosi standardno, kucanjem odgovarajućih naredbi u prozoru.

● izvorni

Ovaj prozor prikazuje izvorni kod programa. Tekuću liniju koja se izvršava i aktivne tačke prekida koje se vidi na prozoru.

● asemblerski

Ovaj prozor prikazuje asemblerski izlaz programa.

● registarski

Ovaj prozor prikazuje registre procesora. Registri su istaknuti kada se promeni njihova vrednost.

Izvorni i asemblerski prozor prikazuju trenutnu poziciju u programu isticanjem linije korišćenjem simbola '>'.

Tačke prekida se označavaju sa dva simbola.

Prvi simbol prikazuje tip tačke prekida:

● B

Tačka prekida koja je posećena bar jednom.

● b

Tačka prekida koja nijednom nije bila posećena.

● H

Hardverska tačka prekida koja je posećena bar jednom.

● h

Hardverska tačka prekida koja nijednom nije bila posećena.

Drugi simbol prikazuje da li je tačka isključena ili ne:
+ Tačka prekida je uključena.
- Tačka prekida je isključena.
Primer posećene i uključene tačke prekida:
tui1

Izvorni, asemblerski i registarski prozor se ažuriraju kada se tekuća nit promeni, kada se stek okvir promeni ili brojač programa.
PC (Program counter) tj. brojač programa je registar koji služi kao pokazivač na narednu instrukciju u programu koja treba da se izvrši.
Nije moguća istovremena vidljivost svih navedenih prozora. Komandni prozor je uvek vidljiv. Ostali se mogu grupisati na sledeće načine:
  ● samo izvorni,
  ● samo asemblerski,
  ● izvorni i asemblerski,
  ● izvorni i registarski ili
  ● asemblerski i registarski.

Natpis iznad komandnog prozora prikazuje sledeće informacije:

target   Prikazuje trenutni cilj GDB-a.

process  Prikazuje tekući proces ili broj niti. Kada se nijedan proces ne debaguje, na ovom polju je upisano No process.

function  Prikazuje naziv funkcije u tekućem stek okviru. Kada se nijedan simbol ne poklapa sa tekućim brojačem programa, prikazuje se string ‘??’.

line  Prikazuje broj linije u tekućem okviru. Kada je broj linije nepoznat, prikazuje se string ‘??’.

pc   Prikazuje adresu brojača programa.

TUI pruža mogućnost korišćenja režima JednoSlovo, koji omogućava povezivanje jednog slova sa odgovarajućom GDB komandom. Neki primeri:

c  nastavi

r  izvršavaj

d  brisanje tačaka prekida

s  idi ‘unutar’ tekuće linije

f  koja linija se sledeća izvršava

u  vrati se jednu liniju unazad

n  idi na sledeću liniju

h  pomoć

q  izadji iz TUI režima

b  postavi tačku prekida

Neke korisne komande za upravljanje TUI prozorima:

info win -ispisuje nazive prozora i njihove veličine
layout next -prikazuje sledeću grupu prozora (u nekom od prethodnih pasusa nabrojana su moguća grupisana)
layout prev -prikazuje prethodnu grupu prozora
layout src -prikazuje izvorni prozor
layout asm -prikazuje asemblerski prozor
layout regs -prikazuje registarski prozor

Spisak navedenih naredbi se može dobiti komandom help layout.

Na sledećoj slici prikazan je TUI režim sa izvornim prozorom.

tui

Debagovanje u TUI režimu

Do sada smo se upoznali sa dovoljno teorije, vreme je da vidimo kako to funkcioniše u praksi. Kao prvi primer na kome ćemo prikazati upotrebu GDB-a uzećemo sledeći C kod koji prikazuje Euklidov algoritam za računanje NZD-a dva broja:

debag1

Koraci su sledeći:

   1. Neka se naš program zove euklid.c. Prevodimo program na sledeći način:

gcc euklid.c -g -o euklid
Dakle, neophodno je koristiti opciju -g kako bismo mogli debagovati izvršni fajl. Izvršni program opcijom -o preimenujemo u euklid (umesto a.out, što je podrazumevani naziv).

   2. Sledećom opcijom pozivamo TUI režim iz konzole:
gdbtui euklid
   3. Trenutno, na raspolaganju imamo prazan izvorni prozor i komandni prozor u kome je neophodno kliknuti c+enter kako bismo obezbedili terminal za unos komandi.

debag2

   4. U okviru izvornog prozora možemo videti izvorni kod fajla euklid:

debag3

Postavljamo tačke prekida na linijama 18, 20 i 22:

b 18
b 20
b 22

Umesto komande b može se koristiti i break, ovde želimo da iskoristimo prednosti JednoSlovo režima.

   5. Sledećom naredbom pokrećemo debagovanje programa i zaustavljamo se na prvoj postavljenoj tački prekida tj. na liniji 18:

run
   6. Sledećim naredbama ispisujemo vrednosti promenljivih a i b:

p a
p b

Komanda p je skraćeni zapis komande print.
Da li je rezultat očekivan? Ovde se jasno može videti da debager ne uzima u obzir tekuću liniju. U promenljivoj a se sasvim slučajno našla upisana jedinica.

debag4

   7. Na ovom mestu možemo jasno prikazati razlike između komandi next i continue. Naime, brisanjem tačke prekida na liniji 20 i upotrebom naredbe next prelazimo na sledeću liniju (na kojoj postoji napisan kod) tj. na liniju broj 20, dok bismo upotrebom naredbe continue prešli na liniju 22 tj. na sledeću tačku prekida. Naredba step bi na ovom primeru funkcionisala isto kao i next zbog toga što u tekućoj liniji nemamo poziv funkcije.

d 20
n

   8. Sada, kada se nalazimo u liniji 20 možemo iskoristiti naredbu step kako bismo iz main-a prešli u funkciju nzd.

   9.Zanimljive naredbe kada se nalazimo u telu neke funkcije su info locals i info registers.

Nakon komande info registers na početku petlje u funkciji nzd:

debag5

   10. Rekli smo da su tačke nadgledanja specijalne vrste tačaka prekida. Postavljanjem tačke nadgledanja nad promenljivom a prikazaćemo njihovu funkcionalnost (još uvek se nalazimo u funkciji nzd):

watch a

Razlog zašto i ovde nismo koristili JednoSlovo režim je to što postoji GDB komanda where, i GDB nije u mogućnosti da prepozna o kojoj naredbi je reč u slučaju kucanja slova w.
   11. Sledeća naredba koju izvršavamo je continue. Očekivano ponašanje bi bilo da pređemo na sledeću tačku prekida tj. na liniju 22. Međutim, kako smo nad promenljivom a postavili tačku nadgledanja i a je promenila vrednost, to je rezultat komande continue sledeći:

debag6

   12. Na samom kraju, kucanjem tri puta naredbe continue uspešno završavamo program pri čemu se u komandnom prozoru može videti ispisana odgovarajuća poruka kao rezultat funkcije printf. Primetimo da nakon prve naredbe continue u komandnom prozoru možemo videti obaveštenje da je tačka nadgledanja nad promenljivom a obrisana. Razlog tome je upravo to što je a u funkciji nzd bila lokalna kopija promenljive a iz main-a, pa samim tim, kada se vratimo u main ta kopija više ne postoji (jer ne postoji ni stek okvir za funkciju nzd).

Telo petlje unutar nzd funkcije se izvršava samo jednom jer se već u prvom koraku dobije ostatak jednak nuli. Ovaj primer je uzet radi jednostavnosti, ukoliko se uzmu dva broja koja nisu deljiva jedan drugim, biće potrebno više continue naredbi do završetka programa. Razlog je isti kao pre, tačka nadgledanja beleži svaku promenu promenljive a u okviru funkcije nzd.

Napomena: Kada se debagovanje vrši u TUI režimu može se desiti da se okruženje nekada ponaša čudno, u smislu duplira neke linije ili menja font u okviru prikazanih prozora. Ovaj problem se jednostavno rešava komandnom ctrl+l za osvežavanje izgleda prozora.
Debagovanje u okviru GDB terminala se izvršava analogno, s tim što je debagovanje u TUI režimu značajno olakšano zahvaljujući dodatnim prozorima jer uvek imamo uvid do koje linije se stiglo sa debagovanjem bez pozivanja dodatnih komandi.

GDB i Emacs

GNU Emacs je tekst editor dizajniran za POSIX operativne sisteme i dostupan je na GNU/Linux-u, Windows-u, macOS-u…

POSIX je zajedničko ime za porodicu povezanih standarda koje definiše Institut inženjera elektrotehnike i elektronike.
Emacs se pokreće naredbom:

emacs
Glavni deo Emacs prozora čini površina za unos teksta. Na samom dnu početnog prozora može se videti statusna linija tzv. minibuffer koja služi za unos dodatnih komandi.

Neke značajnije Emacs komande:   ● C - <chr> ima značenje držati pritisnut taster ctrl dok se ukuca karakter <chr>

  ● M - <chr> ima značenje držati pritisnut taster alt dok se ukuca karakter <chr>

GDB iz Emacs-a pokrećemo sledećom komandom:

M - x gdb
Nakon toga, u okviru statusne linije unosimo:

gdb -i=mi ./euklid
Verovatno će gore navedena komanda biti predložena od strane Emacs-a, ali bez ‘./’ kod naziva izvršnog fajla. Opcija ‘-i=mi’ šalje signal GDB-u da se debagovanje izvršava u integrisanom razvojnom okruženju.
Sada, na ekranu možete videti GDB konzolu sličnu onoj koja se dobija pokretanjem GDB-a iz konzole.

emacs1

Na početku prozora Emacs-a imamo meni sa alatkama koje se mogu koristiti kako bi se olakšao postupak debagovanja programa.

Ukoliko postavimo tačku prekida na nekoj liniji koda i pokrenemo debagovanje komandom ‘run’, Emacs automatski otvara novi prozor u okviru kojeg prikazuje liniju izvornog koda do koje se stiglo debagovanjem. Nadalje, GDB-u možemo zadavati komande iz konzole, kao što smo navikli, ili jednostavno koristiti ugrađene alatke za debagovanje.

Još jedna bitna karakteristika Emacs-a je ta da se nakon završetka izvršavanja programa prikazuje prozor sa izlaznim podacima. Dakle, Emacs razdvaja prozore GDB interpretera, izvorni kod i izlaz i samim tim olakšava preglednost. Međutim, Emacs može i više od ovoga. U okviru meni alata ‘Gud -> GDB-Windows -> …’ možemo sami izabrati koji prozor želimo da se prikaže pored ova tri. Odvojeno otvaranje svakog prozora svaki put kada debagujemo program može postati zamorno. Unošenjem komande ‘M - x’ a zatim:

gdb-many-windows
unutar minibuffer-a dobićemo prikaz svih prozora. Nakon ovoga, raspored prozora je mnogo pregledniji. Primer ovakvog prikaza možemo videti na sledećoj slici:

emacs2

Sve dosadašnje tehnike su veoma korisne ali treba se setiti svaki put komande za prikazivanje više prozora. Ukoliko želimo da nam se svaki put prilikom pokretanja debagera prikazuje svih šest prozora, izvorni kod i slično iskoristićemo promenu podrazumevanih prikaza u okviru Emacs-a. Komandom ‘M - x customize’ otvaramo Emacs podešavanja. Navođenjem opcije ‘Gdb’ u polje za pretragu, prikazuje se sledeći prozor:

emacs3

Listanjem ponuđenih opcija mogu se pronaći opcije za podrazumevani prikaz šest prozora i prikaz izvornog koda prilikom pokretanja GDB-a nad nekim izvršnim fajlom. Prikaz datih opcija nakon promene i čuvanja njihovih novih vrednosti je sledeći:

emacs4

Na samom kraju, u Emacs-u je moguće i ručno postavljanje tačaka prekida, klikom na obojeni deo ispred željene linije koda. Nakon ovoga, prikazuje se crvena tačka koja predstavlja tačku prekida.
Dakle, da bismo debagovali kod u Emacs-u GDB-om skoro da nam nije potrebno znanje komandnih funkcija GDB-a zahvaljujući mnogobrojnim Emacs ugrađenim alatkama.

Primer

Na sledećem primeru ilustrovaćemo postupak debagovanja. Dakle, imamo kod, imamo grešku i kako da je odredimo.

pr2-1

pr2-2

Kod je napisan u C++ i služi za učitavanje i ispis binarnog stabla. Prevođenjem programa ‘g++ -g stablo.cpp -o stablo’ dobijamo izvršni program stablo. Pokretanjem izvršnog programa, nakon učitavanja vrednosti u čvorovima stabla sa standardnog ulaza, dobijamo grešku segmentation fault. Postavlja se pitanje, gde je greška? Vrsta greške nam sugeriše da pristupamo memoriji koja nije naša pa možemo pretpostaviti da je u pitanju neinicijalizovani pokazivač.
Za debagovanje programa koristićemo Emacs okruženje.

  1. Nakon pokretanja debagovanja programa klikom na opciju Run i unošenja čvorova stabla (u odgovarajući prozor za ulaz), zaustavljamo se u liniji 28, gde se izbacuje greška. Dakle, problem nastaje prilikom pokušaja ispisa levog podstabla.

pr2-3

  2. Postavimo tačku prekida negde na početku programa, npr. na liniji 46, kako bismo od početka pratili ponašanje programa. Pokretanjem debagovanja programa zaustavljamo se na početku petlje i dalje debagovanje izvodimo liniju po liniju. Kada dođemo do linije 48, iskoristićemo naredbu step (koja je ugrađena u Emacs-u) i na taj način debagovanje preusmeriti na funkciju koja dodaje čvor u stablo.

  3. Sada, kao i pre, debagujemo program liniju po liniju. Koristeći ugrađenu opciju za naredbu next vidimo da se preskače prvi if (jer nije ispunjen uslov) i da se prelazi na drugi. Međutim, kako je to moguće kada u stablu trenutno nemamo ništa? Očigledno da koren ima neku drugu vrednost.

  4. Naredbom za ispis argumenata funkcije dobijamo prikaz vrednosti svih argumenata (unosimo 1 kao prvi čvor u stablu). Dakle, vidimo da je problem u korenu. Umesto da ima vrednost NULL (što je u nekim jezicima podrazumevana vrednost ukoliko se nije izvršila inicijalizacija), koren je dobio neku sasvim slučajnu vrednost. Greška koju smo pronašli se poklapa sa našom pretpostavkom o neinicijalizovanom pokazivaču.

pr2-4

  5. Postavljanjem na početku (u liniji 43) korena na NULL i ponovnim prevođenjem i pokretanjem programa, vidimo da je problem u potpunosti rešen.

Nakon izmene izvornog koda neophodno je izvršiti ponovno prevođenje kako bi GDB radio sa najnovijom verzijom izvršnog fajla.

GDB u višeprocesnom okruženju

Uvod

Upoznajmo se najpre sa nekim osnovnim pojmovima višeprocesnog okruženja.
Proces definišemo kao program u izvršavanju. Svaki proces ima svoj ID (PID - Process ID) - ceo broj koji je jedinstven za svaki proces na sistemu.

Više informacija i detaljniji opisi koncepata mogu se naći u knjizi The Linux Programming Interface.
Signal je obaveštenje koje se šalje procesu kao indikator nekog događaja. Takođe, signali se mogu koristiti za komunikaciju između različitih procesa tj. jedan proces može poslati signal drugom procesu.

Višeprocesno okruženje se može odnositi na više procesa koji se izvršavaju na jednom procesoru ili na više procesora. Ubuduće, pod pojmom višeprocesno okruženje podrazumevamo izvršavanje više procesa na jednom procesoru. U ovom slučaju programi planeri raspoređuju koji proces će se u kom trenutku izvršavati na procesoru.

U nastavku navodimo sistemske pozive koji će nam biti korisni u daljem radu:

Sistemski pozivi su skupovi funkcija koji predstavljaju interfejs ka operativnom sistemu. Npr. da bi aplikativni program pristupio hardveru (štampaču itd.) neophodno je koristiti odgovarajući sistemski poziv.

● fork - je sistemski poziv koji kreira novi proces dupliranjem tekućeg. Novi proces se naziva dete proces, dok se tekući proces naziva roditeljski. Nakon kreiranja dete procesa, oba procesa izvršavaju instrukcije nakon fork poziva (osim ukoliko se ne postave uslovne naredbe koje će ograditi deo koda koji izvršava dete proces). Dete proces koristi isti brojač programa, iste registre procesora i fajlove kao i roditeljski proces. Fork se poziva bez argumenata i kao povratnu vrednost vraća ceo broj. Ako je povratna vrednost:

  1. negativan ceo broj - kreiranje dete procesa nije uspelo.
  2. nula - nalazimo se u dete procesu.
  3. pozitivan ceo broj - nalazimo se u roditeljskom procesu. Povratna vrednost u ovom slučaju predstavlja ID dete procesa.
● exec - predstavlja funkcionalnost operativnog sistema da pokreće izvršni fajl u kontekstu već postojećeg procesa, zamenjujući prethodnu izvršnu datoteku. POSIX standard deklariše exec funkcije u okviru zaglavlja unistd.h u programskog jeziku C. Postoji veliki broj funkcija iz exec familije, neke od njih navodimo u nastavku:

  1. execvp, execv - kreira se dete proces koje ne mora izvršavati isti program kao i roditeljski proces. Exec familija funkcija omogućava izvršavanje bilo kog programa, uključujući binarne izvršne datoteke, shell skriptove i slično. Deklaracije funkcija su sledeće:

int execvp (const char *file, char *const argv[]);
int execv (const char *path, char *const argv[]);

gde parametar file/path pokazuje na naziv fajla/putanju fajla koji treba da se izvršava, a parametar argv predstavlja niz opcija sa kojima pozivamo dati fajl i obavezno se mora završiti NULL pokazivačem.
  2. execlp, execl - koriste se u iste svrhe pri čemu su potpisi poziva nešto drugačiji:

int execlp (const char *file, const char *arg,.../* (char *) NULL */);

int execl (const char *path, const char *arg,.../* (char *) NULL */);

gde parametar file/path pokazuje na naziv fajla/putanju fajla koji treba da se izvršava, a opcioni parametri predstavljaju opcije sa kojima pozivamo dati fajl. Na kraju svih opcija navodi se kastovani NULL pokazivač.
Povratna vrednost postoji ukoliko je došlo do greške, i u tom slučaju funkcije vraćaju -1. Nastavak ‘p’ (execvp, execlp) označava da u slučaju da naziv fajla ne sadrži ‘/’ pretražuje se Putanja promenljiva, u suprotnom izvršava se fajl na zadatoj putanji i Putanja promenljiva se ignoriše. Nastavak ‘l’ (execlp, execl) označava da se opcije odnosno argumenti željenog procesa navode jedan po jedan, kao lista. Nastavak ‘v’ (execvp, execv) označava da se opcije odnosno argumenti željenog procesa smeštaju u niz i na taj način se prosleđuju.

Putanja promenljiva (eng. PATH variable) sadrži niz direktorijuma koji su razdvojeni ‘:’ i sadrže neke izvršne datoteke.

Debagovanje procesa

Debagovanje procesa čiji ID je poznat može se pokrenuti komandom:

gdb process_ID

Ako drugi argument počinje brojem, GDB će prvo pokušati da mu pridruži proces, ukoliko ne uspe, pokušava da ga otvori kao jezgro fajl. Ako postoji jezgro fajl čije ime počinje cifrom, sprečavanje GDB-a da ga tretira kao PID može se postići dodatkom ‘./’, npr. ‘./12345’
Međutim, postavlja se pitanje šta ukoliko je gdb već pozvan ili program već pokrenut.
GDB omogućava debagovanje već pokrenutih programa. Komanda

attach process_id
debaguje program koji je pokrenut van GDB-a. Komanda kao argument dobija ID procesa. Najjednostavniji način za dobijanje ID procesa na Unix sistemima je korišćenjem funkcije ps, ili jobs -l shell komande. Za upotrebu attach komande neophodno je da program bude pokrenut u okruženju koje podržava procese. Takođe, potrebno je imati dozvolu za slanje signala procesu.
Prilikom korišćenja attach komande, debager pronalazi traženi proces tako što prvo pretražuje tekući direktorijum a zatim fajlove izvorne putanje. Takođe, moguće je koristiti komandu file za učitavanje programa.

Ukoliko se traženi fajl ne nalazi u tekućem direktorijumu GDB pretražuje listu direktorijuma koji se jednim imenom nazivaju izvorna putanja.
Nakon povezivanja sa procesom GDB ga zaustavlja. Nadalje, nad datim procesom moguće je pozivati sve komande GDB-a kao i nad programom čije je debagovanje pokrenuto komandom run. Komandom continue omogućavamo da program nastavi svoje izvršavanje.
Ukoliko izvršen program ne želimo više debagovati, to možemo postići komandom detach. Na ovaj način oslobađamo proces tj. GDB i proces nastavljaju da funkcionišu nezavisno. Kada je GDB povezan sa procesom, izlaz iz GDB-a će dovesti do prekida veze tj. komande detach. Ukoliko smo proces pokrenuli komandom run, izlazom iz GDB-a uništavamo dati proces.

Višeprocesno debagovanje

Na većini sistema, GDB ne pruža specijalnu podršku za debagovanje programa koji su kreirani kao dodatni procesi korišćenjem sistemskog poziva fork. Nakon sistemskog poziva fork, GDB će nastaviti da debaguje roditeljski proces dok će se dete proces izvršiti nesmetano. Ukoliko se postavi tačka prekida u delu koda koji izvršava dete proces, dete proces će dobiti SIGTRAP signal koji će prekinuti izvršavanje procesa, osim u slučaju da dete proces uhvati signal.

Međutim, ipak postoji način za debagovanje dete procesa. Pozivom funkcije sleep u kodu koji dete proces izvršava nakon poziva fork. Možda je korisno pozivati funkciju sleep ukoliko neka promenljiva ima određenu vrednost ili koristiti neki drugi indikator u slučaju da ne želimo debagovanje dete procesa da bismo izbegli nepotrebno kašnjenje (prouzrokovano pozivom sleep). Dok dete proces “spava”, koristeći ps program možemo dobiti njegov ID. Zatim pozivamo GDB (novi poziv GDB-a ukoliko debagujemo i roditeljski proces) koji će pratiti dete proces (debagovanje već pokrenutog programa). Nadalje, moguće je debagovanje dete procesa kao i bilo kojeg drugog programa.

Na HP-UX (od verzije 11 i više) sistemima, GDB pruža podršku za debagovanje programa koji su kreirani kao dodatni procesi korišćenjem fork-a ili exec-a. Nažalost, GDB u jednom trenutku može pratiti samo jedan proces.

Podrazumevano, kada program pozove fork, GDB će nastaviti sa debagovanjem roditeljskog procesa i dete proces se nesmetano izvršava. Da bismo pratili debagovanje dete procesa umesto roditelja, neophodno je koristiti komandu:

set follow-fork-mode mode
mode može biti:

parent - u tom slučaju roditeljski proces se debaguje nakon fork-a (podrazumevano ponašanje)

child - u tom slučaju novi proces se debaguje nakon fork-a, roditeljski se nesmetano izvršava

ask - u tom slučaju debager postavlja pitanje koju od prethodne dve opcije želimo da izaberemo

Takođe, korisna komanda je:

show follow-fork-mode
koja ispisuje naziv procesa koji se debaguje (roditelj ili dete).
Kao što je već rečeno, funkcije iz familije exec kreiraju novi proces na osnovu prosleđenih argumenata. Debagovanje novog procesa je moguće i može se postići navođenjem sledeće komande:

set follow-exec-mode new
Ukoliko ipak želimo debagovanje tekućeg procesa dovoljno je argument new zameniti argumentom same.

Takođe, korisna komanda je:

show follow-exec-mode
koja ispisuje naziv procesa koji se debaguje (novi ili stari).

Primer-fork

Na sledećem primeru ilustrujmo debagovanje u višeprocesnom okruženju koristeći sistemski poziv fork.

pfork1
pfork2
pfork3

Kod kreira dete proces koje treba da ispiše rastuće sortiran niz koristeći algoritam za brzo sortiranje. Kako znamo da svaki proces ima svoj memorijski prostor i da je niz u dete procesu kopija niza iz roditeljskog procesa, to će roditelj očekivano ispisati niz u prvobitnom obliku (a ne sortiranom). Pored toga, kod ispisuje i PID odgovarajućeg procesa i njegovog roditelja/deteta. Za unos: 5 4 5 1 3 2 očekivani izlaz iz dete procesa je 1 2 3 4 5 a iz roditeljskog 4 5 1 3 2 (prva petica označava broj elemenata niza). Međutim, izlaz je sledeći (naziv programa je fork.c):

pfork4

Dakle, očigledno ulazimo u dete proces ali iz nekog razloga ne dobijamo ispis sortiranog niza, zapravo, ne dobijamo ispis niza uopšte. Očigledno da negde postoji problem. Ovoga puta nemamo segmentation fault pa ne možemo tek tako pretpostaviti u čemu je greška. Ovo je idealna prilika za korišćenje debagera.
Kao i pre, debagovanje ćemo prikazati u Emacs okruženju.

  1. Kako roditeljski proces radi sve kako treba, pretpostavljamo da je greška negde u dete procesu. U vezi sa tim, preusmeravamo debagovanje na dete proces komandom:

set follow-fork-mode child
Znamo da nakon što smo preusmerili debagovanje, kod koji izvršava dete proces možemo debagovati kao i bilo koji drugi program.

  2. Najbolje bi bilo postaviti tačku prekida na početku dete procesa. Kako se linije 52 i 53 izvršavaju to je najbolje tačku prekida postaviti na liniji 54.

  3. Pokretanjem debagovanja programa zaustavljamo se na liniji 54 i prozori imaju sledeći prikaz:

pfork5

  4. Očigledno da je preostali deo koji dete izvršava u if bloku potpuno u redu, dakle, ima smisla iskoristiti naredbu step kako bismo proverili šta se dešava u funkciji za sortiranje.

  5. Nakon što smo iskoristili ugrađenu step line komandu otvara se novi stek okvir za funkciju sortiraj. U sklopu prozora koji prikazuje stek okvire, možemo pratiti trenutno aktivne stek okvire, a redni broj 0 označava poslednje otvoreni stek okvir.
Prozor vezan za lokalne promenljive nam mnogo olakšava posao. Moguće je pratiti vrednosti lokalnih promenljivih a da pritom ne moramo pozivati komandu info locals.
Na početku funkcije sortiraj bilo bi dobro proveriti vrednosti svih argumenata. Dakle, sledeća komanda koju pozivamo je:

Stek okviri su numerisani u opadajućem poretku u odnosu na redosled otvaranja.
pfork6

  6. Dalje debagovanje izvodimo liniju po liniju. Petlja je prilično jednostavna, kao i funkcija swap, dakle, na njima se ne moramo zadržavati.

Nakon što smo izašli iz petlje vidimo (iz prozora za lokalne promenljive) da je pozicija pivota 3. Kako smo na početku za pivot izabrali prvi element odnosno 4, njegova pozicija u sortiranom nizu zaista jeste 3.

  7. Sada, kada se nalazimo na liniji 36, pozivamo step ugrađenu komandu, da vidimo šta se dešava sa ovim pozivom. Želimo da sortiramo sve elemente koji se nalaze levo od pivota i vidimo da su to oni koji imaju pozicije od 0 do 2, dakle i dalje je sve u redu.

pfork7

  8. Izlaskom iz petlje dobijamo da je pozicija pivota 1. Kako je za pivota izabran prvi element levo od prethodnog pivota tj. broj 2 (dobijen tako što je 4 u prethodnom pozivu došla na svoje mesto), to je njegova pozicija u sortiranom nizu zaista 1.

  9. Kako smo u prethodnom pozivu ušli u prvi rekurzivni poziv i videli da sve funkcioniše kako treba, hajde ovoga puta da uđemo u drugi rekurzivni poziv da vidimo šta se u tom slučaju dešava.

pfork8

Očigledno da je neophodno sortirati niz od dva elementa, tj. dovoljno je odrediti koji je od njih veći. Nakon izlaska iz petlje u okviru prozora za lokalne promenljive vidimo da je pozicija pivota 1 kao i u prethodnom pozivu.

pfork9

Sada, možemo primetiti gde se potencijalno nalazi problem. Naime, svaki put kada pozivamo drugi rekurzivni poziv za sortiranje uključujemo poziciju pivota. Kako je pozicija pivota jedinstvena, to će u svim narednim pozivima prvi element niza koji se sortira (tj. pivot) biti postavljan na svoju poziciju (na koju je već postavljen u prethodnom pozivu). Dakle, imamo problem beskonačne rekurzije.
Kako da rešimo navedeni problem? Umesto da u drugom rekurzivnom pozivu uključujemo pivot, poziv će biti oblika sortiraj(a, pivot_poz+1, d).
Ponovnim prevođenjem i pokretanjem programa vidimo da sve funkcioniše kako treba i da smo dobili očekivani ispis.

pfork10

Primer-exec

Na sledećem primeru ilustrujmo debagovanje u višeprocesnom okruženju koristeći sistemski poziv exec, konkretno, funkciju execlp čija funkcionalnost je opisana u uvodnom delu.

pexec1
pexec2

Neka je naziv prvog programa exec.c a drugog program.c. Prvi program poziva drugi program korišćenjem poziva exec. Drugi program učitava imena dok se ne unese reč ‘kraj’ i ispisuje ih u onom redosledu u kom su i uneta.

Šta se dešava nakon prevođenja i pokretanja programa?

pexec3

Dakle, nemamo očekivano ponašanje programa. Umesto da se učitavanje imena zaustavi prilikom unosa reči ‘kraj’, nastavlja se. Mogući problemi su da program ne učitava imena kako treba ili da se poređenje ne izvršava kako treba. Iskoristićemo mehanizam debagovanja kako bismo odredili u čemu je zapravo problem. Kao i pre, postupak ćemo prikazati korišćenjem Emacs okruženja.

  1. Unesemo komandu za debagovanje procesa dobijenog korišćenjem funkcija iz familije exec:

set follow-exec-mode new
  2. Postavimo tačku prekida na funkciju main:

break main
Ova naredba je važna . Ukoliko dodajemo tačke prekida po proizvoljnim linijama (koje ne uključuju liniju potpisa main funkcije) prilikom poziva execlp, nećemo biti u mogućnosti da postavljamo tačke prekida u novom procesu. Kako su tačke prekida jedan od ključnih pojmova kada je u pitanju debagovanje, njihovo nepostojanje nema smisla za postupak debagovanja.

  3. Pokrećemo program ugrađenom opcijom ‘Run’. Vidimo da se debagovanje zaustavilo na potpisu funkcije main, što je i očekivano zbog postavljanja tačke prekida.

  4. Komandom ‘Continue’ prikazuje se izvorni kod programa koji pokrećemo sistemskim pozivom exec i debagovanje se zaustavlja na potpisu main-a.

  5. Sada, novi program debagujemo liniju po liniju. Unesimo prvo ime i vidimo šta se dešava sa promenljivom ime.

pexec4

Kao prvo ime unosimo ‘Ana’. Vidimo da je promenljiva učitana kako treba. Nakon imena, dodaje se terminalna nula, a nakon toga idu neki karakteri koji nisu bitni (koristimo statički alocirane nizove karaktera pa se preostala mesta popunjavaju karakterima koji nam ništa ne znače).

  6. Ako nastavimo sa debagovanjem liniju po liniju i unošenjem imena primetićemo da se učitavanje imena korektno izvršava. Dakle, naš problem je očigledno poređenje.

Kako bismo prevazišli problem, umesto operatora ‘!=’ koristimo C ugrađenu funkciju iz zaglavlja string.h koja služi za poređenje stringova.

  7. Ponovnim prevođenjem i pokretanjem programa vidimo da sve funkcioniše kako treba i da smo dobili očekivani ispis.

pexec5

GDB u višenitnom okruženju

Uvod

Osnovne jedinice za izvršavanje u okviru procesa nazivaju se niti (eng. threads). Niti su delovi jednog procesa i izvršavaju se koristeći resurse datog procesa. Pored resursa procesa kojem pripadaju, niti poseduju i sopstvene resurse i to: registre, brojač programa i stek. Često se nazivaju i lakim procesima. Takođe, kao i procesi svaka nit ima identifikator ID (TID - Thread ID).

Na nekim operativnim sistemima, poput GNU/Linux-a i Solaris-a, jedan program može imati više niti u izvršavanju.

GDB pruža podršku za debagovanje višenitnih pograma. Pogodnosti koje pruža su sledeće:

  ● automatsko obaveštenje prilikom pojavljivanja nove niti

  ● ‘thread thread_id’ komanda za prebacivanje sa jedne niti na drugu

  ● ‘info threads’ kao što je već napomenuto, prikazuje identifikatore trenutno poznatih niti

  ● ‘thread apply [thread-id-list | all] args’ komanda koja primenjuje args nad prosleđenom listom niti

  ● postavljanje tačaka prekida u okviru pojedinačnih niti

  ● ‘set print thread-events’ kontroliše ispisivanje poruka pri pokretanju i zavšetku izvršavanja niti

  ● ‘set libthread-db-search-path path’ dopušta korisniku da izabere koju libthread_db želi da koristi ukoliko podrazumevana biblioteka nije kompatibilna sa programom

Pogodnosti koje GDB pruža kada je u pitanju višenitno debagovanje omogućavaju posmatranje svih niti u toku izvršavanja programa - ali kada GDB preuzme kontrolu, moguće je obavljati debagovanje samo jedne niti. Ova nit naziva se trenutna nit. Komande debagovanja prikazuju informacije programa iz ugla trenutne niti.

Kada god GDB prepozna novu nit u programu, prikazuje sistemski identifikator niti sa porukom u obliku ‘[New systag]’, gde je systag identifikator niti koji se razlikuje od sistema do stistema. Npr. na GNU/Linux-u kada GDB prepozna novu nit, može se videti poruka:

libthread_db je biblioteka koja pruža podršku za praćenje i rad sa nitima, uglavnom se koristi u višenitnim programima.
[New Thread 0x41a01234 (LWP 25582)]

Skraćenica LWP - Light-Weight Process.
Za svrhe debagovanja, GDB pridružuje sopstvene brojeve nitima - jedan ceo broj ID kao što je već napomenuto. ID je jedinstven za niti u istom inferior-u, ali može biti isti za niti koje se nalaze u različitim inferior-ima.

GDB stanju programa koji se izvršava pridružuje objekat koji se naziva inferior.
Kada god se debagovanje programa zaustavi iz nekog razloga, zaustavlja se izvršavanje svih niti, ne samo tekuće. Ovakvo ponašanje omogućava ispitivanje celokupnog programa, uključujući i prelaz sa jedne niti na drugu, bez brige da će doći do problema u programu.

Nekada će se desiti da se program zaustavi u jednoj niti a nakon naredbe continue ili jednostavno step u drugoj niti. Ovo se dešava kada god neka druga nit naleti na tačku prekida, signal ili izuzetak pre nego što je prethodna nit završila svoje izvršavanje.

Komandom ‘info threads’ GDB prikazuje sve niti koje su trenutno pokrenute u programu. Pored toga, za svaku nit se prikazuju:

  1. broj niti
  2. sistemski identifikator niti (systag)
  3. rezime stek okvira niti
Karakter ‘*’ na levoj strani označava nit koja se trenutno izvršava.

primer4-1

Komandom ‘thread thread_id’ postavljamo nit sa prosleđenim thread_id-jem kao trenutnu. Nakon komande, GDB prikazuje identifikator postavljene niti kao i rezime stek okvira niti:

primer4-2

Komanda ‘thread apply [thread-id-list | all ]’ omogućava primenu navedene komande na jednu ili više niti. Moguće je proslediti listu ID-jeva niti ili iskoristiti argument all koji će navedenu komandu primeniti nad svakom niti u programu. Takođe, možemo birati redosled kojim želimo da se komanda primenjuje nad nitima, uzlazno ili silazno. Npr. ‘thread apply all command’ primenjuje komandu na nitima od poslednje do prve, dok ‘thread apply all -ascending command’ primenjuje u rastućem poretku.

Argumenti args koji se navode na samom kraju određuju izlaz i način obrade greške koja je nastala primenom prosleđene komande nad određenom niti. Argumenti moraju početi karakterom ‘-’ nakon koji sledi jedno od slova ‘q’, ‘c’ ili ‘s’.

Podrazumevano, GDB prikazuje neke informacije vezane za konkretnu nit pre izlaza iz prosleđene komande i pre greške ukoliko je prouzrokovana navedenom komandom. Za podešavanje načina prikaza ovih informacija mogu se koristiti sledeći argumenti:

−c argument predstavlja skraćen zapis komande ‘continue’, koja prikazuje sve greške izazvane prosleđenom naredbom, a nakon toka nastavlja sa izvršavanjem naredbe nad preostalim prosleđenim nitima
−s argument predstavlja skraćen zapis komande ‘silent’, koja ukoliko postoji neka greška, jednostavno je ignoriše. Dakle, izvršavanje se nastavlja, ali se informacije vezane za izlaz i greške ne ispisuju
−q argument predstavlja skraćen zapis komande ‘quiet’ koja onemogućava ispis informacija vezanih za niti

Napomena: Argumenti -c i -s se ne mogu navoditi istovremeno.

Postoje i neke skraćene verzije komande za primenjivanje nad nitima i to su:

  ● taas [option] command

Skraćenica za thread apply all -s [option] command.

  ● tfaas [option] command

Skaćenica za thread apply all -s -- frame apply all -s [option] command. Primenjuje komandu na stekove svih niti, zanemarujući greške i prazne izlaze. Primetimo da je argument -s naveden dva puta: prvo pojavljivanje ukazuje na to da se prikazuju informacije samo za one niti čiji stekovi imaju neki ispis, dok se drugo pojavljivanje prikazuju informacije samo za one niti nad kojim aje upešno primenjena prosleđena komanda.

Može se koristiti u slučajevima da želimo da ispišemo vrednost lokalne promenljive ili argumente funkcije a da nam pritom ne znamo nit u kojoj se promenljiva odnosno argumenti nalaze.

Komanda ‘thread name [name]’ dodeljuje ime tekućoj niti. Ukoliko nema prosleđenih argumenata, uklanja se ime trenutne niti. Naziv niti se može videti komandom ‘info threads’.

Na nekim sistemima, poput GNU/Linux-a, GDB je u mogućnosti da prikaže naziv niti koji određuje sam operativni sistem. Na ovakvim sistemima, naziv dobijen komandom ‘thread name’ će imati prednost u odnosu na sistemski naziv niti. Uklanjanje naziva omogućava GDB-u da ponovo prikazuje naziv određen sistemom.

Komanda ‘thread find [regexp]’ pronalazi niti čiji ID ili sistemski identifikator odgovara prosleđenom regularnom izrazu.

Primer

Na sledećem primeru ilustrujmo debagovanje u višenitnom okruženju. Nadalje pretpostavljamo da je korisnik upoznat sa bibliotekom pthread.h koja pruža podršku za rad sa nitima kada je u pitanju programski jezik C.

pr4-1
pr4-2
pr4-3
pr4-4

Neka je naziv programa niti.c. Program izračunava skalarni proizvod dva vektora. Broj niti se unosi sa standardnog ulaza. Posao na niti se deli tako što svaka nit dobije deo niza koji treba da obradi. Neka je n dimenzija vektora a m broj niti, pri čemu možemo pretpostaviti da je n > m (u suprotnom imamo besposlene niti a to ne želimo) i m deli n (radi uniformnije podele poslova po nitima). Prva nit obrađuje prvih d = n / m elemenata i tako redom za svaku nit.

Prevođenjem i pokretanjem programa, i unosom jednostavnih vektora x = (1, 0, 1, 0, 1, 0) i y = (2, 0, 2, 0, 2, 0) dobijamo netačan rezultat.

pr4-5

Gde je problem? Kako kod ima preko 100 linija, najbolje bi bilo iskoristiti mehanizam debagovanja za detekciju greške.
Debagovanje prikazujemo u Emacs okruženju.

  1. Postavljamo tačku prekida na funkciju main kao i na neku liniju u okviru funkcije koju izvršavaju niti (funkcija nit), kako bismo mogli debagovanje preusmeriti na kreirane niti, ukoliko to bude potrebno. Da bismo mogli debagovati kompletan deo koji izvršavaju novokreirane niti, tačku prekida postavljamo na liniji 91, odnosno negde na samom početku funkcije nit.

  2. Debagovanjem liniju po liniju i unošenjem odgovarajućih vrednosti vidimo da su vektori x i y uspešno učitani. Ispis elemenata nizova videti u delu vezanom za GDB komande (naredba p x ispisuje adresu niza).

pr4-6


  3. Ako nastavimo sa debagovanjem liniju po liniju očigledno da u main-u nema problema. Nakon kreiranja niti, debagovanje se preusmerava na postavljenu tačku prekida u okviru funkcije koju izvršavaju niti.

  4. Kako sve niti izvršavaju istu funkciju, svejedno je koju ćemo odabrati za debagovanje. Neka je to poslednja odnosno 4. nit. Zašto četvrta? Prva nit obuhvata deo vezan za funkciju main. Iako nas GDB obaveštava ukoliko se pokrene nova nit, dobra je praksa iskoristiti komandu za informacije o nitima, da bismo znali koliko niti je trenutno aktivno i koja nit se trenutno izvršava (zahvaljujući prefiksnom karakteru ID-ja *).

pr4-7

Da bismo preusmerili debagovanje na 4. nit neophodno je pozvati komandu ‘thread 4’:

pr4-8

  5. Sada u prozoru za lokalne promenljive pratimo vrednosti promenljivih u okviru funkcije nit. For petlju debagujemo postupno. Šta možemo primetiti? Petlja se izvršava 3 puta, što nije u skladu sa onim d = m / n iz postavke zadatka koje iznosi 2. Takođe, u toj 3. iteraciji suma se u potpunosti narušava:

pr4-9

Dakle, očigledno je problem u jednakosti kod uslova izlaska iz petlje tj. petlja
se izvršava jedan put više od očekivanog i na taj način pristupamo lokacijama koje prethodno nismo inicijalizivali.
Ispravljanjem greške, ponovnim prevođenjem i pokretanjem programa vidimo da sve funkcioniše
kako treba i da smo dobili očekivani ispis.

pr4-10