在本文中,将探讨商业可用调试器的某些方面,特别是针对操作系统(OS)和CPU操作码(x86-32位)。文章将展示断点和OutputDebugString的工作方式,这两种事件在调试过程中非常常见。建议读者进一步研究条件断点和逐步执行(逐行执行),这些功能大多数调试器都支持。“运行到光标”与断点类似。
在开始之前,读者需要具备基本的操作系统知识。与操作系统相关的讨论超出了本文的范围。如果需要,可以参考其他文章或联系作者。读者需要熟悉商业可用的调试器(本文以VS2010为例),并且在使用断点调试应用程序之前有一定的经验。
断点允许用户在被调试程序的执行流程中设置一个中断。用户可以这样做,以便在执行的这一点评估某些条件。调试器会在特定的地址(用户希望设置断点的地方)的可执行文件进程空间中添加一个指令:
int 3 (操作码: 0xcc)
遇到这个指令后:
当从中断服务例程返回(使用IRET)时,EIP将指向要执行的下一个字节,但希望它指向前一个字节(恢复的那个),这是在处理断点时完成的。尽管正在处理断点服务例程(其EIP指向服务例程中的某个位置),GetThreadContext将返回EIP移动到int 3服务例程之前的寄存器值。将EIP减1,使用SetThreadContext设置EIP。
这个API用于在调试控制台上显示一个字符串,用户可以使用它来显示与状态相关的信息或跟踪。当这个API发生时,会触发OUTPUT_DEBUG_STRING_EVENT事件。附加的调试器将在调试循环中处理这个事件(在代码中称为EnterDebugLoop)。事件处理API将提供有关字符串的信息,相对于被调试进程的空间。使用ReadProcessMemory从另一个进程获取字符串(内存转储)。
在阅读本文时,必须始终参考附加的代码。通过以下方式引入断点(操作码:0xcc):
BYTE p[] = {0xcc}; //0xcc=int 3
::WriteProcessMemory(pi.hProcess, (void*)address_to_set_breakpoint, p, sizeof(p), &d);
第二个参数是需要放置断点指令的地址,通过.PDB文件(调试符号文件)查找。通过.PDB文件,VS2010可以准确地将断点放置在与生成指令的代码行对应的内存位置。上述方法被注释掉了,原因是不能准确地放置断点,因为没有使用任何调试符号,而是使用::DebugBreak()在被调试的进程中引起断点,参考代码。
对于使用VS2010调试(任何)应用程序的读者 - 如果断点放置在代码中(其可执行文件是使用调试设置创建的),使用VS2010 IDE(通过按F9),内存调试视图不会显示0xcc。读者必须转储断点创建点的内存,当然,地址位置必须通过反汇编查找(由于目前正在调试应用程序,可以按ALT-8)。
在附加代码中,使用了EIP的值,从以下代码中获取EIP的值(代码注释使这自解释):
UINT EIP = 0;
_asm {
call f
jmp finish
f: pop eax
mov EIP,eax
push eax
ret
finish:
}
// print the memory dump
BYTE *b = (BYTE*)EIP;
for (int i = 0; i < 200; i++) printf("%x : %x \n", EIP+i, b[i]);
WaitForDebugEvent(&de, INFINITE); //will wait till a debug event is triggered
switch (de.dwDebugEventCode) {
case EXCEPTION_DEBUG_EVENT:
switch (de.u.Exception.ExceptionRecord.ExceptionCode) {
case EXCEPTION_BREAKPOINT:
MessageBoxA(0, "Found break point", "", 0);
break;
}
break;
case OUTPUT_DEBUG_STRING_EVENT:
{
char a[100];
ReadProcessMemory(pi.hProcess, de.u.DebugString.lpDebugStringData, a, de.u.DebugString.nDebugStringLength, NULL);
printf("output from debug string is: %s", a);
}
break;
}
ContinueDebugEvent(de.dwProcessId, de.dwThreadId, DBG_CONTINUE); // After the debug event is handled, Debugger must call ContinueDebugEvent