Preamble

All of us have used some kind of debugger while programming in some language. The debugger you used may be in C++, C#, Java or another language. It might be standalone like WinDbg, or inside an IDE like Visual Studio. But have you been inquisitive over how debuggers work?

Well, this article presents the hidden glory on how debuggers work. This article only covers writing debugger on Windows. Please note that here I am concerned only about the debugger and not about compilers, linkers, or debugging extensions. Thus, we'll only debug executables (like WinDbg). This article assumes a basic understanding of multithreading from the reader (read my article on multithreading).

1. How to Debug a Program?

Two steps:

Starting the process with DEBUG_ONLY_THIS_PROCESS or DEBUG_PROCESS flags.

Setting up the debugger's loop that will handle debugging events.

Before we move further, please remember:

Debugger is the process/program which debugs the other process (target-process).

Debuggee is the process being debugged, by the debugger.

Only one debugger can be attached to a debuggee. However, a debugger can debug multiple processes (in separate threads).

Only the thread that created/spawned the debuggee can debug the target-process. Thus, CreateProcess and the debugger-loop must be in the same thread.

When the debugger thread terminates, the debuggee terminates as well. The debugger process may keep running, however.

When the debugger's debugging thread is busy processing a debug event, all threads in the debuggee (target-process) stand suspended. More on this later.

A. Starting the process with the debugging flag

Use CreateProcess to start the process, specifying DEBUG_ONLY_THIS_PROCESS as the sixth parameter (dwCreationFlags). With this flag, we are asking the Windows OS to communicate this thread for all debugging events, including process creation/termination, thread creation/termination, runtime exceptions, and so on. A detailed explanation is given below. Please note that we'll be using DEBUG_ONLY_THIS_PROCESS in this article. It essentially means we want only to debug the process we are creating, and not any child process(es) that may be created by the process we create.

After this statement, you would see the process in the Task Manager, but the process hasn't started yet. The newly created process is suspended. No, we don't have to call ResumeThread, but write a debugger-loop.

B. The debugger loop

The debugger-loop is the central area for debuggers! The loop runs around the WaitForDebugEvent API. This API takes two parameters: a pointer to the DEBUG_EVENT structure and the DWORD timeout parameter. For timeout, we would simply specify INFINITE. This API exists in kernel32.dll, thus we need not link to any library.

The DEBUG_EVENT structure contains the debugging event information. It has four members: Debug event code, process-ID, thread-ID, and the event information. As soon as WaitForDebugEvent returns, we process the received debugging event, and then eventually call ContinueDebugEvent. Here is a minimal debugger-loop:

Using the ContinueDebugEvent API, we are asking the OS to continue executing the debuggee. The dwProcessId and dwThreadId specify the process and thread. These values are the same that we received form WaitForDebugEvent. The last parameter specifies if the execution should continue or not. This parameter is relevant only if the exception-event is received. We will cover this later. Until then, we'll utilize only DBG_CONTINUE (another possible value is DBG_EXCEPTION_NOT_HANDLED).

2. Handling debugging events

There are nine different major debugging events, and 20 different sub-events under the exception-event category. I will discuss them, starting from the simplest. Here is the DEBUG_EVENT structure:

WaitForDebugEvent, on successful return, fills-in the values in this structure. dwDebugEventCode specifies which debugging-event has occurred. Depending on the event-code received, one of the members of the union u contains the event information, and we should only use the respective union-member. For example, if the debug event code is OUTPUT_DEBUG_STRING_EVENT, the member OUTPUT_DEBUG_STRING_INFO would be valid.

A. Processing OUTPUT_DEBUG_STRING_EVENT

Programmers generally use OutputDebugString to generate debugging-text that would be displayed on the debugger's 'Output' window. Depending on the language/framework you use, you might be familiar with the TRACE, ATLTRACE macros. A .NET programmers may use the System.Diagnostics.Debug.Print/System.Diagnostics.Trace.WriteLine methods (or other methods). But with all these methods, the OutputDebugString API is called, and the debugger would receive this event (unless it is buried with the DEBUG symbol undefined!).

When this event is received, we work on the DebugString member variable. The structure OUTPUT_DEBUG_STRING_INFO is defined as:

The member-variable 'nDebugStringLength' specifies the length of the string, including the terminating null, in characters (not bytes). The variable 'fUnicode' specifies if the string is Unicode (non-zero), or ANSI (zero). That means, we read 'nDebugStringLength' bytes from 'lpDebugStringData' if the string is ANSI; otherwise, we read (nDebugStringLength x 2) bytes. But remember, the address pointed by 'lpDebugStringData' is not from the address-space of the debugger's memory. The address is relevant to the debuggee memory. Thus, we need to read the contents from the debuggee's process memory.

To read data from another process' memory, we use the ReadProcessMemory function. It requires that the calling process should have the appropriate permission. Since the debugger only created the process, we do have the rights. Here is the code to process this debugging event:

What if the debuggee terminates before the debugger copies the memory contents?

Well... In that case, I would like to remind you: when the debugger is processing a debugging event, all threads in the debuggee are suspended. The process cannot kill itself in anyway at this moment. Also, no other method can terminate the process (Task Manager, Process Explorer, Kill utility...). Attempting to kill the process from these utilities will, however, schedule the terminating process. Thus, the debugger would receive EXIT_PROCESS_DEBUG_EVENT as the next event!

B. Processing CREATE_PROCESS_DEBUG_EVENT

This event is raised when the process (debuggee) is being spawned. This would be the first event the sebugger receives. For this event, the relevant member of DEBUG_EVENT would be CreateProcessInfo. Here is the structure definition of CREATE_PROCESS_DEBUG_INFO:

Please note that hProcess and hThread may not have the same handle values we have received in pi (PROCESS_INFORMATION). The process-ID and the thread-ID would, however, be the same. Each handle you get by Windows (for the same resource) is different from other handles, and has a different purpose. So, the debugger may choose to display either the handles or the IDs.

The hFile as well as lpImageName can both be used to get the file-name of the process being debugged. Although, we already know what the name of the process is, since we only created the debuggee. But locating the module name of the EXE or DLL is important, since we would anyway need to find the name of the DLL while processing the LOAD_DLL_DEBUG_EVENT message.

