寫在前面
此係列是本人一個字一個字碼出來的,包括示例和實驗截圖。由於系統核心的複雜性,故可能有錯誤或者不全面的地方,如有錯誤,歡迎批評指正,本教程將會長期更新。 如有好的建議,歡迎反饋。碼字不易,如果本篇文章有幫助你的,如有閒錢,可以打賞支援我的創作。如想轉載,請把我的轉載資訊附在文章後面,並宣告我的個人資訊和本人部落格地址即可,但必須事先通知我。
你如果是從中間插過來看的,請仔細閱讀 羽夏看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
執行會進入,而單步步過不會進入。單步步過是可以基於硬體斷點或者軟體斷點實現。至於實現過程我就不贅述了。
小結
本篇文章主要介紹了偵錯程式斷點和單步的實現相關基本知識,如果要真正的實現一個偵錯程式,還需要大量的實現。在總結與提升篇,我還會詳細介紹所有斷點和單步異常的核心處理流程。
下一篇
除錯篇——總結與提升