在multithreading应用程序中设置硬件断点不会触发

我写了一个debugging器来分析和解决某些问题。 现在我实现了一个硬件断点来检测被覆盖的内存地址的访问。 当我用testing过程运行我的debugging器时,一切正常。 当我访问地址时,断点被触发,并且调用堆栈被logging下来。 问题是,当我运行相同的应用程序运行多个线程。 我将断点复制到每个创build的线程中,也是主线程。 没有一个函数报告错误,一切看起来都很好,但是当地址被访问时,断点不会被触发。

所以我想知道是否有一些文档描述了这些内容,或者在multithreading应用程序的情况下还需要做些额外的事情。

设置断点的函数是这样的:

#ifndef _HARDWARE_BREAKPOINT_H #define _HARDWARE_BREAKPOINT_H #include "breakpoint.h" #define MAX_HARDWARE_BREAKPOINT 4 #define REG_DR0_BIT 1 #define REG_DR1_BIT 4 #define REG_DR2_BIT 16 #define REG_DR3_BIT 64 class HardwareBreakpoint : public Breakpoint { public: typedef enum { REG_INVALID = -1, REG_DR0 = 0, REG_DR1 = 1, REG_DR2 = 2, REG_DR3 = 3 } Register; typedef enum { CODE, READWRITE, WRITE, } Type; typedef enum { SIZE_1, SIZE_2, SIZE_4, SIZE_8, } Size; typedef struct { void *pAddress; bool bBusy; Type nType; Size nSize; Register nRegister; } Info; public: HardwareBreakpoint(HANDLE hThread); virtual ~HardwareBreakpoint(void); /** * Sets a hardware breakpoint. If no register is free or an error occured * REG_INVALID is returned, otherwise the hardware register for the given breakpoint. */ HardwareBreakpoint::Register set(void *pAddress, Type nType, Size nSize); void remove(void *pAddress); void remove(Register nRegister); inline Info const *getInfo(Register nRegister) const { return &mBreakpoint[nRegister]; } private: typedef Breakpoint super; private: Info mBreakpoint[MAX_HARDWARE_BREAKPOINT]; size_t mRegBit[MAX_HARDWARE_BREAKPOINT]; size_t mRegOffset[MAX_HARDWARE_BREAKPOINT]; }; #endif // _HARDWARE_BREAKPOINT_H void SetBits(DWORD_PTR &dw, size_t lowBit, size_t bits, size_t newValue) { DWORD_PTR mask = (1 << bits) - 1; dw = (dw & ~(mask << lowBit)) | (newValue << lowBit); } HardwareBreakpoint::HardwareBreakpoint(HANDLE hThread) : super(hThread) { mRegBit[REG_DR0] = REG_DR0_BIT; mRegBit[REG_DR1] = REG_DR1_BIT; mRegBit[REG_DR2] = REG_DR2_BIT; mRegBit[REG_DR3] = REG_DR3_BIT; CONTEXT ct; mRegOffset[REG_DR0] = reinterpret_cast<size_t>(&ct.Dr0) - reinterpret_cast<size_t>(&ct); mRegOffset[REG_DR1] = reinterpret_cast<size_t>(&ct.Dr1) - reinterpret_cast<size_t>(&ct); mRegOffset[REG_DR2] = reinterpret_cast<size_t>(&ct.Dr2) - reinterpret_cast<size_t>(&ct); mRegOffset[REG_DR3] = reinterpret_cast<size_t>(&ct.Dr3) - reinterpret_cast<size_t>(&ct); memset(&mBreakpoint[0], 0, sizeof(mBreakpoint)); for(int i = 0; i < MAX_HARDWARE_BREAKPOINT; i++) mBreakpoint[i].nRegister = (Register)i; } HardwareBreakpoint::Register HardwareBreakpoint::set(void *pAddress, Type nType, Size nSize) { CONTEXT ct = {0}; super::setAddress(pAddress); ct.ContextFlags = CONTEXT_DEBUG_REGISTERS; if(!GetThreadContext(getThread(), &ct)) return HardwareBreakpoint::REG_INVALID; size_t iReg = 0; for(int i = 0; i < MAX_HARDWARE_BREAKPOINT; i++) { if (ct.Dr7 & mRegBit[i]) mBreakpoint[i].bBusy = true; else mBreakpoint[i].bBusy = false; } Info *reg = NULL; // Address already used? for(int i = 0; i < MAX_HARDWARE_BREAKPOINT; i++) { if(mBreakpoint[i].pAddress == pAddress) { iReg = i; reg = &mBreakpoint[i]; break; } } if(reg == NULL) { for(int i = 0; i < MAX_HARDWARE_BREAKPOINT; i++) { if(!mBreakpoint[i].bBusy) { iReg = i; reg = &mBreakpoint[i]; break; } } } // No free register available if(!reg) return HardwareBreakpoint::REG_INVALID; *(void **)(((char *)&ct)+mRegOffset[iReg]) = pAddress; reg->bBusy = true; ct.Dr6 = 0; int st = 0; if (nType == CODE) st = 0; if (nType == READWRITE) st = 3; if (nType == WRITE) st = 1; int le = 0; if (nSize == SIZE_1) le = 0; else if (nSize == SIZE_2) le = 1; else if (nSize == SIZE_4) le = 3; else if (nSize == SIZE_8) le = 2; SetBits(ct.Dr7, 16 + iReg*4, 2, st); SetBits(ct.Dr7, 18 + iReg*4, 2, le); SetBits(ct.Dr7, iReg*2, 1, 1); ct.ContextFlags = CONTEXT_DEBUG_REGISTERS; if(!SetThreadContext(getThread(), &ct)) return REG_INVALID; return reg->nRegister; } 

我在主debugging器循环中设置断点,每当创build一个新的线程CREATE_THREAD_DEBUG_EVENT但看着GDB的源代码,似乎没有在那里做,所以也许是早?

所以我终于找到了这个问题的答案。

在调试事件循环中,我正在监视窗口发送给我的事件。 其中一个事件是CREATE_THREAD_DEBUG_EVENT ,当我创建一个新的线程时,我用它来设置硬件断点。

问题是,这个事件的通知是在线程实际启动之前发生的。 所以Windows在发送这个事件后第一次设置上下文,这当然覆盖了我之前设置的任何上下文数据。

我现在实现的解决方案是,当CREATE_THREAD_DEBUG_EVENT到来时,我在该线程的起始地址处放置一个软件断点,以便第一条指令是我的断点。 当我收到断点事件时,我恢复原始代码并安装现在正常启动的硬件断点。

如果有更好的解决方案,我全部耳熟能详。 🙂