[英]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
-register 的保存和恢復,但無濟於事。 我想在不禁用編譯器插入的安全檢查的情況下使其正常工作,並希望得到任何建議。
這是完整的代碼:
線程.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_
主文件
#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()
總是會調用未定義的行為。 現在它實際上正在捕捉它。
我要大膽猜測一下,說你是在一本舊書中發現的。 我記得很久以前使用過的一種非常相似的技術,但現在沒有這樣的技術了。 問題是編譯器優化將處理一切。 如果你想要輕量級的用戶調度線程,它比以前更難實現。 如果我是你,我現在會在組裝中完成整個事情。
出於某種原因,當使用編譯器優化運行時,我收到以下錯誤:
*** longjmp causes uninitialized stack frame ***: terminated
也遇到過這種問題的人是這樣說的:
問題是“longjmp 導致未初始化的堆棧幀”確實是誤報。 我正在以超出 glibc 想象的方式濫用堆棧。
“強化”(檢查) longjmp
不考慮堆分配的堆棧; 畢竟, _FORTIFY_SOURCE
是對符合標准的程序進行檢查。
我想在不禁用編譯器插入的安全檢查的情況下使其正常工作,並希望得到任何建議。
您不必到處禁用_FORTIFY_SOURCE
; 如果您只是為關鍵longjmp
所在的threads.c編譯單元禁用它,您的程序就可以工作。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.