WaitForDebugEvent
proto lpDebugEvent:DWORD, dwMilliseconds:DWORD
lpDebugEvent is the address of a DEBUG_EVENT structure that will be filled with information about the debug event that occurs within the debuggee.
dwMilliseconds is the length of time in milliseconds this function will wait for the debug event to occur. If this period elapses and no debug event occurs, WaitForDebugEvent returns to the caller. On the other hand, if you specify INFINITE constant in this argument, the function will not return until a debug event occurs.
Now let's examine the DEBUG_EVENT structure in more detail.
[DEBUG_EVENT:
dwDebugEventCode dd ?
dwProcessId dd ?
dwThreadId dd ?
u DEBUGSTRUCT <> ]
dwDebugEventCode
contains the value that specifies what type of debug event occurs. In short,
there can be many types of events, your program needs to check the value
in this field so it knows what type of event occurs and responds appropriately.
The possible values are:
Value | Meanings |
---|---|
CREATE_PROCESS_DEBUG_EVENT | A process is created. This event will be sent when the debuggee process is just created (and not yet running) or when your program just attaches itself to a running process with DebugActiveProcess. This is the first event your program will receive. |
EXIT_PROCESS_DEBUG_EVENT | A process exits. |
CREATE_THEAD_DEBUG_EVENT | A new thread is created in the debuggee process or when your program first attaches itself to a running process. Note that you'll not receive this notification when the primary thread of the debuggee is created. |
EXIT_THREAD_DEBUG_EVENT | A thread in the debuggee process exits. Your program will not receive this event for the primary thread. In short, you can think of the primary thread of the debuggee as the equivalent of the debuggee process itself. Thus, when your program sees CREATE_PROCESS_DEBUG_EVENT, it's actually the CREATE_THREAD_DEBUG_EVENT for the primary thread. |
LOAD_DLL_DEBUG_EVENT | The debuggee loads a DLL. You'll receive this event when the PE loader first resolves the links to DLLs (you call CreateProcess to load the debuggee) and when the debuggee calls LoadLibrary. |
UNLOAD_DLL_DEBUG_EVENT | A DLL is unloaded from the debuggee process. |
EXCEPTION_DEBUG_EVENT | An exception occurs in the debuggee process. Important: This event will occur once just before the debuggee starts executing its first instruction. The exception is actually a debug break (int 3h). When you want to resume the debuggee, call ContinueDebugEvent with DBG_CONTINUE flag. Don't use DBG_EXCEPTION_NOT_HANDLED flag else the debuggee will refuse to run under NT (on Win98, it works fine). |
OUTPUT_DEBUG_STRING_EVENT | This event is generated when the debuggee calls DebugOutputString function to send a message string to your program. |
RIP_EVENT | System debugging error occurs |
dwProcessId and dwThreadId are the process and thread Ids of the process that the debug event occurs. You can use these values as identifiers of the process/thread you're interested in. Remember that if you use CreateProcess to load the debuggee, you also get the process and thread IDs of the debuggee in the PROCESS_INFO structure. You can use these values to differentiate between the debug events occurring in the debuggee and its child processes (in case you didn't specify DEBUG_ONLY_THIS_PROCESS flag).
u
is a union that contains more information about the debug event. It can
be one of the following structures depending on the value of dwDebugEventCode
above.
value in dwDebugEventCode | Interpretation of u |
---|---|
CREATE_PROCESS_DEBUG_EVENT | A CREATE_PROCESS_DEBUG_INFO structure named CreateProcessInfo |
EXIT_PROCESS_DEBUG_EVENT | An EXIT_PROCESS_DEBUG_INFO structure named ExitProcess |
CREATE_THREAD_DEBUG_EVENT | A CREATE_THREAD_DEBUG_INFO structure named CreateThread |
EXIT_THREAD_DEBUG_EVENT | An EXIT_THREAD_DEBUG_EVENT structure named ExitThread |
LOAD_DLL_DEBUG_EVENT | A LOAD_DLL_DEBUG_INFO structure named LoadDll |
UNLOAD_DLL_DEBUG_EVENT | An UNLOAD_DLL_DEBUG_INFO structure named UnloadDll |
EXCEPTION_DEBUG_EVENT | An EXCEPTION_DEBUG_INFO structure named Exception |
OUTPUT_DEBUG_STRING_EVENT | An OUTPUT_DEBUG_STRING_INFO structure named DebugString |
RIP_EVENT | A RIP_INFO structure named RipInfo |
I won't go into detail about
all those structures in this tutorial, only the CREATE_PROCESS_DEBUG_INFO
structure will be covered here.
Assuming that our program
calls WaitForDebugEvent and it returns.
The first thing we should do is to examine the value in dwDebugEventCode
to see which type of debug event occured in the debuggee process. For example,
if the value in dwDebugEventCode is
CREATE_PROCESS_DEBUG_EVENT, you can
interpret the member in u as CreateProcessInfo
and access it with u.CreateProcessInfo.
ContinueDebugEvent
proto dwProcessId:DWORD, dwThreadId:DWORD, dwContinueStatus:DWORD
This function resumes the thread
that was previously suspended because a debug event occurred.
dwProcessId
and dwThreadId are the process and
thread IDs of the thread that will be resumed. You usually take these two
values from the dwProcessId and dwThreadId
members of the DEBUG_EVENT structure.
dwContinueStatus specifies
how to continue the thread that reported the debug event. There are two
possible values: DBG_CONTINUE and DBG_EXCEPTION_NOT_HANDLED.
For all other debug events, those two values do the same thing: resume
the thread. The exception is the EXCEPTION_DEBUG_EVENT.
If the thread reports an exception debug event, it means an exception occurred
in the debuggee thread. If you specify DBG_CONTINUE,
the thread will ignore its own exception handling and continue with the
execution. In this scenario, your program must examine and resolve the
exception itself before resuming the thread with DBG_CONTINUE
else the exception will occur again and again and again.... If you specify
DBG_EXCEPTION_NOT_HANDLED, your program
is telling Windows that it didn't handle the exception: Windows should
use the default exception handler of the debuggee to handle the exception.
In conclusion, if the debug
event refers to an exception in the debuggee process, you should call ContinueDebugEvent
with DBG_CONTINUE flag if your program
already removed the cause of exception. Otherwise, your program must call
ContinueDebugEvent with DBG_EXCEPTION_NOT_HANDLED
flag. Except in one case which you must always use DBG_CONTINUE
flag: the first EXCEPTION_DEBUG_EVENT
which has the value EXCEPTION_BREAKPOINT
in the ExceptionCode member. When the debuggee is going to execute its
very first instruction, your program will receive the exception debug event.
It's actually a debug break (int 3h). If you respond by calling ContinueDebugEvent
with DBG_EXCEPTION_NOT_HANDLED
flag, Windows NT will refuse to run the debuggee (because no one cares
for it). You must always use DBG_CONTINUE
flag in this case to tell Windows that you want the thread to go on.
.While eax = &TRUE
call 'KERNEL32.WaitForDebugEvent' DEBUG_EVENT &INFINITE
..If D§DE_dwDebugEventCode = &EXIT_PROCESS_DEBUG_EVENT
<Handle the debug events>
call 'KERNEL32.ContinueDebugEvent' D§DE_dwProcessId D§DE_dwThreadId,
&DBG_EXCEPTION_NOT_HANDLED
.End_While
Here's the catch: Once you start debugging a program, you just can't detach from the debuggee until it exits.
________________________________________________________________________________________
; Equates:
[OFN_FLAGS &OFN_FILEMUSTEXIST+&OFN_PATHMUSTEXIST+&OFN_LONGNAMES+&OFN_EXPLORER+&OFN_HIDEREADONLY]
________________________________________________________________________________________
; Data:
[UserFileFilter: 0 # 50] [FullChoosenFile: 0 #50] [ChoosenFile: 0 #20]
[OPENFILENAME: len hwndFileOwner:
0 OF_hInstance: 0 FilterStrings
userFileFilter 200 &NULL FullChoosenFile 200
ChoosenFile
200 &NULL AppName OFN_FLAGS
nFileOffsetinChoosenFile: W§ 0 nFileExtensioninChoosenFile:
0
DefaultExtension: D§ &NULL
HookCustomData: &NULL HookProcPtr: &NULL HookTemplateName:
0]
[AppName: B§
'Win32 Debug Example no.1' 0
FilterStrings: B§ 'Executable
Files' 0 '*.exe' 0
'All Files' 0 '*.*' 0 0
ExitProc:
'The debuggee exits' 0
NewThread:
'A new thread is created' 0
EndThread:
'A thread is destroyed' 0
ProcessInfo:
"File Handle: %lx
Process Handle: %lx
Thread Handle: %lx
Image Base: %lx
Start Address: %lx", 0]
[buffer: B§ 0 #512]
[PROCESS_INFORMATION: PI_hProcess:
0 PI_hThread: 0
PI_dwProcessId: 0 PI_dwThreadId: 0]
[STARTUPINFO: SI_cb: 0
SI_lpReserved: 0 SI_lpDesktop: 0
SI_lpTitle: 0 SI_dwX: 0
SI_dwY: 0
SI_dwXSize: 0 SI_dwYSize:
0 SI_dwXCountChars:
0
SI_dwYCountChars: 0 SI_dwFillAttribute: 0 SI_dwFlags:
0
SI_wShowWindow: W§ 0 SI_cbReserved2: W§ 0 SI_lpReserved2:
D§ 0
SI_hStdInput: 0 SI_hStdOutput: 0
SI_hStdError: 0]
; Structure returned by Win with damned nested included structures (only 2 needed here):
[DEBUG_EVENT: DE_dwDebugEventCode:
0 DE_dwProcessId: 0
DE_dwThreadId: 0
DE_u: CPDI_hFile:
E_pExceptionRecord: 0
CPDI_hProcess: 0
CPDI_hThread: 0
CPDI_lpBaseOfImage: 0
CPDI_lpStartAddress: 0] [DamnedUnionTail: 0 #100]
; CREATE_PROCESS_DEBUG_INFO union
Data (available at 'DE_u'):
;
; HANDLE hFile;
; HANDLE hProcess;
; HANDLE hThread;
; LPVOID lpBaseOfImage;
; DWORD dwDebugInfoFileOffset;
; DWORD nDebugInfoSize;
; LPVOID lpThreadLocalBase;
; LPTHREAD_START_ROUTINE
lpStartAddress;
; LPVOID lpImageName;
; WORD fUnicode;
; EXCEPTION_DEBUG_INFO union Data
(available at 'DE_u'):
;
; EXCEPTION_RECORD
ExceptionRecord;
; DWORD dwFirstChance;
; EXCEPTION_RECORD union Data (available at 'ExceptionRecord' of 'DE_u'):
; DWORD ExceptionCode;
; DWORD ExceptionFlags;
; struct _EXCEPTION_RECORD
*ExceptionRecord;
; PVOID ExceptionAddress;
; DWORD NumberParameters;
; DWORD ExceptionInformation[EXCEPTION_MAXIMUM_PARAMETERS];
____________________________________________________________________________________________
____________________________________________________________________________________________
Main:
call 'Comdlg32.GetOpenFileNameA'
OPENFILENAME
.If eax = &TRUE
call 'KERNEL32.GetStartupInfoA' STARTUPINFO
call 'KERNEL32.CreateProcessA' FullChoosenFile &NULL &NULL &NULL
&FALSE,
&DEBUG_PROCESS+&DEBUG_ONLY_THIS_PROCESS,
&NULL &NULL STARTUPINFO PROCESS_INFORMATION
.While eax = &TRUE
L0:
call 'KERNEL32.WaitForDebugEvent' DEBUG_EVENT &INFINITE
..If D§DE_dwDebugEventCode = &EXIT_PROCESS_DEBUG_EVENT
call 'USER32.MessageBoxA' 0 ExitProc AppName &MB_ICONINFORMATION |
jmp L9>>
..Else_If D§DE_dwDebugEventCode = &CREATE_PROCESS_DEBUG_EVENT
call 'USER32.wsprintfA' FullChoosenFile ProcessInfo,
D§CPDI_hFile D§CPDI_hProcess D§CPDI_hThread,
D§CPDI_lpBaseOfImage D§CPDI_lpStartAddress
pop eax eax eax eax eax eax eax
call 'USER32.MessageBoxA' 0 FullChoosenFile AppName &MB_ICONINFORMATION
..Else_If D§DE_dwDebugEventCode = &EXCEPTION_DEBUG_EVENT
If D§E_pExceptionRecord = &EXCEPTION_BREAKPOINT
call 'KERNEL32.ContinueDebugEvent' D§DE_dwProcessId D§DE_dwThreadId,
&DBG_CONTINUE
jmp L0<<
End_If
..Else_If D§DE_dwDebugEventCode = &CREATE_THREAD_DEBUG_EVENT
call 'USER32.MessageBoxA' 0 NewThread AppName &MB_ICONINFORMATION
..Else_If D§DE_dwDebugEventCode = &EXIT_THREAD_DEBUG_EVENT
call 'USER32.MessageBoxA' 0 EndThread AppName &MB_ICONINFORMATION
..End_If
call 'KERNEL32.ContinueDebugEvent' D§DE_dwProcessId D§DE_dwThreadId,
&DBG_EXCEPTION_NOT_HANDLED
.End_While
.End_If
L9: call 'Kernel32.CloseHandle'
D§PI_hProcess
call 'Kernel32.CloseHandle'
D§PI_hThread
call 'KERNEL32.ExitProcess'
0
call
'KERNEL32.GetStartupInfoA' STARTUPINFO
call 'KERNEL32.CreateProcessA' FullChoosenFile &NULL &NULL &NULL
&FALSE,
&DEBUG_PROCESS+&DEBUG_ONLY_THIS_PROCESS,
&NULL &NULL STARTUPINFO PROCESS_INFORMATION
When the user chose one, it
calls CreateProcess to load the program.
It calls GetStartupInfo to fill the
STARTUPINFO structure with its default
values. Note that we use DEBUG_PROCESS
combined with DEBUG_ONLY_THIS_PROCESS
flags in order to debug only this program, not including its child processes.
.While eax = &TRUE
L0:
call 'KERNEL32.WaitForDebugEvent' DEBUG_EVENT &INFINITE
When the debuggee is loaded,
we enter the infinite debug loop, calling WaitForDebugEvent.
WaitForDebugEvent will not return until
a debug event occurs in the debuggee because we specify INFINITE
as its second parameter. When a debug event occurred, WaitForDebugEvent
returns and DBEvent is filled with information about the debug
event.
..If D§DE_dwDebugEventCode = &EXIT_PROCESS_DEBUG_EVENT
call 'USER32.MessageBoxA' 0 ExitProc AppName &MB_ICONINFORMATION |
jmp L9>>
We first check the value in
dwDebugEventCode. If it's
EXIT_PROCESS_DEBUG_EVENT, we display a message box saying "The
debuggee exits" and then get out of the debug loop.
..Else_If D§DE_dwDebugEventCode = &CREATE_PROCESS_DEBUG_EVENT
call 'USER32.wsprintfA' FullChoosenFile ProcessInfo,
D§CPDI_hFile D§CPDI_hProcess D§CPDI_hThread,
D§CPDI_lpBaseOfImage D§CPDI_lpStartAddress
pop eax eax eax eax eax eax eax
call 'USER32.MessageBoxA' 0 FullChoosenFile AppName &MB_ICONINFORMATION
If the value in dwDebugEventCode is CREATE_PROCESS_DEBUG_EVENT, then we display several interesting information about the debuggee in a message box. We obtain those information from u.CreateProcessInfo. CreateProcessInfo is a structure of type CREATE_PROCESS_DEBUG_INFO. You can get more info about this structure from Win32 API reference.
..Else_If D§DE_dwDebugEventCode = &EXCEPTION_DEBUG_EVENT
If D§E_pExceptionRecord = &EXCEPTION_BREAKPOINT
call 'KERNEL32.ContinueDebugEvent' D§DE_dwProcessId D§DE_dwThreadId,
&DBG_CONTINUE
jmp L0<<
End_If
If the value in dwDebugEventCode
is EXCEPTION_DEBUG_EVENT, we must check
further for the exact type of exception. It's a long line of nested structure
reference but you can obtain the kind of exception from ExceptionCode
member. If the value in ExceptionCode
is EXCEPTION_BREAKPOINT and it occurs
for the first time (or if we are sure that the debuggee has no embedded
int 3h), we can safely assume that this exception occured when the debuggee
was going to execute its very first instruction. When we are done with
the processing, we must call ContinueDebugEvent
with DBG_CONTINUE flag to let the debuggee
run. Then we go back to wait for the next debug event.
..Else_If D§DE_dwDebugEventCode = &CREATE_THREAD_DEBUG_EVENT
call 'USER32.MessageBoxA' 0 NewThread AppName &MB_ICONINFORMATION
..Else_If D§DE_dwDebugEventCode = &EXIT_THREAD_DEBUG_EVENT
call 'USER32.MessageBoxA' 0 EndThread AppName &MB_ICONINFORMATION
..End_If
If the value in dwDebugEventCode
is CREATE_THREAD_DEBUG_EVENT or EXIT_THREAD_DEBUG_EVENT,
we display a message box saying so.
call 'KERNEL32.ContinueDebugEvent' D§DE_dwProcessId D§DE_dwThreadId,
&DBG_EXCEPTION_NOT_HANDLED
.End_While
Except for the
EXCEPTION_DEBUG_EVENT case above, we call ContinueDebugEvent
with DBG_EXCEPTION_NOT_HANDLED flag
to resume the debuggee.
L9: call 'Kernel32.CloseHandle'
D§PI_hProcess
call 'Kernel32.CloseHandle'
D§PI_hThread
When the debuggee exits, we
are out of the debug loop and must close both process and thread handles
of the debuggee. Closing the handles doesn't mean we are killing the process/thread.
It just means we don't want to use those handles to refer to the process/thread
anymore.