Julien Julien - 12 days ago 5
Linux Question

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

Answer

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

enter image description here

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

  • Bit 10 is reserved but set to 1

  • 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 bytes:

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 DR0 (1, 2, 3) linear address
  • 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]
  • Make sure linear address [DR0 to DR3] falls 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 shoud be correct:

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