C语言协作式多线程库开发:使用longjmp触发“longjmp causes uninitialized stackframe”错误的解决方案求助
解决
longjmp causes uninitialized stack frame错误的方案 你遇到的问题是GCC的_FORTIFY_SOURCE安全检查触发的栈帧有效性验证失败。当手动切换栈(通过修改rsp/rbp)并使用longjmp时,编译器的栈保护机制认为目标栈帧是未初始化的——因为它没有看到合法的栈帧建立过程(比如函数调用时的栈帧初始化、栈canary设置等)。
下面是具体的解决方案,不需要关闭_FORTIFY_SOURCE:
核心问题分析
- 栈帧合法性检查:启用
_FORTIFY_SOURCE和栈保护(默认开启)时,GCC会在函数入口设置栈canary值,并在返回时检查。你的线程切换直接修改rsp/rbp,跳过了编译器的栈帧初始化逻辑,导致longjmp后栈canary不匹配,触发错误。 - 帧指针优化干扰:开启优化时,GCC可能启用帧指针优化(
-fomit-frame-pointer),此时rbp不再作为帧指针使用,手动保存/恢复rbp的操作会破坏编译器的寄存器使用假设。 setjmp上下文不完整:setjmp默认只保存部分寄存器,手动切换栈后,这些寄存器的状态可能和栈不匹配,导致栈帧验证失败。
具体修复步骤
1. 禁用帧指针优化(针对线程相关代码)
编译threads.c时添加-fno-omit-frame-pointer,确保rbp始终作为帧指针使用,让手动保存/恢复rbp的操作更安全。
2. 初始化线程栈的合法栈帧
线程第一次运行时,不要直接调用线程函数,而是通过包装函数启动,让编译器自动处理栈帧初始化和栈canary设置。
3. 改进上下文保存逻辑
同时保存rsp和rbp,确保切换栈时的上下文完整性,避免编译器优化干扰。
修改后的完整代码
threads.h
#ifndef THREADS_H_ #define THREADS_H_ #include <stddef.h> #include <setjmp.h> #include <stdbool.h> struct thread { struct thread* next; void (*f)(void*); void* arg; void* stack_ptr; size_t stack_size; bool has_run; size_t rsp; // 新增保存栈指针的字段 size_t rbp; jmp_buf jmp_buf; }; struct thread* thread_create(void (*f)(void*), void* arg); void thread_queue(struct thread* t); void thread_yield(void); void thread_exec(void); #endif // THREADS_H_
threads.c
#include "threads.h" #include <stdlib.h> #include <stdio.h> struct thread* head_thread = NULL; struct thread* tail_thread = NULL; size_t initial_rbp = 0; size_t initial_rsp = 0; // 保存初始栈指针 jmp_buf threading_start_ctx; // 线程启动包装函数,让编译器自动处理栈帧初始化 static void thread_start_wrapper(struct thread* t) { t->f(t->arg); printf("returned from thread\n"); longjmp(threading_start_ctx, 1); } struct thread* thread_create(void (*f)(void*), void* arg) { size_t stack_size = 1 << 20; struct thread* thread = (struct thread*) malloc(sizeof(struct thread)); thread->next = NULL; thread->f = f; thread->arg = arg; thread->stack_ptr = malloc(stack_size); thread->stack_size = stack_size; thread->has_run = false; thread->rsp = 0; thread->rbp = 0; return thread; } void thread_queue(struct thread* thread) { if (!tail_thread) { head_thread = thread; } else { tail_thread->next = thread; } tail_thread = thread; thread->next = NULL; } void thread_yield(void) { // 保存当前栈指针和帧指针 asm volatile( "movq %%rsp, %[RSP]\n" "movq %%rbp, %[RBP]\n" : [RSP] "=rm" (head_thread->rsp), [RBP] "=rm" (head_thread->rbp) : : "memory" ); if (!setjmp(head_thread->jmp_buf)) { schedule(); dispatch(); } // 恢复栈指针和帧指针 asm volatile( "movq %[RSP], %%rsp\n" "movq %[RBP], %%rbp\n" : : [RSP] "rm" (head_thread->rsp), [RBP] "rm" (head_thread->rbp) : "memory" ); } static void schedule(void) { if (head_thread != tail_thread) { struct thread* current = head_thread; head_thread = head_thread->next; tail_thread->next = current; tail_thread = current; tail_thread->next = NULL; } } static void dispatch(void) { if (head_thread) { if (!head_thread->has_run) { head_thread->has_run = true; // 构造合法的调用栈:返回地址 + 函数参数 size_t* stack_top = (size_t*)((char*)head_thread->stack_ptr + head_thread->stack_size); *--stack_top = (size_t)0; // 虚拟返回地址 *--stack_top = (size_t)head_thread; // 传递给wrapper的参数 // 设置栈指针并调用包装函数,编译器自动处理栈帧和canary asm volatile( "movq %[StackTop], %%rsp\n" "movq %%rsp, %%rbp\n" "call *%[WrapperFunc]\n" : : [StackTop] "r" (stack_top), [WrapperFunc] "r" (thread_start_wrapper) : "memory", "rax", "rbx", "rcx", "rdx", "rsi", "rdi" ); } else { // 先恢复栈指针,再执行longjmp asm volatile( "movq %[RSP], %%rsp\n" "movq %[RBP], %%rbp\n" : : [RSP] "rm" (head_thread->rsp), [RBP] "rm" (head_thread->rbp) : "memory" ); longjmp(head_thread->jmp_buf, 1); } } } void thread_exec(void) { // 保存初始栈状态 asm volatile( "movq %%rsp, %[RSP]\n" "movq %%rbp, %[RBP]\n" : [RSP] "=rm" (initial_rsp), [RBP] "=rm" (initial_rbp) : : "memory" ); if (setjmp(threading_start_ctx)) { struct thread* next = head_thread->next; free(head_thread->stack_ptr); free(head_thread); head_thread = next; if (!head_thread) { tail_thread = NULL; } } if (head_thread) { dispatch(); } // 恢复初始栈状态 asm volatile( "movq %[RSP], %%rsp\n" "movq %[RBP], %%rbp\n" : : [RSP] "rm" (initial_rsp), [RBP] "rm" (initial_rbp) : "memory" ); }
编译说明
编译时为threads.c添加帧指针保留参数:
gcc -O2 -fno-omit-frame-pointer threads.c main.c -o threads
这样编译运行后,即使启用默认的_FORTIFY_SOURCE安全检查,程序也能正常输出预期结果,不会触发栈帧错误。
内容的提问来源于stack exchange,提问作者georgijs_
相关产品推荐
相关产品推荐

