簡體   English   中英

C 使用 longjmp 時“錯誤: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 -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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM