Introduction
Debuggers are the apple of the hacker’s eye. We benefit a lot from the debugger, but few of us know the principle of it.
In the book Gray Hat Python , the author has constructed a simple debugger. However, it is too simple, it is only a machine language level debugger, and can only set basic breakpoints and show CPU register information. We also want to know how to
- Show source code
- Set breakpoint based on lines, not memory address
- Set Step In, Step Out, Step Over
- Show stack trace
- Show global and local variables
In this Chinese blog Zplutor’s, I find a excellent series which has covered most above topics. I decide to write a English blog about it, and I will turn his code into a C++ version.
Before getting started, let’s make some limitations:
- It is only a user mode debugger.
- It is only a Windows debugger. Although the principle is quite same, but Windows has offered lots of convenient APIs. The implementation will be different on Linux.
- It is only a terminal-based debugger.
- Different from Gray Hat Python , the debugger will be implemented by C++.
- The debuggee program is single thread.
The modified debugger can be found here. It is only tested under Windows 10 + Visual Studio 2013.
To Start the Debuggee Program
The so-called user mode debugger is to debug the program in user mode. Windows has provided a series of open API for debugging, and they can be devided into three categories:
- API for starting the debuggee program
- API for handling debug event during debug loop
- API for inspecing and modifying debuggee program
The first thing to do before debugging a program is to start it. On Windows, we use CreateProcess to start to program:
1 |
STARTUPINFO startupinfo = { 0 }; |
- DEBUG_ONLY_THIS_PROCESS means the subprocess of the debuggee will not be debugged. If you need subprocess, use DEBUG_PROCESS.
- CREATE_NEW_CONSOLE means the debuggee’s and debugger’s output will be separated in two consoles.
- If the debugger process exits, the debuggee will also exit.
Debugger loop
The debugger loop is a bit like Windows GUI message loop, some operations and exceptions will stop the debuggee and send event to the debugger. We always use
1 |
DEBUG_EVENT debugEvent; |
to capture the debug event.
There are 9 debug event in total:
- CREATE_PROCESS_DEBUG_EVENT. Reports a create-process debugging event.
- CREATE_THREAD_DEBUG_EVENT. Reports a create-thread debugging event.
- EXCEPTION_DEBUG_EVENT. Reports an exception debugging event.
- EXIT_PROCESS_DEBUG_EVENT. Reports an exit-process debugging event.
- EXIT_THREAD_DEBUG_EVENT. Reports an exit-thread debugging event.
- LOAD_DLL_DEBUG_EVENT. Reports a load-dynamic-link-library (DLL) debugging event.
- OUTPUT_DEBUG_STRING_EVENT. Reports an output-debugging-string debugging event.
- RIP_EVENT. Reports a RIP-debugging event (system debugging error).
- UNLOAD_DLL_DEBUG_EVENT. Reports an unload-DLL debugging event.
If the debug event has been handled correctly, then
1 |
ContinueDebugEvent(debuggeeprocessID, debuggeethreadID, DBG_CONTINUE); |
to continue the debuggee process. Let’s combine the above to construct the debug loop:
1 |
while (WaitForDebugEvent(&debugEvent, INFINITE) == TRUE) |
In the next part of the series, I intend to give a brief introduction about the 9 debug events.
近期评论