为什么数据断点不能在未对齐的地址上工作

Why does data breakpoint not work on unaligned address

本文关键字:地址 工作 对齐 数据 断点 不能 为什么      更新时间:2023-10-16

在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
}

如你所见,在函数test中,*p的值将首先归零,然后它会隐式地改变,我有一个小端CPU,所以它必须是65536。

如果你在p(4字节)上设置了一个数据断点来检测变化,你会得到两种不同的结果:命中或未命中。这取决于p所指向的地址

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

如何:在MSDN上设置数据断点这篇文章没有讨论这个。

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

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

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

(重点)

所以,限制在硬件上

发生这种情况的详细解释:

数据断点使用CPU的调试寄存器。在x86上,这些调试寄存器通过屏蔽地址的低位来对齐它们的数据大小:

  • 16位(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是0xfffffffffc (32bits)或0xfffffffffffffffffffc (64bits)

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

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

它们不匹配,所以CPU不会触发断点