![]() |
Wyjście Spis treści Poprzedni Następny
Autor:
©Iczelion |
©2008 mgr Jerzy Wałaszek I LO w Tarnowie
|
Na tej lekcji dowiesz się, co biblioteka Win32 oferuje twórcom oprogramowania w dziedzinie funkcji uruchomieniowych (debugging primitives). Gdy przebrniesz przez ten materiał, będziesz wiedział jak testować proces.

Załaduj {ten przykład}
Biblioteka Win32 zawiera kilka funkcji API pozwalających programistom testować aplikacje. Nazywają się one funkcjami uruchomieniowymi Win32 API (Win32 Debug APIs). Dzięki ich pomocy możesz:
Mówiąc krótko, za pomocą tych funkcji API możesz zaprogramować prosty program uruchomieniowy (debugger). Ponieważ ten temat jest rozległy, podzieliłem go na trzy mniejsze części, które łatwiej objąć umysłem. W toku lekcji wyjaśnię podstawowe koncepcje oraz ogólny schemat roboczy zastosowania funkcji uruchomieniowych Win32 API.
Oto niezbędne kroki zastosowania funkcji uruchomieniowych Win32 API:
WaitForDebugEvent PROTO lpDebugEvent:DWORD, dwMilliseconds:DWORD
Teraz zbadajmy szczegółowo strukturę DEBUG_EVENT:
DEBUG_EVENT STRUCT
dwDebugEventCode DD ?
dwProcessId DD ?
dwThreadId DD ?
u DEBUGSTRUCT
DEBUG_EVENT ENDSCREATE_PROCESS_DEBUG_EVENT - struktura CREATE_PROCESS_DEBUG_INFO nazwana CreateProcessInfo
EXIT_PROCESS_DEBUG_EVENT - struktura EXIT_PROCESS_DEBUG_INFO nazwana ExitProcess
CREATE_THREAD_DEBUG_EVENT - struktura CREATE_THREAD_DEBUG_INFO nazwana CreateThread
EXIT_THREAD_DEBUG_EVENT - struktura EXIT_THREAD_DEBUG_EVENT nazwana ExitThread
LOAD_DLL_DEBUG_EVENT - struktura LOAD_DLL_DEBUG_INFO nazwana LoadDll
UNLOAD_DLL_DEBUG_EVENT - struktura UNLOAD_DLL_DEBUG_INFO nazwana UnloadDll
EXCEPTION_DEBUG_EVENT - struktura EXCEPTION_DEBUG_INFO nazwana Exception
OUTPUT_DEBUG_STRING_EVENT - struktura OUTPUT_DEBUG_STRING_INFO nazwana DebugString
RIP_EVENT - struktura RIP_INFO nazwana RipInfo
Na tej lekcji nie będę wchodził w szczegóły tych wszystkich struktur, jedynie zajmiemy się strukturą CREATE_PROCESS_DEBUG_INFO.
Załóżmy, iż nasz program wywołuje funkcję WaitForDebugEvent, która zwraca sterowanie. Pierwszą rzeczą do zrobienia jest sprawdzenie wartości pola dwDebugEventCode, aby dowiedzieć się, który rodzaj zdarzenia uruchomieniowego wystąpił w testowanym procesie. Na przykład, jeśli pole dwDebugEventCode zawiera wartość CREATE_PROCESS_DEBUG_EVENT, możesz zinterpretować zawartość pola u jako strukturę CreateProcessInfo i mieć dostęp do niej przez adresowanie u.CreateProcessInfo.
ContinueDebugEvent PROTO dwProcessId:DWORD,\
dwThreadId:DWORD,\
dwContinueStatus:DWORDFunkcja ta wznawia wykonywanie wątku, który został poprzednio zawieszony w wyniku wystąpienia zdarzenia uruchomieniowego.
Podsumowując, jeśli zdarzenie uruchomieniowe odnosi się do wyjątku w procesie testowanym, powinieneś wywołać ContinueDebugEvent ze znacznikiem DBG_CONTINUE, jeśli twój program usunął już przyczynę powstania wyjątku. W przeciwnym razie twój program musi wywołać ContinueDebugEvent ze znacznikiem DBG_EXCEPTION_NOT_HANDLED. Odstępstwem jest pojedynczy przypadek, gdy musisz zawsze stosować znacznik DBG_CONTINUE: pierwszy wyjątek EXCEPTION_DEBUG_EVENT posiadający wartość EXCEPTION_BREAKPOINT w polu ExceptionCode. Gdy testowany proces ma zamiar wykonać swoją pierwszą instrukcję, twój program otrzyma zdarzenie uruchomieniowe wyjątku (exception debug event). W rzeczywistości jest to przerwa uruchomieniowa (debug break). Jeśli odpowiesz wywołując ContinueDebugEvent ze znacznikiem DBG_EXCEPTION_NOT_HANDLED, to system Windows NT/2000/XP odmówi uruchomienia testowanego procesu (ponieważ nikt się o niego nie troszczy). W takim przypadku zawsze musisz używać znacznika DBG_CONTINUE, aby system Windows wiedział, iż chcesz kontynuacji tego wątku.
.WHILE TRUE
INVOKE WaitForDebugEvent, ADDR DebugEvent, INFINITE
.BREAK .IF DebugEvent.dwDebugEventCode==EXIT_PROCESS_DEBUG_EVENT
...
<Obsłóż zdarzenia uruchomieniowe>
...
INVOKE ContinueDebugEvent, DebugEvent.dwProcessId,\
DebugEvent.dwThreadId,\
DBG_EXCEPTION_NOT_HANDLED
.ENDWJest tutaj jeden kruczek. Gdy już rozpoczniesz testowanie jakiegoś programu, nie możesz się po prostu odłączyć od niego aż do momentu, gdy zakończy on swoje działanie.
Podsumujmy jeszcze raz powyższe kroki:
Ten przykład uruchamia i testuje aplikację Win32 oraz wyświetla istotne informacje, takie jak uchwyt procesu, numer ID procesu, adres bazowy obrazu procesu itd.
.386
.MODEL FLAT, STDCALL
OPTION CASEMAP:NONE
INCLUDE \masm32\include\windows.inc
INCLUDE \masm32\include\kernel32.inc
INCLUDE \masm32\include\comdlg32.inc
INCLUDE \masm32\include\user32.inc
INCLUDELIB \masm32\lib\kernel32.lib
INCLUDELIB \masm32\lib\comdlg32.lib
INCLUDELIB \masm32\lib\user32.lib
.DATA
AppName DB "Przykład nr 1 testowania Win32", 0
ofn OPENFILENAME <>
FilterString DB "Pliki wykonywalne", 0, "*.exe", 0, "Wszystkie pliki", 0, "*.*", 0, 0
ExitProc DB "Program testowany kończy działanie", 0
NewThread DB "Tworzony jest nowy wątek", 0
EndThread DB "Usuwany jest wątek", 0
ProcessInfo DB "Uchwyt pliku: %lx ", 0dh, 0Ah,
"Uchwyt procesu: %lx", 0Dh, 0Ah,
"Uchwyt wątku: %lx", 0Dh, 0Ah,
"Podstawa obrazu: %lx", 0Dh, 0Ah,
"Adres startowy: %lx", 0
.DATA?
buffer DB 512 DUP(?)
startinfo STARTUPINFO <>
pi PROCESS_INFORMATION <>
DBEvent DEBUG_EVENT <>
.CODE
start:
mov ofn.lStructSize, SIZEOF ofn
mov ofn.lpstrFilter, OFFSET FilterString
mov ofn.lpstrFile, OFFSET buffer
mov ofn.nMaxFile, 512
mov ofn.Flags, OFN_FILEMUSTEXIST OR OFN_PATHMUSTEXIST OR \
OFN_LONGNAMES OR OFN_EXPLORER OR OFN_HIDEREADONLY
INVOKE GetOpenFileName, ADDR ofn
.IF eax==TRUE
INVOKE GetStartupInfo, ADDR startinfo
INVOKE CreateProcess, ADDR buffer, NULL, NULL, NULL, FALSE,
DEBUG_PROCESS + DEBUG_ONLY_THIS_PROCESS,
NULL, NULL, ADDR startinfo, ADDR pi
.WHILE TRUE
INVOKE WaitForDebugEvent, ADDR DBEvent, INFINITE
.IF DBEvent.dwDebugEventCode==EXIT_PROCESS_DEBUG_EVENT
INVOKE MessageBox, 0, ADDR ExitProc, ADDR AppName,
MB_OK + MB_ICONINFORMATION
.BREAK
.ELSEIF DBEvent.dwDebugEventCode==CREATE_PROCESS_DEBUG_EVENT
INVOKE wsprintf, ADDR buffer, ADDR ProcessInfo,
DBEvent.u.CreateProcessInfo.hFile,
DBEvent.u.CreateProcessInfo.hProcess,
DBEvent.u.CreateProcessInfo.hThread,
DBEvent.u.CreateProcessInfo.lpBaseOfImage,
DBEvent.u.CreateProcessInfo.lpStartAddress
INVOKE MessageBox, 0, ADDR buffer, ADDR AppName,
MB_OK + MB_ICONINFORMATION
.ELSEIF DBEvent.dwDebugEventCode==EXCEPTION_DEBUG_EVENT
.IF DBEvent.u.Exception.pExceptionRecord.ExceptionCode==\
EXCEPTION_BREAKPOINT
INVOKE ContinueDebugEvent, DBEvent.dwProcessId,
DBEvent.dwThreadId,
DBG_CONTINUE
.CONTINUE
.ENDIF
.ELSEIF DBEvent.dwDebugEventCode==CREATE_THREAD_DEBUG_EVENT
INVOKE MessageBox, 0, ADDR NewThread, ADDR AppName,
MB_OK + MB_ICONINFORMATION
.ELSEIF DBEvent.dwDebugEventCode==EXIT_THREAD_DEBUG_EVENT
INVOKE MessageBox, 0, ADDR EndThread, ADDR AppName,
MB_OK + MB_ICONINFORMATION
.ENDIF
INVOKE ContinueDebugEvent, DBEvent.dwProcessId,
DBEvent.dwThreadId,
DBG_EXCEPTION_NOT_HANDLED
.ENDW
.ENDIF
INVOKE CloseHandle, pi.hProcess
INVOKE CloseHandle, pi.hThread
INVOKE ExitProcess, 0
END start
Program wypełnia strukturę OPENFILENAME, a następnie wywołuje GetOpenFileName, aby pozwolić użytkownikowi wybrać program do testowania.
INVOKE GetStartupInfo, ADDR startinfo
INVOKE CreateProcess, ADDR buffer, NULL, NULL, NULL, FALSE,
DEBUG_PROCESS + DEBUG_ONLY_THIS_PROCESS,
NULL, NULL, ADDR startinfo, ADDR pi
Gdy użytkownik wybierze jakiś program, wywołana zostaje funkcja GetStartupInfo do wypełnienia struktury STARTUPINFO standardowymi wartościami, a następnie CreateProcess do załadowania wybranego programu. Zwróć uwagę, iż w celu testowania tylko tego programu bez jego procesów potomnych stosujemy znacznik DEBUG_PROCESS w połączeniu ze znacznikiem DEBUG_ONLY_THIS_PROCESS.
.WHILE TRUE
INVOKE WaitForDebugEvent, ADDR DBEvent, INFINITE
Gdy testowany proces zostanie załadowany, wchodzimy do nieskończonej pętli wywołując na jej początku funkcję WaitForDebugEvent. Funkcja WaitForDebugEvent nie zwróci sterowania aż do wystąpienia zdarzenia uruchomieniowego (debug event) w testowanym programie, ponieważ określiliśmy w drugim jej parametrze wartość INFINITE (w nieskończoność). Gdy wystąpi jakieś zdarzenie uruchomieniowe, funkcja WaitForDebugEvent zwróci sterowanie do naszego programu, a struktura DBEvent zostanie wypełniona informacją o zaistniałym zdarzeniu.
.IF DBEvent.dwDebugEventCode==EXIT_PROCESS_DEBUG_EVENT
INVOKE MessageBox, 0, ADDR ExitProc, ADDR AppName,
MB_OK + MB_ICONINFORMATION
.BREAK
Najpierw sprawdzamy wartość w dwDebugEventCode. Jeśli jest nią EXIT_PROCESS_DEBUG_EVENT, wyświetlamy okienko wiadomości z napisem "Program testowany kończy działanie", a następnie wychodzimy z pętli.
.ELSEIF DBEvent.dwDebugEventCode==CREATE_PROCESS_DEBUG_EVENT
INVOKE wsprintf, ADDR buffer, ADDR ProcessInfo,
DBEvent.u.CreateProcessInfo.hFile,
DBEvent.u.CreateProcessInfo.hProcess,
DBEvent.u.CreateProcessInfo.hThread,
DBEvent.u.CreateProcessInfo.lpBaseOfImage,
DBEvent.u.CreateProcessInfo.lpStartAddress
INVOKE MessageBox, 0, ADDR buffer, ADDR AppName,
MB_OK + MB_ICONINFORMATION
Jeśli wartością w dwDebugEventCode jest CREATE_PROCESS_DEBUG_EVENT, to wyświetlamy kilka interesujących informacji o testowanym procesie w oknie wiadomości. Informacje te pobieramy ze unii u.CreateProcessInfo. CreateProcessInfo jest strukturą typu CREATE_PROCESS_DEBUG_INFO. Więcej informacji o niej znajdziesz w pliku pomocy dla biblioteki Win32.
.ELSEIF DBEvent.dwDebugEventCode==EXCEPTION_DEBUG_EVENT
.IF DBEvent.u.Exception.pExceptionRecord.ExceptionCode==\
EXCEPTION_BREAKPOINT
INVOKE ContinueDebugEvent, DBEvent.dwProcessId,
DBEvent.dwThreadId,
DBG_CONTINUE
.CONTINUE
.ENDIF
Jeśli wartością w dwDebugEventCode jest EXCEPTION_DEBUG_EVENT, musimy dalej sprawdzić dokładny typ wyjątku, który odczytasz z ExceptionCode. Jeśli zawartość ExceptionCode wynosi EXCEPTION_BREAKPOINT i zdarza się to po raz pierwszy (lub jesteśmy pewni, iż w testowanym procesie nie osadzono przerwania 3h), możemy bezpiecznie założyć, iż wyjątek ten wystąpił, gdy testowany proces próbował uruchomić swoją pierwszą instrukcję. Gdy skończymy obsługę wyjątku, musimy wywołać ContinueDebugEvent ze znacznikiem DBG_CONTINUE, aby pozwolić testowanemu programowi uruchomić się. Później wracamy z powrotem do oczekiwania na następne zdarzenie uruchomieniowe.
.ELSEIF DBEvent.dwDebugEventCode==CREATE_THREAD_DEBUG_EVENT
INVOKE MessageBox, 0, ADDR NewThread, ADDR AppName,
MB_OK + MB_ICONINFORMATION
.ELSEIF DBEvent.dwDebugEventCode==EXIT_THREAD_DEBUG_EVENT
INVOKE MessageBox, 0, ADDR EndThread, ADDR AppName,
MB_OK + MB_ICONINFORMATION
.ENDIF
Jeśli w dwDebugEventCode jest CREATE_THREAD_DEBUG_EVENT lub EXIT_THREAD_DEBUG_EVENT, wyświetlamy okienko wiadomości z odpowiednimi informacjami o otworzeniu lub zakończeniu wątku w testowanym procesie.
INVOKE ContinueDebugEvent, DBEvent.dwProcessId,
DBEvent.dwThreadId,
DBG_EXCEPTION_NOT_HANDLED
.ENDW
Aby wznowić działanie procesu testowanego, wywołujemy ContinueDebugEvent ze znacznikiem DBG_EXCEPTION_NOT_HANDLED.
INVOKE CloseHandle, pi.hProcess
INVOKE CloseHandle, pi.hThread
INVOKE ExitProcess, 0
END start
Gdy testowany proces zakończy się, jesteśmy poza pętlą uruchomieniową i musimy zamknąć zarówno uchwyt procesu jak i wątku. Zamknięcie uchwytów nie oznacza usunięcie procesu lub wątku. Znaczy jedynie, iż nie potrzebujemy już więcej tych uchwytów do odwoływania się do procesu lub wątku.
|
Autorem
kursu jest
Iczelion. Kurs
programowania Windows znalazł się na serwerze I LO w Tarnowie za pisemną
zgodą autora. |
![]() | I Liceum Ogólnokształcące |
Pytania proszę przesyłać na adres email: i-lo@eduinf.waw.pl
W artykułach serwisu są używane cookies. Jeśli nie chcesz ich otrzymywać,
zablokuj je w swojej przeglądarce.
Informacje dodatkowe