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
是。
我正在尝试用 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
是。