硬件斷點的原理與實現


硬件斷點的原理

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM