调试篇——断点与单步

写在前面 ??此系列是本人一个字一个字码出来的,包括示例和实验截图。由于系统内核的复杂性,故可能有错误或者不全面的地方,如有错误,欢迎批评指正,本教程将会长期更新。 如有好的建议,欢迎反馈。码字不易,如果本篇文章有帮助你的,如有闲钱,可以打赏支持我的创作。如想转载,请把我的转载信息附在文章后面,并声明我的个人信息和本人博客地址即可,但必须事先通知我。

你如果是从中间插过来看的,请仔细阅读 羽夏看Win系统内核——简述 ,方便学习本教程。
??看此教程之前,问几个问题,基础知识储备好了吗?保护模式篇学会了吗?练习做完了吗?没有的话就不要继续了。
?
华丽的分割线
?
概述 ??在软件调试的过程中,最常用的就是断点和单步了。有过调试经验的人知道,断点有软件断点、硬件断点、内存断点;单步有单步步入和单步步过。下面我们来介绍它们的实现原理。
软件断点 ??软件断点是实现的本质就是在下断点的地方插入int3,也被称之为CC断点。下面我们来做个实验:
??我们打开一个调试器调试一个进程,然后在某条汇编下断点,于此同时通过CE来查看当前指令的字节变化情况,如下是操作示例:
调试篇——断点与单步
文章图片

??也就是说,调试器帮我们屏蔽掉了0xCC的显示,本质上还是int3,就凭这些我们就可以写一个接管软件断点的简单的调试器了:
#include "stdafx.h" #include #include BOOL SysInt3 = TRUE; void WaitUserInput(); BOOL BreakPointHandler(EXCEPTION_DEBUG_INFO* info); const UCHAR Int3 = 0xCC; UCHAR bInt3; PROCESS_INFORMATION pi ; #define DBGBREAKPOINTint main(int argc, char* argv[]) { char filename[]= "C:\\WINDOWS\\NOTEPAD.EXE"; STARTUPINFO si ={sizeof(STARTUPINFO)}; DEBUG_EVENT dbgEvent; BOOL isContinue = TRUE; DWORD buffer; BOOL ret =CreateProcess(NULL,filename,NULL,NULL,FALSE,DEBUG_PROCESS|DEBUG_ONLY_THIS_PROCESS,NULL,NULL,&si,&pi); if (ret) { while (isContinue) { ret = WaitForDebugEvent(&dbgEvent,INFINITE); if (!ret) { printf("WaitForDebugEvent 出错:%d",GetLastError()); break; }EXCEPTION_DEBUG_INFO info = dbgEvent.u.Exception; switch (dbgEvent.dwDebugEventCode) { case EXCEPTION_DEBUG_EVENT: puts("EXCEPTION_DEBUG_EVENT"); switch (info.ExceptionRecord.ExceptionCode) { case EXCEPTION_BREAKPOINT: isContinue = BreakPointHandler(&info); break; } break; case CREATE_THREAD_DEBUG_EVENT: puts("CREATE_THREAD_DEBUG_EVENT"); break; case CREATE_PROCESS_DEBUG_EVENT: puts("CREATE_PROCESS_DEBUG_EVENT"); #ifdef DBGBREAKPOINTputs("请输入要下断点的位置:"); scanf("%x",&buffer); if (!ReadProcessMemory(pi.hProcess,(void*)buffer,&bInt3,1,NULL)) { puts("软件断点设置失败!"); isContinue = FALSE; TerminateProcess(pi.hProcess,0); }if (!WriteProcessMemory(pi.hProcess,(void*)buffer,(void*)&Int3,1,NULL)) { puts("软件断点设置失败!"); isContinue = FALSE; TerminateProcess(pi.hProcess,0); }#endifbreak; case EXIT_THREAD_DEBUG_EVENT: puts("EXIT_THREAD_DEBUG_EVENT"); break; case EXIT_PROCESS_DEBUG_EVENT: puts("EXIT_PROCESS_DEBUG_EVENT"); break; case LOAD_DLL_DEBUG_EVENT: puts("LOAD_DLL_DEBUG_EVENT"); break; case UNLOAD_DLL_DEBUG_EVENT: puts("UNLOAD_DLL_DEBUG_EVENT"); break; case OUTPUT_DEBUG_STRING_EVENT: puts("OUTPUT_DEBUG_STRING_EVENT"); break; default: break; }isContinue = ContinueDebugEvent(dbgEvent.dwProcessId,dbgEvent.dwThreadId,DBG_CONTINUE); } CloseHandle(pi.hProcess); CloseHandle(pi.hThread); } else { printf("创建进程失败:%d\n",GetLastError()); } system("pause"); return 0; }BOOL BreakPointHandler(EXCEPTION_DEBUG_INFO* info) { if (SysInt3) { SysInt3 = FALSE; return TRUE; }EXCEPTION_RECORD record = info->ExceptionRecord; WriteProcessMemory(pi.hProcess,record.ExceptionAddress,&bInt3,1,NULL); printf(">> BreakPointHandler - Addr : 0x%X\n",record.ExceptionAddress); WaitUserInput(); CONTEXT context; context.ContextFlags = CONTEXT_FULL |CONTEXT_DEBUG_REGISTERS; GetThreadContext(pi.hThread,&context); context.Eip--; SetThreadContext(pi.hThread,&context); return TRUE; }void WaitUserInput() { bool p = true; char cmd = 0; while(p) { printf("COMMAND>> "); /*************清空缓冲区**************/ scanf("%*[^\n]"); scanf("%*c"); /*************************************/cmd = getchar(); switch (cmd) { case 'g': p = false; break; default: break; } } }

