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

时间:2016-11-26 13:38:54

标签: c linux x86-64 breakpoints ptrace

我正致力于"绑定"允许在OCaml语言中使用ptrace()的库,但我的问题仅与ptrace()有关。

所以,现在,我正在尝试编写一小段代码,以便使用ptrace()在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。为了找到有效值,我使用ptrace检查了GDB如何使用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;
}

1 个答案:

答案 0 :(得分:4)

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

enter image description here

一些评论(引用来自Intel Manual chap 17.2: Debug Registers):

  • 保留位:
    • 第10位保留但设为1.
    • 第12,14和15位保留,必须设置为0(memset整个结构为0)。

其他领域:

  • 应该也实现第8位和第9位并将它们设置为1
  

LE和GE(本地和全局精确断点启用)标志(位8,9)    - 稍后,P6系列处理器不支持此功能   IA-32处理器和Intel 64处理器。 [...],我们建议   如果需要确切的断点,则LE和GE标志设置为1

  • 指令的指令断点必须将长度设置为1个字节:
  

指令断点地址的长度规范必须为1   byte( LENn字段设置为00 )。其他操作数大小的代码断点未定义

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

  • 将保留位设置为正确的值
  • 将DR7.LE和DR7.GE设置为1
  • 将DR7.L0(L1,L2,L3)设置为 1 [本地断点]
  • 确保DR7.RW/0(RW / 1,RW / 2,RW / 3) 0 [指令执行中断]
  • 确保DR7.LEN0(LEN1,LEN2,LEN3) 0 [1字节长度]
  • 将DR0(1,2,3)设置为指令线性地址
    • 确保线性地址[DR0至DR3]落在指令的第一个字节
  

处理器仅在识别时识别指令断点地址   它指向指令的第一个字节。如果指令有   前缀,断点地址必须指向第一个前缀。

修改

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

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

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