Niniejszy dokument dotyczy podstawowych kwestii związanych z odpluskwianiem programów w środowisku MS Windows.
W pierwszej kolejności zostanie krótko przedstawione zestawienie podstawowych narzędzi. Następnie zostanie pokrótce omówione debugowanie programów przy użyciu Windbg. Ponieważ możliwości debugowania programów pod Windbg są porównywalne do tych, dostępnych w gdb pod Linuksa, zatem opis będzie dotyczył bardziej elementów Windbg, niż samych poleceń i sposobów debugowania, które są analogiczne do tych, przedstawionych w dokumencie opisującym gdb.
Do podstawowych odpluskwiaczy dostępnych w środowisku MS Windows należą:
Cecha | KD | NTSD | WinDbg | Visual Studio .NET |
Kernel-mode debugging | Y | N | Y | N |
User-mode debugging | Y | Y | Y | |
Unmanaged debugging | Y | Y | Y | Y |
Managed debugging | Y | Y | Y | |
Remote debugging | Y | Y | Y | Y |
Attach to process | Y | Y | Y | Y |
Detach from process in Win2k and XP | Y | Y | Y | Y |
SQL debugging | N | N | N | Y |
The Microsoft Windows Debugger (WinDbg) udostępnia opcję debugowania programów zarówno w trybie użytkownika jak i w trybie jądra. Pozwala na:
Po zainstalowaniu i uruchomieniu WinDbg pierwszą rzeczą, jaką musimy uczynić, jest podanie ścieżki do plików symboli. W tym celu w File | Symbol File Path dodajemy:
SRV*<c:\katalog_symboli>*http://msdl.microsoft.com/download/symbols |
Jak już wspomniano, WinDbg udostępnia kilka opcji debugowania programu. Możliwe jest otwarcie pliku wykonywalnego programu, podłączenie do działającego procesu, otwarcie pliku zrzutu pamięci, a także podpięcie do zdalnej sesji debugowania. Wszystkie te opcje dostępne są z menu File.
Windows XP oferuje przygotowywanie trzech rodzajów zrzutów
pamięci:
Opcje te można znaleźć we Właściwości systemu w zakładce
Zaawansowane pod pozycją Uruchamianie i odzyskiwanie.
|
Debugowanie zdalne polega na odpluskwianiu systemu działającego na jednym komputerze, przy wykorzystaniu drugiego komputera. Na jednym komputerze - serwerze - instalujemy debuger, który pozwoli analizować daną maszynę. Drugi komputer - klient - kontroluje sesję debugowania uruchomiona na serwerze.
W pierwszej kolejności należy zainstalować CDB, NTSD lub WinDbg na serwerze. Klient WinDbg może nawiązać połączenie z każdym z wymienionych debugerów (i vice versa) przy wykorzystaniu TCP lub łączy nazwanych (named pipes).
Uruchamianie serwera:
WinDbg server npipe:pipe=pipename (możliwość podłączenia wielu klientów) lub
z WinDbg: .server npipe:pipe=pipename (tylko jeden klient)
Możliwe jest uruchomienie wielu serwerów używających różnych protokołów. Dostępna jest także opcja ochrony sesji hasłem.
Połączenie od strony klienta:
WinDbg -remote npipe:server=Server, pipe=PipeName[,password=Password]
z WinDbg: File | Connect to Remote Session: npipe:server=Server, pipe=PipeName [,password=Password]
Serwer wyświetli informację na temat podłączonych klientów oraz poleceń, które wydają. Możliwe jest zakończenie serwera przez wpisanie qq. Zakończenie klienta następuje po wybraniu File | Exit. Aby debugować zdalnie trzeba być członkiem grupy Debugger Users, a serwer musi zapewniać możliwość zdalnego podłączenia.
Możliwe jest ustawienie WinDbg jako domyślnego debugera JIT. W tym celu należy uruchomić WinDbg z opcją '-I'.
WinDbg -I
Od tego momentu, WinDbg będzie uruchamiany automatycznie w przypadku, gdy aplikacja zgłasza wyjątek (poza sesją debugowania), którego nie potrafi obsłużyć.
Podczas debugowania, kiedy aplikacja zgłasza wyjątek, debuger otrzymuje dwie szanse obsłużenia tego wyjątku. Pierwsza nadarza się przed próbą obsłużenia wyjątku przez samą aplikację ("first chance exception"). Druga dotyczy sytuacji, kiedy aplikacja nie była w stanie obsłużyć samodzielnie wyjątku ("second-chance exception"). Jeśli debuger nie obsłuży wyjątku w drugim przypadku, aplikacja kończy działanie.
W takim przypadku przydatne stają się funkcje, które pozwalają na podgląd zapisu wyjątku i stosu wywołań aplikacji. Do tych funkcji należą:
.lastevent oraz !analyze -v
Na szczególną uwagę zasługuje rozdział Debugging Techniques z dokumentacji Debugging Tools for Windows. Zawiera on opis podstawowych, jak i zaawansowanych sposobów debugowania programów.
Po otwarciu pliku z programem do debugowania wygląd WinDbg będzie przypominał ten, przedstawiony na poniższym rysunku (rysunek przedstawia WinDbg w momencie debugowania programu w asemblerze):
Z menu View dostępny jest podgląd okien, które oferuje WinDbg.
Zawiera kod źródłowy programu. Aktualnie wykonywana linia kodu jest podświetlana.
Okno command udostępnia szczegółową informację na temat plików DLL oraz EXE, które zostały załadowane od chwili uruchomienia WinDbg. Daje możliwość wprowadzania poleceń dla WinDbg.
Skrócony opis niektórych poleceń zamieszczono na poniższych tabelach, sporządzonych na podstawie źródeł materiałów do projektu. Po bardziej szczegółowy opis sposobu wykorzystania poleceń odsyłam do opisu debugowania w gdb (Linuks) oraz do dokumentacji WinDbg, gdzie wyczerpująco zostały opisane wszystkie polecenia wraz z przykładowym sposobem ich wykorzystania.
Dotyczy | Polecenia | Opis |
przeglądanie stosu (stack trace) | k, kb x | Wyświetla stack trace dla stosu aktualnego wątku (x ramek). Kb uwzgędnia wypisanie trzech pierwszych argumentów przekazanych do każdej z funkcji. |
ramka | .frame x | |
podgląd rejestrów | r | Wyświetla zawartość rejestrów; reax - wyświetla zawartość rejestru eax. |
krok (step) | t | Step into (F11) - wejście wewnątrz wykonywanej funkcji. |
p | Step over (F10) - przejście ponad wykonaniem funkcji | |
Step out | Shift + F11 - wyjście jeden poziom do góry z funkcji | |
(disassemble) | u | Deasemblowanie kilku następnych funkcji. |
punkty kontrolne (breakpoints) | Bl | Wypisz punkty kontrolne. |
be, bd, bc | Aktywuj, deaktywuj, wyczyść punkt kontrolny | |
bp | Ustaw punkt kontrolny. | |
bu | Ustawia punkt kontrolny z wyprzedzeniem. Używany w przypadku ustawiania punktu kontrolnego przy funkcji, znajdującej się w nie załadowanym(dotychczas) module. | |
kontynuowanie | G<address/symbol> | Kontynuuje wykonywanie do chwili osiągnięcia wskazanego miejsca. |
(dumping data) | dv | Wyświetla zmienne. |
ds, da | Wyświetla string. | |
dt [dt module!typedef adr] | Pozwala ona wyświetlić zawartość zmiennej sformatowaną zgodnie z rozpoznanym typem. Jest to przydatne gdy mamy do czynienia ze wskaźnikami na skomplikowane struktury, zatem ręczne przeglądanie zawartości wskaźnika byłoby męczące. | |
edycja/zmiana wartości zmiennej | eb (byte), ed (dword), ea (ASCII), eu (Unicode) | Edytuje wartość zmiennej. |
moduły | im | Wyświetla moduły. |
wątki | ~ | Wyświetla listę wątków. |
wyszukiwanie symbolu w module | X module!<pattern> | |
(dump) | .dump | |
ln address | Wyświetli symbol leżący najbliżej lokacji. |
Wyświetla listę kodu programu w asemblerze. Przykład:
AddSub2!main: 00401010 cc int 3 00401011 a100404000 mov eax,[AddSub2!val1 (00404000)] 00401016 030504404000 add eax,[AddSub2!val2 (00404004)] 0040101c 2b0508404000 sub eax,[AddSub2!val3 (00404008)] 00401022 a30c404000 mov [AddSub2!finalVal (0040400c)],eax 00401027 e855010000 call AddSub2!DumpRegs (00401181) 0040102c 6a00 push 0x0 0040102e e859090000 call AddSub2!ExitProcess (0040198c) |
Wyświetla aktualne wartości rejestrów procesora. Możliwe jest dostosowania okna do własnych potrzeb tak, aby wyświetlane były tylko wymagane przez nas rejestry. |
.
Okno wyświetla aktualne wartości śledzonych zmiennych.
Zawiera obraz pamięci. |
W momencie awarii systemu operacyjnego, generowany jest plik zrzutu pamięci, w którym zapisywane są informacje na temat procesu w momencie jego nieprzewidzianego zakończenia. Możliwe jest także wygenerowanie pliku core dump przez użycie polecenia .dump.
Analiza pliku zrzutu pamięci może przebiegać w następujących krokach:
Poglądowy przykład zwracanego przez !analyze raportu:
Debugging Details: FAULTING_IP: TRAP_FRAME: f51dc7ec -- (.trap fffffffff51dc7ec) CUSTOMER_CRASH_COUNT: 1 DEFAULT_BUCKET_ID: DRIVER_FAULT BUGCHECK_STR: 0x8E LAST_CONTROL_TRANSFER: from f4e3a970 to f4e5bf51 STACK_TEXT: FOLLOWUP_IP: SYMBOL_STACK_INDEX: 0 FOLLOWUP_NAME: MachineOwner SYMBOL_NAME: HSF_CNXT+50f51 MODULE_NAME: HSF_CNXT IMAGE_NAME: HSF_CNXT.sys DEBUG_FLR_IMAGE_TIMESTAMP: 3de3a497 STACK_COMMAND: .trap fffffffff51dc7ec ; kb FAILURE_BUCKET_ID: 0x8E_HSF_CNXT+50f51 BUCKET_ID: 0x8E_HSF_CNXT+50f51 |
autor: Paweł Kaczan