2013-04-22 3 views
5

Я пытаюсь понять некоторые основы ОС, используя некоторые задания. Я уже опубликовал аналогичный вопрос и получил удовлетворительные ответы. Но это немного отличается, но я не смог его отладить. Итак, вот что я делаю:Ошибка сегментации, создающая поток пользовательского уровня с C и сборкой

Что я хочу сделать, это запустить основную программу, malloc пробел, использовать ее как стек, чтобы начать поток пользовательского уровня. Моя проблема связана с обратным адресом. Вот код до сих пор:

[я редактирую свой код, чтобы сделать его современным к текущему состоянию моего ответа]

#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 является сборка код, который я написал, как следующим образом:

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

Этот код работает очень хорошо под GDB и дает ожидаемый результат (который, передавая управление simple_function и печати «Я функция к является: 0». Но при запуске отдельно, это дает ошибку сегментации. Я сбив с толку этим результатом.

Любая помощь будет оценена по достоинству. Заранее спасибо.

+0

Вам не кажется, что вы должны заботиться о других регистрах при вызове функции? – Alex

+2

Не забудьте выровнять свой стек на 16. –

+0

@AkiSuihkonen Это один момент, который я хочу уточнить. Не могли бы вы объяснить, почему он должен быть выровнен на 16? – user2290802

ответ

7

Две проблемы с вашим кодом:

  1. Если ваш поток не является на самом деле внутри соответствующей процедуры (или вложенной процедуры), нет такого понятия, как «базовый указатель». Это делает значение% rbp несущественным, поскольку поток не находится внутри конкретной процедуры в точке инициализации.

  2. Вопреки тому, что вы думаете, когда команда ret выполняется, значение, которое ссылается на% rsp, становится новым значением счетчика программ. Это означает, что вместо *(base_pointer + 1), *(base_pointer) будет использоваться при его выполнении. Опять же, значение% rbp здесь не имеет значения.

Ваш код (с минимальными изменениями, чтобы сделать его запустить) должен выглядеть следующим образом:

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

Ваш switch_thread процедура должна выглядеть следующим образом:

.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: Если вы» повторите инициализацию потока самостоятельно, я предлагаю вам сначала создать правильный батут, который действует как точка ввода потока (например, RtlUserThreadStart от ntdll). Это сделает вещи намного более чистыми, особенно если вы хотите сделать свою программу многопотоковой, а также передать любые параметры в программу запуска.

+1

Вам не нужно указывать возвращаемое значение 'malloc' в программе C. –

+0

Ваши объяснения не очень понятны. Вы можете перефразировать его? Кроме того, что такое использование команды вызова% rsi? – user2290802

+1

@ user2290802% ebp рассматривается только при фактическом построении/уничтожении фрейма стека для конкретной процедуры, то есть при выполнении команд «ENTER/LEAVE» (или пролог/эпилог аналогичной процедуры). В вашей функции switch_thread нет такого фрейма (и вам все равно не нужно). Что касается значения регистра% rsi, он будет удерживать указатель на 'simple_function()'. – JosephH

0

base_pointer необходимо соответствующим образом выровнять для хранения значений void (*)(), в противном случае вы имеете дело с неопределенным поведением. Я думаю, вы имеете в виду что-то вроде этого:

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

Нет необходимости бросать malloc. Как правило, плохая идея бросать указатели на целые типы или указатели на объекты для типов указателей объектов.

Я понимаю, что это все портативный C-ничтожный совет, но он действительно помогает максимально точно записать ваше программное обеспечение в переносном коде, а не полагаться на неопределенное поведение.