如何在信号处理程序中获取触发地址并修改内存页权限?
能否在SIGSEGV信号处理程序中获取触发地址并修改页权限?
当然可以!当你通过SA_SIGINFO标志注册信号处理函数时,内核会通过siginfo_t结构体传递触发SIGSEGV的关键信息——其中**si_addr字段就存储了导致内存访问错误的具体地址**。你完全可以利用这个地址定位到对应的内存页,再通过mprotect()修改该页的权限,从而避免信号反复触发。
关键细节说明
- 只有当信号处理函数是通过
sa_sigaction(而非sa_handler)注册,且设置了SA_SIGINFO标志时,才能拿到包含si_addr的siginfo_t结构体。 - 对
mprotect()的调用需要操作整个内存页,所以你需要把si_addr对齐到所在页的起始地址(可以用页大小做位运算实现)。 - 信号处理函数内尽量使用异步信号安全的系统调用(比如
write()),避免调用非线程安全的函数(比如printf()),不过示例中为了可读性暂时保留了printf,实际生产环境建议替换。
修改后的代码示例
#define _GNU_SOURCE #include <stdio.h> #include <stdlib.h> #include <signal.h> #include <unistd.h> #include <sys/mman.h> #include <string.h> static void hdl(int sig, siginfo_t *siginfo, void *context) { printf("Caught Signal %d\n", sig); if (sig == SIGSEGV) { // 获取触发SIGSEGV的内存地址 void *fault_addr = siginfo->si_addr; printf("Fault occurred at address: %p\n", fault_addr); // 对齐到内存页起始地址 long page_size = getpagesize(); void *page_start = (void *)((unsigned long)fault_addr & ~(page_size - 1)); // 修改该页权限为可读可写 if (mprotect(page_start, page_size, PROT_READ | PROT_WRITE) == -1) { perror("mprotect failed"); exit(EXIT_FAILURE); } printf("Modified page permissions for address: %p\n", page_start); } } int main() { struct sigaction act; memset(&act, '\0', sizeof(act)); // 使用sa_sigaction并设置SA_SIGINFO标志,以获取siginfo_t信息 act.sa_sigaction = &hdl; act.sa_flags = SA_SIGINFO; if (sigaction(SIGSEGV, &act, NULL) < 0) { perror("sigaction failed"); return 1; } // 映射一个只读的匿名内存页 int *ip = (int *)mmap(NULL, getpagesize(), PROT_READ, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); if (ip == MAP_FAILED) { perror("mmap failed"); return 1; } // 尝试写入只读页,触发SIGSEGV *ip = 1; printf("Value after write: %d\n", *ip); // 释放映射的内存 munmap(ip, getpagesize()); return 0; }
代码运行逻辑
- 程序先映射一个只读的匿名内存页。
- 当尝试向只读页写入数据时,触发SIGSEGV信号。
- 信号处理函数捕获信号,通过
siginfo->si_addr拿到故障地址,对齐到页起始位置后,用mprotect()将该页权限改为可读可写。 - 信号处理完成后,程序回到触发故障的指令继续执行,此时写入操作已合法,不会再触发SIGSEGV。
内容的提问来源于stack exchange,提问作者Jamie Davenport
相关产品推荐
相关产品推荐

