如何识别在SIGSEGV上使用sigaction处理程序时读取或写入页面错误的操作?(LINUX)

我使用sigaction来处理页面错误exception,处理函数定义如下:

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

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

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

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

mcontext_t中定义了一个cr2字段,但是不幸的是,当x86_64没有被定义的时候它是唯一可用的,因此我不能用cr2来标识读/写操作。

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

这是从内核arch/x86/mm/fault.c__bad_area_nosemaphore()函数生成的SIGSEGV: http : __bad_area_nosemaphore() 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字段,它的值也在arch/x86/mm/fault.c中定义: http : arch/x86/mm/fault.c

  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 : thread_struct.error_code

error_code字段没有被导出到siginfo_t结构中,正如我所看到的(它在http://man7.org/linux/man-pages/man2/sigaction.2.html中定&#x4E49;..搜索si_signo)。

所以你可以

  • 破解内核导出tsk->thread.error_code (或者检查,是否已经导出,例如在ptrace
  • 获取内存地址,读取/proc/self/maps ,解析它们并检查页面上的访问位。 如果页面存在并且是只读的,唯一可能的错误是写入,如果页面不存在这两种访问是可能的,并且如果…应该没有只写页面。
  • 你也可以尝试找到失败指令的地址,读取它并进行反汇编。

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

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

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

 bool write_fault = !(err & 0x2); 

如果访问是写访问,则为真,否则为假。

您可以通过参考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 } ... }