Search code examples
clinuxx86-64breakpointsptrace

How to set the value of dr7 register in order to create a hardware breakpoint on x86-64?


I'm working on a "binding" library that allows to use ptrace() in the OCaml language but my question only relates to ptrace().

So, right now, I'm trying to write a small piece of code in order to create a simple hardware breakpoint on Linux x86-64 by using ptrace():

#define DR_OFFSET(x) (((struct user *)0)->u_debugreg + x)

typedef struct {
    int           dr0_local:    1;
    int           dr0_global:   1;
    int           dr1_local:    1;
    int           dr1_global:   1;
    int           dr2_local:    1;
    int           dr2_global:   1;
    int           dr3_local:    1;
    int           dr3_global:   1;
    int           reserverd:    8;
    break_flag_t  dr0_break:    2;
    data_length_t dr0_len:      2;
    break_flag_t  dr1_break:    2;
    data_length_t dr1_len:      2;
    break_flag_t  dr2_break:    2;
    data_length_t dr2_len:      2;
    break_flag_t  dr3_break:    2;
    data_length_t dr3_len:      2;
} dr7_t;

CAMLprim value ptrace_breakpoint(value ml_pid, value ml_addr)
{
    CAMLparam2(ml_pid, ml_addr);
    dr7_t dr7 = {0};

    dr7.dr0_local = 1;
    dr7.dr0_break = 0; /* break on execution */
    dr7.dr0_len   = 0x03; /* len 4 */

    ptrace(PTRACE_POKEUSER, Int_val(ml_pid), DR_OFFSET(0), (void*)Int64_val(ml_addr));
    ptrace(PTRACE_POKEUSER, Int_val(ml_pid), DR_OFFSET(7), (void*)dr7));
    ptrace(PTRACE_POKEUSER, Int_val(ml_pid), DR_OFFSET(6), (void*)0);
    CAMLreturn0;
}

When I execute this code, I got an Invalid argument. The value of dr7 is 0xc0001. In order to find a valid value, I inspected how GDB use ptrace by using strace:

ptrace(PTRACE_POKEUSER, 6459, offsetof(struct user, u_debugreg), 0x400519) = 0
ptrace(PTRACE_POKEUSER, 6459, offsetof(struct user, u_debugreg) + 56, 0x101) = 0
ptrace(PTRACE_POKEUSER, 6459, offsetof(struct user, u_debugreg) + 48, 0) = 0

So, GDB set the dr7 register to the value 0x101. I tried this value and it works. Thus, I'm wondering what is the meaning of the value used by GDB? Is the dr7_t bit fields that I used before valid?

Thank you.

Edit:

Thanks to Neitsa, here is the solution:

typedef struct {
    unsigned int  dr0_local:      1;  
    unsigned int  dr0_global:     1;  
    unsigned int  dr1_local:      1;  
    unsigned int  dr1_global:     1;  
    unsigned int  dr2_local:      1;  
    unsigned int  dr2_global:     1;  
    unsigned int  dr3_local:      1;  
    unsigned int  dr3_global:     1;  
    unsigned int  le:             1;  
    unsigned int  ge:             1;  
    unsigned int  reserved_10:    1;  
    unsigned int  rtm:            1;  
    unsigned int  reserved_12:    1;  
    unsigned int  gd:             1;  
    unsigned int  reserved_14_15: 2;
    break_flag_t  dr0_break:      2;  
    data_length_t dr0_len:        2;  
    break_flag_t  dr1_break:      2;  
    data_length_t dr1_len:        2;  
    break_flag_t  dr2_break:      2;  
    data_length_t dr2_len:        2;  
    break_flag_t  dr3_break:      2;  
    data_length_t dr3_len:        2;  
} dr7_t;

CAMLprim value ptrace_breakpoint(value ml_pid, value ml_addr)
{
    CAMLparam2(ml_pid, ml_addr);
    dr7_t dr7 = {0};

    dr7.dr0_local = 1;
    dr7.le = 1;
    dr7.ge = 1;
    dr7.reserved_10 = 1;

    my_ptrace(PTRACE_POKEUSER, Int_val(ml_pid), DR_OFFSET(0), (void*)Int64_val(ml_addr));
    my_ptrace(PTRACE_POKEUSER, Int_val(ml_pid), DR_OFFSET(7), (void*)dr7));
    my_ptrace(PTRACE_POKEUSER, Int_val(ml_pid), DR_OFFSET(6), (void*)0);
    CAMLreturn0;
}

Solution

  • Your structure looks good (I would have used unsigned int though).

    DR7 bits

    A few remarks (quotes are from Intel Manual chap 17.2: Debug Registers):

    • Reserved bits (must be set accordingly to the documentation):
      • Bit 10 is reserved but set to 1.
      • Bits 12, 14 and 15 are reserved and must be set to 0 (memset the whole structure to 0 beforehand).

    Other fields:

    • You should also implement bit 8 and bit 9 and set them to 1

    LE and GE (local and global exact breakpoint enable) flags (bits 8, 9) — This feature is not supported in the P6 family processors, later IA-32 processors, and Intel 64 processors. [...] , we recommend that the LE and GE flags be set to 1 if exact breakpoints are required

    • Instruction breakpoint for instructions must have length set to 1 byte (which implies that the corresponding LENn field must be set to 0):

    Instruction breakpoint addresses must have a length specification of 1 byte (the LENn field is set to 00). Code breakpoints for other operand sizes are undefined

    So, to set a breakpoint on execution:

    • Set the reserved bits to their right values
    • Set DR7.LE and DR7.GE to 1
    • Set DR7.L0 (L1, L2, L3) to 1 [local breakpoint]
    • Make sure DR7.RW/0 (RW/1, RW/2, RW/3) is 0 [break on instruction exec]
    • Make sure DR7.LEN0 (LEN1, LEN2, LEN3) is 0 [1 byte length]
    • Set DR0 (1, 2, 3) to the instruction linear address
      • The linear address of the breakpoint [DR0 to DR3] must fall on the first byte of the instruction.

    The processor recognizes an instruction breakpoint address only when it points to the first byte of an instruction. If the instruction has prefixes, the breakpoint address must point to the first prefix.


    edit

    • 0x101 :
      • bin(0x101) = '0b100000001'
      • DR7.L0 & DR7.LE set to 1

    Technically, 0x701 should be correct:

    • 0x701 :
      • bin(0x701) = '0b11100000001'
      • DR7.L0 & DR7.LE & DR7.GE & DR7.bit10 set to 1