8

我使用 sigaction 来处理缺页异常,处理函数定义如下:

void sigaction_handler(int signum, siginfo_t *info, void *_context)

所以通过阅读info->si_addr很容易得到缺页地址。

问题是,如何知道这个操作是内存READ还是WRITE

我发现_context参数的类型是在/usr/include/sys/ucontext.h中定义ucontext_t

mcontext_t中定义了一个 cr2 字段,但不幸的是,它仅在未定义x86_64时可用,因此我无法使用 cr2 来识别读/写操作。

另一方面,在 /usr/include/bits/sigcontext.h中定义了一个名为sigcontext的结构 该结构包含cr2字段。但我不知道从哪里得到它。

4

3 回答 3

7

您可以通过参考 ucontext 的 mcontext 结构和 err 寄存器在 x86_64 中检查这一点:

void pf_sighandler(int sig, siginfo_t *info, ucontext_t *ctx) {
    ...
    if (ctx->uc_mcontext.gregs[REG_ERR] & 0x2) {
        // Write fault
    } else {
        // Read fault
    }
    ...
}
于 2014-04-27T20:58:44.623 回答
6

这是从内核生成SIGSEGV arch/x86/mm/fault.c__bad_area_nosemaphore()函数: http: //lxr.missinglinkelectronics.com/linux+v3.12/arch/x86/mm/fault.c#L760

 760                tsk->thread.cr2         = address;
 761                tsk->thread.error_code  = error_code;
 762                tsk->thread.trap_nr     = X86_TRAP_PF;
 763
 764                force_sig_info_fault(SIGSEGV, si_code, address, tsk, 0);

error_code字段,它的值也定义在:http arch/x86/mm/fault.c: //lxr.missinglinkelectronics.com/linux+v3.12/arch/x86/mm/fault.c#L23

  23/*
  24 * Page fault error code bits:
  25 *
  26 *   bit 0 ==    0: no page found       1: protection fault
  27 *   bit 1 ==    0: read access         1: write access
  28 *   bit 2 ==    0: kernel-mode access  1: user-mode access
  29 *   bit 3 ==                           1: use of reserved bit detected
  30 *   bit 4 ==                           1: fault was an instruction fetch
  31 */
  32enum x86_pf_error_code {
  33
  34        PF_PROT         =               1 << 0,
  35        PF_WRITE        =               1 << 1,
  36        PF_USER         =               1 << 2,
  37        PF_RSVD         =               1 << 3,
  38        PF_INSTR        =               1 << 4,
  39};

因此,有关访问类型的确切信息存储在thread_struct.error_code: http: //lxr.missinglinkelectronics.com/linux+v3.12/arch/x86/include/asm/processor.h#L470

如我所见,该error_code字段未导出到siginfo_t结构中(它在 http://man7.org/linux/man-pages/man2/sigaction.2.html中定义.. 搜索 si_signo)。

这样你就可以

  • 破解内核以导出tsk->thread.error_code(或检查是否已导出,例如在 中ptrace
  • 获取内存地址,读取/proc/self/maps,解析它们并检查页面上的访问位。如果页面存在且只读,则唯一可能的错误是写入,如果页面不存在,则两种访问都是可能的,并且如果......不应该有只写页面。
  • 您也可以尝试查找失败指令的地址,读取并反汇编。
于 2014-04-19T00:12:53.200 回答
5

可以通过以下方式访问 error_code 信息:

err = ((ucontext_t*)context)->uc_mcontext.gregs[REG_ERR] 

它由堆栈上的硬件传递,然后由内核传递给信号处理程序,因为内核传递整个“帧”。然后

bool write_fault = !(err & 0x2);

如果访问是写访问,则为 true,否则为 false。

于 2014-12-26T14:02:46.930 回答