amanjiang
amanjiang

Reputation: 1283

Why does data breakpoint not work on unaligned address

In Visual Studio while debugging a C++ project, some data breakpoints never hit.

So I wrote some code for testing:

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

As you see, in function test, the value of *p will be zeroed first, then it will be changed implicitly, I got a litte-endian CPU so it must be 65536.

If you set a data breakpoint on p (4 bytes) to detect the changing, you'll get two different results: hit or not. It depends on the address of that p pointed at.

In my testing code above, test #1 will hit and test #2 will not, the difference between #1 and #2 are the addresses returned by alloc(0) and alloc(2).

This article How to: Set a Data Breakpoint on MSDN does not talk about this.

Does data breakpoint not work on an unaligned address ?

Upvotes: 6

Views: 2326

Answers (2)

Stephane Hockenhull
Stephane Hockenhull

Reputation: 195

Detailed explanation of why this happens:

Data breakpoints use the CPU's debug registers. On x86 these debug registers get aligned to their data size by masking the address' lower bits:

  • 16bits (2 bytes) breakpoints get its lowest address bit cleared (addr & -2).
  • 32bits (4 bytes) breakpoints get its 2 lowest address bits cleared (addr & -4).
  • 64bits (8 bytes) breakpoints get its 3 lowest address bits cleared (addr & -8).

When the x86 CPU access memory it compares the address by masking it in the same way to the debug register address and if the two are equal it triggers a breakpoint.


This is a trick to simplify the electronic circuit in the debug breakpoint comparator: Only the two aligned addresses need to be compared.

Electronically it translates to the pseudo-code:

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

Instead of:

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

Which would be much more complex to implement in silicon and slow down the CPU. Even in software the first one would run faster.

The address-masking trick works perfectly as long as all memory accesses are aligned.


So let's say p points to address 0xF02 and the breakpoint is 32bits (4 bytes), then the breakpoint address gets aligned to ((0xF02 & -4) == 0xF00).

note: -4 is 0xFFFFFFFC (32bits) or 0xFFFFFFFFFFFFFFFC (64bits)

You then access the address (0xF02+2 == 0xF04)

The CPU then masks 0xF04 ((0xF04 & -4) == 0xF04) before comparing it to the debug breakpoint address (0xF00).

They don't match so the CPU does not trigger the breakpoint.

Upvotes: 2

Matteo Italia
Matteo Italia

Reputation: 126787

The data breakpoints are set with the assistance of the CPU, using the debug registers on x86; about them, the Intel manual says (§17.2.5):

Breakpoint address registers (debug registers DR0 through DR3) and the LENn fields for each breakpoint define a range of sequential byte addresses for a data or I/O breakpoint. The LENn fields permit specification of a 1-, 2-, 4- , or 8-byte range, beginning at the linear address specified in the corresponding debug register (DRn). Two-byte ranges must be aligned on word boundaries; 4-byte ranges must be aligned on doubleword boundaries. I/O addresses are zero-extended (from 16 to 32 bits, for comparison with the breakpoint address in the selected debug register). These requirements are enforced by the processor; it uses LENn field bits to mask the lower address bits in the debug registers. Unaligned data or I/O breakpoint addresses do not yield valid results.

(emphasis added)

So, the limitation is in hardware.

Upvotes: 6

Related Questions