硬件斷點的原理
Intel 80306以上的CPU給我們提供了調試寄存器用於軟件調試,硬件斷點是通過設置調試寄存器實現的。

上圖為Intel手冊提供的32位操作系統下8個調試寄存器的圖示(Intel手冊卷3 17章第二節 Debug Registers,有興趣的朋友可以查閱),根據介紹,DR0-DR3為設置斷點的地址,DR4和DR5為保留,DR6為調試異常產生后顯示的一些信息,DR7保存了斷點是否啟用、斷點類型和長度等信息。
我們在使用硬件斷點的時候,就是要設置調試寄存器,將斷點的位置設置到DR0-DR3中,斷點的長度設置到DR7的LEN0-LEN3中,將斷點的類型設置到DR7的RW0-RW3中,將是否啟用斷點設置到DR7的L0-L3中。設置硬件斷點需要的DR0-DR3很簡單,就是下斷點的地址,DR7寄存器很復雜,位段信息結構體如下:
typedef struct _DBG_REG7 { /* // 局部斷點(L0~3)與全局斷點(G0~3)的標記位 */ unsigned L0 : 1; // 對Dr0保存的地址啟用 局部斷點 unsigned G0 : 1; // 對Dr0保存的地址啟用 全局斷點 unsigned L1 : 1; // 對Dr1保存的地址啟用 局部斷點 unsigned G1 : 1; // 對Dr1保存的地址啟用 全局斷點 unsigned L2 : 1; // 對Dr2保存的地址啟用 局部斷點 unsigned G2 : 1; // 對Dr2保存的地址啟用 全局斷點 unsigned L3 : 1; // 對Dr3保存的地址啟用 局部斷點 unsigned G3 : 1; // 對Dr3保存的地址啟用 全局斷點 /* // 【以棄用】用於降低CPU頻率,以方便准確檢測斷點異常 */ unsigned LE : 1; unsigned GE : 1; /* // 保留字段 */ unsigned Reserve1 : 3; /* // 保護調試寄存器標志位,如果此位為1,則有指令修改條是寄存器時會觸發異常 */ unsigned GD : 1; /* // 保留字段 */ unsigned Reserve2 : 2; unsigned RW0 : 2; // 設定Dr0指向地址的斷點類型 unsigned LEN0 : 2; // 設定Dr0指向地址的斷點長度 unsigned RW1 : 2; // 設定Dr1指向地址的斷點類型 unsigned LEN1 : 2; // 設定Dr1指向地址的斷點長度 unsigned RW2 : 2; // 設定Dr2指向地址的斷點類型 unsigned LEN2 : 2; // 設定Dr2指向地址的斷點長度 unsigned RW3 : 2; // 設定Dr3指向地址的斷點類型 unsigned LEN3 : 2; // 設定Dr3指向地址的斷點長度 }DBG_REG7, *PDBG_REG7;
需要注意的是,設置硬件斷點時,斷點的長度、類型和地址是有要求的。

上圖所示,保存DR0-DR3地址所指向位置的斷點類型(RW0-RW3)與斷點長度(LEN0-LEN3),狀態描述如下:
00:執行 01:寫入 11:讀寫
00:1字節 01:2字節 11:4字節
設置硬件執行斷點時,長度只能為1(LEN0-LEN3設置為0時表示長度為1)
設置讀寫斷點時,如果長度為1,地址不需要對齊,如果長度為2,則地址必須是2的整數倍,如果長度為4,則地址必須是4的整數倍。
原理大概就是這么多了,下面就是實現了。
硬件斷點的實現
實現硬件斷點,首先要獲取當前線程環境
//獲取線程環境 CONTEXT g_Context = { 0 }; g_Context.ContextFlags = CONTEXT_CONTROL; GetThreadContext(hThread, &g_Context);
在CONTEXT結構體中,存放了諸多當前線程環境的信息,以下是從winnt.h文件中找到的CONTEXT結構體
typedef struct _CONTEXT { // // The flags values within this flag control the contents of // a CONTEXT record. // // If the context record is used as an input parameter, then // for each portion of the context record controlled by a flag // whose value is set, it is assumed that that portion of the // context record contains valid context. If the context record // is being used to modify a threads context, then only that // portion of the threads context will be modified. // // If the context record is used as an IN OUT parameter to capture // the context of a thread, then only those portions of the thread's // context corresponding to set flags will be returned. // // The context record is never used as an OUT only parameter. // DWORD ContextFlags; // // This section is specified/returned if CONTEXT_DEBUG_REGISTERS is // set in ContextFlags. Note that CONTEXT_DEBUG_REGISTERS is NOT // included in CONTEXT_FULL. // DWORD Dr0; DWORD Dr1; DWORD Dr2; DWORD Dr3; DWORD Dr6; DWORD Dr7; // // This section is specified/returned if the // ContextFlags word contians the flag CONTEXT_FLOATING_POINT. // FLOATING_SAVE_AREA FloatSave; // // This section is specified/returned if the // ContextFlags word contians the flag CONTEXT_SEGMENTS. // DWORD SegGs; DWORD SegFs; DWORD SegEs; DWORD SegDs; // // This section is specified/returned if the // ContextFlags word contians the flag CONTEXT_INTEGER. // DWORD Edi; DWORD Esi; DWORD Ebx; DWORD Edx; DWORD Ecx; DWORD Eax; // // This section is specified/returned if the // ContextFlags word contians the flag CONTEXT_CONTROL. // DWORD Ebp; DWORD Eip; DWORD SegCs; // MUST BE SANITIZED DWORD EFlags; // MUST BE SANITIZED DWORD Esp; DWORD SegSs; // // This section is specified/returned if the ContextFlags word // contains the flag CONTEXT_EXTENDED_REGISTERS. // The format and contexts are processor specific // BYTE ExtendedRegisters[MAXIMUM_SUPPORTED_EXTENSION]; } CONTEXT;
從CONTEXT結構體中我們可以看到存放了調試寄存器 Dr0-Dr3和Dr6、Dr7,通過設置這些寄存器我們可以實現硬件斷點。
已經獲取了當前線程環境,接下來就是設置調試寄存器
//傳入下斷點的地址、類型、長度 void SetHardBP(DWORD addr, BreakPointHard type, BreakPointLen len) { //利用上文中的DR7寄存器位段信息 DBG_REG7 *pDr7 = (DBG_REG7 *)&g_Context.Dr7; if (len == 1) { //兩字節的對齊粒度 addr = addr - addr % 2; } else if (len == 3) { //四字節的對齊粒度 addr = addr - addr % 4; } if (pDr7->L0 == 0) { g_Context.Dr0 = addr; //利用Dr0寄存器存放地址 pDr7->RW0 = type; //Dr7寄存器中的RW0設置類型 pDr7->LEN0 = len; //Dr7寄存器中的LEN0設置長度 pDr7->L0 = 1; //Dr7寄存器中的L0啟用斷點 } else if (pDr7->L1 == 0) { g_Context.Dr1 = addr; pDr7->RW1 = type; pDr7->LEN1 = len; pDr7->L1 = 1; } else if (pDr7->L2 == 0) { g_Context.Dr2 = addr; pDr7->RW2 = type; pDr7->LEN2 = len; pDr7->L2 = 1; } else if (pDr7->L3 == 0) { g_Context.Dr3 = addr; pDr7->RW3 = type; pDr7->LEN3 = len; pDr7->L3 = 1; } }
調試寄存器的信息設置好之后,我們要將當前環境保存
//設置當前環境 SetThreadContext(hThread, &g_Context);
由此,硬件斷點的大致實現思路已經完成。
轉自:https://www.52pojie.cn/forum.php?mod=viewthread&tid=846934&extra=page%3D3%26filter%3Dauthor%26orderby%3Ddateline
