C "error: longjmp causes uninitialized stackframe" when using longjmp

C "error: longjmp causes uninitialized stackframe" when using longjmp

我正在尝试用 C 构建一个简单的协作多线程库。基本上,可以使用 thread_create 创建线程,使用 thread_queue 将它们添加到运行队列,然后执行它们使用 thread_exec 完成。在分配给线程的函数中,可以调用 thread_yield 将当前线程放在运行队列的末尾并继续下一个线程。

因为线程可能会被中断,它们的堆栈是堆分配的。然后,我在 thread_yield 中使用 setjmp 来记住当前的执行状态,并在 dispatch 函数中使用 longjmp 来恢复线程。

出于某种原因,当 运行 编译器优化时,出现以下错误:

*** longjmp causes uninitialized stack frame ***: terminated

使用 -D_FORTIFY_SOURCE=0 编译时我没有收到此错误,并获得了预期的输出:

started at main
printing from 1, now yielding
printing from 2, now yielding
printing from 3, now yielding
printing from 1, now yielding
printing from 2, now yielding
printing from 3, now yielding
printing from 1, now yielding
printing from 2, now yielding
printing from 3, now yielding
returned from thread
returned from thread
returned from thread
finished at main

我尝试实现 rsp-寄存器的保存和恢复,但无济于事。我希望在不禁用编译器插入的安全检查的情况下使它正常工作,我将不胜感激任何建议。

完整代码如下:

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;

jmp_buf threading_start_ctx;

// create a thread executing f(arg)
struct thread* thread_create(void (*f)(void*), void* arg) {
    // allocate stack of one megabyte
    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->rbp = 0;
    return thread;
}

// add a thread to back queue
void thread_queue(struct thread* thread) {
    if (!tail_thread) {
        head_thread = thread;
    } else {
        tail_thread->next = thread;
    }
    tail_thread = thread;
    thread->next = NULL;
}

// yield from thread, transfering execution to another thread
void thread_yield(void) {
    // save rbp
    asm("movq %%rbp, %[RBP]" : [RBP] "=rm" (head_thread->rbp) :);
    if (!setjmp(head_thread->jmp_buf)) {
        // if jmp was just set, schedule a different thread and execute it 
        schedule();
        dispatch();
    } 
    // restore rbp
    asm("movq %[RBP], %%rbp" : : [RBP] "rm" (head_thread->rbp));
}

// reschedule threads
static void schedule(void) {
    // only swap if more than one thread
    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;
    }
}

// execute current head thread
static void dispatch(void) {
    if (head_thread) {
        // stack grows downwards, therefore add offset
        size_t stack_top = (size_t) head_thread->stack_ptr + head_thread->stack_size;
        if (!head_thread->has_run) {
            head_thread->has_run = true;
            // set up rbp and rsp to thread stack
            asm(
                "movq %[StackTop], %%rbp\n"
                "movq %[StackTop], %%rsp" 
                : : [StackTop] "r" (stack_top)
            );
            // execute thread
            head_thread->f(head_thread->arg);

            printf("returned from thread\n");

            // returned from thread, load starting rbp 
            asm("movq %[RBP], %%rbp" : : [RBP] "rm" (initial_rbp));
            longjmp(threading_start_ctx, 1);
        } else {
            // load rbp of current head and continue where it yielded
            asm("movq %[RBP], %%rbp": : [RBP] "rm" (head_thread->rbp));
            longjmp(head_thread->jmp_buf, 1);
        }
    }
}

// start executing queued threads
void thread_exec(void) {
    // save starting rbp
    asm("movq %%rbp, %[RBP]" : [RBP] "=rm" (initial_rbp) :);
    if (setjmp(threading_start_ctx)) {
        // if arrive from longjmp, free current head
        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();
    }
}

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 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);

static void dispatch(void);
static void schedule(void);

#endif  // THREADS_H_

main.c

#include <stdio.h>
#include <stdlib.h>
#include "threads.h"

static int a3 = 3;

void arg_printer(void* arg) {
    int id = *(int*)arg;
    if (id == 1) {
        thread_queue(thread_create(arg_printer, &a3));
    }
    for (size_t i = 0; i < 3; ++i) {
        printf("printing from %i, now yielding\n", id);
        thread_yield();
    }    
}

int main() {
    printf("started at main\n");
    
    int a1 = 1;
    int a2 = 2;

    thread_queue(thread_create(arg_printer, &a1));
    thread_queue(thread_create(arg_printer, &a2));

    thread_exec();
    printf("finished at main\n");
    return 0;
}

你不能再这样做了(假设它曾经有效)。从调用 setjmp() 的函数返回然后尝试返回 longjmp() 总是调用未定义的行为。现在它实际上正在捕捉它。

我要大胆猜测一下,你是在一本旧书中找到的。我记得很久以前曾经使用过一种非常相似的技术,但现在没有类似的方法了。问题是编译器优化将管束一切。如果你想要轻量级的用户调度线程,它比以前更难实现。如果我是你,我现在就直接在组装中完成所有事情。

For some reason, when running with compiler optimizations, I get the following error:

*** longjmp causes uninitialized stack frame ***: terminated

也有人遇到过这样的问题this way:

The problem is that the "longjmp causes uninitialized stack frame" is really a false positive. I'm abusing the stack in ways beyond the imagination of glibc.

“强化”(选中)longjmp 不考虑堆分配的堆栈;毕竟,_FORTIFY_SOURCE 是执行标准 符合程序 .

的检查

I'd like to get this working without disabling compiler inserted safety checks and would appreciate any suggestions.

您不必在任何地方都禁用 _FORTIFY_SOURCE;如果你只是为 threads.c 编译单元禁用它,你的程序就可以工作,其中关键的 longjmp 是。