Strona główna.
Poprzedni rozdział: Techniki odpluskwiania jądra: ksymoops, kgdb, UML.


Odpluskwianie w systemach opartych na Windows NT

Spis treści


Bibliografia

Powrót do góry strony


Wstęp

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

Okna właściwości systemu i konfiguracji opcji 
			dla uruchamiania i odzyskiwania w Windows XP

Rysunek: Okna właściwości systemu i konfiguracji opcji dla uruchamiania i odzyskiwania w Windows XP.

Jest to przykład z Windows XP, na którym w miarę możliwości testowałem opisane niżej narzędzia - w innych systemach opartych na jądrze Windows NT (bo głównie o takich systemach mówimy w tym dokumencie), okno może się znajdować w innym miejscu, charakterystycznym dla konkretnego systemu.

Powrót do góry strony


Słowniczek

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).

Powrót do góry strony


Windows vs. Linux

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.

Powrót do góry strony


Narzędzia dla Developerów

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:

  1. Driver Verifier (Weryfikator sterownikó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.

  2. PREfast

    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 ;) ).

  3. Event Tracing (Śledzenie zdarzeń)

    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:

Jako sposoby na ułatwienie odpluskwiania sterowników podaje się również stosowanie innych rozwiązań, takich jak:

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:

  1. WinDbg

    Graficzny odpluskwiacz do debugowania zarówno kodu trybu użytkownika jak i trybu jądra.

  2. KD (Microsoft Kernel Debugger)

    Aplikacja konsolowa do debugowania sterowników jądra.

  3. CDB (Microsoft Console Debugger)

    Aplikacja konsolowa do debugowania aplikacji trybu użytkownika i sterowników.

  4. NTSD (Microsoft NT Symbolic Debugger)

    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. WinDbgKD 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ą:

Powrót do góry strony


Narzędzia do testowania kodu

Driver Verifier (Weryfikator sterowników)

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:

Powrót do góry strony


PREfast

Powrót do góry strony


Event Tracing (Śledzenie zdarzeń)

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 straceltrace 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.

Powrót do góry strony


Narzędzia do debugowania

Powrót do góry strony

Wprowadzenie

Powrót do góry strony


Podstawy WinDbg (KD)

główne okno WinGdb z "zadokowanymi" oknami komend i rejestrów

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).

Powrót do góry strony


Mini tutorial

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:

W takich artchitekturach host (na którym działa silnik odpluskwiacza i rozszerzenia i na którym ładowane są symbole) staje się serwerem do odpluskwiania komputera docelowego dla maszyn-klientów:

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łączenia

Nie 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.

Poprzez witrynę firmy Microsoft w sieci Web udostępniane są pakiety symboli dla systemu Windows. Aby skorzystać z tych zasobów, należy utworzyć na dysku folder do przechowywania pobranych symboli lokalnych lub buforu symboli dla serwera symboli. Dla przykładu wykorzystamy katalog c:\symbole, dla którego ścieżka symboli ustawiana w odpluskwiaczu dla ściągania w locie ma postać:

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.

okno Attach to Process

Rysunek: okno Attach to Process

Należy jednak uważać, bo proces jest wstrzymywany na pierwszym punkcie kontrolnym. W dodatku standardowo zakończenie debugowania kończy się zabiciem procesu!! Oczywiście taki sam skutek ma sztuczne wywolanie krytycznego błędu podczas debugowania procesu, co skutkuje zazwyczaj ponownym uruchomieniem systemu. Odpluskwiacze w wersji dla Windows XP (w przeciwienstwie do wersji dla Windows NT czy Windows 2000), są w stanie odłączyć się od debugowanej aplikacji (w menu Debug -> Detach Debugee lub 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ł.

Obszary robocze

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.

Podstawy

Mamy trzy rodziny komend:

Przyjrzymy się dokładniej zwykłym poleceniom, które warto poznać ze względu na częste ich wykorzystanie. Stanowią one podstawę wykorzystania bardziej skomplikowanych poleceń, a także skryptów i rozszerzeń, które może tworzyć również sam użytkownik.

Punkty kontrolne - podstawa debugowania

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).

Zmienne

dv wyświetla informacje o wartosci zmiennych widocznych w danym momencie.

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

okno Locals - "niezadokowane" w głównym oknie WinGdb

Rysunek: okno Locals - "niezadokowane" w głównym oknie WinGdb (źródło: kernel_debugging_tutorial.doc).

Rejestry

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>

Okna

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żą:

dostępne również poprzez przyciski paska narzędzi głównego okna WinDbg. Wartości w oknie pamięci można modyfikować w dowolnej postaci, począwszy od znaków ASCII, przez bity, longi, wartości w systemie dwójkowym, dziesiętnym czy szesnastkowym (pole wyboru odpowiedniego systemu błyskawicznie wykonuje konwersję).

Kontrola wykonania kodu

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ą.

Stos wywołań

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.

Nazwy w modułach

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 *)

Powrót do góry strony


Rozszerzenia, aliasy i skrypty (cd. tutoriala)

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:

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.

Pliki zrzutu

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).

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:

Dokładny opis każdego fragmentu wypisanego raportu znajduje się w dokumentacji DTW w rozdziale "Using the !analyze Extension".

Aliasy

Można definiować aliasy:

Przykład użycia:

kd> as Demo r; !process -1 0; k; !thread -1 0
kd> al
  Alias            Value  
 -------          ------- 
 Demo             r; !process -1 0; k; !thread -1 0 

Skrypty

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).

Powrót do góry strony


Coś na deser (WinGdb, KD i OCA)

Powrót do góry strony


© Piotr Buczek