3 votes

C "error : longjmp causes uninitialized stackframe" lors de l'utilisation de longjmp

J'essaie de construire une bibliothèque simple de multithreading coopératif en C. En gros, il est possible de créer des threads à l'aide des éléments suivants thread_create et les ajouter à la file d'attente avec thread_queue puis les exécuter jusqu'au bout en utilisant thread_exec . Dans la fonction qui est attribuée à un thread, il est possible d'appeler thread_yield pour placer le thread actuel à la fin de la runqueue et continuer avec le thread suivant.

Comme les threads peuvent être interrompus, leurs piles sont allouées au tas. J'utilise alors setjmp en thread_yield pour se souvenir de l'état actuel de l'exécution, et longjmp dans le dispatch -pour reprendre un fil.

Pour une raison quelconque, lors de l'exécution avec les optimisations du compilateur, j'obtiens l'erreur suivante :

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

Je n'obtiens pas cette erreur en compilant avec -D_FORTIFY_SOURCE=0 et obtenir le résultat attendu :

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

J'ai essayé de mettre en œuvre la sauvegarde et la restauration de la rsp - s'enregistrer, mais en vain. J'aimerais que cela fonctionne sans désactiver les contrôles de sécurité insérés dans le compilateur et j'apprécierais toute suggestion.

Voici le code complet :

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

2voto

Armali Points 1170

Pour une raison quelconque, lors de l'exécution avec les optimisations du compilateur, j'obtiens l'erreur suivante :

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

Quelqu'un qui a également rencontré un tel problème l'a exprimé ainsi de cette façon :

Le problème est que le "longjmp cause uninitialized stack frame" est en fait un faux positif. J'abuse de la pile d'une manière qui dépasse l'imagination de la glibc.

Le "fortifié" (coché) longjmp ne prend pas en compte les piles allouées au tas ; après tout, _FORTIFY_SOURCE consiste à effectuer des contrôles de normes programmes conformes .

J'aimerais que cela fonctionne sans désactiver les contrôles de sécurité insérés dans le compilateur et j'apprécierais toute suggestion.

Vous n'avez pas besoin de désactiver _FORTIFY_SOURCE partout ; votre programme fonctionne si vous le désactivez juste pour la threads.c unité de compilation, où les longjmp est.

0voto

Joshua Points 13231

Vous ne pouvez plus le faire (en supposant qu'il ait jamais été valide). Le retour d'une fonction qui invoque setjmp() et ensuite essayer de longjmp() en arrière a toujours invoqué un comportement non défini. En ce moment, il le rattrape.

Je vais deviner et dire que vous avez trouvé ça dans un vieux livre. Je me souviens d'une technique très similaire qui fonctionnait il y a longtemps, mais rien de tel ne fonctionne aujourd'hui. Le problème est que les optimisations du compilateur vont tout gâcher. Si vous voulez des threads légers programmés par l'utilisateur, c'est beaucoup plus difficile à mettre en œuvre qu'auparavant. Si j'étais vous, je ferais tout simplement tout en assembleur maintenant.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X