??我定义DBGBREAKPOINT这个宏是为了方便之后实验只需定义宏来控制单个功能的测试。WaitUserInput函数里面清空缓冲区的代码不清楚的话可以阅读 羽夏闲谈—— C 的 scanf 的高级用法 ,如下是我的实验结果:
调试篇——断点与单步
文章图片

??当然我们只是简单的实现,一般的调试器都会保留这个断点,具体实现我就不实现了。
内存断点 ??内存断点是通过修改页属性来制造异常出现接管的,既然是修改其他程序的,就需要调用下面的函数:
BOOL VirtualProtectEx( HANDLE hProcess,// handle to process LPVOID lpAddress,// region of committed pages SIZE_T dwSize,// size of region DWORD flNewProtect,// desired access protection PDWORD lpflOldProtect// old protection );

??内存断点有被分为内存访问断点和内存写入断点,它们分别对应的第四个参数为PAGE_NOACCESSPAGE_EXECUTE_READPAGE_NOACCESS属性会把对应的页属性的P位改为0,而PAGE_EXECUTE_READ对应的页属性的P位虽然是1,但R/W0。下面我们来实现一个简单的内存访问断点:
#include "stdafx.h" #include #include BOOL SysInt3 = TRUE; void WaitUserInput(); BOOL BreakPointHandler(EXCEPTION_DEBUG_INFO* info); BOOL MemBreakPointHandler(EXCEPTION_DEBUG_INFO* info); const UCHAR Int3 = 0xCC; UCHAR bInt3; PROCESS_INFORMATION pi ; DWORD orignExcuteAccess; DWORD buffer; //#define DBGBREAKPOINT #define MemBREAKPOINTint main(int argc, char* argv[]) { char filename[]= "C:\\WINDOWS\\NOTEPAD.EXE"; STARTUPINFO si ={sizeof(STARTUPINFO)}; DEBUG_EVENT dbgEvent; BOOL isContinue = TRUE; BOOL ret =CreateProcess(NULL,filename,NULL,NULL,FALSE,DEBUG_PROCESS|DEBUG_ONLY_THIS_PROCESS,NULL,NULL,&si,&pi); if (ret) { while (isContinue) { ret = WaitForDebugEvent(&dbgEvent,INFINITE); if (!ret) { printf("WaitForDebugEvent 出错:%d",GetLastError()); break; }EXCEPTION_DEBUG_INFO info = dbgEvent.u.Exception; switch (dbgEvent.dwDebugEventCode) { case EXCEPTION_DEBUG_EVENT: puts("EXCEPTION_DEBUG_EVENT"); switch (info.ExceptionRecord.ExceptionCode) { case EXCEPTION_BREAKPOINT: ret = BreakPointHandler(&info); break; case EXCEPTION_ACCESS_VIOLATION: ret = MemBreakPointHandler(&info); break; } if (!ret) { printf("出错:%d\n",GetLastError()); isContinue = FALSE; TerminateProcess(pi.hProcess,0); } isContinue = ret; break; case CREATE_THREAD_DEBUG_EVENT: puts("CREATE_THREAD_DEBUG_EVENT"); break; case CREATE_PROCESS_DEBUG_EVENT: puts("CREATE_PROCESS_DEBUG_EVENT"); #ifdef DBGBREAKPOINTputs("请输入要下断点的位置:"); scanf("%x",&buffer); if (!ReadProcessMemory(pi.hProcess,(void*)buffer,&bInt3,1,NULL)) { puts("软件断点设置失败!"); isContinue = FALSE; TerminateProcess(pi.hProcess,0); }if (!WriteProcessMemory(pi.hProcess,(void*)buffer,(void*)&Int3,1,NULL)) { puts("软件断点设置失败!"); isContinue = FALSE; TerminateProcess(pi.hProcess,0); }#endif#ifdef MemBREAKPOINT buffer = (DWORD)dbgEvent.u.CreateProcessInfo.lpStartAddress; if (!VirtualProtectEx(pi.hProcess,(void*)buffer,1,PAGE_NOACCESS,&orignExcuteAccess)) { puts("内存执行断点设置失败!"); isContinue = FALSE; TerminateProcess(pi.hProcess,0); } printf("内存访问断点的位置:0x%X\n",buffer); #endif break; case EXIT_THREAD_DEBUG_EVENT: puts("EXIT_THREAD_DEBUG_EVENT"); break; case EXIT_PROCESS_DEBUG_EVENT: puts("EXIT_PROCESS_DEBUG_EVENT"); break; case LOAD_DLL_DEBUG_EVENT: puts("LOAD_DLL_DEBUG_EVENT"); break; case UNLOAD_DLL_DEBUG_EVENT: puts("UNLOAD_DLL_DEBUG_EVENT"); break; case OUTPUT_DEBUG_STRING_EVENT: puts("OUTPUT_DEBUG_STRING_EVENT"); break; default: break; }if (!isContinue) { break; }isContinue = ContinueDebugEvent(dbgEvent.dwProcessId,dbgEvent.dwThreadId,ret?DBG_CONTINUE:DBG_EXCEPTION_NOT_HANDLED); } CloseHandle(pi.hProcess); CloseHandle(pi.hThread); } else { printf("创建进程失败:%d\n",GetLastError()); } system("pause"); return 0; }BOOL BreakPointHandler(EXCEPTION_DEBUG_INFO* info) { if (SysInt3) { SysInt3 = FALSE; return TRUE; }EXCEPTION_RECORD record = info->ExceptionRecord; WriteProcessMemory(pi.hProcess,record.ExceptionAddress,&bInt3,1,NULL); printf(">> BreakPointHandler - Addr : 0x%X\n",record.ExceptionAddress); WaitUserInput(); CONTEXT context; context.ContextFlags = CONTEXT_FULL |CONTEXT_DEBUG_REGISTERS; GetThreadContext(pi.hThread,&context); context.Eip--; SetThreadContext(pi.hThread,&context); return TRUE; }BOOL MemBreakPointHandler(EXCEPTION_DEBUG_INFO* info) { DWORD ExceptionADDR = info->ExceptionRecord.ExceptionInformation[1]; DWORD ExceptionAccess = info->ExceptionRecord.ExceptionInformation[0]; if (ExceptionADDR>>12 == buffer>>12)//这里的判断是不对的,但为了做示例足够了 { printf("内存执行断点:%x %x\n",ExceptionADDR,ExceptionAccess); WaitUserInput(); if (!VirtualProtectEx(pi.hProcess,(void*)buffer,1,orignExcuteAccess,&orignExcuteAccess)) { printf("内存断点修复失败:%d\n",GetLastError()); } return TRUE; } return FALSE; }void WaitUserInput() { bool p = true; char cmd = 0; while(p) {/*************清空缓冲区**************/ scanf("%*[^\n]"); scanf("%*c"); /*************************************/printf("COMMAND>> "); cmd = getchar(); switch (cmd) { case 'g': p = false; break; default: break; } } }

??可以看出,该代码是基于软件断点实验基础上写的。在WaitUserInput有一个小Bug,就是没有输入的话至少需要随意输入字符来继续清空缓冲区操作,不过对于示例来说无伤大雅,如下是测试效果图:
调试篇——断点与单步
文章图片

??注意,我是简单是实现内存断点,为什么这么说呢?是因为这个函数一旦影响就是一个物理页,所以你得做判断。如果对数据下访问断点,如果多于1个字节,你还得考虑在多个物理页的情况,还得实现内存断点的记录功能。这些都是一个基本能用的调试器所具备的功能。
硬件断点 概述
??硬件断点和上面的不同,它是基于硬件的,不依赖调试程序,有自己的优势,如果通过CRC校验是不会被检测到的。如下是与硬件断点相关的寄存器结构:
调试篇——断点与单步
文章图片

??Dr0 ~ Dr3用于设置硬件断点,Dr4Dr5被保留了。由于只有4个断点寄存器,所以最多只能设置4个硬件调试断点。Dr7是最重要的寄存器,它比较复杂,我们来看看它的结构:
L0/G0 ~ L3/G3 ??控制Dr0 ~ Dr3是否有效,局部还是全局。每次异常后,Lx都被清零,Gx不清零。
LEN0 ~ LEN3 ??表示硬件断点的长度。如果是0表示1个字节;是1表示2个字节;是3表示4个字节。
R/W0 ~ R/W3 ??指示断点类型。如果是0表示执行断点;是1表示写入断点;是3表示访问断点。
处理
【调试篇——断点与单步】??硬件调试断点产生的异常是STATUS_SINGLE_STEP,即单步异常。触发异常后,B0 ~ B3对应的位会被置1,以此可以区分单步步入产生的单步异常,后续会详细讲解。
实验
??如下是实验代码:
#include "stdafx.h" #include #include BOOL SysInt3 = TRUE; void WaitUserInput(); BOOL BreakPointHandler(EXCEPTION_DEBUG_INFO* info); BOOL MemBreakPointHandler(EXCEPTION_DEBUG_INFO* info); BOOL SingleStepHandler(EXCEPTION_DEBUG_INFO* info); const UCHAR Int3 = 0xCC; UCHAR bInt3; PROCESS_INFORMATION pi ; DWORD orignExcuteAccess; DWORD buffer; //#define DBGBREAKPOINT //#define MemBREAKPOINT #define HardwareBREAKPOINTint main(int argc, char* argv[]) { char filename[]= "C:\\WINDOWS\\NOTEPAD.EXE"; STARTUPINFO si ={sizeof(STARTUPINFO)}; DEBUG_EVENT dbgEvent; BOOL isContinue = TRUE; BOOL ret =CreateProcess(NULL,filename,NULL,NULL,FALSE,DEBUG_PROCESS|DEBUG_ONLY_THIS_PROCESS,NULL,NULL,&si,&pi); if (ret) { while (isContinue) { ret = WaitForDebugEvent(&dbgEvent,INFINITE); if (!ret) { printf("WaitForDebugEvent 出错:%d",GetLastError()); break; }EXCEPTION_DEBUG_INFO info = dbgEvent.u.Exception; switch (dbgEvent.dwDebugEventCode) { case EXCEPTION_DEBUG_EVENT: puts("EXCEPTION_DEBUG_EVENT"); switch (info.ExceptionRecord.ExceptionCode) { case EXCEPTION_BREAKPOINT: ret = BreakPointHandler(&info); break; case EXCEPTION_ACCESS_VIOLATION: ret = MemBreakPointHandler(&info); break; case EXCEPTION_SINGLE_STEP: ret = SingleStepHandler(&info); break; default: ret = FALSE; break; } if (!ret) { printf("出错:%d\n",GetLastError()); isContinue = FALSE; TerminateProcess(pi.hProcess,0); } isContinue = ret; break; case CREATE_THREAD_DEBUG_EVENT: puts("CREATE_THREAD_DEBUG_EVENT"); break; case CREATE_PROCESS_DEBUG_EVENT: puts("CREATE_PROCESS_DEBUG_EVENT"); #ifdef DBGBREAKPOINTputs("请输入要下断点的位置:"); scanf("%x",&buffer); if (!ReadProcessMemory(pi.hProcess,(void*)buffer,&bInt3,1,NULL)) { puts("软件断点设置失败!"); isContinue = FALSE; TerminateProcess(pi.hProcess,0); }if (!WriteProcessMemory(pi.hProcess,(void*)buffer,(void*)&Int3,1,NULL))puts("软件断点设置失败!"); isContinue = FALSE; TerminateProcess(pi.hProcess,0); }#endif#ifdef MemBREAKPOINT buffer = (DWORD)dbgEvent.u.CreateProcessInfo.lpStartAddress; if (!VirtualProtectEx(pi.hProcess,(void*)buffer,1,PAGE_NOACCESS,&orignExcuteAccess)) { puts("内存执行断点设置失败!"); isContinue = FALSE; TerminateProcess(pi.hProcess,0); } printf("内存访问断点的位置:0x%X\n",buffer); #endif#ifdef HardwareBREAKPOINT SuspendThread(pi.hThread); puts("请输入要下断点的位置:"); scanf("%x",&buffer); CONTEXT context; context.ContextFlags = CONTEXT_FULL | CONTEXT_DEBUG_REGISTERS; GetThreadContext(pi.hThread,&context); context.Dr0 = buffer; context.Dr7 |= 1; if (!SetThreadContext(pi.hThread,&context)) { puts("硬件断点设置失败!"); } ResumeThread(pi.hThread); #endifbreak; case EXIT_THREAD_DEBUG_EVENT: puts("EXIT_THREAD_DEBUG_EVENT"); break; case EXIT_PROCESS_DEBUG_EVENT: puts("EXIT_PROCESS_DEBUG_EVENT"); break; case LOAD_DLL_DEBUG_EVENT: puts("LOAD_DLL_DEBUG_EVENT"); break; case UNLOAD_DLL_DEBUG_EVENT: puts("UNLOAD_DLL_DEBUG_EVENT"); break; case OUTPUT_DEBUG_STRING_EVENT: puts("OUTPUT_DEBUG_STRING_EVENT"); break; default: break; }if (!isContinue) { break; }isContinue = ContinueDebugEvent(dbgEvent.dwProcessId,dbgEvent.dwThreadId,ret?DBG_CONTINUE:DBG_EXCEPTION_NOT_HANDLED); } CloseHandle(pi.hProcess); CloseHandle(pi.hThread); } else { printf("创建进程失败:%d\n",GetLastError()); } system("pause"); return 0; }BOOL BreakPointHandler(EXCEPTION_DEBUG_INFO* info) { if (SysInt3) { SysInt3 = FALSE; return TRUE; }EXCEPTION_RECORD record = info->ExceptionRecord; WriteProcessMemory(pi.hProcess,record.ExceptionAddress,&bInt3,1,NULL); printf(">> BreakPointHandler - Addr : 0x%X\n",record.ExceptionAddress); WaitUserInput(); CONTEXT context; context.ContextFlags = CONTEXT_FULL |CONTEXT_DEBUG_REGISTERS; GetThreadContext(pi.hThread,&context); context.Eip--; SetThreadContext(pi.hThread,&context); return TRUE; }BOOL MemBreakPointHandler(EXCEPTION_DEBUG_INFO* info) { DWORD ExceptionADDR = info->ExceptionRecord.ExceptionInformation[1]; DWORD ExceptionAccess = info->ExceptionRecord.ExceptionInformation[0]; if (ExceptionADDR>>12 == buffer>>12)//这里的判断是不对的,但为了做示例足够了 { printf("内存执行断点:%x %x\n",ExceptionADDR,ExceptionAccess); WaitUserInput(); if (!VirtualProtectEx(pi.hProcess,(void*)buffer,1,orignExcuteAccess,&orignExcuteAccess)) { printf("内存断点修复失败:%d\n",GetLastError()); } return TRUE; } return FALSE; }BOOL SingleStepHandler(EXCEPTION_DEBUG_INFO* info) {CONTEXT context; context.ContextFlags = CONTEXT_FULL |CONTEXT_DEBUG_REGISTERS; GetThreadContext(pi.hThread,&context); if (context.Dr6&0xF) { puts("硬件断点被触发!"); context.Dr7&=~1; } else { printf("单步中……:0x%X\n",context.Eip); }WaitUserInput(); SetThreadContext(pi.hThread,&context); return TRUE; }void WaitUserInput() { bool p = true; char cmd = 0; while(p) {/*************清空缓冲区**************/ scanf("%*[^\n]"); scanf("%*c"); /*************************************/printf("COMMAND>> "); cmd = getchar(); switch (cmd) { case 'g': p = false; break; default: break; } } }

??硬件断点的实现十分简单,就不赘述了,如下是实验效果图:
调试篇——断点与单步
文章图片

单步步入 ??在调试中我们经常一条指令一条指令的进行调试,这大大方便了我们查阅结果,CPU提供了这样的基址,就是在Eflag中的TF位实现的,如下图所示:
调试篇——断点与单步
文章图片

??好我们继续在之前的代码基础上扩展单步功能:
#include "stdafx.h" #include #include BOOL SysInt3 = TRUE; void WaitUserInput(); BOOL BreakPointHandler(EXCEPTION_DEBUG_INFO* info); BOOL MemBreakPointHandler(EXCEPTION_DEBUG_INFO* info); BOOL SingleStepHandler(EXCEPTION_DEBUG_INFO* info); const UCHAR Int3 = 0xCC; UCHAR bInt3; PROCESS_INFORMATION pi ; DWORD orignExcuteAccess; DWORD buffer; #define DBGBREAKPOINT //#define MemBREAKPOINT //#define HardwareBREAKPOINTint main(int argc, char* argv[]) { char filename[]= "C:\\WINDOWS\\NOTEPAD.EXE"; STARTUPINFO si ={sizeof(STARTUPINFO)}; DEBUG_EVENT dbgEvent; BOOL isContinue = TRUE; BOOL ret =CreateProcess(NULL,filename,NULL,NULL,FALSE,DEBUG_PROCESS|DEBUG_ONLY_THIS_PROCESS,NULL,NULL,&si,&pi); if (ret) { while (isContinue) { ret = WaitForDebugEvent(&dbgEvent,INFINITE); if (!ret) { printf("WaitForDebugEvent 出错:%d",GetLastError()); break; }EXCEPTION_DEBUG_INFO info = dbgEvent.u.Exception; switch (dbgEvent.dwDebugEventCode) { case EXCEPTION_DEBUG_EVENT: puts("EXCEPTION_DEBUG_EVENT"); switch (info.ExceptionRecord.ExceptionCode) { case EXCEPTION_BREAKPOINT: ret = BreakPointHandler(&info); break; case EXCEPTION_ACCESS_VIOLATION: ret = MemBreakPointHandler(&info); break; case EXCEPTION_SINGLE_STEP: ret = SingleStepHandler(&info); break; default: ret = FALSE; break; } if (!ret) { printf("出错:%d\n",GetLastError()); isContinue = FALSE; TerminateProcess(pi.hProcess,0); } isContinue = ret; break; case CREATE_THREAD_DEBUG_EVENT: puts("CREATE_THREAD_DEBUG_EVENT"); break; case CREATE_PROCESS_DEBUG_EVENT: puts("CREATE_PROCESS_DEBUG_EVENT"); #ifdef DBGBREAKPOINTbuffer = (DWORD)dbgEvent.u.CreateProcessInfo.lpStartAddress; if (!ReadProcessMemory(pi.hProcess,(void*)buffer,&bInt3,1,NULL)) { puts("软件断点设置失败!"); isContinue = FALSE; TerminateProcess(pi.hProcess,0); }if (!WriteProcessMemory(pi.hProcess,(void*)buffer,(void*)&Int3,1,NULL)) { puts("软件断点设置失败!"); isContinue = FALSE; TerminateProcess(pi.hProcess,0); }#endif#ifdef MemBREAKPOINT buffer = (DWORD)dbgEvent.u.CreateProcessInfo.lpStartAddress; if (!VirtualProtectEx(pi.hProcess,(void*)buffer,1,PAGE_NOACCESS,&orignExcuteAccess)) { puts("内存执行断点设置失败!"); isContinue = FALSE; TerminateProcess(pi.hProcess,0); } printf("内存访问断点的位置:0x%X\n",buffer); #endif#ifdef HardwareBREAKPOINT SuspendThread(pi.hThread); puts("请输入要下断点的位置:"); scanf("%x",&buffer); CONTEXT context; context.ContextFlags = CONTEXT_FULL | CONTEXT_DEBUG_REGISTERS; GetThreadContext(pi.hThread,&context); context.Dr0 = buffer; context.Dr7 |= 1; if (!SetThreadContext(pi.hThread,&context)) { puts("硬件断点设置失败!"); } ResumeThread(pi.hThread); #endifbreak; case EXIT_THREAD_DEBUG_EVENT: puts("EXIT_THREAD_DEBUG_EVENT"); break; case EXIT_PROCESS_DEBUG_EVENT: puts("EXIT_PROCESS_DEBUG_EVENT"); break; case LOAD_DLL_DEBUG_EVENT: puts("LOAD_DLL_DEBUG_EVENT"); break; case UNLOAD_DLL_DEBUG_EVENT: puts("UNLOAD_DLL_DEBUG_EVENT"); break; case OUTPUT_DEBUG_STRING_EVENT: puts("OUTPUT_DEBUG_STRING_EVENT"); break; default: break; }if (!isContinue) { break; }isContinue = ContinueDebugEvent(dbgEvent.dwProcessId,dbgEvent.dwThreadId,ret?DBG_CONTINUE:DBG_EXCEPTION_NOT_HANDLED); } CloseHandle(pi.hProcess); CloseHandle(pi.hThread); } else { printf("创建进程失败:%d\n",GetLastError()); } system("pause"); return 0; }BOOL BreakPointHandler(EXCEPTION_DEBUG_INFO* info) { if (SysInt3) { SysInt3 = FALSE; return TRUE; }EXCEPTION_RECORD record = info->ExceptionRecord; WriteProcessMemory(pi.hProcess,record.ExceptionAddress,&bInt3,1,NULL); printf(">> BreakPointHandler - Addr : 0x%X\n",record.ExceptionAddress); WaitUserInput(); SuspendThread(pi.hThread); CONTEXT context; context.ContextFlags = CONTEXT_FULL |CONTEXT_DEBUG_REGISTERS; GetThreadContext(pi.hThread,&context); context.Eip--; SetThreadContext(pi.hThread,&context); ResumeThread(pi.hThread); return TRUE; }BOOL MemBreakPointHandler(EXCEPTION_DEBUG_INFO* info) { DWORD ExceptionADDR = info->ExceptionRecord.ExceptionInformation[1]; DWORD ExceptionAccess = info->ExceptionRecord.ExceptionInformation[0]; if (ExceptionADDR>>12 == buffer>>12)//这里的判断是不对的,但为了做示例足够了 { printf("内存执行断点:%x %x\n",ExceptionADDR,ExceptionAccess); WaitUserInput(); if (!VirtualProtectEx(pi.hProcess,(void*)buffer,1,orignExcuteAccess,&orignExcuteAccess)) { printf("内存断点修复失败:%d\n",GetLastError()); } return TRUE; } return FALSE; }BOOL SingleStepHandler(EXCEPTION_DEBUG_INFO* info) {CONTEXT context; context.ContextFlags = CONTEXT_FULL |CONTEXT_DEBUG_REGISTERS; SuspendThread(pi.hThread); GetThreadContext(pi.hThread,&context); if (context.Dr6&0xF) { puts("硬件断点被触发!"); context.Dr7&=~1; } else { printf("单步中:0x%X\n",context.Eip); context.EFlags &= ~0x100; } SetThreadContext(pi.hThread,&context); ResumeThread(pi.hThread); WaitUserInput(); return TRUE; }void WaitUserInput() { bool p = true; char cmd = 0; while(p) {/*************清空缓冲区**************/ scanf("%*[^\n]"); scanf("%*c"); /*************************************/printf("COMMAND>> "); cmd = getchar(); switch (cmd) { case 'g': p = false; break; case 't': p=false; SuspendThread(pi.hThread); CONTEXT context; context.ContextFlags = CONTEXT_FULL | CONTEXT_DEBUG_REGISTERS; GetThreadContext(pi.hThread,&context); context.EFlags |= 0x100; if (!SetThreadContext(pi.hThread,&context)) { puts("单步设置失败!"); } ResumeThread(pi.hThread); break; default: break; } } }

??单步是实现比较简单,如下是效果图:
调试篇——断点与单步
文章图片

单步步过 ??单步步过和单步步入不同,单步步入会逐个走指令,到达call执行会进入,而单步步过不会进入。单步步过是可以基于硬件断点或者软件断点实现。至于实现过程我就不赘述了。
小结 ??本篇文章主要介绍了调试器断点和单步的实现相关基本知识,如果要真正的实现一个调试器,还需要大量的实现。在总结与提升篇,我还会详细介绍所有断点和单步异常的内核处理流程。
下一篇 ??调试篇——总结与提升

    推荐阅读