2013-04-22 11 views
5

Sto cercando di capire alcuni fondamentali del sistema operativo utilizzando alcuni compiti. Ho già postato una domanda simile e ho ottenuto risposte soddisfacenti. Ma questo è leggermente diverso ma non sono stato in grado di eseguire il debug. Quindi ecco cosa faccio:Errore di segmentazione durante la creazione di un thread a livello utente con C e assembly

Quello che voglio fare è avviare un programma principale, malloc uno spazio, usarlo come una pila per avviare un thread a livello utente. Il mio problema è con l'indirizzo di ritorno. Ecco il codice finora:

[sto modificando il mio codice per renderlo up-to-date allo stato attuale della mia risposta]

#include <stdio.h> 
#include <stdlib.h> 
#include <assert.h> 

#define STACK_SIZE 512 

void switch_thread(int*,int*); 

int k = 0; 

void simple_function() 
{ 
    printf("I am the function! k is: %d\n",k); 
    exit(0); 
} 

void create_thread(void (*function)()) 
{ 
    int* stack = malloc(STACK_SIZE + 32); 
    stack = (int*)(((long)stack & (-1 << 4)) + 0x10); 
    stack = (int*) ((long)stack + STACK_SIZE); 
    *stack = (long) function; 
    switch_thread(stack,stack); 
} 

int main() 
{ 
    create_thread(simple_function); 
    assert(0); 
    return 0; 
} 

switch_thread è un codice assembly che ho scritto come segue:

.text 
    .globl switch_thread 
switch_thread: 
    movq %rdi, %rsp 
    movq %rsi, %rbp 
    ret 

questo codice viene eseguito molto bene sotto GDB e dà i risultati attesi (che è, passando il controllo per simple_function e la stampa: "io sono la funzione k è:! 0". Ma quando viene eseguito a parte, questo dà un errore di segmentazione.Mi sono sconcertato da questo risultato

Qualsiasi aiuto sarebbe apprezzato. Grazie in anticipo.

+0

Non pensate che dovreste preoccuparvi di altri registri sulla chiamata di funzione? – Alex

+2

Non dimenticare di allineare il tuo stack di 16. –

+0

@AkiSuihkonen Questo è un punto che voglio chiarire. Puoi spiegare perché dovrebbe essere allineato per 16? – user2290802

risposta

7

due problemi con il tuo codice:

  1. A meno che il filo è in realtà all'interno di una procedura corretta (o una procedura nidificato), non esiste una cosa come "puntatore base". Ciò rende irrilevante il valore di% rbp poiché il thread non si trova all'interno di una particolare procedura nel punto di inizializzazione.

  2. Contrariamente a quanto si pensa, quando viene eseguita l'istruzione ret, il valore a cui fa riferimento% rsp diventa il nuovo valore del contatore del programma. Ciò significa che anziché *(base_pointer + 1), verrà consultato il *(base_pointer) quando viene eseguito. Ancora, il valore di% rbp è irrilevante qui.

vostro codice (con modifiche minime per farlo funzionare) dovrebbe essere simile a questo:

void switch_thread(int* stack_pointer,int* entry_point); 

void create_thread(void (*function)()) 
{ 
    int* stack_pointer = malloc(STACK_SIZE + 8); 
    stack_pointer += STACK_SIZE; //you'd probably want to back up the original allocated address if you intend to free it later for any reason. 
    switch_thread(stack_pointer,function);  
} 

Vostra routine switch_thread dovrebbe essere simile a questo:

.text 
    .globl switch_thread 
switch_thread: 
    mov  %rsp, %rax //move the original stack pointer to a scratch register 
    mov  %rdi, %rsp //set stack pointer 
    push %rax  //back-up the original stack pointer 
    call %rsi  //call the function 
    pop  %rsp  //restore the original stack pointer 
    ret    //return to create_thread 

FYI: Se si' re inizializzando un thread da solo, ti suggerisco di creare un vero trampolino che funga da punto di ingresso del thread (ad es. RtlUserThreadStart di ntdll). Ciò renderà le cose molto più pulite, specialmente se si desidera rendere il programma multithreading e anche passare tutti i parametri alla routine di avvio.

+1

Non è necessario eseguire il cast del valore di ritorno di 'malloc' in un programma C. –

+0

La tua spiegazione non è molto chiara. Puoi riformularlo per favore? Inoltre, a che cosa serve l'istruzione chiamata% rsi? – user2290802

+1

@ user2290802% ebp viene consultato solo quando si sta effettivamente costruendo/distruggendo uno stack frame per una particolare procedura, ad esempio quando si eseguono le istruzioni 'ENTER/LEAVE' (o il prologo/l'epilogo della procedura equivalente). Nella tua funzione switch_thread, non esiste tale frame (e non ne hai comunque bisogno). Per quanto riguarda il valore del registro% rsi, terrà il puntatore su 'simple_function()'. – JosephH

0

base_pointer deve essere opportunamente allineato per memorizzare i valori void (*)(), altrimenti si ha a che fare con un comportamento non definito. Penso che tu intenda qualcosa del genere:

void create_thread(void (*function)()) 
{ 
    size_t offset = STACK_SIZE + sizeof function - STACK_SIZE % sizeof function; 
    char *stack_pointer = malloc(offset + sizeof *base_pointer); 
    void (**base_pointer)() = stack_pointer + offset; 
    *base_pointer = function; 
    switch_thread(stack_pointer,base_pointer);  
} 

Non è necessario eseguire il cast di malloc. In genere è una cattiva idea lanciare puntatori a tipi interi o puntatori di funzioni ai tipi di puntatori oggetto.

Capisco che questo è tutto un consiglio C-picky C-portatile, ma aiuta davvero a scrivere quanto più il software è possibile nel codice portatile piuttosto che fare affidamento su un comportamento non definito.

Problemi correlati