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

时间:2015-09-29 06:23:17

标签: c++ windows multithreading debugging breakpoints

我写了一个小调试器来分析和解决某些问题。现在我实现了一个硬件断点,用于检测被覆盖的内存地址的访问。当我使用测试过程运行我的调试器时,一切正常。当我访问该地址时,会触发断点并记录callstack。问题是,当我对运行多个线程的应用程序运行相同的操作时。我将断点复制到每个创建的线程以及主线程中。没有任何函数报告错误,一切看起来都很好,但是当访问地址时,断点永远不会触发。

所以我想知道是否有一些文档描述了这一点,或者在多线程应用程序的情况下是否还有其他事情要做。

设置断点的功能如下:

#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;
}

我在创建新线程CREATE_THREAD_DEBUG_EVENT时在主调试器循环中设置断点但查看GDB的源代码似乎没有在那里完成,所以可能是到了早?

1 个答案:

答案 0 :(得分:1)

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

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

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

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

如果有更好的解决方案,我会全力以赴。 :)