if (x[i].a == y[n-i].a) printf("Niemozliwe: %d %d",x[i].a,y[n-i].a);Assert
if
oraz printf
, którego składnia jest
skomplikowana, a po usunięciu usterki trzeba je wszystkie wykomentować,
lub usunąć, co znów zabiera cenny czas programisty. Dlatego stworzono
funkcję assert()
, o następującej składni:
#include <assert.h> void assert(scalar wyrazenie);Ta funkcja sprawdza podany jej warunek logiczny i jeśli jest on fałszywy (równy 0) to kończy działanie programu wypisując na ekran komunikat o błędzie. Na przykład dla programu:
#include <assert.h> int main(){ int x = 5; assert(x==665); return 0; }Na ekran wypisane zostanie:
a.out: ble.c:5: main: Assertion `x==665' failed. AbortedOznacza to, że błąd powstał w pliku wykonywalnym a.out, którego źródłem jest ble.c. Nieprawdziwa asercja pojawiła się w linii 5 w funkcji main().
ptrace
. Pozwala ona jednemu procesowi, zwanemu dalej
kontrolującym (lub śledzącym), śledzić wykonanie drugiego procesu (śledzonego).
getppid
w procesie śledzonym spowoduje zwrócenie numeru ID oryginalnego ojca, a nie procesu śledzącego.
struct task_struct { ... unsigned long ptrace; ... }Pole to pełni rolę maski bitowej, w której zapamiętywane są flagi, oto niektóre z nich:
#include <sys/ptrace.h> long int ptrace(enum __ptrace_request request, pid_t pid, void *addr, void * data)Znaczenie poszczególnych argumentów:
ptrace(PTRACE_ATTACH,pid,NULL,NULL)
, spowoduje to zmianę
wskaźnika p_pptr w strukturze task_struct procesu określonego przez pid,
tak aby wskazywał na proces kontrolującyptrace(PTRACE_TRACEME,0,NULL,NULL)
, w tym wypadku pola rodzinne
w strukturze task_struct nie muszą być modyfikowane, gdyż proces kontrolujący
jest jednocześnie ojcem wait
lub
waitpid
.
ptrace
z pierwszym argumentem:ptrace
proces
kontrolujący może (w momencie w którym proces śledzony jest zawieszony):ptrace
(program pochodzi z książki: Maurice J. Bach, Budowa systemu operacyjnego):#include <stdio.h> int dane[5]; int main() { int i; for( i = 0; i < 5; i++) printf("dane[%d] = %d\n", i, dane[i] ); printf("Adres sledzonych danych: 0x%x\n", dane ); }Uruchomienie programu ./trace spowoduje wypisanie na ekran:
[cygan@duch prezentacja]$ ./trace dane[0] = 0 dane[1] = 0 dane[2] = 0 dane[3] = 0 dane[4] = 0 Adres sledzonych danych: 0x500a00Proces kontrolujący:
#include <stdio.h> #include <sys/ptrace.h> #include <sys/types.h> #include <sys/wait.h> #include <unistd.h> int adres; int main( int argc, char *argv[] ) { int i, ident; sscanf( argv[1], "%x", &adres ); if(( ident = fork() ) == 0 ) { ptrace( PTRACE_TRACEME, 0, 0, 0 ); execl("trace","trace", 0 ); return -1; } wait((int *)0); for( i = 0; i < 5; i++ ) { if (ptrace(PTRACE_POKEDATA,ident,(void *)adres,i) == -1) return -1; adres += sizeof(int); } ptrace(PTRACE_CONT,ident,1,0); return 0; }Uruchomienie programu kontrolującego ./ptrace 0x500a00 spowoduje:
[cygan@duch prezentacja]$ ./ptrace 0x500a00 [cygan@duch prezentacja]$ dane[0] = 0 dane[1] = 1 dane[2] = 2 dane[3] = 3 dane[4] = 4 Adres sledzonych danych: 0x500a00
int fastcall do_signal(struct pt_regs *regs, sigset_t *oldset){ ... unsigned long signr; spin_lock_irq(¤t->sigmask_lock); signr = dequeue_signal(¤t->blocked, &info); spin_unlock_irq(¤t->sigmask_lock); if (!signr) break; if ((current->ptrace & PT_PTRACED) && signr != SIGKILL) { /* Let the debugger run. */ current->exit_code = signr; current->state = TASK_STOPPED; notify_parent(current, SIGCHLD); schedule(); ... } ... }
ptrace_detach
,
gdzie pierwszym argumentem jest wskaźnik na strukturę procesu śledzonego:
int ptrace_detach(struct task_struct *child, unsigned int data) { ... child->ptrace = 0; /*zerowanie wszystkich flag*/ ... child->p_pptr = child->p_opptr; /*przywrócenie oryginalnego ojca*/ ... wake_up_process(child); /*budzenie procesu*/ return 0; }
syscall_trace
. Jeśli proces jest śledzony
i flaga PT_TRACESYS jest ustawiona, to zostaje on wstrzymany (stan procesu zostaje zmieniony
na TASK_STOPPED) i powiadomiony zostaje proces kontrolujący.
Proces śledzony zostanie również wstrzymany przy wyjściu
z funkcji systemowej.
int X
wywołuje
przerwanie. Jednakże
instrukcja int 3
różni się od pozostałych tym, że ma specjalny,
jednobajtowy kod, dzięki czemu łatwiej wstawić ją w kod programu
(wystarczy podmienić jeden bajt). Procesor, napotkawszy tę instrukcję
generuje przerwanie numer 3, co powoduje wysłanie do procesu sygnału
SIGTRAP. Jeśli proces jest śledzony, to sygnał ten zostanie dostarczony do
procesu kontrolującego, a proces śledzony zostanie wstrzymany.
Proces kontrolujący powinien usunąć instrukcję int 3
(zastępując ją zapamiętanym, oryginalnym bajtem) przed
wznawianiem wykonania procesu śledzonego, a następnie zmniejszyć adres
powrotu (EIP) o 1 (za pomocą funkcji ptrace(PTRACE_POKEUSR,...)
),
gdyż przerwanie int 3
powoduje umieszczenie na stosie
adresu powrotu wskazującego na kolejną po int 3
instrukcję.
int 3
(gdy nie wiemy, która instrukcja
powoduje zmianę naszych danych).
Procesor sygnalizuje dostęp do danych poprzez
przerwanie int 1
(nazywany po angielsku debug exceptions).
strace
nie musimy posiadać źródeł danego programu, a także program
nie musi być skompilowany z opcją -g
.
Dla początkującego użytkownika dużą wadą jest rozmiar
danych wypisywanych przez strace
(nawet dla prostego programu).
Bardzo podobnym programem do strace
jest program ltrace
,
który oprócz sygnałów i wywołań funkcji systemowych potrafi śledzić wywołania
funkcji z bibliotek dzielonych (ang. shared libraries).
strace
, jednakże projekt ten nie jest obecnie
rozwijany (w szczególności nie współpracuje z nowymi wersjami gcc) dlatego też
nie będziemy w naszej prezentacji omawiać tego (jednakże ciekawego) projektu.
.stabs "string",type,other,desc,value .stabn type,other,desc,value .stabd type,other,descOstatnie literki (s,n,d) to skróty odpowiednio od: string, number i dot. Po każdym polu następuje lista informacji, które występują po nim:
name:symbol-descriptor type-informationPrzykładowo automatycznie dołączana deklaracja typu int wygląda następująco:
.stabs "int:t1=r1;-2147483648;2147483647;",128,0,0,0Pole name może być opuszczone, i tak na przykład napis
:t10=*2oznacza, że typ 10 jest wskaźnikiem do typu 2. Możliwość omijania nazwy jest obsługiwana przez gdb, ale niekoniecznie przez inne debuggery co sprawia, że dane w formacie stabs mogą nie współpracować z i niektórymi debuggerami. Jedną z najistotniejszych informacji dla debuggera jest numer linii kodu źródłowego z której pochodzi dana instrukcja. Żeby ją zapamiętać w pliku umieszczany jest wpis, który w polu string zawiera stałą N_SLINE, w polu value umieszczamy adres kodu wykonywalnego, natomiast w polu desc numer linii w kodzie źródłowym.
ptrace
, dlatego też
główne funkcjonalności są realizowane w sposób przedstawiony przy omawianiu
możliwości jakie nam daje funkcja ptrace
.
Dodatkowo, aby zapamiętywać wywołania oraz wywoływane funkcje gdb używa
struktury ramki, której deklaracja umieszczona jest poniżej
(czytanie zalecany tylko dla najwytrwalszych):
struct frame_info { /* Level of this frame. The inner-most (youngest) frame is at level 0. As you move towards the outer-most (oldest) frame, the level increases. This is a cached value. It could just as easily be computed by counting back from the selected frame to the inner most frame. */ /* NOTE: cagney/2002-04-05: Perhaps a level of ``-1'' should be reserved to indicate a bogus frame - one that has been created just to keep GDB happy (GDB always needs a frame). For the moment leave this as speculation. */ int level; /* The frame's low-level unwinder and corresponding cache. The low-level unwinder is responsible for unwinding register values for the previous frame. The low-level unwind methods are selected based on the presence, or otherwise, of register unwind information such as CFI. */ void *prologue_cache; const struct frame_unwind *unwind; /* Cached copy of the previous frame's resume address. */ struct { int p; CORE_ADDR value; } prev_pc; /* Cached copy of the previous frame's function address. */ struct { CORE_ADDR addr; int p; } prev_func; /* This frame's ID. */ struct { int p; struct frame_id value; } this_id; /* The frame's high-level base methods, and corresponding cache. The high level base methods are selected based on the frame's debug info. */ const struct frame_base *base; void *base_cache; /* Pointers to the next (down, inner, younger) and previous (up, outer, older) frame_info's in the frame cache. */ struct frame_info *next; /* down, inner, younger */ };
#include <stdio.h> int f(int x){ int y=5; return 2*x+y; } int main(){ int x; x=15; printf("%d\n",f(x)); return 0; }W tym celu musimy go skompilować z opcją odpluskwiania -g:
gcc -g -o costam costam.cPowstał plik wykonywalny "costam" przygotowany do odpluskwiania.
gdb costamMożemy to również zrobić uruchamiając gdb i wpisując "file costam".
run [parametry]Większość komend gdb posiada jednoliterowe skróty od swoich nazw. I tak na przykład uruchomienie:
r [parametry]będzie miało równoważny skutek.
print zmienna=wartośćktóra spowoduje przypisanie zmiennej zmienna wartości wartość. Można również użyć komendy set (różnica jest taka, że komenda set nie spowoduje wypisania nowej wartości zmiennej).
set {int}0x83040 = 4Wynikiem tej instrukcji będzie umieszczenie wartości 4 w komórce pamięci o adresie 0x83040.
gdb costamUruchomi się program gdb i przywita nas tekstem:
GNU gdb Red Hat Linux (6.3.0.0-1.21rh) Copyright 2004 Free Software Foundation, Inc. GDB is free software, covered by the GNU General Public License, and you are welcome to change it and/or distribute copies of it under certain conditions. Type "show copying" to see the conditions. There is absolutely no warranty for GDB. Type "show warranty" for details. This GDB was configured as "i386-redhat-linux-gnu"...Using host libthread_db library "/lib/libthread_db.so.1". (gdb)Na początku ustawiamy punkt przerwania (breakpoint) na początku funkcji main:
(gdb) break main Breakpoint 1 at 0x80483af: file costam.c, line 10.Następnie uruchamiamy program:
(gdb) run Starting program: /home/marek/studia/SO/public_html/costam Reading symbols from shared object read from target memory...done. Loaded system supplied DSO at 0x93f000 Breakpoint 1, main () at costam.c:10 10 x=15; (gdb)Gdb poinformował nas jaką instrukcję program wykona za chwilę i w której linii programu (10) się aktualnie znajduje. Zaczynamy wykonywać program krok po kroku:
(gdb) step 11 printf("%dn",f(x)); (gdb)Teraz sprawdzamy wartość zmiennej
x
:
(gdb) print x $1 = 15 (gdb)Kontynuujemy wykonanie programu:
(gdb) step f (x=15) at costam.c:4 4 int y=5; (gdb)Gdb poinformował nas, że wywołana została funkcja
f
z argumentem 15.
Możemy sprawdzić jak aktualnie wygląda stos wywołań:
(gdb) backtrace #0 f (x=15) at costam.c:4 #1 0x080483be in main () at costam.c:11 (gdb)Teraz zmienimy wartość zmiennej
x
na 330:
(gdb) print x=330 $2 = 330 (gdb)Po czym kontynuujemy wykonanie programu (bez zatrzymywania się po każdej instrukcji):
(gdb) continue Continuing. 665 Program exited normally. (gdb)Gdb poinformował nas, że program pomyślnie zakończył swoje działanie.
-g
możemy również używać w celu wykrycia okoliczności
w jakich program zakończył swoje działanie błędem wykonania.
-g
,
plik core może posłużyć do znalezienia linii programu, w której
jego działanie zostało przerwane, jak również do sprawdzenia
wartości zmiennych w tym momencie.
#include <stdlib.h> int a(int *p) { int y = *p; return y; } int main () { int *p = 0; /* null pointer */ return a(p); }Musimy w tym celu skompilować ten program z opcją
-g
:
gcc -Wall -g -o core core.cUwaga: Kompilator nie wygenerował żadnego ostrzeżenia. Teraz uruchamiamy program:
./core Naruszenie ochrony pamięci (core dumped)Program ten próbował odczytać wartości pamięci pod adresem 0, co spowodowało jego unicestwienie. Pojawienie się napisu 'core dumped' oznacza, że system operacyjny utworzył plik core w bieżącym katalogu (w naszym przypadku jest to plik core.4157) Plik ten zawiera kopie wszystkich stron pamięci wykorzystywanych przez program w chwili jego zakończenie. Teraz możemy uruchomić gdb na naszym pliku core za pomocą polecenia:
gdb core core.4157
Należy zauważyć, że zarówno plik wykonywalny (skompilowany z opcją -g
)
jak i plik core.4157 są potrzebne, do odpluskwiania programu.
Uruchomienie gdb spowoduje wypisanie informacji diagnostycznych
oraz numeru linii, w której program zakończył swoje działanie.
[marek@dhcp2-240 gdb]$ gdb core core.4157 Core was generated by './a.out'. Program terminated with signal 11, Segmentation fault. Reading symbols from /lib/libc.so.6...done. Loaded symbols for /lib/libc.so.6 Reading symbols from /lib/ld-linux.so.2...done. Loaded symbols for /lib/ld-linux.so.2 #0 0x080483ed in a (p=0x0) at null.c:4 4 int y = *p; (gdb)Aby znaleźć przyczynę błędu wykonania wyświetlamy wartość wskaźnika
p
za pomocą polecenia print
:
(gdb) print p $1 = (int *) 0x0Dowiadujemy się, że przyczyną naruszenia ochrony pamięci była próba odczytania wartości pod adresem
0x0
.
Aby dowiedzieć się, jakie funkcje były wywoływane w momencie zakończenia programu
możemy użyć polecenia backtrace
:
(gdb) backtrace #0 0x080483ed in a (p=0x0) at core.c:4 #1 0x080483d9 in main () at core.c:11
ulimit -c
możemy kontrolować maksymalny rozmiar plików core. Jeśli rozmiar
ten jest ustawiony na 0, pliki core nie są tworzone.
int main(){ // 1 int x,a,b,pa,pb,d; // 1 scanf("%d %d",&n, &m); // 1 x = m; // 1 for (int i=1; i<=n; i++){ // 2 wys[i] = 0; // 3 p[i] = i; // 3 } for (int j=0; j<=m; j++){ // 4 scanf("%d %d",&a, &b); // 5 pa = find(a); // 6-nowy blok wywołanie funkcji find pb = find(b); // 7-nowy blok wywołanie funkcji find if (pa == pb) // 8 x--; // 9 else unify(pa,pb); // 10 } printf("%dn",x); // 11 return 0; // 12 }stworzy się nam graf podobny do poniższego:
#include <stdlib.h> void f(int a) { if (a <= 0) return ; int* x = malloc(a * sizeof(int)); x[1] = 665;//to sie nie uda gdy a=1 f(a-1); return ; } int main(void) { f(3); return 0; }W tym celu kompilujemy go z opcjami optymalizacji, oraz -fprofile-arcs i -ftest-coverage:
gcc -fprofile-arcs -ftest-coverage -O3 przyklad2.cPowstał plik a.out. Uruchamiamy go:
./a.outJeśli uruchomimy program wielokrotnie policzą nam się statystyki wszystkich wykonań. My w tym przykładzie uruchomimy go tylko raz. Program na większości maszyn mimo zawartego w nim błędu zakończy się poprawnie, co nie jest dużym zaskoczeniem - mamy tu do czynienia z profilerem, a nie debuggerem czy menadżerem błędów pamięci. Powstały nam pliki przykład2.da i przykład2.bbg(ten ostatni plik powstał już po kompilacji). Teraz użyjemy programu gcov aby uzyskać potrzebne nam informacje:
gcov przykład2.daWynikiem będzie komunikat:
File `przyklad2.c' Lines executed:100.00% of 10 przyklad2.c:creating `przyklad2.c.gcov'informujący o utworzeniu pliku przyklad2.c.gcov, który wygląda następująco:
-: 0:Source:przyklad2.c -: 0:Graph:przyklad2.bbg -: 0:Data:przyklad2.da -: 1:#include <stdlib.h> -: 2: -: 3:void f(int a) 4: 4:{ 4: 5: if (a <= 0) 2: 6: return ; 3: 7: int* x = malloc(a * sizeof(int)); 3: 8: x[1] = 665;//to sie nie uda gdy a=1 3: 9: f(a-1); 3: 10: return ; -: 11:} -: 12: -: 13:int main(void) 1: 14:{ 1: 15: f(3); 1: 16: return 0; -: 17:}Pobieżna analiza doprowadza nas do przekonania, że numerki po prawej stronie prawie odpowiadają liczbie wykonań poszczególnych linii. Występuje tutaj jedna błędna wartość. Ten błąd nie wynika z niekompetencji autorów tego narzędzia, jest wynikiem faktu, że program skompilowaliśmy z opcjami optymalizacji, które sprawiają problemy wszystkim programom do odpluskwiania i profilowania kodu. Zaletą tego narzędzia jest fakt, że nawet dla skomplikowanych programów obliczone wartości są bardzo bliskie rzeczywistym. Skompilowanie programów z opcją -fprofile-arcs, a bez opcji optymalizacji jest jednak pozbawione sensu (choć możliwe), gdyż chcemy poprawiać efektywność, a więc musimy kompilować program tak, aby działał jak najszybciej. Jako ćwiczenie dla zainteresowanych pozostawiamy sprawdzenie, że bez opcji optymalizacji liczby wykonań poszczególnych linii są poprawne.
./konstantynopolitanczykowianeczkaGdy uruchomimy nasz program będzie tylko działał odrobinę wolniej, ze względu na konieczność zbierania informacji dla profilera. Program zapisze swój "profil" w pliku gmon.out tuż przed zamknięciem. Jeśli w katalogu istniał wcześniej taki plik to jego zawartość zostanie nadpisana. Program musi się zakończyć poprawnie - czyli przez wywołanie return z funkcji main() ew. przez wywołanie funkcji exit(). Każde inne zakończenie (np. Segmentation Fault) nie spowoduje zapisania pliku gmon.out. Trzeba też pamiętać, że plik gmon.out zostanie zapisany w aktualnym katalogu roboczym. Nie musi to koniecznie być równoznaczne z katalogiem, z którego program został wywołany. Jeśli program podczas pracy zmienia katalog roboczy (za pomocą funkcji chdir) to gmon.out zostanie zapisany w ostatnim katalogu roboczym. W szczególności, jeśli akurat nie mamy praw zapisu do tego ostatniego katalogu - to zostaniemy przywitani komunikatem o błędzie.
gprof opcje [plik-wykonywalny [pliki-z-danymi-do-profilowania...]] [> plikwyjsciowy]Jeśli nie wyspecyfikujemy pliku wykonywalnego, który chcemy badać gprof przyjmie domyślne a.out. Domyślną nazwą pliku z profilem jest wspomniane wyżej gmon.out. Oczywiście jeśli któryś z podanych plików nie istnieje, lub wygląda na to że plik profilu "nie pasuje" do pliku programu - dostaniemy komunikat o błędzie.
Flat profile Each sample counts as 0.01 seconds. % cumulative self self total time seconds seconds calls ms/call ms/call name 33.34 0.02 0.02 7208 0.00 0.00 open 16.67 0.03 0.01 244 0.04 0.12 offtime 16.67 0.04 0.01 8 1.25 1.25 memccpy 16.67 0.05 0.01 7 1.43 1.43 write 16.67 0.06 0.01 mcount 0.00 0.06 0.00 236 0.00 0.00 tzset 0.00 0.06 0.00 192 0.00 0.00 tolower 0.00 0.06 0.00 47 0.00 0.00 strlen 0.00 0.06 0.00 45 0.00 0.00 strchr 0.00 0.06 0.00 1 0.00 50.00 main 0.00 0.06 0.00 1 0.00 0.00 memcpy 0.00 0.06 0.00 1 0.00 10.11 print 0.00 0.06 0.00 1 0.00 0.00 profil 0.00 0.06 0.00 1 0.00 50.00 reportPoszczególne kolumny zawierają następujące informacje: (czasy odnoszą się oczywiście do czasu wykorzystania procesora a nie do realnego "kalendarzowego" czasu działania programu)
granularity: each sample hit covers 2 byte(s) for 20.00% of 0.05 seconds index % time self children called name <spontaneous> [1] 100.0 0.00 0.05 start [1] 0.00 0.05 1/1 main [2] 0.00 0.00 1/2 on_exit [28] 0.00 0.00 1/1 exit [59] ----------------------------------------------- 0.00 0.05 1/1 start [1] [2] 100.0 0.00 0.05 1 main [2] 0.00 0.05 1/1 report [3] ----------------------------------------------- 0.00 0.05 1/1 main [2] [3] 100.0 0.00 0.05 1 report [3] 0.00 0.03 8/8 timelocal [6] 0.00 0.01 1/1 print [9] 0.00 0.01 9/9 fgets [12] 0.00 0.00 12/34 strncmp <cycle 1> [40] 0.00 0.00 8/8 lookup [20] 0.00 0.00 1/1 fopen [21] 0.00 0.00 8/8 chewtime [24] 0.00 0.00 8/16 skipspace [44] ----------------------------------------------- [4] 59.8 0.01 0.02 8+472 <cycle 2 as a whole> [4] 0.01 0.02 244+260 offtime <cycle 2> [7] 0.00 0.00 236+1 tzset <cycle 2> [26] -----------------------------------------------Linie z samych myślników dzielą tabelę na wpisy odpowiadające pojedynczym funkcjom. Każdy wpis ma jedną lub więcej linijek. Każdy wpis zawiera jedną tzw. "linijkę główną". To ta, która rozpoczyna się numerem porządkowym w nawiasach kwadratowych. Koniec linii głównej mówi jakiej funkcji odpowiada dany wpis. Linie poprzedzające linię główną opisują funkcje wywołujące ("rodziców"), natomiast linie następujące po linii głównej opisują funkcje wywoływane ("potomków"). Wpisy uporządkowane od najbardziej do najmniej czasochłonnego. Bierze się tu pod uwagę czas wykonania samej funkcji, jak i wszystkich jej potomków. Warto wspomnieć, że wewnętrzna funkcja profilująca mcount nigdy nie pojawia się w grafie wywołań.
----------------------------------------------- [4] 59.8 0.01 0.02 8+472 <cycle 2 as a whole> [4] 0.01 0.02 244+260 offtime <cycle 2> [7] 0.00 0.00 236+1 tzset <cycle 2> [26] -----------------------------------------------Liczby w kolejnych kolumnach mają następujące znaczenia:
Jest to najbardziej szczegółowa informacja, jaką potrafi udzielić gprof. Dzięki niej nasz program przestanie przed nami kryć jakiekolwiek tajemnice. Będziemy mogli na przykład wykryć, że w pojedynczej funkcji pewne linijki kodu nie wykonują się wcale (np. warunek if nigdy nie jest spełniony, choć myślimy, że powinien) podczas gdy inne wykonują się zdecydowanie zbyt często.
Profilowanie na poziomie pojedynczych linii.Pierwszym etapem w zwiększaniu szczegółowości analizy programu jest opcja -l. (zazwyczaj niezbędne jest też, by przy kompilacji użyto -g - gprof korzysta z informacji debuggera). Dzięki niej wywołania funkcji zostaną przypisane do konkretnych linijek kodu. Będzie to miało wpływ zarówno na widok płaskiego profilu jak i na graf wywołań.
Płaski profil może na przykład wyglądać tak:
Flat profile Each sample counts as 0.01 seconds. % cumulative self time seconds seconds calls name 7.69 0.10 0.01 ct_init (trees.c:349) 7.69 0.11 0.01 ct_init (trees.c:351) 7.69 0.12 0.01 ct_init (trees.c:382) 7.69 0.13 0.01 ct_init (trees.c:385)
Widać, że linijka dotycząca funkcji ct_init została rozbita na cztery, gdyż funkcja ct_init jest wywoływana z czterech miejsc. Możemy więc stwierdzić która linijka kodu odpowiada za najbardziej czasochłonne wykorzystanie funkcji ct_init.
Graf wywołań otrzymuje następującą postać:
% time self children called name 0.00 0.00 1/13496 name_too_long (gzip.c:1440) 0.00 0.00 1/13496 deflate (deflate.c:763) 0.00 0.00 1/13496 ct_init (trees.c:396) 0.00 0.00 2/13496 deflate (deflate.c:727) 0.00 0.00 4/13496 deflate (deflate.c:686) 0.00 0.00 5/13496 deflate (deflate.c:675) 0.00 0.00 12/13496 deflate (deflate.c:679) 0.00 0.00 16/13496 deflate (deflate.c:730) 0.00 0.00 128/13496 deflate_fast (deflate.c:654) 0.00 0.00 3071/13496 ct_init (trees.c:384) 0.00 0.00 3730/13496 ct_init (trees.c:385) 0.00 0.00 6525/13496 ct_init (trees.c:387) [6] 0.0 0.00 0.00 13496 init_block (trees.c:408)
Mamy tutaj fragment wpisu dotyczącego funkcji init_block obejmujący wszystkich jej "rodziców". Można odczytać gdzie dokładnie init_block była wywoływana.
Kod źródłowy z komentarzemDzięki opcji -A, mamy możliwość stwierdzić ile razy wykonała się każda pojedyncza linijka kodu (zazwyczaj niezbędne jest wbudowanie tej funkcjonalności w sam program podczas kompilacji, by gprof mógł z niej skorzystać. Służy do tego opcja gcc -a)
Tak poinstruowany gprof potrafi zmienić taki kod:
1 ulg updcrc(s, n) 2 uch *s; 3 unsigned n; 4 { 5 register ulg c; 6 7 static ulg crc = (ulg)0xffffffffL; 8 9 if (s == NULL) { 10 c = 0xffffffffL; 11 } else { 12 c = crc; 13 if (n) do { 14 c = crc_32_tab[...]; 15 } while (--n); 16 } 17 crc = c; 18 return c ^ 0xffffffffL; 19 }
... w coś takiego:
ulg updcrc(s, n) uch *s; unsigned n; 2 ->{ register ulg c; static ulg crc = (ulg)0xffffffffL; 2 -> if (s == NULL) { 1 -> c = 0xffffffffL; 1 -> } else { 1 -> c = crc; 1 -> if (n) do { 26312 -> c = crc_32_tab[...]; 26312,1,26311 -> } while (--n); } 2 -> crc = c; 2 -> return c ^ 0xffffffffL; 2 ->}
Tu już widać jak na dłoni, że funkcja updcrc() wykonała się dwa razy, że s zostało dwukrotnie sprawdzone, czy jest NULL'em. Raz okazało się, że tak i wykonała się linijka c = 0xffffffffL;, a raz okazało się, że nie i wtedy mieliśmy owe 26312 podstawień, bo tyle było trzeba by n sprowadzić do zera.
Przy odrobinie wprawy taka informacja może oddać nieocenione usługi w zrozumieniu działania programu.
valgrind ./konstantynopolitanczykowianeczkao ile valgrind znajduje się w katalogu zawartym w zmiennej $PATH (jeśli nie to należy wpisać katalog_z_valgrindem/valgrind ./konstantynopolitanczykowianeczka). Należy jednak pamiętać, aby program konstantynopolitanczykowianeczka skompilować z opcjami debugowania -g, na przykład:
gcc -o konstantynopolitanczykowianeczka -g konstantynopolitanczykowianeczka.cJeśli tego nie zrobimy Valgrind będzie miał ograniczone pole manewru i zacznie zgadywać pewne rzeczy, które powinien wiedzieć. Ponadto najlepiej jest nie używać optymalizacji na poziomie powyżej drugiego, gdyż może to powodować pojawienie nieoczekiwanych ostrzeżeń i ujemnie wpłynąć na czytelność wypisywanych komunikatów.
#include <stdlib.h> void f(void) { int* x = malloc(10 * sizeof(int)); x[10] = 0; // problem: piszemy po nie swojej pamieci free(x); } int main(void) { f(); return 0; }Uzyskamy następujący komunikat:
gcc -g -O0 przyklad1.c valgrind ./a.out ==2801== Memcheck, a memory error detector. ==2801== Copyright (C) 2002-2005, and GNU GPL'd, by Julian Seward et al. ==2801== Using LibVEX rev 1471, a library for dynamic binary translation. ==2801== Copyright (C) 2004-2005, and GNU GPL'd, by OpenWorks LLP. ==2801== Using valgrind-3.1.0, a dynamic binary instrumentation framework. ==2801== Copyright (C) 2000-2005, and GNU GPL'd, by Julian Seward et al. ==2801== For more details, rerun with: -v ==2801== ==2801== Invalid write of size 4 ==2801== at 0x80483CF: f (przyklad1.c:6) ==2801== by 0x80483F6: main (przyklad1.c:12) ==2801== Address 0x414F050 is 0 bytes after a block of size 40 alloc'd ==2801== at 0x401B495: malloc (in /usr/lib/valgrind/x86-linux/vgpreload_memcheck.so) ==2801== by 0x80483C5: f (przyklad1.c:5) ==2801== by 0x80483F6: main (przyklad1.c:12) ==2801== ==2801== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 13 from 1) ==2801== malloc/free: in use at exit: 0 bytes in 0 blocks. ==2801== malloc/free: 1 allocs, 1 frees, 40 bytes allocated. ==2801== For counts of detected errors, rerun with: -v ==2801== No malloc'd blocks -- no leaks are possible.Pojawiający się na początku każdej linii numer 2801 to pid procesu, który jest dla nas nieistotny. Na początku Valgrind zawsze uprzejmie przywita nas i poinformuje o wszelkich copyrightach. Zaraz potem następują informacje istotne - komunikaty o błędach. W tym przypadku prawidłowo stwierdził, że błąd jest w linii 6 w funkcji f ("at 0x80483CF: f (przyklad1.c:6)"), która została wywołana przez funkcję main w 12 linii programu ("by 0x80483F6: main (przyklad1.c:12)"). Następnie błąd jest dokładnie scharakteryzowany, włącznie z podaniem numerów linii w której wystąpiła alokacja bloku pamięci w który nie trafiliśmy.
#include <stdlib.h> void f(int a) { if (a <= 0) return ; int* x = malloc(a * sizeof(int)); x[1] = 665;//to sie nie uda gdy a=1 f(a-1); return ; } int main(void) { f(3); return 0; }Valgrind z taką złośliwością również sobie radzi i informuje o tym użytkownika w sposób w miarę zrozumiały:
==2665== Memcheck, a memory error detector. ==2665== Copyright (C) 2002-2005, and GNU GPL'd, by Julian Seward et al. ==2665== Using LibVEX rev 1471, a library for dynamic binary translation. ==2665== Copyright (C) 2004-2005, and GNU GPL'd, by OpenWorks LLP. ==2665== Using valgrind-3.1.0, a dynamic binary instrumentation framework. ==2665== Copyright (C) 2000-2005, and GNU GPL'd, by Julian Seward et al. ==2665== For more details, rerun with: -v ==2665== ==2665== Invalid write of size 4 ==2665== at 0x80483A9: f (przyklad2.c:8) ==2665== by 0x80483BA: f (przyklad2.c:9) ==2665== by 0x80483BA: f (przyklad2.c:9) ==2665== by 0x80483D8: main (przyklad2.c:15) ==2665== Address 0x414F0A4 is 0 bytes after a block of size 4 alloc'd ==2665== at 0x401B495: malloc (in /usr/lib/valgrind/x86-linux/vgpreload_memcheck.so) ==2665== by 0x804839F: f (przyklad2.c:7) ==2665== by 0x80483BA: f (przyklad2.c:9) ==2665== by 0x80483BA: f (przyklad2.c:9) ==2665== by 0x80483D8: main (przyklad2.c:15) ==2665== ==2665== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 13 from 1) ==2665== malloc/free: in use at exit: 24 bytes in 3 blocks. ==2665== malloc/free: 3 allocs, 0 frees, 24 bytes allocated. ==2665== For counts of detected errors, rerun with: -v ==2665== searching for pointers to 3 not-freed blocks. ==2665== checked 48,540 bytes. ==2665== ==2665== LEAK SUMMARY: ==2665== definitely lost: 24 bytes in 3 blocks. ==2665== possibly lost: 0 bytes in 0 blocks. ==2665== still reachable: 0 bytes in 0 blocks. ==2665== suppressed: 0 bytes in 0 blocks. ==2665== Use --leak-check=full to see details of leaked memory.Napisał nam co kogo wywołuje i w której linijce. Dodatkowo, całkiem słusznie zauważył, że nie zwolniliśmy pamięci.
#include <stdlib.h> void f(void) { int* x=malloc(5*sizeof(int)); x[1] = 0; // problem: nie zwalniamy pamięci } int main(void) { f(); return 0; }Valgrind oczywiście to odnotowuje, pokazując nawet linię w której wystąpił niesparowany malloc:
==2780== Memcheck, a memory error detector. ==2780== Copyright (C) 2002-2005, and GNU GPL'd, by Julian Seward et al. ==2780== Using LibVEX rev 1471, a library for dynamic binary translation. ==2780== Copyright (C) 2004-2005, and GNU GPL'd, by OpenWorks LLP. ==2780== Using valgrind-3.1.0, a dynamic binary instrumentation framework. ==2780== Copyright (C) 2000-2005, and GNU GPL'd, by Julian Seward et al. ==2780== For more details, rerun with: -v ==2780== ==2780== ==2780== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 13 from 1) ==2780== malloc/free: in use at exit: 20 bytes in 1 blocks. ==2780== malloc/free: 1 allocs, 0 frees, 20 bytes allocated. ==2780== For counts of detected errors, rerun with: -v ==2780== searching for pointers to 1 not-freed blocks. ==2780== checked 48,540 bytes. ==2780== ==2780== LEAK SUMMARY: ==2780== definitely lost: 20 bytes in 1 blocks. ==2780== possibly lost: 0 bytes in 0 blocks. ==2780== still reachable: 0 bytes in 0 blocks. ==2780== suppressed: 0 bytes in 0 blocks. ==2780== Use --leak-check=full to see details of leaked memory.
#include <stdlib.h> void f(void) { int* x = malloc(10 * sizeof(int)); int* y; y = x; free(x); free(y); //zwalniamy drugi raz } int main(void) { f(); return 0; }Znów Valgrind pokazuje dokładnie w którym miejscu było nieszczęsne free:
==2817== Memcheck, a memory error detector. ==2817== Copyright (C) 2002-2005, and GNU GPL'd, by Julian Seward et al. ==2817== Using LibVEX rev 1471, a library for dynamic binary translation. ==2817== Copyright (C) 2004-2005, and GNU GPL'd, by OpenWorks LLP. ==2817== Using valgrind-3.1.0, a dynamic binary instrumentation framework. ==2817== Copyright (C) 2000-2005, and GNU GPL'd, by Julian Seward et al. ==2817== For more details, rerun with: -v ==2817== ==2817== Invalid free() / delete / delete[] ==2817== at 0x401C1B9: free (in /usr/lib/valgrind/x86-linux/vgpreload_memcheck.so) ==2817== by 0x80483E4: f (przyklad4.c:9) ==2817== by 0x80483FB: main (przyklad4.c:14) ==2817== Address 0x414F028 is 0 bytes inside a block of size 40 free'd ==2817== at 0x401C1B9: free (in /usr/lib/valgrind/x86-linux/vgpreload_memcheck.so) ==2817== by 0x80483D9: f (przyklad4.c:8) ==2817== by 0x80483FB: main (przyklad4.c:14) ==2817== ==2817== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 13 from 1) ==2817== malloc/free: in use at exit: 0 bytes in 0 blocks. ==2817== malloc/free: 1 allocs, 2 frees, 40 bytes allocated. ==2817== For counts of detected errors, rerun with: -v ==2817== No malloc'd blocks -- no leaks are possible.
#include <stdlib.h> void f(void) { int a,b; // problem: korzystamy z wartosci niezainicjalizowanej zmiennej if (b != a) printf("Karramba!!\n"); else printf("Jest gorzej niz myslisz!!\n"); } int main(void) { f(); return 0; }Wykonując powyższy kod dostaniemy komunikat:
==2864== Memcheck, a memory error detector. ==2864== Copyright (C) 2002-2005, and GNU GPL'd, by Julian Seward et al. ==2864== Using LibVEX rev 1471, a library for dynamic binary translation. ==2864== Copyright (C) 2004-2005, and GNU GPL'd, by OpenWorks LLP. ==2864== Using valgrind-3.1.0, a dynamic binary instrumentation framework. ==2864== Copyright (C) 2000-2005, and GNU GPL'd, by Julian Seward et al. ==2864== For more details, rerun with: -v ==2864== ==2864== Conditional jump or move depends on uninitialised value(s) ==2864== at 0x8048390: f (przyklad5.c:7) ==2864== by 0x80483C2: main (przyklad5.c:15) Karramba!! ==2864== ==2864== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 13 from 1) ==2864== malloc/free: in use at exit: 0 bytes in 0 blocks. ==2864== malloc/free: 0 allocs, 0 frees, 0 bytes allocated. ==2864== For counts of detected errors, rerun with: -v ==2864== No malloc'd blocks -- no leaks are possible.Na ekran wypisało się "Karramba", czyli wartości a i b były różne (wbrew naiwnej intuicji, która podpowiadałaby, że oba będą zerami, lub chociaż ich wartości będą równe).
#include <stdlib.h> #include <unistd.h> int main(void) { char* napis= malloc(10); write(1,napis,10); return 0; }Valgrind znów pokonał naszą drobną złośliwość i wskazał nielegalną instrukcję:
==2899== Memcheck, a memory error detector. ==2899== Copyright (C) 2002-2005, and GNU GPL'd, by Julian Seward et al. ==2899== Using LibVEX rev 1471, a library for dynamic binary translation. ==2899== Copyright (C) 2004-2005, and GNU GPL'd, by OpenWorks LLP. ==2899== Using valgrind-3.1.0, a dynamic binary instrumentation framework. ==2899== Copyright (C) 2000-2005, and GNU GPL'd, by Julian Seward et al. ==2899== For more details, rerun with: -v ==2899== ==2899== Syscall param write(buf) points to uninitialised byte(s) ==2899== at 0x40E0828: write (in /lib/libc-2.3.5.so) ==2899== by 0x404C554: __libc_start_main (in /lib/libc-2.3.5.so) ==2899== Address 0x414F028 is 0 bytes inside a block of size 10 alloc'd ==2899== at 0x401B495: malloc (in /usr/lib/valgrind/x86-linux/vgpreload_memcheck.so) ==2899== by 0x80483CF: main (przyklad6.c:5) ==2899== ==2899== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 13 from 1) ==2899== malloc/free: in use at exit: 10 bytes in 1 blocks. ==2899== malloc/free: 1 allocs, 0 frees, 10 bytes allocated. ==2899== For counts of detected errors, rerun with: -v ==2899== searching for pointers to 1 not-freed blocks. ==2899== checked 48,540 bytes. ==2899== ==2899== LEAK SUMMARY: ==2899== definitely lost: 10 bytes in 1 blocks. ==2899== possibly lost: 0 bytes in 0 blocks. ==2899== still reachable: 0 bytes in 0 blocks. ==2899== suppressed: 0 bytes in 0 blocks. ==2899== Use --leak-check=full to see details of leaked memory.
valgrind --tool=cachegrind ./konstantynopolitanczykowianeczkao ile valgrind znajduje się w odpowiednim katalogu. Należy jednak pamiętać, o włączeniu wszystkich optymalizacji, ponieważ istotne jest zaobserwować jak będzie się zachowywać program w rzeczywistym środowisku.
-g
).
Dostępy poza zaalokowany obszar pamięci często powodują dziwne błędy -- program zamiast
zakończyć działanie nadpisuje zawartość innej zmiennej, i trudno jest zidentyfikować
źródło problemu. Electric fence każdą alokację umieszcza pod koniec (lub na początku)
sprzętowej strony pamięci, tak że każde wykroczenie za (odpowiednio przed) zaalokowany
obszar spowoduje wygenerowanie wyjątku przez procesor. Ponieważ strony pamięci
(przynajmniej na procesorach rodziny x86) muszą mieć rozmiar będący wielokrotnością
4096 bajtów, nie ma możliwości ochrony przed oboma tymi przypadkami jednocześnie,
na szczęście jednak po włączeniu electric fence przekroczenia w drugą stronę nadpisywać
będą bezużyteczne bajty, nie inne zmienne.
Electric fence zajmuje się głównie czytaniem i pisaniem kilka bajtów poza zaalokowanym obszarem,
nie zajmuje się innymi rodzajami błędnego dostępu.
Weźmy prosty program z błędem off by one:
#include <stdlib.h> #define SZ 100 int main() { int *foo; int i; foo = malloc (sizeof(int) * SZ); for(i=0; i<=SZ; i++) foo[i] = 0; return 0; }Chociaż program zawiera błąd, prawdopodobnie uruchomi się prawidłowo, ponieważ malloc alokuje zwykle kilka bajtów więcej niż jest to żądane. Sam gdb nic nie znajduje, jednak gdb w połączeniu z electric fence powoduje otrzymanie sygnału i możemy sprawdzić co spowodowało błąd. (komenda gdb efence pojawi się dopiero po skopiowaniu do pliku .gdbinit odpowiedniego fragmentu rozpowszechnianego razem z electric fence).
$ gdb ./foo GNU gdb 6.1-debian Copyright 2004 Free Software Foundation, Inc. GDB is free software, covered by the GNU General Public License, and you are welcome to change it and/or distribute copies of it under certain conditions. Type "show copying" to see the conditions. There is absolutely no warranty for GDB. Type "show warranty" for details. This GDB was configured as "i386-linux"...Using host libthread_db library "/lib/tls/libthread_db.so.1". (gdb) r Starting program: /home/taw/foo Program exited normally. (gdb) efence Enabled Electric Fence (gdb) r Starting program: /home/taw/foo [Thread debugging using libthread_db enabled] [New Thread 1073830880 (LWP 3227)] Electric Fence 2.1 Copyright (C) 1987-1998 Bruce Perens. Program received signal SIGSEGV, Segmentation fault. [Switching to Thread 1073830880 (LWP 3227)] 0x0804839f in main () at foo.c:11 11 foo[i] = 0; (gdb) p i $1 = 100
mkdir ~/Ksymoops cd ~/Ksymoops wget http://www.kernel.org/pub/linux/utils/kernel/ksymoops/v2.4/ksymoops-2.4.9.tar.gz tar xzf ksymoops-2.4.9.tar.gz cd ksymoops-2.4.9 make make install # jako root
Unable to handle kernel paging request at virtual address 00003615 c0116a3b *pde = 00000000 Oops: 0000 CPU: 0 EIP: 0010:[<c0116a3b>] Not tainted Using defaults from ksymoops -t elf32-i386 -a i386 EFLAGS: 00010082 eax: c32b332c ebx: 00003615 ecx: 00000001 edx: 00000001 esi: c32b332c edi: 00000001 ebp: c2b37e9c esp: c2b37e80 ds: 0018 es: 0018 ss: 0018 Process ifconfig (pid: 897, stackpage=c2b37000) Stack: 00000001 00000001 00000001 00000282 c3e39380 c556c560 00001002 00000000 c01f5295 c10f55d0 c4d00800 c5546bc0 c3e39380 c01f4828 c3e39380 c4a639c0 c01f5982 c4a639c0 00000000 00000030 00000009 c601d369 c4a639c0 c556c400 Call Trace: [<c01f5295>] [<c01f4828>] [<c01f5982>] [<c601d369>] [<c01f96f5>] [<c01fa879>] [<c01f91df>] [<c022b340>] [<c01f2500>] [<c01f2526>] [<c0146380>] [<c010734f>] Code: 8b 13 0f 0d 02 39 c3 74 16 8b 4b fc 8b 01 85 c7 75 19 8b 02 >>EIP; c0116a3b <__wake_up+1b/70> <===== >>eax; c32b332c <_end+2fb1994/5d196e8> >>esi; c32b332c <_end+2fb1994/5d196e8> >>ebp; c2b37e9c <_end+2836504/5d196e8> >>esp; c2b37e80 <_end+28364e8/5d196e8> Trace; c01f5295 <sock_def_write_space+75/80> Trace; c01f4828 <sock_wfree+48/50> Trace; c01f5982 <__kfree_skb+42/150> Trace; c601d369 <[sis900]sis900_close+99/c0> Trace; c01f96f5 <dev_close+c5/d0> Trace; c01fa879 <dev_change_flags+129/140> Trace; c01f91df <dev_get+f/20> Trace; c022b340 <devinet_ioctl+290/610> Trace; c01f2500 <sock_ioctl+0/30> Trace; c01f2526 <sock_ioctl+26/30> Trace; c0146380 <sys_ioctl+b0/1b0> Trace; c010734f <system_call+33/38> Code; c0116a3b <__wake_up+1b/70> 00000000 <_EIP>: Code; c0116a3b <__wake_up+1b/70> <===== 0: 8b 13 mov (%ebx),%edx <===== Code; c0116a3d <__wake_up+1d/70> 2: 0f 0d 02 prefetch (%edx) Code; c0116a40 <__wake_up+20/70> 5: 39 c3 cmp %eax,%ebx Code; c0116a42 <__wake_up+22/70> 7: 74 16 je 1f <_EIP+0x1f> Code; c0116a44 <__wake_up+24/70> 9: 8b 4b fc mov 0xfffffffc(%ebx),%ecx Code; c0116a47 <__wake_up+27/70> c: 8b 01 mov (%ecx),%eax Code; c0116a49 <__wake_up+29/70> e: 85 c7 test %eax,%edi Code; c0116a4b <__wake_up+2b/70> 10: 75 19 jne 2b <_EIP+0x2b> Code; c0116a4d <__wake_up+2d/70> 12: 8b 02 mov (%edx),%eax </OOPS>
mkdir ~/UML mkdir ~/UML/archives mkdir ~/UML/kernel mkdir ~/UML/tools cd ~/UML/archives wget http://www.kernel.org/pub/linux/kernel/v2.4/linux-2.4.31.tar.bz2 wget http://heanet.dl.sourceforge.net/sourceforge/user-mode-linux/uml-patch-2.4.27-1.bz2 cd ~/UML/kernel tar xjf ../archives/linux-2.4.31.tar.bz2 cd linux-2.4.31 bzcat2 ~/UML/archives/uml-patch-2.4.27-1.bz2 | patch -p1 make menuconfig ARCH=um # (Bedziemy zadowoleni z default-owej konfiguracji) make linux ARCH=um strip linux # (Aby zmniejszyc rozmiar) # Instalacja UML tools cd ~UML/tmp cvs -d:pserver:anonymous@www.user-mode-linux.org:/cvsroot/user-mode-linux -z3 co tools cd tools # Warto zmienic w Makefile BIN_DIR na /opt/uml_tools/bin oraz LIB_DIR na /opt/uml_tools/lib make make install # Jako root
CONFIG_DEBUGSYM
- Enable Kernel debugging symbols -
kompilacja jądra z opcją -gCONFIG_PT_PROXY
- Kernel ptrace proxy - wspomaganie dla
gdb, przy śledzeniu wątków systemowychgdb ./linuxJest to najprostszy sposób odpliskwiania jądra, lecz nie daje nam zbyt dużo możliwości. Możemy używać tylko standardowych komend gdb, co może być niewsytarczająco wygodne w użyciu.
./linux debugUruchomi nam się dodatkowe okienko z otwartym gdb, po czym uruchomi się jądro i od razu zatrzyma. Poza standardowymi komendami gdb, posiadamy przydatną opcję: w każdym momencie działania jądra możemy nacisnąc CTRL+C (w oknie gdb) co zatrzyma nam działanie jądra.
kill -USR1 PIDUruchomi nam się okienko gdb i będziemy mogli odpluskwiać jądra w sposób analogiczny do wcześniejszego.
mkdir ~/Kgdb mkdir ~/Kgdb/archives mkdir ~/Kgdb/kernel cd ~/Kgdb/archives wget http://www.kernel.org/pub/linux/kernel/v2.4/linux-2.4.31.tar.bz2 wget http://kgdb.linsyssoft.com/downloads/linux-2.4.23-kgdb-1.9.patch cd ~/Kgdb/kernel tar xjf ../archives/linux-2.4.31.tar.bz2 cd linux-2.4.31 cat ~/gdb/archives/linux-2.4.23-kgdb-1.9.patch | patch -p1 make menuconfig # dokonujemy odpowiedniej konfiguracji make dep make bzImageKopiujemy następnie nowe jądro na maszynę "test":
rcp System.map testmach:/boot/System.map-2.4.31-kgdb rcp arch/i386/boot/bzImage testmach:/boot/vmlinuz-2.4.31-kgdbNa maszynie "test" dodajemy odpowiedni wpis dla lilo:
image=/boot/vmlinuz-2.4.31-kgdb label=kgdb read-only root=/dev/hda1 append="gdb gdbttyS=1 gdbbaud=115200"lub dla gruba:
title 2.4.31 kgdb root (hd0,0) kernel /boot/vmlinuz-2.4.31-kgdb ro root=/dev/hda1 rootfstype=ext3 kgdbwait kgdb8250=1,57600
CONFIG_KGDB
- Remote (serial) kernel debugging with gdb -
możliwość odpluskwiania przez łącze szeregoweCONFIG_KGDB_THREAD
- Thread analysis - pomocne przy
odpluskwianiu wątkówCONFIG_GDB_CONSOLE
- Console messages through gdb -
wiadomości z konsoli są przekazywane za pomocą gdbWaiting for connection from remote gdb...Należy teraz uruchomić gdb na maszynie "development":
gdb vmlinux # w katalogu ze zrodlami
Pojawi się komunikat:
GNU gdb 20000204 Copyright 2000 Free Software Foundation, Inc. GDB is free software, covered by the GNU General Public License, and you are welcome to change it and/or distribute copies of it under certain conditions. Type "show copying" to see the conditions. There is absolutely no warranty for GDB. Type "show warranty" for details. This GDB was configured as "i686-pc-linux-gnu"... (gdb)W oknie gdb wpisujemy:
shell echo -e "\003" > /dev/ttyS1 set remotebaud 115200 target remote /dev/ttyS1Pojawi się komunikat:
Remote debugging using /dev/ttyS1 breakpoint () at gdbstub.c:1153 1153 }Teraz jesteśmy podłączeni do jądra na maszynie "test" i możemy je odpluskwiać.
init/main.c
) dodając tam wpis:
#ifdef CONFIG_KGDB #include <linux/kgdb.h> #endif ... asmlinkage void __init start_kernel(void) { ... #ifdef CONFIG_KGDB if (gdb_enter) { gdb_hook(); /* right at boot time */ } #endif ... }Co spowoduje wywołanie
gdb_hook()
podczas ładowania jądra.
Inicjalizacja połączenia szeregowego i przekazywanie komunikatów do gdb
opisane są w plikach:
drivers/char/gdbserial.c
drivers/char/serial.c
drivers/char/sh-sci.c
arch/i386/kernel/gdbstart.c
kernel/kgdbstub.c
drivers/char/gdbserial.c
):
int gdb_hook(void) { ... printk("Waiting for connection from remote gdb... "); breakpoint(); gdb_null(); printk("Connected.\n"); gdb_initialized = 1; return(0); }
Możliwość | KD | NTSD | Windbg | Visual Studio .NET |
Tryb jądra | + | - | + | - |
Tryb użytkownika | - | + | + | + |
Zdalne odpluskwianie | + | + | + | + |
Podpięcie do procesu | + | + | + | + |
Odpięcie od procesu | + | + | + | + |
Opdluskwianie SQL | - | - | - | + |
File
opcje Open Crash Dump
. Teraz
musimy odszukać nasz plik "dump". Standardowo Windows zapisuje je w
%systemroot%/minidump
. Pliki te posiadają unikalną nazwę np.
Mini121404-01.dmp
. Jest to pierwszy plik "dump" utworzony 14
grudnia 2004 roku. Otwieramy plik. Przykładowy efekt:
Symbol search path is: SRV*c:\symbols*http://msdl.microsoft.com/download/symbols Microsoft ® Windows Debugger Version 6.3.0017.0 Copyright © Microsoft Corporation. All rights reserved. Loading Dump File [C:WINDOWSMinidumpMini061904-01.dmp] Mini Kernel Dump File: Only registers and stack trace are available Symbol search path is: SRV*c:\symbols*http://msdl.microsoft.com/download/symbols Executable search path is: Windows XP Kernel Version 2600 (Service Pack 1) UP Free x86 compatible Product: WinNt, suite: TerminalServer SingleUserTS Built by: 2600.xpsp2.030422-1633 Kernel base = 0x804d4000 PsLoadedModuleList = 0x80543530 Debug session time: Sat Jun 19 19:06:57 2004 System Uptime: 0 days 1:03:36.951 Loading Kernel Symbols .................................................................................................................................... Loading unloaded module list .......... Loading User Symbols ******************************************************************************* Bugcheck Analysis ******************************************************************************* Use !analyze -v to get detailed debugging information. BugCheck 86427532, {1db, 2, 3, b} Unable to load image pavdrv51.sys, Win32 error 2 *** WARNING: Unable to verify timestamp for pavdrv51.sys *** ERROR: Module load completed but symbols could not be loaded for pavdrv51.sys Probably caused by : pavdrv51.sys Followup: MachineOwnerMożemy łatwo zauważyć, że kod błędu to
86427532, {1db, 2, 3, b}
i został on spowodowany przez pavdrv51.sys
. Dzięki temu mamy
informację o sterowniku/programie, który prawdopodobnie jest wadliwy i
należy dokonać jego aktualizacji. Do usunięcia usterki pomocna również
będzie wyszukiwarka Google.
File
wybrać Symbol File
Path
i wpisać tam:
SRV*c:\symbols*http://msdl.microsoft.com/download/symbolswówczas symbole będą ściągane do katalogu c:\symbols. Należy jednak być wtedy cierpliwym, ponieważ niektóre pliki z symbolami zajmuja dużo miejsca (przykładowo 10 MB).
Opis | Komenda |
Krok w przód | p |
Kontynuacja programu | g |
Lista breakpoint-ow | Bl |
Założenie breakpoint-a | bp |
Wyłączenie breakpoint-a | bd |
Włączenie breakpoint-a | be |
Wyczyszczenie breakpoint-a | bc |
Komentarz | * |
Wyświetl zmienną | dv |
lm
- pojawi się lista modułów zaladowanych przez notatnik:
0:001> lm start end module name 01000000 01036000 notepad (deferred) 10000000 10030000 asOEHook (deferred) 59410000 595da000 AcGenral (deferred) 5b1d0000 5b208000 UxTheme (deferred) 5cfe0000 5d006000 ShimEng (deferred) 72f90000 72fb6000 WINSPOOL (deferred) 746d0000 7471b000 MSCTF (deferred) 76380000 763c9000 comdlg32 (deferred) 769a0000 76a54000 USERENV (deferred) 76b20000 76b4e000 WINMM (deferred) 77110000 7719c000 OLEAUT32 (deferred) 773c0000 774c2000 COMCTL32 (deferred) 774d0000 7760c000 ole32 (deferred) 77bd0000 77be5000 MSACM32 (deferred) 77bf0000 77bf8000 VERSION (deferred) 77c00000 77c58000 msvcrt (deferred) 77d30000 77dc0000 USER32 (deferred) 77dc0000 77e6c000 ADVAPI32 (deferred) 77e70000 77f01000 RPCRT4 (deferred) 77f10000 77f56000 GDI32 (deferred) 77f60000 77fd6000 SHLWAPI (deferred) 7c000000 7c054000 MSVCR70 (deferred) 7c800000 7c8fb000 kernel32 (deferred) 7c900000 7c9b2000 ntdll (export symbols) C:\WINDOWS\system32\ntdll.dll 7c9c0000 7e791000 SHELL32 (deferred)
.reload /f
(jeżeli
korzystamy z automatycznego pobierania symboli, może to troche potrwać).
Warto przed tym krokiem sprawdzić, że mamy dobrze ustawioną ścierzkę do
katalogu z symbolami.tart end module name 01000000 01036000 notepad (pdb symbols) c:\symbols\notepad.pdb\F679AEF8BE1F44CAB4CBC4B52D77241B1\notepad.pdb 10000000 10030000 asOEHook (export symbols) C:\PROGRA~1\COMMON~1\SYMANT~1\ANTISPAM\asOEHook.dll 59410000 595da000 AcGenral (pdb symbols) c:\symbols\AcGenral.pdb\8EAA70A85F8D4628BF68C27E0707DDBC1\AcGenral.pdb 5b1d0000 5b208000 UxTheme (pdb symbols) c:\symbols\uxtheme.pdb\B982B8FE390B4359AD3CCECC16C0D59F2\uxtheme.pdb 5cfe0000 5d006000 ShimEng (pdb symbols) c:\symbols\ShimEng.pdb\AAD8C3E52C274CD6A0604971A64C44721\ShimEng.pdb 72f90000 72fb6000 WINSPOOL (pdb symbols) c:\symbols\winspool.pdb\97A6ECC94EA7450CA7D375BD9DFFCA5E2\winspool.pdb 746d0000 7471b000 MSCTF (pdb symbols) c:\symbols\msctf.pdb\27CE4025AEE44B569B9ED28F7B4E15E32\msctf.pdb 76380000 763c9000 comdlg32 (pdb symbols) c:\symbols\comdlg32.pdb\4FCBEAD63D7345998C1F92D8DBB0DC272\comdlg32.pdb 769a0000 76a54000 USERENV (export symbols) C:\WINDOWS\system32\USERENV.dll 76b20000 76b4e000 WINMM (pdb symbols) c:\symbols\winmm.pdb\4FC9F179964745CAA3C78D6FADFC28322\winmm.pdb 77110000 7719c000 OLEAUT32 (pdb symbols) c:\symbols\oleaut32.pdb\149FB0C830BC400DBA99728EFB58A1132\oleaut32.pdb 773c0000 774c2000 COMCTL32 (pdb symbols) c:\symbols\MicrosoftWindowsCommon-Controls-6.0.2600.2180-comctl32.pdb\ C454919C031643618F4CAC675CBC64401\MicrosoftWindowsCommon-Controls-6.0.2600.2180-comctl32.pdb 774d0000 7760c000 ole32 (pdb symbols) c:\symbols\ole32.pdb\092F43621A1A4763AF651D154C2AEEE02\ole32.pdb 77bd0000 77be5000 MSACM32 (pdb symbols) c:\symbols\msacm32.pdb\07D5460BEF0E4FD4964C7091C8860BF82\msacm32.pdb 77bf0000 77bf8000 VERSION (pdb symbols) c:\symbols\version.pdb\180A90C40384463E82DDC45B2C8AB76E2\version.pdb 77c00000 77c58000 msvcrt (pdb symbols) c:\symbols\msvcrt.pdb\A678F3C30DED426B839032B996987E381\msvcrt.pdb 77d30000 77dc0000 USER32 (pdb symbols) c:\symbols\user32.pdb\036A117A6A5C43DE835AE71302E905042\user32.pdb 77dc0000 77e6c000 ADVAPI32 (pdb symbols) c:\symbols\advapi32.pdb\455D6C5F184D45BBB5C5F30F829751142\advapi32.pdb 77e70000 77f01000 RPCRT4 (pdb symbols) c:\symbols\rpcrt4.pdb\BEA45A721DA141DAA3BA86B3A20311532\rpcrt4.pdb 77f10000 77f56000 GDI32 (pdb symbols) c:\symbols\gdi32.pdb\1FA0F418684D4EFA9F8447E4192B18522\gdi32.pdb 77f60000 77fd6000 SHLWAPI (pdb symbols) c:\symbols\shlwapi.pdb\0CF120C0AC874F06B64256D96DCFEB1E2\shlwapi.pdb 7c000000 7c054000 MSVCR70 (pdb symbols) c:\symbols\msvcr70.pdb\3295127CD51C4935A617363ABFE9B8A02\msvcr70.pdb 7c800000 7c8fb000 kernel32 (pdb symbols) c:\symbols\kernel32.pdb\FB334FB28FA34128BDE9229285BE4C2F2\kernel32.pdb 7c900000 7c9b2000 ntdll (pdb symbols) c:\symbols\ntdll.pdb\36515FB5D04345E491F672FA2E2878C02\ntdll.pdb 7c9c0000 7e791000 SHELL32 (pdb symbols) c:\symbols\shell32.pdb\2087DF0BBCC84D3F926626937555E4D52\shell32.pdb
kernel32!CreateFileW
poleceniem bp
kernel32!CreateFileW
po czym za pomoca polecenia g
wznawiamy pracę notatnika.0:001> bp kernel32!CreateFileW 0:001> g ModLoad: 77b30000 77b52000 C:\WINDOWS\system32\appHelp.dll ModLoad: 76fc0000 7703f000 C:\WINDOWS\system32\CLBCATQ.DLL ModLoad: 77040000 7710d000 C:\WINDOWS\system32\COMRes.dll ModLoad: 77a10000 77a65000 C:\WINDOWS\System32\cscui.dll ModLoad: 765d0000 765ed000 C:\WINDOWS\System32\CSCDLL.dll ModLoad: 75f50000 7604c000 C:\WINDOWS\system32\browseui.dll ModLoad: 00c40000 00f35000 C:\WINDOWS\system32\SETUPAPI.dll Breakpoint 0 hit eax=40000000 ebx=0007d338 ecx=00000000 edx=0000111c esi=7c8092ac edi=0007d358 eip=7c810976 esp=0007d318 ebp=0007d378 iopl=0 nv up ei pl nz na po nc cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000206 kernel32!CreateFileW: 7c810976 8bff mov edi,edi
kb
:
0:000> kb ChildEBP RetAddr Args to Child 0007d314 77e84f42 0007d338 c0000000 00000003 kernel32!CreateFileW 0007d378 77e84719 000ab768 000bc9c0 00111c3e RPCRT4!NMP_Open+0x17a 0007d3c4 77e84c83 00000001 000bc9c0 000aaa50 RPCRT4!OSF_CCONNECTION::TransOpen+0x5e 0007d418 77e84e0d 000bcad8 000dbba0 00000000 RPCRT4!OSF_CCONNECTION::OpenConnectionAndBind+0xbc 0007d45c 77e84d5c 00000000 0007d548 000bcad8 RPCRT4!OSF_CCALL::BindToServer+0xed 0007d4c0 77e800ac 0007d4e4 00000000 00000000 RPCRT4!OSF_BINDING_HANDLE::AllocateCCall+0x2b0 0007d4f0 77e78dc9 00000000 0007d574 00000001 RPCRT4!OSF_BINDING_HANDLE::NegotiateTransferSyntax+0x28 0007d508 77e78e00 0007d548 00000000 0007d528 RPCRT4!I_RpcGetBufferWithObject+0x5b 0007d518 77e7942d 0007d548 0007d924 0007d908 RPCRT4!I_RpcGetBuffer+0xf 0007d528 77ef360b 0007d574 0000002c 000bcad8 RPCRT4!NdrGetBuffer+0x28 0007d908 77dd1cd0 77dcf370 77dd1374 0007d924 RPCRT4!NdrClientCall2+0x195 0007d91c 77dd1c52 00000000 0007d990 00000800 ADVAPI32!LsarOpenPolicy2+0x1b 0007d970 77decb54 00000000 0007d990 00000800 ADVAPI32!LsaOpenPolicy+0x95 0007d9c4 00c48feb 00000000 00c48ffc 00cbe3a8 ADVAPI32!LookupPrivilegeValueW+0x66 0007d9e4 00c4a005 00000000 0007dae0 00000000 SETUPAPI!EnablePnPPrivileges+0x2d 0007da14 00c49068 00000000 00000000 0007da38 SETUPAPI!PnPGetGlobalHandles+0x1d 0007da58 7ca025c6 0007dae0 7ca0264c 00000000 SETUPAPI!CM_Get_Device_Interface_List_Size_ExW+0x45 0007dad8 7ca02564 00000000 00000000 00000002 SHELL32!CMountPoint::_EnumVolumes+0xc5 0007daf0 7ca0243d 00000000 7cbbc5c8 80004005 SHELL32!CMountPoint::_InitLocalDriveHelper+0x52 0007db10 7ca02509 00000000 7cbbc5c8 00000000 SHELL32!CMountPoint::_InitLocalDrives+0xc8
bc 0
gdzie 0 oznacza numer breakpoint-a. Listę wszystkich breakpoint-ów można wyświetlić poleceniem bl
:
0:000> bl 0 e 7c810976 0001 (0001) 0:**** kernel32!CreateFileW 0:000> bc 0
g
. W
każdej chwili możemy zatrzymać jego działanie skrótem klawiszowym
CTRL+BREAK
.