You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C语言协作式多线程库开发:使用longjmp触发“longjmp causes uninitialized stackframe”错误的解决方案求助

解决longjmp causes uninitialized stack frame错误的方案

你遇到的问题是GCC的_FORTIFY_SOURCE安全检查触发的栈帧有效性验证失败。当手动切换栈(通过修改rsp/rbp)并使用longjmp时,编译器的栈保护机制认为目标栈帧是未初始化的——因为它没有看到合法的栈帧建立过程(比如函数调用时的栈帧初始化、栈canary设置等)。

下面是具体的解决方案,不需要关闭_FORTIFY_SOURCE:


核心问题分析

  1. 栈帧合法性检查:启用_FORTIFY_SOURCE和栈保护(默认开启)时,GCC会在函数入口设置栈canary值,并在返回时检查。你的线程切换直接修改rsp/rbp,跳过了编译器的栈帧初始化逻辑,导致longjmp后栈canary不匹配,触发错误。
  2. 帧指针优化干扰:开启优化时,GCC可能启用帧指针优化(-fomit-frame-pointer),此时rbp不再作为帧指针使用,手动保存/恢复rbp的操作会破坏编译器的寄存器使用假设。
  3. 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_

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.30 17:57:49