GDB w przykªadach 1 Wst¦p 2 Bªedy programów przestrzeni u

Transkrypt

GDB w przykªadach 1 Wst¦p 2 Bªedy programów przestrzeni u
GDB w przykªadach
Maciej Borz¦cki
April 2, 2009
1
Wst¦p
Poni»szy dokument opisuje przykªad zastosowania debuggera GDB1 do zlokalizowania bª¦du w kodzie.
W przykªadzie posªu»ono si¦ implementacj¡ programu z ¢wiczenia Wie»e
Hanoi. W celu zaprezentowania
mo»liwe realistycznego scenariusza, który mo»e pojawi¢ si¦ podczas zaj¦¢, u»yty kompliator gcc2 jest
uruchamiany z przeª¡cznikiem -fbounds-checking.
W tym przykªadzie zakªada si¦ znajomo±¢ takich poj¦¢ jak: stos, ramka stosu, sygnaª. Czytelnicy powinni
ponadto zapozna¢ si¦ ze stronami podr¦cznika (man) dla funkcji raise(3)3 , abort(3).
2
Bªedy programów przestrzeni u»ytkownika
Podczas zaj¦¢, najcz¦±ciej napotykanymi s¡ bª¦dy zwi¡zane z dost¦pem/modykacj¡ obszaru pami¦ci pod
niewªa±ciwym adresem. Przykªadowy program zawieraj¡cy taki bª¡d pokazany jest ponizej:
i n t main ( ) {
int ∗ x ;
∗x = 2 ;
}
Listing 1: Bª¦dny kod
Próba wykonania tego programu spowoduje pojawienie si¦ nast¦puj¡cego tekstu:
[ m a c i e k @ c o r s a i r : ~ ] $ . / a . out
Segmentation f a u l t
Listing 2: Wynik wykonania bª¦dnego programu
W przypadku ¢wicze« zwi¡zanych z operacjami na tablicach, zaleca si¦ korzystanie podczas kompilacji z
przeª¡cznika -fbounds-checking w celu uªatwienia wykrycia bª¦dów z niewªa±ciwym dost¦pem do tablic.
Przykªadowy wynik dziaªania programu z takim bª¦dem pokazany jest ponizej:
[ m a c i e k @ c o r s a i r : ~ / phd/ z a j e c i a /pp/ h a n o i / p r i m l i b ] $ . /HAN
Bounds Checking GCC v gcc − 4.0.2 − 3.2 Copyright (C) 1995 Richard W.M. Jones
Bounds Checking comes with ABSOLUTELY NO WARRANTY. For d e t a i l s s e e f i l e
`COPYING' t h a t s h o u l d have come with t h e s o u r c e t o t h i s program .
Bounds Checking i s f r e e s o f t w a r e , and you a r e welcome t o r e d i s t r i b u t e i t
under c e r t a i n c o n d i t i o n s . See t h e f i l e `COPYING' f o r d e t a i l s .
For more i n f o r m a t i o n , s e t GCC_BOUNDS_OPTS t o ` − help '
HAN. c : 1 5 1 : Bounds e r r o r : a r r a y r e f e r e n c e ( 6 ) o u t s i d e bounds o f t h e a r r a y .
1 GNU debugger: http://www.gnu.org/software/gdb/
2 GCC, the GNU Compiler Collection: http://gcc.gnu.org/
3 notacja: fkc(nr) oznacza funkcj¦ o nazwie fkc, dost¦pn¡
raise
nale»y wywoªac:
man 3 raise
1
w sekcji
nr
manuala; przykªadowo aby uzyska¢ opis funkcji
HAN. c : 1 5 1 :
HAN. c : 1 5 1 :
HAN. c : 1 5 1 :
HAN. c : 1 5 1 :
HAN. c : 1 5 1 :
HAN. c : 1 5 1 :
HAN. c : 1 5 1 :
HAN. c : 1 5 1 :
Aborted
P o i n t e r v a l u e : 0 x806b734
Object ` tab ' :
Address i n memory :
0 x806b72e . . 0 x806b733
Size :
6 bytes
Element s i z e :
1 bytes
Number o f e l e m e n t s :
6
Created a t :
HAN. c , l i n e 13
Storage c l a s s :
static
Listing 3: Wynik wykonania bª¦dnego programu
Informacje zgªoszone podczas wykrycia bªedu okreslaj¡ do jakiej tablicy nastepowaª dost¦p oraz gdzie byªa
ona utworzona. Natomiast nie jest dost¦pna informacja dotycz¡ca kodu który spowodowaª wyst¡pienie
problemu.
O ile w pierwszym programie bª¡d jest prosty do zlokalizowania, w skomplikowanym kodzie, zadanie to
moze nie byc tak ªatwe. U»ycie gdb pozwala w znacznym stopniu skrócic czas po±wi¦cony na znalezienie
bªedu.
3
3.1
U»ycie gdb
Prosty przykªad
W cely skorzystania z debuggera nale»y wyda¢ nast¦puj¡ce polecenie: gdb <nazwa pliku binarnego
do debugowania>. W wyniku wykonania tego polecenia pojawi si¦ nast¦puj¡cy tekst:
[ m a c i e k @ c o r s a i r : ~ ] $ gdb . / a . out
GNU gdb 6.8 − d e b i a n
Copyright (C) 2008 Free S o f t w a r e Foundation , I n c .
L i c e n s e GPLv3+: GNU GPL v e r s i o n 3 o r l a t e r <h t t p : / / gnu . o r g / l i c e n s e s / g p l . html>
This i s f r e e s o f t w a r e : you a r e f r e e t o change and r e d i s t r i b u t e i t .
There i s NO WARRANTY, t o t h e e x t e n t p e r m i t t e d by law . Type "show c o p y i n g "
and "show warranty " f o r d e t a i l s .
This GDB was c o n f i g u r e d a s " i 4 8 6 −l i n u x −gnu " . . .
( gdb )
Listing 4: Wynik wykonania bª¦dnego programu
W tym momencie debugger oczekuje na polecenia uzytkownika. W celu uruchomienia programu nale»y
wydac polecenie start. Debugger automatycznie przejdzie do funkcji main() danego programu o czym
poinformuje u»ytkownika w nast¦puj¡cy sposób:
( gdb ) s t a r t
B r e a k p o i n t 1 a t 0 x80483a5 : f i l e a . c , l i n e 3 .
S t a r t i n g program : /home/ maciek / a . out
main ( ) a t a . c : 3
3
∗x = 2 ;
( gdb )
Listing 5: Wynik wykonania bª¦dnego programu
W kazdym momencie gdy debugger zatrzyma si¦ podczas wykonywania programu, w celu kotynujacji
nale»y wyda¢ polecenie cont. Program b¦dzie wykonywany bez ingerencji debuggera tak dªugo jak to
mo»liwe, a» do jego skonczenia, dotarcia do punktu przerwania (breakpoint) lub wyst¡pienia bª¦du.
W ostanim z wymienionych przypadkow, debugger informuje u»ytkownika o wyst¡pieniu problemu w
nast¦puj¡cy sposób:
2
( gdb ) c o n t
Continuing .
Program r e c e i v e d s i g n a l SIGSEGV , Segmentation f a u l t .
0 x080483a8 i n main ( ) a t a . c : 3
3
∗x = 2 ;
( gdb )
Listing 6: Wynik wykonania bª¦dnego programu
Jak widac, instrukcja zapisania warto±ci pod adresem zakonczyªa si¦ niepowodzeniem.
W j¡drze Linux (podobnie jak kazdym innym j¡drze Unixo-podobnym), aplikacje przestrzeni u»ytkownika
s¡ informowane o wyst¡pieniu wyj¡tku na procesorze za pomoc¡ sygnaªow. W tym przypadku wyst¡piª
bª¡d dost¦pu do pami¦ci (segmentation fault), co zwi¡zane jest z odebranie sygnalu SIGSEGV.
3.2
Wie»e Hanoi
Jak juz zostaªo powiedziane w pierwszej sekcji, kod wykonuj¡cy bª¦dne operacje na tablicach spowoduje
przedwczesne zako«czenie programu. Niestety uzytkownik nie jest w stane ªatwo zidentykowac przyczyny takiego zaj±cia. W tym celu nale»y u»y¢ debuggera uruchamiaj¡c program identycznie jak w
poprzednim przykªadzie. Po wydaniu polecenie« start i cont, program b¦dzie si¦ wykonywaª a» do
zaistniena bª¦du. W momencie wyst¡pienia wyj¡tku, gdb zatrzyma program oddaj¡c kontrol¦ u»ytkownikowi. Przedstawione jest to na listingu poni»ej:
( gdb ) c o n t
Continuing .
HAN. c : 1 5 1 : Bounds e r r o r : a r r a y r e f e r e n c e ( 6 ) o u t s i d e bounds o f t h e a r r a y .
HAN. c : 1 5 1 : P o i n t e r v a l u e : 0 x806b734
HAN. c : 1 5 1 : Object ` tab ' :
HAN. c : 1 5 1 :
Address i n memory :
0 x806b72e . . 0 x806b733
HAN. c : 1 5 1 :
Size :
6 bytes
HAN. c : 1 5 1 :
Element s i z e :
1 bytes
HAN. c : 1 5 1 :
Number o f e l e m e n t s :
6
HAN. c : 1 5 1 :
Created a t :
HAN. c , l i n e 13
HAN. c : 1 5 1 :
Storage c l a s s :
static
Program r e c e i v e d s i g n a l SIGABRT, Aborted .
0 xb7f02430 in __kernel_vsyscall ( )
( gdb )
Listing 7: Wynik wykonania bª¦dnego programu
Jak zostaªo juz wcze±niej wspomniane, program odebraª sygnaª, jednak w tym przykªadzie jest to SIGABRT,
gdy» bª¡d nie zostaª wykryty przez procesor podczas wykonania programu, a przez dodatkow¡ funkcjonalno±¢ kompilatora gcc wª¡czon¡ uzyciem przeª¡cznika -fbounds-checking.
Aby uzyska¢ informacje mog¡ce pomóc w zidentykowaniu zródªa problemu, warto przeanalizowa¢ aktualnie wykonywany kod. Najpierw nalezy sprawdzi¢ zawarto±¢ stosu wydaj¡c komend¦ bt:
( gdb ) bt
#0 0 x b 7 f 0 2 4 3 0 i n _ _ k e r n e l _ v s y s c a l l ( )
#1 0 xb7cd58a0 i n r a i s e ( ) from / l i b / t l s / i 6 8 6 /cmov/ l i b c . s o . 6
#2 0 xb7cd7268 i n a b o r t ( ) from / l i b / t l s / i 6 8 6 /cmov/ l i b c . s o . 6
#3 0 x 0 8 0 5 4 f a f i n __bounds_errorf ( f i l e n a m e =0x8067ad8 "HAN. c " , l i n e =151 ,
p o i n t e r =0x806b734 , o b j=0x9a1e080 , format=0x0 )
3
#4
#5
a t / u s r / s r c / gcc − 4.0.2 − s r c / g c c / bounds / l i b / e r r o r . c : 2 0 0
0 x0804d663 i n __bounds_check_array_reference_obj ( p o i n t e r =0x806b72e ,
o f f s e t =6, s i z e =1, a r r a y _ s i z e =6, f i l e n a m e =0x8067ad8 "HAN. c " , l i n e =151)
a t / u s r / s r c / gcc − 4.0.2 − s r c / g c c / bounds / l i b / check . c : 2 2 8
0 x0804a2c2 i n main ( ) a t HAN. c : 1 5 1
Listing 8: Wynik wykonania bª¦dnego programu
Stos jest podzielony na ramki, aktualna ramka stosu ma zawsze numer 0. Jak wida¢ na listingu powy»ej,
sygnaª zostaª odebrany gdy program znajdowaª si¦ w funkcji __kernel_vsyscall. Spowodowane zostaªo
to wywoªaniem funkcji abort4 , która to odwoªuje si¦ do funkcji raise5 . Niestety »adna z tych funkcji
nie jest bezpo±rednio cz¦sci¡ analizowanego programu6 , st¡d nale»y przenie±¢ si¦ na stosie do funkcji
zw¡zanych z programem. W tym celu nalezy wykonac polecenie frame 5:
( gdb ) frame 5
#5 0 x0804a2c2 i n main ( ) a t HAN. c : 1 5 1
151
tab [ p r z y c i s k 2 ] [ y+1]=pamiec ;
Listing 9: Wynik wykonania bª¦dnego programu
Jak wida¢, odwoªanie do tablicy jest zale»ne od warto±ci dwóch zmiennych: przycisk2 oraz y. Wartosci
tych zmiennych mozna wyswietlic w nast¦puj¡cy sposób:
( gdb ) p r i n t p r z y c i s k 2
$1 = 1
( gdb ) p r i n t y
$2 = 5
Listing 10: Wynik wykonania bª¦dnego programu
Analizuj¡c kod mo»na znale»¢ nast¦puj¡ce fragmenty:
#d e f i n e IL_KLOCKOW 6
u n s i g n e d c h a r tab [ 3 ] [IL_KLOCKOW ] ;
...
tab [ p r z y c i s k 2 ] [ y+1]=pamiec ;
Listing 11: Wynik wykonania bª¦dnego programu
Tablica tab ma rozmiar 3x6 elementów. St¡d zakªadaj¡c warto±¢ zmiennej przycisk2 równe 1, mozna
odwoªa¢ si¦ do elementów tablicy pomi¦dzy: tab[1][0] do tab[1][5]. Odwoªadnie do pozycji tab[1][6]
dla y równego 6 spowoduje wyst¡pienie bª¦du.
Zatrzymanie gdb mo»na wykona¢ w nast¦puj¡cy sposób:
( gdb ) q u i t
The program i s r u n n i n g .
E x i t anyway ? ( y o r n ) y
Listing 12: Wynik wykonania bª¦dnego programu
4 program próbuje wyj±¢ z krzytycznym bªedem
5 program prosi o wysªanie do siebie sygnaªu wskazanego
6 funkcje te s¡ cz¦±ci¡ biblioteki C
jako parametr
4