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