栈无执行权限程序触发SIGSEGV后的终止机制及相关疑问
关于栈上执行代码、SIGSEGV及相关问题的解答
1. 为什么trap '' SIGSEGV无法阻止程序终止?
trap '' SIGSEGV是shell层面的信号忽略配置,但进程自身的信号处理规则优先级更高。内核会优先检查进程自身的信号处理表:如果进程没有显式设置SIG_IGN或自定义处理函数,才会继承shell的设置。另外,涉及特权指令触发的严重错误,内核可能强制终止进程,这类场景下信号忽略配置无法生效。
2. 无执行权限的栈上代码为何能运行(eax值改变)?
这是因为CPU的页权限检查存在延迟,或者你的栈页被临时标记为可执行(部分编译器、调试器的默认行为)。另外,x86的NX(不可执行)位是页级硬件检查,若你的代码很短,修改eax的指令可能在页错误触发前就已执行完成,之后才触发后续的权限或指令错误。
3. 为什么outsl触发的是SIGSEGV而非其他信号?
outsl是I/O指令,要求当前特权级(CPL)为0(内核级),用户态执行时会触发通用保护错误(#GP)。但Linux内核会将这类用户态特权指令错误统一映射为SIGSEGV信号发送给进程,只有非法指令编码这类场景才会触发SIGILL。
4. 如何定位导致终止的指令?
- 用GDB调试:运行
gdb ./your_program,启动程序后触发崩溃,执行info registers查看寄存器状态,x/i $rip直接查看崩溃时的指令地址及内容。 - 生成core dump:先执行
ulimit -c unlimited,程序崩溃后生成core文件,再用gdb ./your_program core加载分析,同样通过x/i $rip定位指令。 - strace辅助:查看strace输出的最后一条系统调用,结合寄存器上下文推断,但GDB的定位更直接准确。
5. 如何处理SIGSEGV不立即终止以用于ptrace?
需要在进程内部注册自定义SIGSEGV处理函数,而非依赖shell的trap。示例代码:
#include <signal.h> #include <stdio.h> #include <ucontext.h> void sigsegv_handler(int sig, siginfo_t *info, void *ctx) { ucontext_t *uc = (ucontext_t*)ctx; printf("SIGSEGV触发地址: %p\n", (void*)uc->uc_mcontext.gregs[REG_RIP]); // 修改RIP跳转到下一条指令,避免无限触发信号(需根据指令长度调整偏移) uc->uc_mcontext.gregs[REG_RIP] += 4; } int main() { struct sigaction sa; sa.sa_sigaction = sigsegv_handler; sa.sa_flags = SA_SIGINFO; sigemptyset(&sa.sa_mask); sigaction(SIGSEGV, &sa, NULL); // 你的栈上代码执行逻辑 return 0; }
若用ptrace跟踪,内核会将信号转发给跟踪进程,你可以在跟踪端处理信号,控制被跟踪进程继续执行。
6. 为什么strace只捕获一次SIGSEGV而非无限次?
默认情况下,进程收到SIGSEGV且无自定义处理函数时,内核会直接终止进程,因此只会触发一次信号。如果注册了自定义处理函数并让进程继续执行(比如修改RIP跳过错误指令),strace会捕获到多次SIGSEGV;若信号直接导致进程终止,自然不会有后续信号产生。
内容的提问来源于stack exchange,提问作者nadav levin
相关产品推荐
相关产品推荐

