为什么数据断点不适用于未对齐的地址

时间:2014-07-22 06:49:02

标签: c++ debugging visual-c++ breakpoints

在Visual Studio中调试C ++项目时, 一些数据断点从未命中过。

所以我写了一些测试代码:

#include <iostream>
#include <stdint.h>

void test(uint32_t* p)
{
    *p = 0;

    // set a data breakpoint on p

    *((char*)p + 2) = 0x1;

    std::cout << *p << std::endl;
}

uint32_t* alloc(size_t offset)
{
    char* p = new char[sizeof(uint32_t) + offset];
    p = p + offset;
    return (uint32_t*)p;
}

int main()
{
    test(alloc(0));    // test #1
    test(alloc(2));    // test #2
}

如您所见,在功能测试中,* p的值将首先归零, 然后它会被隐式更改,我得到了一个litte-endian CPU 必须是65536。

如果在p(4字节)上设置数据断点以检测更改, 你会得到两个不同的结果:命中与否。这取决于 该p的地址指向。

在上面的测试代码中,测试#1将会命中,测试#2将不会, #1和#2之间的区别是返回的地址 alloc(0)和alloc(2)。

MSDN上的这篇文章How to: Set a Data Breakpoint没有谈到这一点。

数据断点不能在未对齐的地址上工作吗?

2 个答案:

答案 0 :(得分:4)

使用x86上的调试寄存器,在CPU的帮助下设置数据断点;关于他们,英特尔手册说(§17.2.5):

  

断点地址寄存器(调试寄存器DR0DR3)和每个断点的LENn字段定义了   数据或I / O断点的连续字节地址范围。 LENn字段允许指定1-,2-,4-   ,或8字节范围,从相应调试寄存器(DRn)中指定的线性地址开始。的两字节   范围必须在字边界上对齐; 4字节范围必须在双字边界上对齐。 I / O.   地址是零扩展的(从16位到32位,用于与所选调试中的断点地址进行比较   寄存器)。这些要求由处理器强制执行;它使用LENn字段位来屏蔽低地址位   在调试寄存器中。 未对齐的数据或I / O断点地址不会产生有效的结果。

(强调补充)

因此,限制在于硬件。

答案 1 :(得分:1)

详细说明原因:

数据断点使用CPU的调试寄存器。在x86上,通过屏蔽地址“低位:

,这些调试寄存器与其数据大小对齐
  • 16bits(2字节)断点获得其最低地址位清除(addr & -2)
  • 32位(4字节)断点将其2个最低地址位清除(addr & -4)
  • 64位(8字节)断点将其3个最低地址位清除(addr & -8)

当x86 CPU访问内存时,它通过以与调试寄存器地址相同的方式屏蔽它来比较地址,如果两者相等则 它会触发断点。

这是简化调试断点比较器中电子电路的技巧:只需要比较两个对齐的地址。

以电子方式转换为伪代码:

if(!((address ^ debug_address) & debug_mask))
    breakpoint();

而不是:

if((address >= debug_address) & (address < debug_address_plus_length))
    breakpoint();

在硅片中实现这一点要复杂得多,并且会降低CPU的运行速度。即使在软件中,第一个也会运行得更快。

只要所有内存访问都已对齐,地址屏蔽技巧就可以正常工作。

因此,假设 p 指向 0xF02 ,断点为32位(4字节),则断点地址与((0xF02 & -4) == 0xF00)对齐。

注意:-4是0xFFFFFFFC(32位)或0xFFFFFFFFFFFFFFFC(64位)

然后访问地址(0xF02 + 2 == 0xF04)

然后CPU在将其与调试断点地址(0xF00)进行比较之前屏蔽0xF04 ((0xF04 & -4) == 0xF04)

它们不匹配,因此CPU不会触发断点。