调试篇——断点与单步
写在前面
此系列是本人一个字一个字码出来的,包括示例和实验截图。由于系统内核的复杂性,故可能有错误或者不全面的地方,如有错误,欢迎批评指正,本教程将会长期更新。 如有好的建议,欢迎反馈。码字不易,如果本篇文章有帮助你的,如有闲钱,可以打赏支持我的创作。如想转载,请把我的转载信息附在文章后面,并声明我的个人信息和本人博客地址即可,但必须事先通知我。
你如果是从中间插过来看的,请仔细阅读 羽夏看Win系统内核——简述 ,方便学习本教程。
看此教程之前,问几个问题,基础知识储备好了吗?保护模式篇学会了吗?练习做完了吗?没有的话就不要继续了。
🔒 华丽的分割线 🔒
概述
在软件调试的过程中,最常用的就是断点和单步了。有过调试经验的人知道,断点有软件断点、硬件断点、内存断点;单步有单步步入和单步步过。下面我们来介绍它们的实现原理。
软件断点
软件断点是实现的本质就是在下断点的地方插入int3
,也被称之为CC
断点。下面我们来做个实验:
我们打开一个调试器调试一个进程,然后在某条汇编下断点,于此同时通过CE
来查看当前指令的字节变化情况,如下是操作示例:
也就是说,调试器帮我们屏蔽掉了0xCC
的显示,本质上还是int3
,就凭这些我们就可以写一个接管软件断点的简单的调试器了:
#include "stdafx.h"
#include <windows.h>
#include <stdlib.h>
BOOL SysInt3 = TRUE;
void WaitUserInput();
BOOL BreakPointHandler(EXCEPTION_DEBUG_INFO* info);
const UCHAR Int3 = 0xCC;
UCHAR bInt3;
PROCESS_INFORMATION pi ;
#define DBGBREAKPOINT
int 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 DBGBREAKPOINT
puts("请输入要下断点的位置:");
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
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;
}
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_NOACCESS
和PAGE_EXECUTE_READ
。PAGE_NOACCESS
属性会把对应的页属性的P
位改为0
,而PAGE_EXECUTE_READ
对应的页属性的P
位虽然是1
,但R/W
是0
。下面我们来实现一个简单的内存访问断点:
#include "stdafx.h"
#include <windows.h>
#include <stdlib.h>
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 MemBREAKPOINT
int 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 DBGBREAKPOINT
puts("请输入要下断点的位置:");
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
用于设置硬件断点,Dr4
和Dr5
被保留了。由于只有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 <windows.h>
#include <stdlib.h>
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 HardwareBREAKPOINT
int 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 DBGBREAKPOINT
puts("请输入要下断点的位置:");
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);
#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;
}
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 <windows.h>
#include <stdlib.h>
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 HardwareBREAKPOINT
int 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 DBGBREAKPOINT
buffer = (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);
#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();
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
执行会进入,而单步步过不会进入。单步步过是可以基于硬件断点或者软件断点实现。至于实现过程我就不赘述了。
小结
本篇文章主要介绍了调试器断点和单步的实现相关基本知识,如果要真正的实现一个调试器,还需要大量的实现。在总结与提升篇,我还会详细介绍所有断点和单步异常的内核处理流程。
下一篇
调试篇——总结与提升
本文来自博客园,作者:寂静的羽夏 ,一个热爱计算机技术的菜鸟
转载请注明原文链接:https://www.cnblogs.com/wingsummer/p/15960780.html