我使用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中定义..搜索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 } ... }