如何在Sigaction中区分SIGSEGV的读/写内存访问类型?
问题分析与解决方案
你的代码存在两个核心问题,导致无法正确区分读写类型的SIGSEGV:
1. mprotect操作粒度不符合要求
mprotect要求操作的内存区域必须页对齐,且大小是页的整数倍。你当前用info->si_addr(大概率不是页起始地址)和sizeof(int)调用mprotect,会直接导致调用失败(可通过perror("mprotect")验证),内存权限未被正确修改,后续访问仍触发错误,干扰错误码判断。
2. 错误码获取方式依赖架构且不正确
你直接使用uc->uc_mcontext.gregs[REG_ERR]获取错误码,这仅适用于32位x86架构。在64位x86(x86_64)架构下,错误码存储在uc->uc_mcontext.err字段中:
- 错误码的位1置1时,表示是写访问触发的SIGSEGV;
- 位1置0时,表示是读访问触发的SIGSEGV。
修正后的代码
#include <signal.h> #include <ucontext.h> #include <sys/mman.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <stdint.h> void handler(int sig, siginfo_t* info, void* ucontext) { int access_type = -1; ucontext_t* uc = (ucontext_t *)ucontext; // x86_64架构下,错误码存在uc_mcontext.err中 unsigned long err_code = uc->uc_mcontext.err; // 位1标识写访问 if (err_code & (1 << 1)) { access_type = 2; // 写操作 } else { access_type = 4; // 读操作 } // 将地址对齐到当前页的起始位置 long PAGE_SIZE = sysconf(_SC_PAGE_SIZE); void* page_start = (void *)((uintptr_t)info->si_addr & ~(PAGE_SIZE - 1)); // 修改整页的内存权限 if (access_type == 2) { if (mprotect(page_start, PAGE_SIZE, PROT_READ | PROT_WRITE) == -1) { perror("mprotect set write"); exit(1); } } else if (access_type == 4) { if (mprotect(page_start, PAGE_SIZE, PROT_READ) == -1) { perror("mprotect set read"); exit(1); } } } int main() { void *vm_ptr; long PAGE_SIZE = sysconf(_SC_PAGE_SIZE); int vm_size = 16 * PAGE_SIZE; if (posix_memalign(&vm_ptr, PAGE_SIZE, vm_size)) { fprintf(stderr, "posix_memalign failed\n"); return -1; } if (mprotect(vm_ptr, vm_size, PROT_NONE)) { fprintf(stderr, "mprotect failed\n"); return -1; } struct sigaction sa; sa.sa_flags = SA_SIGINFO | SA_ONSTACK; sigemptyset(&sa.sa_mask); sa.sa_sigaction = handler; if (sigaction(SIGSEGV, &sa, NULL) == -1) { perror("sigaction"); return -1; } // 触发写类型SIGSEGV int *ptr = (int *)vm_ptr; *ptr = 10; printf("Write completed: %d\n", *ptr); // 可选:测试读类型SIGSEGV(先将权限重置为PROT_NONE) if (mprotect(vm_ptr, PAGE_SIZE, PROT_NONE) == -1) { perror("mprotect reset for read test"); return -1; } printf("Read completed: %d\n", *ptr); return 0; }
额外说明
- 如果程序运行在32位x86架构下,需将错误码获取改回
uc->uc_mcontext.gregs[REG_ERR],同样检查位1是否置1; - 信号处理函数中修改权限后,内核会重新执行触发错误的指令,务必确保权限修改正确,避免无限触发SIGSEGV;
- 用
perror可以快速排查系统调用的错误原因。
内容的提问来源于stack exchange,提问作者Anurag Srivastava
相关产品推荐
相关产品推荐

