Strona główna. |
Poprzedni rozdział: Techniki odpluskwiania jądra: ksymoops, kgdb, UML. |
Zacznijmy od miejsca w systemie Windows, w którym można ustawić kilka istotnych dla debugowania opcji dotyczących zachowania systemu podczas startu (poprzez modyfikację pliku boot.ini) i po wystąpieniu krytycznego błędu systemu (innymi słowy awarii systemu). W dalszej części tekstu (szczególnie w poniższym Słowniczku), jeśli będzie mowa o możliwości skonfigurowania systemu Windows, dotyczyć to będzie właśnie okna: Panel Sterowania -> System -> Zaawansowane -> Uruchamianie i odzyskiwanie -> Ustawienia
Rysunek: Okna właściwości systemu i konfiguracji opcji dla uruchamiania i odzyskiwania w Windows XP.
Znajduje się tu kilka wyjaśnień stosowanych zwrotów, w stosunku do czasem popularniejszych odpowiedników w języku angielskim, wraz z dokładniejszym wyjaśnieniem tam, gdzie uznałem to za potrzebne, dla zrozumienia szczegółów pewnych rozwiązań w Windowsie. Czytanie wszystkich wyjaśnień nie jest konieczne dla osób zaznajomionych z systemem Windows "od środka" lub takich, którym wystarczy intuicyjne znaczenie zawartych tu zwrotów. Nie jest to słownik alfabetyczny. Zalecam jego przeczytanie od początku do końca, gdyż kolejne definicje z zasady korzystają z informacji podanych jedynie przed nimi (w innym przypadku jest to wyraźnie zaznaczone).
KeBugCheck
lub KeBugCheckEx
)
powoduje stworzenie pliku zrzutu pamięci (o ile tak został
skonfigurowany system Windows), przydatnego dla późniejszego
znalezienia powodu powstania problemu.
SHELL_SYSMODAL_Message
,
nie wstrzymuje działania systemu - zazwyczaj użytkownik może kontynuować
pracę w systemie (mimo jego niestabilnego stanu).
Topologia systemu Windows różni się znacznie od znanej nam topologii Linux'a. W dodatku dla zwykłego użytkownika dostęp do kodu Windowsa jest bardzo ograniczony. Dlatego w naszej analizie narzędzi Windowsa opieramy się w dużej mierze na informacjach teoretycznych znalezionych w Internecie, a nie na informacjach praktycznie przetestowanych. Przedstawimy jedynie najbardziej podstawowe możliwości tych narzędzi, możliwe do zrozumienia dla użytkowników, którzy nie przeszli specjalistycznych kursów programowania w systemie Windows. Część ograniczeń wynika z komercyjności pakietów dla developerów (w szczególności Windows Driver Development Kit (DDK), który jest niezbędny w procesie tworzenia sterowników dla Windowsa), które są jedynymi źródłami kilku ważnych narzędzi. Część narzędzi wymaga zastosowania skomplikowanych rozwiązań sieciowych, które również były dla nas niedostępne dla przetestowania. W miarę możliwości przy opisach odwołamy się do podobieństwa narzędzi do odpluskwiania Windowsa ze znanymi nam rozwiązaniami z innych aplikacji czy rozwiązań Linux'a.
Wbrew pozorom Microsoft, po wejsciu na ich stronę dla Developerów (www.microsoft.com/whcd/DevTools), jawi się nam jako firma bardzo poważnie podchodząca do procesu powstawania oprogramowania. Zapewniają bowiem nie tylko narzędzia do odpluskwiania programów, czy samego systemu, ale również programy do znajdowania błędów i ulepszania kodu w trakcie wszystkich faz jego powstawania. Są to narzędzia, które przy odpowiednim użyciu zastępują odpluskwiacze.
Wspomnieć tu można krótko o trzech podstawowych rozwiązaniach dostępnych (po zainstalowaniu Windows DDK) dla programistów sterowników i testerów:
Podstawowe narzędzie do znajdowania problemów w sterownikach przydatne od samego początku ich tworzenia, kiedy jeszcze można je łatwo poprawić w stosunkowo krótkim czasie. Testuje stabilność działania dowolnego sterownika w bardzo ciężkich warunkach.
Statyczne narzędzie analizy kodu, które wykrywa błędy poprzez wykonywanie wszystkich możliwych ścieżek wykonań dla każdej funkcji (ciekawe, czy testuje wszystkie możliwe przeploty ;) ).
Dzięki temu narzędziu otrzymujemy informacje diagnostyczne o uruchomionym kodzie bez narzutu specjalnej kompilacji (z informacjami dla debugowania) i bez użycia odpluskwiacza.
Szczegółowe informacje na temat tych narzędzi znajdują się w kolejnym rozdziale tego dokumentu.
Dodatkowo, mamy mniejsze narzędzia, dla znajdowania błędów w użyciu pamięci:
Ustawia flagi, które umożliwiają nadawanie znaczników pulom pamięci, wykrywają przepełnienie bufora i określają znaczniki dla alokacji pamięci ze specjalnej puli jedynie dla jądra.
Sprawdza, czy wszystkie struktury danych, które muszą być w puli pamięci niestronicowanej, są tam rzeczywiście (dobre uzupełnienie Driver Verifier).
Zbierają i wyświetlają dane na temat alokacji pamięci. Śledzenie tych danych pozwala znaleźć przecieki pamięci.
Jako sposoby na ułatwienie odpluskwiania sterowników podaje się również stosowanie innych rozwiązań, takich jak:
ASSERT()
, które oblicza wyrażenie
i jeśli jego wynik jest fałszywy, powoduje wstrzymanie odpluskwiacza
(odpowiednik punktu kontrolnego na stałe zapisanego w kodzie).
Łatwo można w ten sposób zidentyfikować niepoprawne dane.
Przydatnym efektem ubocznym korzystania z tego makra jest swego rodzaju
dokumentacja kodu, ułatwiająca jego późniejszą analizę i modyfikację.
Działa jedynie dla plików skompilowanych z opcjami dla odpluskiwacza.
Zaleca się używanie tego makra m.in.:
Jeśli mowa o właściwym debugowaniu, Microsoft zawarł w swoim pakiecie dla Developerów (Windows DDK - niestety niedostępnym dla zwykłych klientów Windows XP) pakiet Debugging Tools for Windows (DTW, dostępny również oddzielnie, tym razem dla wszystkich, do ściągnięcia m.in. z wyżej wymienionego serwisu). W środku znajdziemy 4 programy do odpluskwiania:
Graficzny odpluskwiacz do debugowania zarówno kodu trybu użytkownika jak i trybu jądra.
Aplikacja konsolowa do debugowania sterowników jądra.
Aplikacja konsolowa do debugowania aplikacji trybu użytkownika i sterowników.
Odmiana CDB. Kopia tego programu znajduje sie również w katalogu system32 w katalogu systemu Windows.
Odpluskwiacze te mogą być uruchamiane na wszystkich systemach operacyjnych opartych na Windows NT. Mogą debugować wszystkie aplikacje, usługi i sterowniki, które działają pod tymi systemami. Standardowo nie obsługują Windows 95/98/Me (jedynie ograniczona obsługa trybu użytkownika, brak możliwości debugowania jadra - szczegóły odnośnie możliwości pakietu i procesu instalacji na tych systemach opisane są w dokumentacji). Dostępne są osobne wersje na architektury 32 i 64 bitowe (x86, Intel Itanium, x64). Odpowiednie pakiety są dostępne na stronach Microsoftu. Podobnie pakiety z symbolami, wykorzystywanymi przez odpluskwiacze.
Pakiet zawiera również dokumentację odpluskwiaczy, wraz z informacjami pomocnymi do debugowania różnych rodzajów sterowników i aplikacji, do tworzenia i analizy plików zrzutów pamięci, do uruchamiania zdalnej sesji debugowania i do użycia komend z rozszerzeń dla odpluskwiaczy. Oprócz tego jest w nim kilka mniej istotnych dodatkowych narzędzi do debugowania (ADPlus, GFlags, KDbgCtrl, Logger, itd.).
Wybór odpluskwiacza zależy od typu sterownika, który debugujemy i od docelowego systemu operacyjnego. WinDbg i KD są najbardziej powszechnie używane dla debugowania sterowników do systemów operacyjnych opartych na Windows NT. W dalszych rozdziałach skupimy się właśnie na tych dwóch programach.
Dodatkowo, odpluskwiacz dla Microsoft Visual Studio może być używany do debugowania programów działających w trybie użytkownika na wszystkie Windowsy. Szczegóły znajdują się w dokumentacji Visual Studio.
Po zainstalowaniu odpluskwiacze są dostępne poprzez zakładkę: Start -> Programy -> Debugging Tools for Windows.
Oprócz DTW, do efektywnego odpluskwiania potrzebne są:
Potężne narzędzie do testowania sterowników. Jest jednym z narzędzi używanych w teście Hardware Compatibility Test (HCT), do weryfikacji jakości sterownika. Aby sterownik otrzymał logo "Designed for Microsoft® Windows®", musi przejść ten test. Cechy programu:
Zapewnia testy i pułapki na wiele warunków, które mogą przejść niezauważone podczas normalnego użycia. Sprawdza czy sterownik nie powoduje wywołań funkcji, które są nielegalne, albo powodują błąd krytyczny systemu. Rozpoznaje warunki powstające podczas wadliwej obsługi pamięci, żądań I/O, nieprawidłowemu dostępowi do DMA, użycia bufora (pamięci podręcznej) czy możliwych zakleszczeń. Szczegóły:
Do użycia z linii komend lub poprzez GUI. Istnieje rozszerzenie odpluskwiacza jądra (dla KD i WinDbg) o nazwie "!verifier", które może służyć do monitorowania i raportowania statystyk związanych z tym narzędziem.
Instalowany (w %windir%/system32/verifier.exe) jest razem z wszystkimi wersjami MWS2003, Windows XP i Windows 2000, więc może być używany również przez zwykłych klientów Microsoftu, w celu znalezienia powodu problemów ze sterownikami.
Dokładna dokumentacja znajduje się w Windows DDK.
W wyniku jego działania dostajemy listę ostrzeżeń (zwykle przynajmniej początkowo długą, podobnie jak przy kompilacji z restrykcyjnymi opcjami jak 'pedantic' przy użyciu gcc) w oknie listy komunikatów (Message List). Listę tą możemy dynamicznie odfiltrowywać.
Rysunek: Okno listy komunikatów (Message List) (źródło: PREfast_steps_21.doc)
Po podwójnym kliknięciu na komunikacie otwiera się nam okno kodu źródłowego z nim powiązanego. Przyciskami 'Next' i 'Previous' można się poruszać w obie strony po kolejnych (następnych i poprzednich) ostrzeżeniach. Mamy również przyciski do pokazania całego kodu ("Show Entire File") i przejścia do linii, która jest powodem ostrzeżenia ("Warning Line").
Rysunek: Okno kodu źródłowego powiązanego z komunikatem (View Annotated Source) (źródło: PREfast_steps_21.doc)
Działa jedynie dla kodu skompilowanego dla 32bit na platformy x86.
Wykrywa klasy błędów, które są rzadko wykrywane przez typowe kompilatory. Dostępny w dwóch wersjach:
Analizuje kod źródłowy C i C++ poprzez przechodzenie wszystkich możliwych ścieżek wywołań funkcji i symulowanie ich wywołania dla znalezienia problemów przy obliczeniach dla każdej ścieżki. W rzeczywistości nie wywołuje kodu i nie potrafi znaleźć wszystkich możliwych błędów, ale potrafi znaleźć część błędów, które kompilator zignoruje, a dla odpluskwiacza mogą być trudne do znalezienia.
Wersja dla sterowników sprawdza specyficzne dla tych programów kwestie, takie jak:
Zaletą stosowania PREfast w procesie rozwoju kodu jest przede wszystkim
lepsza jakość sterownika, ale również większa wydajność pracy.
Istotne cechy, które mają na to wpływ:
prefast [/drivers] <polecenie kompilacji>
analizuje kod, każdą funkcję z osobna, tworzy plik logu,
po czym kompiluje kod tworząc zwykły plik obiektowy),
Znajduje błędy różnych kategorii, takich jak:
Często komunikaty, które programista otrzymuje od tego narzędzia sprawiają, że poprawienie błędu pozostaje rzeczą trywialną.
Zdarzenie to dowolne interesujące nas działanie w SO, przy czym szczególnie istotne są tu wyniki działań.
W każdym systemie operacyjnym są zdarzenia, które mogą być śledzone i logowane. Zazwyczaj są to operacje wejścia/wyjścia na dysku i błędy braku strony, lub zdefiniowane przez programistów zdarzenia specyficzne dla ich sterowników. W Linux'ie używaliśmy strace i ltrace do śledzenia wykonywania odpowiednio funkcji systemowych i bibliotecznych.
ETW - Event Tracing for Windows, bardzo efektywny mechanizm logowania przydatny zarówno dla trybu użytkownika jak i trybu jądra. Niestety, skomplikowany w użyciu.
Developerzy mogą implementować śledzenie w sterowniku, przy użyciu specjalnego preprocesora (WPP), który wzmacnia i uzupełnia śledzenie WMI (Windows Management Instrumentation), dodając mechanizmy ułatwiające śledzenie operacji sterownika.
WPP - programowy preprocesor do śledzenia dla Windowsa. Używany poprzez dodawanie specjalnych dyrektywy preprocesora C i makrodefinicji WPP do kodu sterownika urządzenia. Podczas śledzenia konwertuje binarne komunikaty otrzymywane w trybie rzeczywistym do formatu czytelnego dla człowieka.
Rysunek: główne okno WinGdb z widocznym paskiem narzędzi oraz "zadokowanymi" oknami komend (Command) i rejestrów (Registers)
Wiele podstawowych funkcji odpluskwiacza możliwych do uzyskania z linii komend, jest często dostępnych za pomocą kilku kliknięć myszką (najszybciej poprzez przyciski paska narzędzi głównego okna WinDbg lub opcję górnego menu tego okna), względnie użycia skrótu klawiszowego. Znacznie przyspiesza to debugowanie i z tego powodu najczęściej nie korzysta się bezpośrednio z KD, tylko właśnie z WinDbg. W dodatku zawsze można dowolną komendę KD wpisać w oknie komend. Jednak właśnie ta alternatywa przycisków i funkcji dostępnych z menu pozwala nam skupić się nie na złożonych składniowo instrukcjach KD, lecz na samej funkcjonalności odpluskwiacza. Dodatkowo korzystanie z okien jest intuicyjne i mamy możliwośc zmieniania ich układu na wiele sposobów.
Razem z DTW dostajemy nie tylko dokładną dokumentację całego pakietu, ale także tutorial szczegółowo opisujący, w jaki sposób należy korzystać z WinDbg. Z różnych powodów (których część była opisana wyżej) nie dane mi było w pełni przetestować możliwości tego odpluskwiacza, dlatego informacje poniżej są jedynie skrótem wspomnianego wyżej tutoriala uzupełnionego w kilku miejscach o dodatkowe informacje i komentarze (w tym też związane z problemami w debugowaniu). Należy przy tym pamiętać, że WinGdb to tylko nakładka graficzna na KD, więc większość rzeczy opisanych dalej powinno się dać wykonać również w KD (oprócz tych odziedziczonych po CDB).
Dla debugowania jądra standardowo potrzebne są dwa komputery (maszyna hosta i maszyna docelowa). Zazwyczaj łączy sie je kablem 1394 (szybka magistrala szeregowa - do 400Mbps, teoretycznie nawet na 63 systemy, niewielkie wymagania na docelowym systemie; pełny zrzut pamięci poprzez zwykły port szeregowy przy prędkości 115200bps zajmie kilka/kilkanaście godzin, podczas gdy poprzez 1394 tylko kilkadziesiąt sekund!). Lokalne debugowanie (gdzie odpluskwiacz sprawdza system na komputerze, na którym sam jest uruchomiony), jest bardzo ograniczone i wykorzystuje się je tylko w trybie użytkownika. Można również tworzyć bardziej złożone architektury, z:
.lsrcpath
)
Sesja debugowania jest procesem, w którym aplikacja odpluskwiacza (WinDbg lub KD) uruchomiona na hoście współpracuje z systemem operacyjnym działającym na maszynie docelowej. WinDbg jest jedynie aplikacją, która działa razem z systemem operacyjnym drugiej maszyny (która jest świadoma swojej roli w procesie debugowania). Maszyna docelowa wysyła i odbiera informacje od WinDbg. Mechanizm komunikacji musi być solidny i efektywny.
Protokół szeregowy jest wypróbowanym i sprawdzonym mechanizmem do komunikacji pomiędzy odpluskwiaczem a docelowym systemem. Maszyny łączy się z wykorzystaniem portów COM lub mechanizmu 1394 ("Fire Wire" - obecnie najpopularniejszy, w grę wchodzi również zastosowanie USB 2.0).
Pierwsza sesja i aktywacja połączeniaNie jest to proces skomplikowany. Na hoscie po wejściu w File -> Kernel Debug wybieramy zakładkę dotyczącą naszego protokołu, np. 1394, po czym ustawiamy kanał, po którym będziemy komunikować się z maszyną docelową (dla 1394 są to maksymalnie 63 maszyny, możliwe wartości 0..62).
Należy teraz przejść do maszyny docelowej i uruchomić Windowsa ze specjalną opcją do debugowania. Uzyskujemy to poprzez modyfikację c:/boot.ini - dopisanie parametru /debug z opcją określającą protokół połączenia z hostem i restart systemu. Przykładowy plik dla łączenia przez kanał 10 z wykorzystaniem 1394 wygląda następująco:
[boot loader] timeout=30 default=multi(0)disk(0)rdisk(0)partition(1)\WINDOWS [operating systems] multi(0)disk(0)rdisk(0)partition(1)\WINDOWS="Microsoft Windows XP Professional" /noexecute=optin /fastdetect /debug /debugport=1394 /channel=10
Powracamy do okna WinDbg, wciskamy Ctrl+Break i po kilku sekundach
połączenie powinno dojść do skutku. Na dole okna komend (Command)
mamy prompt'a kd>
, który czeka na komendy
(przed promptem może być jeszcze numer procesora,
jeśli debugowana maszyna jest wieloprocesorowa). Jeśli prompt nie jest aktywny,
oznacza to, że WinDbg jest zajęty. Czasem ten stan może się przeciągać,
np. gdy WinDbg oczekuje na dane z maszyny docelowej, która nigdy nie odpowie.
Nie ma wtedy wyjścia - pozostaje jedynie Ctrl+Break lub zakończenie WinDbg
i ponowne nawiązanie połączenia.
Host nie musi chodzić na tym samym systemie, co docelowy system operacyjny, który jest debugowany. Powinien być podłączony do serwera sybmoli, jeśli nie ma lokalnego dostępu do symboli.
Znajdowanie symboliŻeby zacząć właściwe debugowanie należy zrobić jeszcze kilka rzeczy, które znacznie polepszą pracę z odpluskwiaczem.
Po pierwsze trzeba zapewnić WinDbg (działającemu na hoście)
sposób na znalezienie symboli dla modułu, który go interesuje.
Do ustawienie lokalizacji symboli (ścieżki symboli)
służy komenda .sympath
. Można również użyć do tego celu
zakładki 'File -> Symbol File Path' w menu WinDbg.
SRV*c:\symbole*http://msdl.microsoft.com/download/symbols
Jeśli pobierze się symbole do folderu lokalnego, jako ścieżki symboli należy użyć zwykłej ścieżki do tego folderu. Ze względu na wielkość pakietów symboli warto wiedzieć, że część symboli dla Windows XP jest również dostępna na płytce instalacyjnej dodatku Service Pack 2 (SUPPORT/SYMBOLS).
Jeśli odpluskwiacz jest podłączony do działającego systemu/procesu można np. wstrzymać jego działanie (Ctrl+Break) aby uaktywnić prompta i wpisać:
.sympath <ścieżka symboli>
Dla dokładniejszych danych otrzymywanych od odpluskwiacza wykonujemy komendę
!sym noisy
. Następnie można sprawdzić, używając komendy
!lmi
, co WinDbg potrafi powiedzieć o jednym z podstawowych
modułów Windowsa, np. o svchost.
Wykonując po tym komendę .reload /f
otrzymamy symbole dla modułu.
Jeśli nieprawidłowo podłączyliśmy symbole zostaniemy o tym poinformowani.
Powyższą funkcjonalność udało mi się sprawdzić nawet używając jednej maszyny, debugując jeden z działających na niej procesów, np. svchost. Najłatwiej to uzyskać korzystając z okna otwieranego poprzez opcję menu: File -> Attach to a process, w którym to oknie wybieramy proces z listy działających aktualnie na systemie procesów.
Rysunek: okno Attach to Process
qd
w linii komend).
Dzięki temu można np. dokonać zrzutu pamięci do pliku
(.dump <plik>
), po czym odłączyć się od aplikacji i przejść
do debugowania pliku zrzutu (File -> Open Crash Dump) bez
niepotrzebnego zatrzymywania aplikacji, której działanie jest dla systemu
istotne. Dla systemów starszych niż Windows XP (wymienionych wyżej)
rozwiązaniem jest nieinwazyjne podłączanie się do procesów
(z zaznaczoną opcja Noninvasive w oknie
Attach to Process - patrz rysunek wyżej; szczegóły w Słowniczku).
Jednak
po zakończeniu takiego podłączenia proces mimo wszystko jest zabijany.
Po samym podłączeniu się do działającego procesu na komputerze, który jest jednocześnie hostem i celem odpluskwiania, prompt będzie wskazywał numer procesu i numer wątku, na którego działaniu zatrzymało się wykonywanie programu, np. 0:016> (jeśli podłączyliśmy się do kilku systemów mamy jeszcze najpierw numer systemu, np. 3:2:005>). Identyczna sytuacja zachodzi przy debugowaniu aplikacji trybu użytkownika. Tak więc:
0:016> !sym noisy noisy mode - symbol prompts on 0:016> !lmi svchost Loaded Module Info: [svchost] Module: svchost Base Address: 01000000 Image Name: C:\WINDOWS\system32\svchost.exe Machine Type: 332 (I386) Time Stamp: 41107ed6 Wed Aug 04 08:14:46 2004 Size: 6000 CheckSum: 8d92 Characteristics: 10f perf Debug Data Dirs: Type Size VA Pointer CODEVIEW 24, 3bdc, 2fdc RSDS - GUID: (0x7a7c3a19, 0x2403, 0x4c38, 0x9a, 0xe3, 0xe5, 0xdc, 0xb0, 0xc6, 0x9c, 0x49) Age: 2, Pdb: svchost.pdb CLSID 4, 3bd8, 2fd8 [Data not mapped] Image Type: FILE - Image read successfully from debugger. C:\WINDOWS\system32\svchost.exe Symbol Type: PDB - Symbols loaded successfully from symbol search path. C:\WINDOWS\Symbols\exe\svchost.pdb Load Report: public symbols , not source indexed C:\WINDOWS\Symbols\exe\svchost.pdb
Wszystko się powiodło, jednak ostatnia informacja jest niezbyt optymistyczna - publiczne symbole svchost nie są indeksowane z kodem źródłowym, przez co niedostępne jest debugowanie z poziomu kodu źródłowego. Czasem (np. do analizy minizrzutów), należy podobnie jak ścieżkę do symboli ustawić ścieżkę do obrazów plików wykonywalnych, jednak w tym przypadku potrzebny plik znajdował się w standardowym miejscu systemu (w katalogu C:\WINDOWS\system32).
Pliki źródłowe
Dla analizy kodu powinno się również podłączyć pliki źródłowe.
W najprostszym przypadku lokalizacja plików źródłowych jest taka sama jak
podczas kompilacji (można ją wyciągnąć z pliku binarnego i plików z symbolami).
W wielu przypadkach jednak trzeba podać nową lokalizację,
przy użyciu .srcpath
(lub w WinDbg File -> Source File Patch
).
Można również wykorzystać serwery źródeł.
Wszystkie ustawienia danej sesji (w tym ustawione ścieżki symboli, źródeł czy obrazów) można zapisywać w obszarach roboczych (File -> Save Workspace), które umożliwiają kontynuację sesji debugowania po ponownym uruchomieniu WinDbg.
PodstawyMamy trzy rodziny komend:
Jeśli jesteśmy poprawnie połączeni z debugowanym systemem, wciskając wielokrotnie Ctrl+Alt+K na hoście, aż do otrzymania informacji:
Will reqest initial breakpoint at next boot. Will breakin on first symbol load at next boot.
zatrzymamy pracę debugowanego systemu zaraz po jego uruchomieniu. W ten sposób będzie można ustawić punkty kontrolne na funkcje ładujące moduły podczas startu systemu. W oknie z wyświetlonym kodem źródłowym będzie podświetlone miejsce, w którym program się zatrzymał.
Zwykłe punkty kontrolne ustawiamy przy użyciu komendy bp
,
np. bp MyDriver!xyz
(<moduł>!<nazwa>, gdzie nazwa musi
być znana przez symbole z plików .pdb), bp f89adeaa
(adres).
Kiedy wykonanie programu dojdzie do tych punktów, system jest zawieszany
i kontrola wraca do WinDbg.
Punkty kontrolne typu odroczonego - bu <moduł>!<nazwa>
.
Różnica pomiędzy nimi a zwykłymi punktami kontrolnymi jest taka,
że są pamiętane po kolejnym starcie systemu, jak również po odładowaniu modułu.
Ponadto działamy na nazwach, a nie na adresach.
Punkty kontrolne można również ustawiać w kodzie źródłowym, np. przechodząc do odpowiedniej linii i wciskając klawisz F9 (zawsze mamy alternatywę przy użyciu myszki).
Ponadto dzięki ustawieniu punktu kontrolnego ba
system zostanie
wstrzymany przy próbie dostępu do danego obszaru pamięci.
Komendą bl
wylistowujemy wszystkie ustawione breakpoint'y.
Przy każdym mamy jego numer i literkę e (enabled) - aktywny,
lub d (disabled) - nieaktywny, gdyż punkty kontrolne można
deaktywować używając komendy bd
.
Można również podać warunek, przy jakim program ma się zatrzymać przy danym
wywołaniu funkcji, np. przy użyciu komendy
bp <moduł>!<nazwa> "<warunek>"
(możliwe
jest również użycie bu
),
gdzie warunki mają specyficzną, dość skomplikowaną składnię i
mogą automatycznie wykonywać instrukcje przed aktywowaniem okna odpluskwiacza
(szczegóły w dokumentacji DTW - rozdział "Setting a Conditional Breakpoint").
Można również zaznaczyć, aby
punkt kontrolny działał jedynie za pierwszym razem (bp /1 ...
),
jedynie dla danego procesu (/p
) lub danego wątku (/t
).
dv
wyświetla informacje o wartosci zmiennych widocznych
w danym momencie.
wszystkie:
kd> dv DeviceObject = 0x82361348 Irp = 0xff70fbc0 outBufLength = 0x64 buffer = 0x00000000 "" irpSp = 0xff70fc30 data = 0xf886b0c0 "This String is from Device Driver !!!"
konkretna zmienna:
kd> dv outBufLength outBufLength = 0x64
dt
(display type) - wyświetla typ zmiennych.
Jeśli zmienna wskazuje na strukturę, można pokazać wartości odpowiednich pól,
a nawet zagłębiać się dalej, podając ilość poziomów zagłębienia.
Do pokazania konkretnego pola wystarczy użyć ??
-
operatora wyliczającego wyrażenia C++:
kd> ?? Irp->Size unsigned short 0x94
Wykorzystujemy go również do ustawiania wartości zmiennych:
kd> ?? outBufLength = 0 unsigned long 0
dd, dw, db
- (double word, word, byte) wyświetlają
informacje na temat danych znajdujących się pod konkretnym adresem,
bez formatowania czytelnego dla człowieka.
Okno Locals znacznie ułatwia obserwowanie struktur z wskaźnikami (pozwala również na zmianę wartości):
Rysunek: okno Locals - "niezadokowane" w głównym oknie WinGdb (źródło: kernel_debugging_tutorial.doc).
Wyświetlanie podstawowych rejestrów uzyskujemy za pomocą polecenia r
(pojedynczy rejestr - r <rejestr>
; wszystkie rejestry rM
):
kd> r eax=81478f68 ebx=00000000 ecx=814243a8 edx=0000003c esi=81778ea0 edi=81478f68 eip=f8803553 esp=f7813bb4 ebp=f7813c3c iopl=0 nv up ei ng nz ac pe nc cs=0008 ss=0010 ds=0023 es=0023 fs=0030 gs=0000 efl=00000292
Również tutaj z pomocą przychodzi nam okno - Registers, w którym dodatkowo rejestry w ostatnim kroku są oznaczone czerwoną czcionką (patrz: rysunek na początku rozdziału).
Semantyka używania rejestrów w trybie komend jest troche bardziej zaawansowana, ale zezwala również na bardziej zaawansowane modyfikacje (dodanie wartosci do adresu pod rejestem itp). Przydatne może być np. przy ustawianiu warunków dla punktów kontrolnych (można wykonywać obliczenia na rejestrach).
Ustawianie wartości zmiennej na wartość spod rejestru:
?? <zmienna> = @<rejestr>
Jak wspomiałem wyżej w WinDbg mamy specjalne okna dla wskazania różnych wartości, jednak można za ich pomocą również modyfikować te wartości, po prostu klikając na odpowiednie pole i modyfikując tam wpisaną wartość. Do takich okien należą:
Jak zwykle mamy do wyboru korzystanie z przycisków paska narzędzi, odpowiednich opcji menu lub skrótów klawiszowych. Dostępne są standardowe opcje, znane chociażby z Borland Pascala, choć z większą funkcjonalnością.
Zapisywany jest na nim lokalny stan, włączając w to parametry
i adresy zwrotne funkcji.
Po zatrzymaniu się przez program na punkcie kontrolnym zazwyczaj na stosie
znajduje się conajmniej kilka procedur. Komenda k
(Stack Backtrace) pokazuje stos począwszy od informacji
znajdujących się na jego wierzchu. To samo daje okno Call Stack.
Po kliknięciu na konkretnej "ramce" otworzone zostanie okno z kodem źródłowym,
o ile jest dostępny.
Przy pomocy komendy x <moduł>[!*<część nazwy>*]
,
można wyświetlić wszystkie nazwy znane w module,
lub tylko te pasujące do wzorca, np.
kd> x sioctl!*ioctl* f8883080 SIoctl!SioctlUnloadDriver (struct _DRIVER_OBJECT *) f8883010 SIoctl!SioctlCreateClose (struct _DEVICE_OBJECT *, struct _IRP *) f8883450 SIoctl!SioctlDeviceControl (struct _DEVICE_OBJECT *, struct _IRP *)
Często korzysta się z bardziej złożonych komend (niż podstawowe operacje), zazwyczaj napisanych jako rozszerzenia. Są to specjalne biblioteki DLL ładowane przez odpluskwiacza i działające w kontekście jego procesu (na hoście). Cechy istotne dla debugowania:
dt
Przyjrzymy się najpierw niektórym standardowo wbudowanym i powszechnie używanym rozszerzeniom.
Procesy i wątki
!process
(pokazuje aktualny proces w momencie zatrzymania)
kd> !process PROCESS 816fc3c0 SessionId: 1 Cid: 08f8 Peb: 7ffdf000 ParentCid: 0d8c DirBase: 10503000 ObjectTable: e1afeaa8 HandleCount: 19. Image: ioctlapp.exe VadRoot 825145e0 Vads 22 Clone 0 Private 38. Modified 0. Locked 0. DeviceMap e10d0198 Token e1c8e030 ElapsedTime 00:00:00.518 UserTime 00:00:00.000 KernelTime 00:00:00.109 QuotaPoolUsage[PagedPool] 9096 QuotaPoolUsage[NonPagedPool] 992 Working Set Sizes (now,min,max) (263, 50, 345) (1052KB, 200KB, 1380KB) PeakWorkingSetSize 263 VirtualSize 6 Mb PeakVirtualSize 6 Mb PageFaultCount 259 MemoryPriority BACKGROUND BasePriority 8 CommitCharge 48 THREAD 825d2020 Cid 08f8.0708 Teb: 7ffde000 Win32Thread: 00000000 RUNNING on processor 0
Wyświetlony zostaje m.in. adres bloku procesu (EPROCESS),
zaraz za PROCESS. Podobnie adres bloku wątku (ETHREAD),
po THREAD. Mogą one być wykorzystywane w warunkowych punktach
kontrolnych. !thread
pokazuje dane wątku.
Oba rozszerzenia mogą podać dane odnośnie konkretnego wątku.
Jak wiemy są trzy rodzaje plików zrzutu. Pełen zrzut pamięci jest najlepszy do odpluskwiania jednak dla obecnych wielkości pamięci operacyjnej instalowanych na komputerach ma on olbrzymie rozmiary. Już mniejszy zrzut pamięci jądra wystarcza dla większości przypadków. Trzeci typ - minizrzuty pamięci, nie posiadają pełnej informacji o plikach wykonywalnych, dlatego może zaistnieć potrzeba dostarczenia dla ich użycia ścieżki do obrazów plików wykonywalnych (dzięki zachowanym w tych zrzutach timestamp'ach obrazów - dacie w systemie szesnastkowym, można później je zidentyfikować, najlepiej jednak pozostawić ich znalezienie serwerowi symboli).
!analyze -v
aby dostać podsumowanie analizy.
Zostańmy jeszcze na chwilę przy temacie minizrzutów. Zawierają one najmniejszy zestaw przydatnych informacji, które ułatwiają określenie, dlaczego komputer się zatrzymał (szczegóły w Słowniczku). Pliki te mogą być użyteczne, gdy na dysku twardym jest mało miejsca lub gdy przesyła się je dalej do analizy (patrz: OCA). Ponieważ jednak w takich plikach nie można umieścić zbyt wiele informacji, w przypadku wystąpienia problemów niewywołanych bezpośrednio przez wątek uruchomiony w czasie wystąpienia błędu, analiza pliku nie może pomóc w ustaleniu przyczyn problemu.
Jeśli nastąpi kolejny błąd krytyczny i system Windows utworzy następny plik małego zrzutu pamięci, poprzedni plik zostanie zachowany. Każdy z plików otrzymuje bowiem unikatową nazwę zawierającą datę. Na przykład Mini022084-01.dmp to pierwszy plik zrzutu pamięci wygenerowany 20 lutego 1984 roku. System Windows standardowo przechowuje listę wszystkich minizrzutów w folderze %SystemRoot%\Minidump.
W jednej wiadomości na forum dyskusyjnym forum.tweak.pl znajduje się znajduje się m.in. listing otrzymany w WinDbg, powstały z analizy pliku minizrzutu. W następnym podrozdziale uzyskamy podobny listing powstały podczas analizy awarii systemu, do którego był podłączony odpluskwiacz.
Debugowanie błędu krytycznego
Błąd krytyczny można wymusić komendą .crash
lub, np. poprzez
ustawienie rejestru EIP na 0, po zatrzymaniu wykonywania procesu
(0 nigdy nie jest poprawną wartością, ponieważ wskaźnik insrukcji
nie może być równy 0). Kontynuowanie wykonywania procesu (F5)
spowoduje błąd jądra systemu, po czym nastąpi wywołanie błędu krytycznego.
Można go analizować komendą !analyze -v.
kd> !analyze -v
*******************************************************************************
* *
* Bugcheck Analysis *
* *
*******************************************************************************
SYSTEM_THREAD_EXCEPTION_NOT_HANDLED (7e)
This is a very common bugcheck. Usually the exception address pinpoints
the driver/function that caused the problem. Always note this address
as well as the link date of the driver/image that contains this address.
Arguments:
Arg1: c0000005, The exception code that was not handled
Arg2: 00000000, The address that the exception occurred at
Arg3: f88f2bd8, Exception Record Address
Arg4: f88f2828, Context Record Address
Debugging Details:
------------------
EXCEPTION_CODE: (NTSTATUS) 0xc0000005 - The instruction at "0x%08lx" referenced memory at "0x%08lx". The memory could not be "%s".
FAULTING_IP:
+0
00000000 ?? ???
EXCEPTION_RECORD: f88f2bd8 -- (.exr fffffffff88f2bd8)
ExceptionAddress: 00000000
ExceptionCode: c0000005 (Access violation)
ExceptionFlags: 00000000
NumberParameters: 2
Parameter[0]: 00000000
Parameter[1]: 00000000
Attempt to read from address 00000000
CONTEXT: f88f2828 -- (.cxr fffffffff88f2828)
eax=ffff99ea ebx=00000000 ecx=0000bb40 edx=8055f7a4 esi=e190049e edi=81e826e8
eip=00000000 esp=f88f2ca0 ebp=f88f2cf0 iopl=0 nv up ei pl nz na pe nc
cs=0008 ss=0010 ds=0023 es=0023 fs=0030 gs=0000 efl=00010202
00000000 ?? ???
Resetting default scope
DEFAULT_BUCKET_ID: DRIVER_FAULT
CURRENT_IRQL: 0
ERROR_CODE: (NTSTATUS) 0xc0000005 - The instruction at "0x%08lx" referenced memory at "0x%08lx". The memory could not be "%s".
READ_ADDRESS: 00000000
BUGCHECK_STR: 0x7E
LAST_CONTROL_TRANSFER: from 805b9cbb to 00000000
STACK_TEXT:
WARNING: Frame IP not in any known module. Following frames may be wrong.
f88f2c9c 805b9cbb 81e826e8 8123a000 00000000 0x0
f88f2d58 805b9ee5 80000234 8123a000 81e826e8 nt!IopLoadDriver+0x5e1
f88f2d80 804ec5c8 80000234 00000000 822aeda0 nt!IopLoadUnloadDriver+0x43
f88f2dac 805f1828 f7718cf4 00000000 00000000 nt!ExpWorkerThread+0xe9
f88f2ddc 8050058e 804ec50d 00000001 00000000 nt!PspSystemThreadStartup+0x2e
00000000 00000000 00000000 00000000 00000000 nt!KiThreadStartup+0x16
FAILED_INSTRUCTION_ADDRESS:
+0
00000000 ?? ???
FOLLOWUP_IP:
nt!IopLoadDriver+5e1
805b9cbb 3bc3 cmp eax,ebx
SYMBOL_STACK_INDEX: 1
SYMBOL_NAME: nt!IopLoadDriver+5e1
MODULE_NAME: nt
IMAGE_NAME: ntoskrnl.exe
DEBUG_FLR_IMAGE_TIMESTAMP: 3e800a79
STACK_COMMAND: .cxr fffffffff88f2828 ; kb
FAILURE_BUCKET_ID: 0x7E_NULL_IP_nt!IopLoadDriver+5e1
BUCKET_ID: 0x7E_NULL_IP_nt!IopLoadDriver+5e1
kd> .cxr fffffffff88f2828
eax=ffff99ea ebx=00000000 ecx=0000bb40 edx=8055f7a4 esi=e190049e edi=81e826e8
eip=00000000 esp=f88f2ca0 ebp=f88f2cf0 iopl=0 nv up ei pl nz na pe nc
cs=0008 ss=0010 ds=0023 es=0023 fs=0030 gs=0000 efl=00010202
00000000 ?? ???
kd> kb
*** Stack trace for last set context - .thread/.cxr resets it
ChildEBP RetAddr Args to Child
WARNING: Frame IP not in any known module. Following frames may be wrong.
f88f2c9c 805b9cbb 81e826e8 8123a000 00000000 0x0
f88f2d58 805b9ee5 80000234 8123a000 81e826e8 nt!IopLoadDriver+0x5e1
f88f2d80 804ec5c8 80000234 00000000 822aeda0 nt!IopLoadUnloadDriver+0x43
f88f2dac 805f1828 f7718cf4 00000000 00000000 nt!ExpWorkerThread+0xe9
f88f2ddc 8050058e 804ec50d 00000001 00000000 nt!PspSystemThreadStartup+0x2e
00000000 00000000 00000000 00000000 00000000 nt!KiThreadStartup+0x16
Wśród ogromu informacji przydatnych do debugowania wielu krytycznych błędów bardzo istotny jest fragment zaznaczony na czerwono - BUCKET_ID. Ten identyfikator wskazuje często (o ile prawidłowo poczepiliśmy zarówno symbole jak i obrazy - przy czym te ostatnie są istotne głównie dla debugowania minizrzutów) w czytelny sposób powód powstania krytycznego błędu. W tym przypadku jest to ustawienie na NULL wskaźnika instrukcji (Instruction Pointer - IP) podczas wykonywania funkcji IopLoadDriver modułu nt. Przykłądowe Bucket ID jakie mogą się pojawić to:
Można definiować aliasy:
kd> as Demo r; !process -1 0; k; !thread -1 0 kd> al Alias Value ------- ------- Demo r; !process -1 0; k; !thread -1 0
Można pisać specjalne skrypty służące do debugowania z wyrazeniami .if, .for, .while itp. (przydatne przy częstym korzystaniu z tych samych ciągów komend, a przy tym nie tak skomplikowane jak pisanie własnych rozszerzeń odpluskwiacza).
$thread
- aktualny wątek,
$ra
- adres powrotu)
un
: deasemblacja również przed aktualną instrukcją
.printf
: formatowane wypisywanie informacji
(%y
: adres -> symbol)
$t0
-$t19
(mogą trzymać informacje różnych typów)
!for_each_process
,
!for_each_thread
,
!for_each_processor
,
!for_each_module
!analyze -show
- wyświetla kod błędu zatrzymania i jego parametry,
!analyze -v
- wyświetla pełne dane wyjściowe,
lm N T
- wyświetla listę załadowanych modułów (stan modułu, ścieżkę do niego).
!analyze
w odpluskwiaczu - generuje Bucket ID
© Piotr Buczek |