Salut! numele meu este Ionut si sunt aici din partea ItAssistant pentru a va prezenta cateva notiuni de baza despre compilatorul GCC, fazele compilarii, utilitarul MAKE, dar si debugger-ul GDB.
GCC
GCC este suita de compilatoare implicită pe majoritatea distributiilor Linux. GCC este unul din primele pachete software dezvoltate de organizatia Free Software Fundation in cadrul proiectului GNU (Gnu’s Not Unix). Proiectul GNU a fost initiat de Richard Stallman ca un protest impotriva software-ului proprietar la inceputul anilor ’80.
La inceput, GCC se traducea prin GNU C Compiler, pentru că initial scopul proiectului GCC era dezvoltarea unui compilator C portabil pe platforme UNIX. Ulterior, proiectul a evoluat astazi fiind un compilator multi-frontend, multi-backend cu suport pentru limbajele C, C++, Objective-C, Fortran, Java, Ada. Drept urmare, acronimul GCC inseamna, astazi: GNU Compiler Collection.
La numarul impresionant de limbaje de mai sus se adauga si numarul mare de platforme suportate atat din punctul de vedere al arhitecturii hardware (i386, alpha, vax, m68k, sparc, HPPA, arm, MIPS, PowerPC, etc.), cat si al sistemelor de operare (GNU/Linux, DOS, Windows 9x/NT/2000, *BSD, Solaris, Tru64, VMS, etc.). La ora actuala, GCC-ul este cel mai portat compilator.
Care este de fapt scopul compilarii? Obtinerea unui executabil dintr-un fisier sursa.
Eu am in cele ce urmeaza o sursa C care afiseaza pe ecran mesajul ‘Hello world!’; Aceasta sursa va fi compilata cu ajutorul compilatorului GCC; Sursa este data ca parametru programului GCC si in mod default executabilul se va numi a.out.
$ cat hello.c #include <stdio.h> int main (){ printf ("Hello world!\n"); return 0; } $ gcc hello.c $ ./a.out Hello world!
Sau putem sa specificam numele executabilului pe care il dorim cu ajutorul parametrului -o
$ gcc hello.c -o hello $ ./hello Hello world!
Insa GCC trece prin mai multe faze de prelucrare a fisierului sursa pentru a obtine executabilul:
1. Preprocesarea
Preprocesarea presupune inlocuirea directivelor de preprocesare din fisierul sursă C. Directivele de preprocesare incep cu # ; printre cele mai folosite fiind:
#include – pentru includerea fisierelor header într-un alt fisier.
#define și #undef – pentru definirea, respectiv anularea definirii de macrouri.
#if, #ifdef, #ifndef, #else, #elif, #endif, pentru compilarea conditionata.
Pentru a opri compilarea la prima etava vom folosi parametru -E la gcc:
$ gcc -E hello.c -o hello.i
2. Compilarea
Compilarea este faza in care din fisierul preprocesat se obtine un fisier in limbaj de asamblare. Asa cum am mai precizat obtinerea fisierului in limbaj de asamblare din fisierul sursă C se face folosind optiunea -S.
$ gcc -S hello.i -o hello.s $ cat hello.s .file "hello.c" .section .rodata .LC0: .string "Hello world!" .text .globl main .type main, @function main: pushl %ebp movl %esp, %ebp andl $-16, %esp subl $32, %esp movl $.LC0, (%esp) call puts movl $0, %eax leave ret .size main, .-main .ident "GCC: (Ubuntu 4.4.3-4ubuntu5) 4.4.3" .section .note.GNU-stack,"",@progbits
3. Asamblarea
Asamblarea este faza in care codul scris în limbaj de asamblare este tradus in cod masină reprezentand codificarea binară a instructiunilor programului initial. Fisierul obtinut poarta numele de fisier cod obiect, se obtine folosind optiunea -c a compilatorului si are extensia .o
$ gcc -c hello.s -o hello.o
4. Editarea de legaturi
Pentru obtinerea unui fisier executabil este necesara rezolvarea diverselor simboluri prezente in fisierul obiect. Această operatie poarta denumirea de editare de legaturi, link-editare, linking sau legare.
$ gcc hello.o -o hello $ ./hello Hello world!
Alte optiuni ale GCC
Activarea avertismentelor
In mod implicit, o rulare a gcc ofera putine avertismente utilizatorului. Pentru a activa afisarea de avertismente se folosesc optiunile de tip -W cu sintaxa -Woptiune-avertisment. optiune-avertisment poate lua mai multe valori posibile printre care return-type, switch, unused-variable, uninitialized, implicit, all. Folosirea optiunii -Wall inseamnă afisarea tuturor avertismentelor care pot cauza inconsistente la rulare.
Un exemplu trivial ar fi atunci cand avem o variabila care nu este utilizata:
$ cat exemplu.c #include <stdio.h> int main (){ int a; return 0; } $ gcc hello.c -o hello -Wall hello.c: In function ‘main’: hello.c:6: warning: unused variable ‘a’
Consideram ca fiind indispensabila folosirea optiunii -Wall pentru a putea detecta inca din momentul compilarii posibilele erori. O cauză importanta a aparitiilor acestor erori o constituie sintaxa foarte permisiva a limbajului C.
Optiuni utile:
-Onivel-optimizari, instuieste compilatorul ce nivel de optimizare trebuie aplicat:
-O0, va determina compilatorul sa nu optimizeze codul generat;
-O3, va determina compilatorul sa optimizeze la maxim codul generat;
-O2, este pragul de unde compilatorul va incepe sa insereze direct in cod functiile inline in loc sa le apeleze;
-Os, va pune accentul pe optimizarile care duc la reducerea dimensiunii codului generat, si nu a vitezei la executie.
-g, daca se foloseste aceasta optiune compilatorul va genera in fisierele de iesire informatii care pot fi apoi folosite de un debugger (informasii despre fisierele sursa si o mapare intre codul masina si liniile de cod ale fisierelor sursa).
Va recomanda sa consultati paginile de manual pentru a afla lista cu toate optiunile posibile ale GCC.
man gcc info gcc
GNU Make
Make este un utilitar care permite automatizarea si eficientizarea sarcinilor. In mod particular este folosit pentru automatizarea compilarii programelor. Dupa cum s-a precizat, pentru obtinerea unui executabil provenind din mai multe surse este ineficienta compilarea de fiecara data a fiecarui fisier si apoi link-editarea. Se compilează fiecare fisier separat, iar la o modificare se va recompila doar fisierul modificat.
Exemplu simplu de Makefile
Utilitarul Make foloseste un fisier de configurare denumit Makefile. Un astfel de fisier contine reguli si comenzi de automatizare. In continuare este prezentat un exemplu foarte simplu de Makefile cu ajutorul caruia se va specifica sintaxa Make
$ cat Makefile all: hello hello: hello.c gcc hello.c -o hello clean: rm hello $ make gcc hello.c -o hello $ ./hello Hello world! $ make clean rm hello
Se observa ca nu se transmite niciun argument comenzii make pentru a preciza fisierul Makefile care va trebui analizat. In mod implicit, GNU Make cauta, in ordine, fisierele: GNUmakefile, Makefile, makefile si le analizeaza. Pentru a preciza ce fisier Makefile trebuie interpretat, se foloseste optiunea -f
$ mv Makefile Makefile1 $ make make: *** No targets specified and no makefile found. Stop. $ make -f Makefile1 gcc hello.c -o hello $ ./hello Hello world!
In prima faza se incearca rularea simpla a comenzii make. Intrucât make nu gaseşte niciunul din fisierele GNUmakefile, Makefile sau makefile, returneaza eroare. Prin precizarea optiunii -f Makefile1 se specifica fisierul Makefile de analizat. De asemenea, se poate preciza si regula care sa fie executata.
Un alt exemplu de Makefile este:
$ cat Makefile all: hello hello: hello.o gcc hello.o -o hello hello.o: hello.s gcc -c hello.s -o hello.o hello.s: hello.i gcc -S hello.i -o hello.s hello.i: hello.c gcc -E hello.c -o hello.i clean: rm hello hello.i hello.s hello.o 2$ make gcc -E hello.c -o hello.i gcc -S hello.i -o hello.s gcc -c hello.s -o hello.o gcc hello.o -o hello $ ./hello Hello world!
In continuare este prezentata sintaxa unei reguli dintr-un fisier Makefile:
target: prerequisites <tab> command
– target este, de obicei, fisierul care se va obtine prin rularea comenzii command. Dupa cum s-a observat si din exemplul anterior, poate sa fie o tintă virtuala care nu are asociat un fisier.
- prerequisites reprezinta dependintele necesare pentru a urmari regula; de obicei sunt fisiere necesare pentru obtinerea tintei.
- <tab> reprezinta caracterul tab si trebuie neaparat folosit inaintea precizarii comenzii.
- command reprezinta o lista de comenzi (niciuna, una, oricate) rulate in momentul in care se trece la obtinerea tintei.
Un exemplu indicat pentru un fisier Makefile este:
all: hello
hello: hello.o
gcc hello.o -o hello
hello.o: hello.c
gcc -Wall -c hello.c
clean:
rm -f *.o *~ helloSe observa prezenta regulii all care va fi executata implicit.
– all are ca dependinta hello si nu executa nicio comanda;
– hello are ca dependinta hello.o si realizeaza link-editarea fisierului hello.o;
– hello.o are ca dependinta hello.c si realizeaza compilarea si asamblarea fisierului hello.c.
Pentru obtinerea executabilului se foloseste comanda:
$ make gcc -Wall -c hello.c gcc hello.o -o hello
Functionarea unui fisier Makefile
Pentru explicarea functionarii unui fisier Makefile, vom folosi exemplul de mai sus. In momentul rularii comenzii make se poate preciza target-ul care se doreste a fi obtinut. Daca acesta nu este precizat, este considerat implicit primul target intalnit in fisierul Makefile folosit; de obicei, acesta se va numi all.
Pentru obtinerea unui target trebuie satisfacute dependintele (prerequisites) acestuia. Astfel:
- pentru obtinerea target-ului all trebuie obtinut target-ul hello, care este un nume de executabil
- pentru obtinerea target-ului hello trebuie obtinut target-ul hello.o
- pentru obtinerea target-ului hello.o trebuie obtinut hello.c; acest fisier există deja, si cum acesta nu apare la rândul lui ca target in Makefile, nu mai trebuie obtinut drept urmare se ruleaza comanda asociata obtinerii hello.o; aceasta este gcc -Wall -c hello.c
- rularea comenzii duce la obtinerea target-ului hello.o, care este folosit ca dependinta pentru hello; se rulează comanda gcc hello.o -o hello pentru obtinerea hello
- hello este folosit ca dependina pentru all; acesta nu are asociată nicio comanda deci este automat obtinut.
De remarcat este faptul ca un target nu trebuie să aiba neaparat numele fisierului care se obtine. Se recomanda, insa, acest lucru pentru întelegerea mai usoara a fisierului Makefile, si pentru a beneficia de faptul ca make utilizeaza timpul de modificare al fisierelor pentru a decide cand nu trebuie sa faca nimic.
Acest format al fisierului Makefile are avantajul eficientizării procesului de compilare. Astfel, dupa ce s-a obtinut executabilul hello conform fisierului Makefile anterior, o noua rulare a make nu va genera nimic:
$ make make: Nothing to be done for `all'.
Mesajul “Nothing to be done for ‘all’” inseamna ca tinta all are toate dependintele satisfacute. Daca, insa, folosim comanda touch pe fisierul obiect, se va considera ca a fost modificat si vor trebui refacute target-urile care depindeau de el.
Folosirea variabilelor
Un fisier Makefile permite folosirea de variabile. Astfel, un exemplu uzual de fisier Makefile este:
CC = gcc CFLAGS = -Wall -g all: hello hello: hello.o $(CC) $^ -o $@ hello.o: hello.c $(CC) $(CFLAGS) -c $< .PHONY: clean clean: rm -f *.o *~ hello
In exemplul de mai sus au fost definite variabilele CC si CFLAGS. Variabila CC reprezinta compilatorul folosit, iar variabila CFLAGS reprezinta optiunile (flag-urile) de compilare utilizate; in cazul de fata sunt afisarea avertismentelor si compilarea cu suport de depanare.
Referirea unei variabile se realizeaza prin intermediul construcţiei $(VAR_NAME). Astfel, $(CC) se inlocuieste cu gcc, iar $(CFLAGS) se inlocuieste cu -Wall -g.
Variabile predefinite folositoare sunt:
$@ se expandează la numele target-ului. $^ se expandează la lista de cerinţe. $< se expandează la prima cerinţă.
Folosirea regulilor implicite
De foarte multe ori nu este nevoie sa se precizeze comanda care trebuie rulata; aceasta poate fi detectată implicit.
Astfel, in cazul in care se precizeaza regula: main.o: main.c se foloseste implicit comanda $(CC) $(CFLAGS) -c -o $@ $<
Astfel, Makefile-ul de mai sus poate fi simplificat, folosind reguli implicite:
CC = gcc CFLAGS = -Wall -g all: hello hello: hello.o hello.o: hello.c .PHONY: clean clean: rm -f *.o *~ hello
Se observa ca se folosesc reguli implicite. Makefile-ul poate fi simplificat si mai mult, ca în exemplul de mai jos:
CC = gcc CFLAGS = -Wall -g all: hello hello: hello.o .PHONY: clean clean: rm -f *.o *~ hello
In exemplul de mai sus s-a eliminat regula hello.o: hello.c. Make “vede” ca nu exista fisierul hello.o si caută fisierul C din care poate sa-l obtina. Pentru aceasta creeaza o regula implicita si compileaza fisierul hello.c.
Biblioteci
O biblioteca este o colectie de functii precompilate. In momentul in care un program are nevoie de o functie, linker-ul va apela respectiva functie din biblioteca. Numele fisierului reprezentand biblioteca trebuie sa aibă prefixul lib:
$ ls -l /usr/lib/libm.* -rw-r--r-- 1 root root 496218 2010-01-03 15:19 /usr/lib/libm.a lrwxrwxrwx 1 root root 14 2010-01-14 12:17 /usr/lib/libm.so -> /lib/libm.so.6
Biblioteca matematica este denumită libm.a sau libm.so. In Linux bibliotecile sunt de doua tipuri:
– statice, au de obicei, extensia .a
– partajate, au extensia .so
Legarea se face folosind optiunea -l transmisa comenzii GCC. Astfel, daca se doreste folosirea unor functii din math.h, trebuie legata biblioteca matematica:
$ cat sin.c #include <stdio.h> #include <math.h> #define PI 3.14159265 int main () { double unghi, rezultat; unghi = 30.0; rezultat = sin (unghi * PI / 180); printf ("Sin de %lf grade este: %lf\n", unghi, rezultat); return 0; } $ gcc sin.c -o sin /tmp/ccFTpk4s.o: In function `main': sin.c:(.text+0x2b): undefined reference to `sin' collect2: ld returned 1 exit status $ gcc sin.c -o sin -lm $ ./sin Sin de 30.000000 grade este: 0.500000
Se observa ca, in prima faza, nu s-a rezolvat simbolul sin. Dupa legarea bibliotecii matematice, programul s-a compilat si a rulat fara probleme.
Putem folosi proframul nm care listeaza simbolurile dintr-un fisier obiect sau executabil.
$ gcc -c sin.c -o sin.o $ nm sin.o 00000000 T main U printf U sin
Simbolurile care au T in fata sunt definte iar cele care au U nu sunt definte. Se observa ca functia sin nu este definita.
Pentru a vedea bibliotecile folosite de executabil se poate folosi comanda ldd:
itassistant@ubuntu:~/Desktop/sin$ ldd sin linux-gate.so.1 => (0x00e3a000) libm.so.6 => /lib/tls/i686/cmov/libm.so.6 (0x00110000) libc.so.6 => /lib/tls/i686/cmov/libc.so.6 (0x00b1e000) /lib/ld-linux.so.2 (0x005ef000)
Observarm ca s-au folosit urmatoarele biblioteci: libc.so.6 pentru functia printf care nu era definita si libm.so.6 pentru functia sin.
Pentru a lega in mod static biblioteca matematica putem folosi parametru -static la compilare.
$ gcc sin.c -o sindinamic -lm $ gcc sin.c -o sinstatic -lm -static $ ./sindinamic Sin de 30.000000 grade este: 0.500000 $ ./sinstatic Sin de 30.000000 grade este: 0.500000 $ ls -lah -rwxr-xr-x 1 itassistant itassistant 7.1K 2010-12-19 22:58 sindinamic -rwxr-xr-x 1 itassistant itassistant 565K 2010-12-19 22:59 sinstatic
Se observa ca executabilul sinstatic este cu mult mai mare decat executabilul sindinamic.
Utilitarul objdump este folosit pentru a afisa informatii despre fisierele obiect; de exemplu -t afiseaza tabela de simboluri sau -d dezansambleaza fisierul.
$ objdump -t sin.o sin.o: file format elf32-i386 SYMBOL TABLE: 00000000 l df *ABS* 00000000 sin.c 00000000 l d .text 00000000 .text 00000000 l d .data 00000000 .data 00000000 l d .bss 00000000 .bss 00000000 l d .rodata 00000000 .rodata 00000000 l d .note.GNU-stack 00000000 .note.GNU-stack 00000000 l d .comment 00000000 .comment 00000000 g F .text 00000057 main 00000000 *UND* 00000000 sin 00000000 *UND* 00000000 printf $ objdump -d sin.o sin.o: file format elf32-i386 Disassembly of section .text: 00000000 <main>: 0: 55 push %ebp 1: 89 e5 mov %esp,%ebp 3: 83 e4 f0 and $0xfffffff0,%esp 6: 83 ec 30 sub $0x30,%esp 9: dd 05 20 00 00 00 fldl 0x20 f: dd 5c 24 28 fstpl 0x28(%esp) 13: dd 44 24 28 fldl 0x28(%esp) 17: dd 05 28 00 00 00 fldl 0x28 1d: de c9 fmulp %st,%st(1) 1f: dd 05 30 00 00 00 fldl 0x30 25: de f9 fdivrp %st,%st(1) 27: dd 1c 24 fstpl (%esp) 2a: e8 fc ff ff ff call 2b <main+0x2b> 2f: dd 5c 24 20 fstpl 0x20(%esp) 33: b8 00 00 00 00 mov $0x0,%eax 38: dd 44 24 20 fldl 0x20(%esp) 3c: dd 5c 24 0c fstpl 0xc(%esp) 40: dd 44 24 28 fldl 0x28(%esp) 44: dd 5c 24 04 fstpl 0x4(%esp) 48: 89 04 24 mov %eax,(%esp) 4b: e8 fc ff ff ff call 4c <main+0x4c> 50: b8 00 00 00 00 mov $0x0,%eax 55: c9 leave 56: c3 ret
O lista completa cu toate optiunile utilitarului este:
Usage: objdump <option(s)> <file(s)> Display information from object <file(s)>. At least one of the following switches must be given: -a, --archive-headers Display archive header information -f, --file-headers Display the contents of the overall file header -p, --private-headers Display object format specific file header contents -h, --[section-]headers Display the contents of the section headers -x, --all-headers Display the contents of all headers -d, --disassemble Display assembler contents of executable sections -D, --disassemble-all Display assembler contents of all sections -S, --source Intermix source code with disassembly -s, --full-contents Display the full contents of all sections requested -g, --debugging Display debug information in object file -e, --debugging-tags Display debug information using ctags style -G, --stabs Display (in raw form) any STABS info in the file -W[lLiaprmfFsoR] or --dwarf[=rawline,=decodedline,=info,=abbrev,=pubnames,=aranges,=macro,=frames,=str,=loc,=Ranges] Display DWARF info in the file -t, --syms Display the contents of the symbol table(s) -T, --dynamic-syms Display the contents of the dynamic symbol table -r, --reloc Display the relocation entries in the file -R, --dynamic-reloc Display the dynamic relocation entries in the file @<file> Read options from <file> -v, --version Display this program's version number -i, --info List object formats and architectures supported -H, --help Display this information
Crearea unei biblioteci
Pentru crearea de biblioteci vom folosi 2 fisiere: f1.c si f2.c. Vom include modulele obiect rezultate din fisierele sursa f1.c şi f2.c intr-o biblioteca pe care o vom folosi ulterior pentru obtinerea executabilului final.
Primul pas consta in obtinerea modulelor obiect asociate:
$ gcc -Wall -c f1.c $ gcc -Wall -c f2.c
Crearea unei biblioteci statice
O biblioteca statica este o arhiva ce contine fisiere obiect creata cu ajutorul utilitarului ar.
$ ar rc libintro.a f1.o f2.o $ gcc -Wall main.c -o main -lintro /usr/bin/ld: cannot find -lintro collect2: ld returned 1 exit status
Linker-ul returneaza eroare precizand ca nu gaseste biblioteca libintro. Aceasta deoarece linker-ul nu a fost configurat sa caute si în directorul curent.
Pentru aceasta se foloseste optiunea -L, urmata de directorul in care trebuie cautata biblioteca (in cazul nostru este vorba de directorul curent):
$ gcc -Wall main.c -o main -lintro -L. $ ./main Executabilul a rulat cu succes! :)
Crearea unei biblioteci partajate
Spre deosebire de o biblioteca statica despre care am vazut ca nu este nimic altceva decâa o arhiva de fisiere obiect, o biblioteca partajata este ea insasi un fisier obiect.
Crearea unei biblioteci partajate se realizeaza prin intermediul linker-ului. Optiunea -shared indica compilatorului sa creeze un obiect partajat si nu un fisier executabil. Este, de asemenea, indicata folosirea optiunii -fPIC la crearea fisierelor obiect.
$ gcc -fPIC -c f1.c $ gcc -fPIC -c f2.c $ gcc -shared f1.o f2.o -o libintro_shared.so $ gcc -Wall main.c -o main -lintro_shared -L. $ ./main ./main: error while loading shared libraries: libintro_shared.so: cannot open shared object file: No such file or directory
La rularea executabilului se poate observa ca nu se poate incarca biblioteca partajata. Cauza este deosebirea dintre bibliotecile statice si bibliotecile partajate. In cazul bibliotecilor statice codul functiei de bibliotecă este copiat in codul executabil la link-editare. De partea cealalta, in cazul bibliotecilor partajate, codul este incarcat în memorie in momentul rularii.
Astfel, in momentul rularii unui program, loader-ul (programul responsabil cu incarcarea programului in memorie), trebuie sa stie unde sa caute biblioteca partajata pentru a o incarca in memorie in cazul in care aceasta nu a fost încarcata deja.
Loader-ul foloseste cateva cai predefinite (/lib, /usr/lib, etc) si de asemenea locatii definite in variabila de mediu LD_LIBRARY_PATH:
$ export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:. $ ./main Executabilul a rulat cu succes! :)
In exemplul de mai sus variabilei de mediu LD_LIBRARY_PATH i-a fost adaugata calea catre directorul curent rezultand in posibilitatea rularii programului.
LD_LIBRARY_PATH va ramane modificata cat timp va rula consola curentă. Pentru a face o modificare a unei variabile de mediu doar pentru o instanta a unui program se face atribuirea noii valori inaintea comenzii de executie:
$ LD_LIBRARY_PATH=$LD_LIBRARY_PATH:. ./main Executabilul a rulat cu succes! :) $ ./main ./main: error while loading shared libraries: libintro_shared.so: cannot open shared object file: No such file or directory
Depanarea programelor – GDB
Exista cateva unelte GNU care pot fi folosite atunci cand nu reusim sa facem un program sa ne asculte.
GDB, acronimul de la “Gnu DeBugger” este probabil cel mai util dintre ele, dar exista si altele, cum ar fi ElectricFence, gprof sau mtrace.
GDB va fi prezentat pe scurt in sectiunile ce urmeaza.
Rulare gdb
GDB poate fi folosit în doua moduri pentru a depana programul:
– rulandu-l folosind comanda gdb
– folosind fisierul core generat in urma unei erori grave (de obicei segmentation fault)
Cea de a doua modalitate este utila in cazul in care bug-ul nu a fost corectat inainte de lansarea programului.
In acest caz, daca utilizatorul intalneste o eroare grava, poate trimite programatorului fisierul core cu care acesta poate depana programul si corecta bug-ul.
Cea mai simpla forma de depanare cu ajutorul GDB este cea in care dorim sa determinam linia programului la care s-a produs eroarea.
Pentru exemplificare consideram urmatorul program:
#include <stdio.h> int f(int a, int b) { int c; c = a + b; return c; } int main() { char *bug = 0; *bug = f(1, 2); return 0; }
Dupa compilarea programului acesta poate fi depanat folosind GDB. Dupa incarcarea programului de depanat, GDB intra in mod interactiv. Utilizatorul poate folosi apoi comenzi pentru a depana programul:
$ gcc -Wall -g add.c $ gdb a.out [...] (gdb) run Starting program: a.out Program received signal SIGSEGV, Segmentation fault. 0x08048411 in main () at add.c:16 16 *bug=f(1, 2); (gdb)
Prima comanda folosita este run. Aceasta comanda va porni executia programului. Daca aceasta comanda primeste argumente de la utilizator, acestea vor fi transmise programului.
Inainte de a trece la prezentarea unor comenzi de baza din gdb, sa demonstram cum se poate depana un program cu ajutorul fisierului core:
# ulimit -c 4 # ./a.out Segmentation fault (core dumped) # gdb a.out core Core was generated by `./a.out'. Program terminated with signal 11, Segmentation fault. #0 0x08048411 in main () at add.c:16 16 *bug=f(1, 2); (gdb)
Comenzi de baza GDB
Cateva din comenzile de bază in gdb sunt:
– breakpoint, primeste ca argument un nume de functie (ex: main), un numar de linie si, eventual, un fisier (ex: break sursa.c:50) sau o adresa (ex: break *0x80483d3).
– next va continua executia programului pana ce se va ajunge la urmatoarea linie din codul sursa. Daca linia de executat contine un apel de functie, functia se va executa complet.
– step daca se doreste si inspectarea functilor.
Folosirea acestor comenzi este exemplificata mai jos:
$ gdb a.out (gdb) break main Breakpoint 1 at 0x80483f6: file add.c, line 14. (gdb) run Starting program: a.out Breakpoint 1, main () at add.c:14 14 char *bug=0; (gdb) next 16 *bug=f(1, 2); (gdb) next Program received signal SIGSEGV, Segmentation fault. 0x08048411 in main () at add.c:16 16 *bug=f(1, 2); (gdb) run The program being debugged has been started already. Start it from the beginning? (y or n) y Starting program: a.out Breakpoint 1, main () at add.c:14 14 char *bug=0; (gdb) next 16 *bug=f(1, 2); (gdb) step f (a=1, b=2) at add.c:8 8 c=a+b; (gdb) next 9 return c; (gdb) next 10 } (gdb) next Program received signal SIGSEGV, Segmentation fault. 0x08048411 in main () at add.c:16 16 *bug=f(1, 2); (gdb)
O alta comanda utila este list. Aceasta va lista fisierul sursa al programului depanat. Comanda primeste ca argument un numar de linie (eventual nume fisier), o functie sau o adresa de la care să listeze. Al doilea argument este optional si precizeaza cate linii vor fi afisate. In cazul in care comanda nu are niciun parametru, ea va lista de unde s-a oprit ultima afisare.
$ gdb a.out (gdb) list add.c:1 1 /* add.c */ 2 #include <stdio.h> 3 4 int f(int a, int b) 5 { 6 int c; 7 8 c=a+b; 9 return c; 10 } (gdb) break add.c:8 Breakpoint 1 at 0x80483d6: file add.c, line 8. (gdb) run Starting program: a.out Breakpoint 1, f (a=1, b=2) at add.c:8 8 c=a+b; (gdb) next 9 return c; (gdb) continue Continuing. Program received signal SIGSEGV, Segmentation fault. 0x08048411 in main () at add.c:16 16 *bug=f(1, 2);
Comanda continue se foloseste atunci cand se doreste continuarea executiei programului.
Ultima comanda de baza este print. Cu ajutorul acesteia se pot afisa valorile variabilelor din functia curenta sau a variabilelor globale; print poate primi ca argument si expresii complicate (dereferentieri de pointeri, referentieri ale variabilelor, expresii aritmetice, aproape orice expresie C valida). In plus, print poate afisa structuri de date precum struct si union.
$ gdb a.out (gdb) break f Breakpoint 1 at 0x80483d6: file add.c, line 8. (gdb) run Starting program: a.out Breakpoint 1, f (a=1, b=2) at add.c:8 8 c=a+b; (gdb) print a $1 = 1 (gdb) print b $2 = 2 (gdb) print c $3 = 1073792080 (gdb) next 9 return c; (gdb) print c $4 = 3 (gdb) finish Run till exit from #0 f (a=1, b=2) at add.c:9 0x08048409 in main () at add.c:16 16 *bug=f(1, 2); Value returned is $5 = 3 (gdb) print bug $6 = 0x0 (gdb) print (struct sigaction)bug $13 = {__sigaction_handler = { sa_handler = 0x8049590 <object.2>, sa_sigaction = 0x8049590 <object.2> }, sa_mask = { __val = { 3221223384, 1073992320, 1, 3221223428, 3221223436, 134513290, 134513760, 0, 3221223384, 1073992298, 0, 3221223436, 1075157952, 1073827112, 1, 134513360, 0, 134513393, 134513648, 1, 3221223428, 134513268, 134513760, 1073794080, 3221223420, 1073828556, 1, 3221223760, 0, 3221223804, 3221223846, 3221223866 } }, sa_flags = -1073743402, sa_restorer = 0xbffff9f2} (gdb)



