如何正确搭建sigreturn系统调用的栈帧以实现原子跳转?
问题分析与解决方案
你的核心问题是直接将libc的ucontext_t结构放到栈上调用sigreturn,但sigreturn系统调用期望栈顶是内核定义的信号帧(rt_sigframe)结构,两者格式不兼容,导致内核无法正确解析上下文并完成跳转。
关键原理
sigreturn/rt_sigreturn是内核为信号处理函数返回提供的入口,它读取栈上的信号上下文帧来恢复进程状态:
- 旧版
sigreturn(syscall 15)仅支持32位信号掩码,现代x86_64系统推荐使用rt_sigreturn(syscall 139),支持64位掩码。 - 内核的
rt_sigframe结构包含struct sigcontext(寄存器上下文)和sigset_t(信号掩码)等字段,和libc的ucontext_t结构存在映射关系,但不能直接替换使用。
修正后的代码
#define _GNU_SOURCE #include <pthread.h> #include <ucontext.h> #include <signal.h> #include <unistd.h> #include <stdio.h> #include <malloc.h> #include <string.h> // x86_64下rt_sigreturn的系统调用号 #define __NR_rt_sigreturn 139 // 模拟内核的rt_sigframe结构(简化版,仅保留关键字段) typedef struct { struct sigcontext sc; sigset_t mask; } rt_sigframe_t; void sigreturn_to(ucontext_t *ctx) { // 分配足够空间构造信号帧(预留冗余空间避免越界) const size_t frame_buf_size = sizeof(rt_sigframe_t) + 256; void *frame_buf = malloc(frame_buf_size); if (!frame_buf) return; // 将信号帧放在缓冲区的尾部(栈向下生长,符合内核预期的栈布局) rt_sigframe_t *sf = (rt_sigframe_t*)((char*)frame_buf + frame_buf_size - sizeof(rt_sigframe_t)); // 拷贝寄存器上下文:ucontext_t的uc_mcontext就是内核的sigcontext结构 memcpy(&sf->sc, &ctx->uc_mcontext, sizeof(struct sigcontext)); // 拷贝信号掩码 memcpy(&sf->mask, &ctx->uc_sigmask, sizeof(sigset_t)); // 切换RSP到信号帧起始位置,调用rt_sigreturn __asm__ __volatile__( "mov %0, %%rsp\n\t" "mov %1, %%rax\n\t" "syscall" : : "r"(sf), "i"(__NR_rt_sigreturn) : "memory", "rax", "rsp" ); // 代码不会执行到这里,提前释放只是为了消除编译警告 free(frame_buf); } void come_here() { while (1) { printf("123\n"); sleep(1); } } int main() { ucontext_t ctx; getcontext(&ctx); // 分配新栈空间:栈向下生长,ss_sp是栈底,makecontext会自动设置RSP为ss_sp + ss_size void *main_stack = malloc(40960); if (!main_stack) return 1; // 初始化上下文参数 sigemptyset(&ctx.uc_sigmask); ctx.uc_link = NULL; ctx.uc_stack.ss_sp = main_stack; ctx.uc_stack.ss_size = 40960; ctx.uc_stack.ss_flags = 0; // 修改上下文的RIP为come_here,RSP为新栈顶端 makecontext(&ctx, come_here, 0); // 触发rt_sigreturn完成原子性上下文切换 sigreturn_to(&ctx); // 代码不会执行到这里 free(main_stack); return 0; }
关键修正点
- 构造正确的信号帧:手动模拟内核
rt_sigframe结构,将ucontext_t中的寄存器和掩码拷贝到对应字段。 - 使用
rt_sigreturn:替换旧版sigreturn,支持64位信号掩码,符合现代x86_64系统规范。 - 栈布局调整:将信号帧放在缓冲区尾部,符合x86_64栈向下生长的特性,确保内核能正确读取上下文。
- 寄存器约束:内联汇编显式声明被修改的寄存器,避免编译器优化导致的错误。
内容的提问来源于stack exchange,提问作者Markity
相关产品推荐
相关产品推荐

