在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没有谈到这一点。
数据断点不能在未对齐的地址上工作吗?
答案 0 :(得分:4)
使用x86上的调试寄存器,在CPU的帮助下设置数据断点;关于他们,英特尔手册说(§17.2.5):
断点地址寄存器(调试寄存器
DR0
到DR3
)和每个断点的LENn
字段定义了 数据或I / O断点的连续字节地址范围。LENn
字段允许指定1-,2-,4- ,或8字节范围,从相应调试寄存器(DRn)中指定的线性地址开始。的两字节 范围必须在字边界上对齐; 4字节范围必须在双字边界上对齐。 I / O. 地址是零扩展的(从16位到32位,用于与所选调试中的断点地址进行比较 寄存器)。这些要求由处理器强制执行;它使用LENn字段位来屏蔽低地址位 在调试寄存器中。 未对齐的数据或I / O断点地址不会产生有效的结果。
(强调补充)
因此,限制在于硬件。
答案 1 :(得分:1)
详细说明原因:
数据断点使用CPU的调试寄存器。在x86上,通过屏蔽地址“低位:
,这些调试寄存器与其数据大小对齐(addr & -2)
。(addr & -4)
。(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不会触发断点。