Utilizarea Sistemelor de Operare 11


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 *~ hello

Se 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)
Tags:

V-a placut acest tutorial? Aveti anumite sugestii pentru urmatoarele tutoriale video? Lasati un comentariu! Feedback-ul vostru este foarte important pentru noi.

Pentru intrebari mai elaborate, cu caracter general, va rugam folositi forumul si in cel mai scurt timp veti primi un raspuns. Astfel ii vom ajuta si pe ceilalti sa invete din eventualele probleme.