As you can read in MSDN, lpImageName will never return the filename directly, and the name would be in the target-process. Furthermore, it may not have a filename in the target-process too (i.e., via ReadProcessMemory). Also, the filename may not be fully qualified (as I've tested). Thus, we will not use this method. We'll retrieve the filename from the hFile member.

How to get the name of the file by HANDLE

Unfortunately, we need to use the method described in MSDN that uses around 10 API calls to get the filename from the handle. I have slightly modified the function GetFileNameFromHandle. The code is not shown here for brevity, it is available in the source file attached with this article. Anyway, here is the basic code to process this event:

case CREATE_PROCESS_DEBUG_EVENT:
{
CString strEventMessage =
GetFileNameFromHandle(debug_event.u.CreateProcessInfo.hFile);
// Use strEventMessage, and other members
// of CreateProcessInfo to intimate the user of this event.
}

You may have noticed that I did not cover a few members of this structure. I would probably cover all of them in the next part of this article.

C. Processing LOAD_DLL_DEBUG_EVENT

This event is similar to CREATE_PROCESS_DEBUG_EVENT, and as you might have guessed, it is raised when a DLL is loaded by the OS. This event is raised whenever a DLL is loaded, either implicitly or explicitly (when the debuggee calls LoadLibrary). This debugging event only occurs the first time the system attaches a DLL to the virtual address space of a process. For this event processing, we use the 'LoadDll' member of the union. It is of type LOAD_DLL_DEBUG_INFO:

For retrieving the filename, we would use the same function, GetFileNameFromHandle, as we have used in CREATE_PROCESS_DEBUG_EVENT. I will list out the code for processing this event when I would describe UNLOAD_DLL_DEBUG_EVENT, since the UNLOAD_DLL_DEBUG_EVENT does not have any direct information available to find out the name of the DLL file.

D. Processing CREATE_THREAD_DEBUG_EVENT

This debug event is generated whenever a new thread is created in the debuggee. Like CREATE_PROCESS_DEBUG_EVENT, this event is raised before the thread actually gets to run. To get information about this event, we use the 'CreateThread' union member. This variable is of type CREATE_THREAD_DEBUG_INFO:

The 'lpStartAddress' is relevant to the debuggee and not the debugger; we are just displaying it for completeness. Remember this event is not received for the primary/initial thread of the process. It is received only for the subsequent thread creations in the debuggee.

E. Processing EXIT_THREAD_DEBUG_EVENT

This event is raised as soon as the thread returns, and the return code is available to the system. The 'dwThreadId' member of DEBUG_EVENT specifies which thread exited. To get the thread handle and other information that we received in CREATE_THREAD_DEBUG_EVENT, we need to store the information in some map. This event has a relevant member named 'ExitThread', which is of type EXIT_THREAD_DEBUG_INFO:

F. Processing UNLOAD_DLL_DEBUG_EVENT

Of course, this event occurs when a DLL is unloaded from the debuggee's memory. But wait! It is only generated against FreeLibrary calls, and not when the system unloads DLLs. The debuggee may call LoadLibrary multiple times, and thus only the last call to FreeLibrary would raise this event. It means, the implicitly loaded DLLs will not receive this event when they are unloaded, when the process exits. (You can verify this assertion in your favorite debugger!)

For this event, you use the 'UnloadDll' member of the union, which is of type UNLOAD_DLL_DEBUG_INFO:

struct UNLOAD_DLL_DEBUG_INFO
{
LPVOID lpBaseOfDll;
};

As you can see, only the base-address of the DLL (a simple pointer) is available for us to process this event. This is the reason I had delayed giving the code for LOAD_DLL_DEBUG_EVENT. In the DLL loading event, we get the 'lpBaseOfDll' also. We can use the map (or another data structure you like) to store the name of the DLL against the base-address of the DLL. The same base-address would arrive while processing UNLOAD_DLL_DEBUG_EVENT.

It should be noted that not all DLL-load events would get the DLL-unload event; still, we have to store all DLL names into the map, since LOAD_DLL_DEBUG_EVENT doesn't provide us info on how the DLL was loaded.

G. Processing EXIT_PROCESS_DEBUG_EVENT

This is one of the simplest debugging event, and as you can assess, would arrive when the process exists. This event would arrive irrespective of how the process exits - normally, terminated externally (Task Manager etc.), or the application's (debuggee) fault leading it to crash.

We use the 'ExitProcess' member, which is of type EXIT_PROCESS_DEBUG_INFO:

struct EXIT_PROCESS_DEBUG_INFO
{
DWORD dwExitCode;
};

As soon as this event occurs, we also end the debugger-loop and terminate the debugging thread. For this, we can use a variable to control the loop (the 'for' loop shown in the first page), and set its value to indicate loop termination. Please download the attached files to see the entire code.

H. Processing EXCEPTION_DEBUG_EVENT

This is the prodigious event amongst all the debugging events! From MSDN:

This is generated whenever an exception occurs in the process being debugged. Possible exceptions include attempting to access inaccessible memory, executing breakpoint instructions, attempting to divide by zero, or any other exception noted in Structured Exception Handling. The DEBUG_EVENT structure contains an EXCEPTION_DEBUG_INFO structure. This structure describes the exception that caused the debugging event.

This debugging event needs a separate article to complete it fully (or partially!). Thus, I would discuss only one type of exception event, along with an introduction to this event itself.

The member variable 'Exception' holds the information regarding the exception just occurred. It is of type EXCEPTION_DEBUG_INFO:

The detailed information is put into this sub-structure, because exceptions may appear nested, and would be linked to each other in a linked-list manner. It is out of topic for now to discuss nested exceptions.

Before we delve into EXCEPTION_RECORD, it is important to discuss EXCEPTION_DEBUG_INFO::dwFirstChance.

Are exceptions giving chances?

Not exactly! When a process is being debugged, the debugger always receives the exception before the debuggee gets it. You must have seen "First-chance exception at 0x00412882 in SomeModule:..." while debugging your Visual C++ module. This is referred to as First Chance Exception. The same exception may or may not follow with a second chance exception.

When the debuggee gets the exception, it is termed as Second Chance Exception. The debuggee may handle the exception, or may simply crash down. These types of exceptions are not C++ exceptions, but Windows' SEH (structure exception handling) mechanism. I would cover more about it in the next part of this article.

The debugger gets exceptions first (First-chance exception), so that it can handle it before giving it to the debuggee. The break-point exception is a kind of exception, which is relevant to the debugger, not the debuggee. Some libraries also generate First chance exceptions to aid the debugger and the debugging process.

A word for ContinueDebugEvent

The third parameter (dwContinueStatus) of this function is relevant only after an exception event is received. For non-exception events that we discussed, the system ignores the value passed to this function.

After the exception event processing, ContinueDebugEvent should be called with:

DBG_CONTINUE if the exception event was successfully handled by the debugger. No action is required by the debuggee, and the debuggee can run normally.

DBG_EXCEPTION_NOT_HANDLED if this event is not handled/resolved by the debuggee. The debugger might just record this event, notify the debugger-user, or do something else.

Please note that returning DBG_CONTINUE for the improper debugging event would raise the same event in the debugger, and the same event would arrive indefinitely. Since we are in the early stage of writing debuggers, we should play safe, and return EXCEPTION_NOT_HANDLED (give up flag!). The exclusion, for this article, is the Break-point event, which I am discussing next.

Exceptions codes

The EXCEPTION_RECORD::ExceptionCode variable holds the arrived exception code, and can have one of these codes (ignore nested exceptions!):

EXCEPTION_ACCESS_VIOLATION

EXCEPTION_ARRAY_BOUNDS_EXCEEDED

EXCEPTION_BREAKPOINT

EXCEPTION_DATATYPE_MISALIGNMENT

EXCEPTION_FLT_DENORMAL_OPERAND

EXCEPTION_FLT_DIVIDE_BY_ZERO

EXCEPTION_FLT_INEXACT_RESULT

EXCEPTION_FLT_INVALID_OPERATION

EXCEPTION_FLT_OVERFLOW

EXCEPTION_FLT_STACK_CHECK

EXCEPTION_FLT_UNDERFLOW

EXCEPTION_ILLEGAL_INSTRUCTION

EXCEPTION_IN_PAGE_ERROR

EXCEPTION_INT_DIVIDE_BY_ZERO

EXCEPTION_INT_OVERFLOW

EXCEPTION_INVALID_DISPOSITION

EXCEPTION_NONCONTINUABLE_EXCEPTION

EXCEPTION_PRIV_INSTRUCTION

EXCEPTION_SINGLE_STEP

EXCEPTION_STACK_OVERFLOW

Relax! I am not discussing all of them, but one: EXCEPTION_BREAKPOINT. Okay, here is the code:

You might be aware of what a breakpoint is. Out of the standard debugger perspective, the break-pointing can happen with the DebugBreak API, or the {int 3} assembly instruction, or System.Diagnostics.Debugger.Break in the .NET Framework. The debugger would receive the Debug-exception code STATUS_BREAKPOINT (same as EXCEPTION_BREAKPOINT) when any of these occur in the running process. The debuggers generally use this event to break the running process, and may display the source code where the event occurred. But in our basic debugger, we would just display this event to the user. No source code or the instruction location is shown. We'll cover displaying the source code in the next part of this article.

Raising breakpoint from a process which is not being debugged would crash the application, or may display the JIT dialog box. The is the reason I used:

As a final note to this simplest debug-exception event: EXCEPTION_DEBUG_EVENT would be raised first time by the kernel itself, and would always arrive. Debuggers like Visual Studio ignore this very first breakpoint exception, but debuggers like WinDbg would always show you this event too.

Winding up...

Use any process to debug or use the attached debuggee named DebugMe:

The binaries (EXEs) attached here are compiled with Visual Studio 2005 Service Pack 1. You may not have the VC++ runtime libraries for the same version. You can download them from Microsoft.com or rebuild the projects from your IDE.

.NET program and the debugging is different than native debugging, and it has separate set of interfaces in .NET class library itself.

There is no significance in writing a native debugger in managed (C#) language - you will have to import all required functions from DbgHelp.DLL, do pointer abuse, write more of "unsafe" code and things like that.

I thought to use your "Crude Debugger.exe" as the "receptacle" (as it is now - not written !); the strings, messages, ... should be sended from any C# application - like your "DebugMe.exe" but written in C#

im currently searching for a good(extensible) way of tracing jmp and calls
of an server-like application

i've got an function jmp/call graph of the exe and want to combine that with something
like an "hot-routes" feature that should easily show what functions are (more,most)used
when starting descent functionality of the executable.

is your debugger (still)small enough to introduce(well by me) such a feature? i don't need any
gui or stuff like that - "just" the observation of jmp/calls

As mentioned in second part of this article, we need to have complete understanding of how x86 instructions should be interpreted. Unless and until we know what those code-bytes are, it is hard to find the jmp or any other assembly instruction.

You can use OllyDbg to find jmp instructions, and sample code (somewhere in same site) to find how instructions are interpreted. Even that sample doesn't cover entire set of instructions (for eg. SSE2, SSE3...).

Unless and until we know the size of those "unknown" instructions, it is almost impossible to find "correct" instructions we are looking for (jmp in your case). If we can successfully find jmp instructions, properly ignoring any instructions we don't know, we can place invisible breakpoints (See second part).

You can see dumpbin /disasm's output, which correctly ignores instructions it doesn't understand.

but the problem (if i understand your problem correct) is that we are not sitting always on the
beginning of each instruction (of different size)?

whould it be more easy if i provide you with an extract_call_jmp_info( buffer_start, is_call/jmp, target_adress ) function
which is be able to extract the right information (if buffer_start always points to the begining of the opcode(which could be multibyte)

Let's assume the code is of 20 bytes.
The first 2 byte is valid, we understand it. This is 2-byte opcode, we process it.
The byte at 3rd location is an opcode of 4 bytes. We understand it, but we ignore it.
Now we are are 7th byte, whose opcode we dont understand, NEITHER we do understand the size.

So, how we move further from 7th byte, to find next valid (interested or not) instruction.

The problem is not if we are interested in instruction or not, we do actually understand it or not, but determining the size of opcode which we don't understand.
Along with this, there can be opcode which aren't even recognized or processed by processor, and OS would trigger "Illegal Instruction", we may also need to handle those cases too!

(before i pressed mistakenly the "email" button in the forum, no idea what then happen to my text)

so now i've understand all your concerns, and i've written my own small
debugger thx to your and others tutorials...

i've got no problemen with patching the "int 3" in and wait for EXCEPTION_BREAK_POINT
but my re-enableing of the breakpoint still does not work correctly

after EXCEPTION_BREAK_POINT, i recover the original code and activate single stepping
but in EXCEPTION_SINGLE_STEP my Eip is behind my original code

you talk about saving the context in EXCEPTION_BREAK_POINT and
a recover of the context in EXCEPTION_SINGLE_STEP - this code is missing
in my example, and i don't realy know what todo

In EXCEPTION_BREAKPOINT event,

Get thread context, save it. !!! what to save here? !!!
Reduce EIP by one.
Set processor trap flag for single stepping.
Set thread context.
call WriteProcessMemory, and FlushInstructionCache, as usual to revert instruction.

In EXCEPTION_SINGLE_STEP event,

Render the registers for the context saved in previous step. !!! that part is missing !!!
Enumerate call-stack from saved context.
Halt debugging.

I've been thinking about the "understand all instructions" problem. One solution in your situation might be to single-step the program from the outset until you come across a JMP, CALL or RET instruction. You single-step past the JMP/CALL/RET, set a break-point, and then continue to single-step as before.

However, as all this single-stepping is going on you keep track of the location of all instructions. When you single-step and discover the EIP is back in a location that had previously been single-stepped you can just run the program normally. At that point it will run fast until a break-point. Again, you single-step, reset the break-point, and (depending on the EIP value) you either run the program full speed or start single-stepping again.

At first the program will run slowly with all that single-stepping. But it will quickly pick up speed as your debugger learns where the JMP/CALL/RET instructions reside. Most programs repeat vast parts of their code base over and over so hopefully this wouldn't be an issue.

I found this article just in time. I made a minor modification to allow an application to set and clear a memory change breakpoint using the debug registers and was able to locate a memory corruption bug. Thanks for the example.

Hi,
The article is really good and bring some good information about the debugging API process.
You show how to call the debugger from the debugge, but my question is how to do the inverse, how the IDE like VS can set a break point in a source code line and the debugged program will stop when it come to that line.

Good info but to clarify this is not a limitation in the function itself but a limitation in the operating system.
This is not a limitation due to hardware limitations but a decision made by the programmers at microsoft.
( One of those "to make more money decisions" )

It really disgusts me when they limit the functionality on a software like this.