如何设置 dr7 寄存器的值以便在 x86-64 上创建硬件断点?

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

我正在开发一个允许在 OCaml 语言中使用 ptrace() 的 "binding" 库,但我的问题只涉及 ptrace().

所以,现在,我正在尝试编写一小段代码,以便使用 ptrace():[=23= 在 Linux x86-64 上创建一个简单的硬件断点]

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

当我执行这段代码时,我得到了一个 Invalid argumentdr7 的值为 0xc0001。为了找到有效值,我检查了 GDB 如何使用 ptrace 通过使用 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

因此,GDB 将 dr7 寄存器设置为值 0x101。我试过这个值,它有效。因此,我想知道 GDB 使用的值的含义是什么?我之前使用的dr7_t位域有效吗?

谢谢。

编辑:

感谢 Neitsa,这是解决方案:

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

你的结构看起来不错(不过我会使用 unsigned int)。

几点说明(引自Intel Manual chap 17.2: Debug Registers):

  • 保留位(必须根据文档进行相应设置):
    • 第 10 位保留但设置为 1。
    • 第 12、14 和 15 位保留,必须设置为 0(memset 整个结构事先设置为 0)。

其他领域:

  • 应该 也实现第 8 位和第 9 位并将它们设置为 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

  • 指令的指令断点必须的长度设置为1字节(这意味着相应的LENn字段必须设置为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

因此,要在执行时设置断点:

  • 将保留位设置为正确的值
  • 将 DR7.LE 和 DR7.GE 设置为 1
  • 设置DR7.L0(L1,L2,L3)为1[局部断点]
  • 确保 DR7.RW/0 (RW/1, RW/2, RW/3) 是 0 [break on instruction exec ]
  • 确保 DR7.LEN0 (LEN1, LEN2, LEN3) 是 0 [1 字节长度]
  • 设置DR0(1,2,3)为指令线性地址
    • 断点[DR0到DR3]的线性地址必须落在指令的第一个字节。

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.


编辑

  • 0x101 :
    • bin(0x101) = '0b100000001'
    • DR7.L0 & DR7.LE 设置为 1

从技术上讲,0x701 应该是正确的:

  • 0x701 :
    • bin(0x701) = '0b11100000001'
    • DR7.L0 & DR7.LE & DR7.GE & DR7.bit10 设置为 1