【问题标题】:Code a simple thread with C and assembly用 C 和汇编编写一个简单的线程
【发布时间】:2013-04-21 07:02:48
【问题描述】:

我正在尝试编写一个简单的用户级线程库作为我的操作系统课程的练习。作为第一步,我试图运行一个程序并跳转到离开第一个程序的函数。到目前为止的代码是这样的:

初始程序:

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

#define STACK_SIZE (sizeof(void *) * 512)


void proc2() //This is the function that should run as the thread.
{
    int i;
    for(i=0;i<30;i++)
    {
        printf("Here I am!\n");
        sleep(0.5);
    }
    exit(0);
}

void* malloc_stack() //used to malloc the stack for the new thread. 
{
    void *ptr = malloc(STACK_SIZE + 16);
    if (!ptr) return NULL;
        ptr = (void *)(((unsigned long)ptr & (-1 << 4)) + 0x10); //size align
    return ptr;
}

int main()
{
    int *bp, *sp; 
    sp = malloc_stack();
    bp  = (int*) ((unsigned long)sp + STACK_SIZE);
    proc1(&proc2,sp,bp); //the actual code that runs the thread. Written in assembly
    assert(0);
}

然后我编写了一个名为 proc1 的简单汇编代码,它接受三个参数,指向函数的指针(用作指令指针)、堆栈指针和基指针,并用这些值替换当前寄存器。我写的代码是:

.globl  proc1
proc1:   
movq    %rdx, %rbp        #store the new base pointer
movq    %rsi,%rsp         #store the new stack pointer  
jmp     %rdi              #jump to the new instruction pointer.

但是当我运行这段代码时,我得到的是一个分段错误。请帮我在这里找到错误。

当我使用以下命令在 GDB 下运行它时,它工作正常:

gcc -g test.c switch.s
gdb a.out
run

但是当它像 ./a.out 一样单独运行时,它就不起作用了!!!! 请帮忙。

提前致谢。

【问题讨论】:

  • 对于初学者,您可能不想要“&proc2”... 问:您的编译器/调试器是什么?海合会和 GDB?您是否尝试过在调试器下单步执行您的代码,一路查看关键变量?如果没有,为什么不呢?
  • 遇到段错误时,程序员通常会启动调试器以查看段错误发生的确切位置,特别是如果段错误是 100% 可重复的。您也应该这样做,然后可能会更新问题。
  • @paulsm4 在获取函数地址时使用&amp; 是可以的,许多人更喜欢它,因为(可以说)它可以更清楚地说明发生了什么。
  • 补充@hyde 的评论:强烈建议在 Linux 机器上使用 coredumps 和 gdb。

标签: c assembly x86-64


【解决方案1】:

尝试更改代码以将汇编指令直接包含在 C 源代码中,如下所示:

void proc1(void (*fun)(), int *sp, int *bp){
    register int *sptr asm ("%rsi") = sp;
    register int *bptr asm ("%rdx") = bp;
    register void (*fptr)() asm ("%rdi") = fun;

    asm (
        "mov %rdx, %ebp\n"
        "mov %rsi, %esp\n"
        "jmp *%rdi\n"
    );
}

上面的代码确保proc1 的参数位于正确的寄存器中(尽管您的代码在 abi 中似乎是正确的)。请注意 jmp 参数前面的 *,这是我的 gnu 版本在我第一次尝试您的代码时警告过的。

通过上面的函数,以及用-g编译的代码,你应该可以正确调试它(使用proc1info registers上的breakpoint指令来检查cpu的内容)。


问题实际上出在%rsp 指针上,该指针必须始终等于或大于%rbp(堆栈向下增长)。只需在 main 中将 bp 而不是 sp 传递给 proc1 即可解决问题:

 proc1(&proc2, bp, bp);

2个小备注:

  • 不要忘记在 asm 版本的 C 代码中给出 proc1 原型:

    extern void proc1(void (*)(), int *, int *);
    
  • sleep libc 函数只接受unsigned long,而不接受float

    sleep(1);
    

【讨论】:

  • 非常感谢。这解决了这个问题。其实我把 sp 和 bp 搞混了。感谢您对函数原型的提醒,我想知道为什么 GCC 没有警告我睡眠?也许我应该使用 -Wall 选项。再次感谢。
  • 我不知道。我也没有使用-Wall,尽管我应该使用(这是很好的做法)。
【解决方案2】:

您在程序集顶部的movq(嗯,在您编辑之前是“曾经”:-))写成

movq dst,src

但是jmp 之前的movq 写成movq %rax,%rsp%rsp 显然是所需的dst。这显然是错误的,其他的不确定。

【讨论】:

  • -1:这显然是AT&T语法,AT&T语法中操作数的顺序是src、dest。您似乎将 Intel 语法与 AT&T 语法混淆了。在 Intel 语法中,操作数的顺序是 dest, src。
  • @nrz:OP 编辑​​了他的问题;早些时候,他使用 (dst,src) 顺序将参数从堆栈中拉入寄存器。大概他没有用一种语法组合一行,而用另一种语法组合下一行,这使得至少有一行不正确。当时我最好的猜测是他从一些编译器输出中获取了 (dst,src) 顺序,但他有可能是从其他地方得到的。
  • @torek 好的,在您回答后问题已被编辑。 movq %rsi,%rsp(当前代码)对我来说似乎是正确的,相当于 Intel 语法中的 mov rsp,rsi
  • @nrz 很抱歉没有通知我的更改。是的,我确实编辑了它。现在它在 GDB 中工作,但并不孤单。你能猜到为什么吗??
猜你喜欢
  • 1970-01-01
  • 2015-02-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-06-22
  • 2015-08-24
相关资源
最近更新 更多