【问题标题】:C "error: longjmp causes uninitialized stackframe" when using longjmp使用 longjmp 时 C“错误:longjmp 导致未初始化的堆栈帧”
【发布时间】:2021-11-28 20:41:24
【问题描述】:

我正在尝试在 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-register 的保存和恢复,但无济于事。我希望在不禁用编译器插入的安全检查的情况下使其正常工作,并希望有任何建议。

这是完整的代码:

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

线程.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;
}

【问题讨论】:

    标签: c memory setjmp


    【解决方案1】:

    由于某种原因,在使用编译器优化运行时,我收到以下错误:

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

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

    问题是“longjmp 导致未初始化的堆栈帧”确实是误报。我正在以超出 glibc 想象的方式滥用堆栈。

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

    我希望在不禁用编译器插入的安全检查的情况下使其正常工作,并希望得到任何建议。

    您不必到处禁用_FORTIFY_SOURCE;如果你只是为 threads.c 编译单元禁用它,你的程序就可以工作,关键的longjmp 是。

    【讨论】:

      【解决方案2】:

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

      我要大胆猜测一下,说你在一本旧书中找到了这个。我记得很久以前曾经有过一种非常相似的技术,但现在没有这样的技术了。问题是编译器优化将解决所有问题。如果您想要轻量级的用户调度线程,那么实现起来比以前要困难得多。如果我是你,我现在就直接在汇编中完成所有事情。

      【讨论】:

      • 我知道跳转到已经返回的函数是ub。但在这种情况下,在thread_yield 中调用setjmp 之后,会调用dispatch,这会将longjmp 调用到另一个线程。因此,最初调用setjmpthread_yield 永远不会返回。
      猜你喜欢
      • 2012-02-29
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-11-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多