2

IT工程专业的学生在这里。我们被要求进行上下文切换,一项特定的任务让我们实现了一个相当粗糙的尝试/抛出系统。这是我们一直在编写的代码:

struct ctx_s {
  int esp;
  int ebp;
};

struct ctx_s * pctx;

typedef int (func_t)(int); /* a function that returns an int from an int */

int try(func_t *f, int arg)
{
  /* saving context by storing values of %esp and %ebp */      
    asm ("movl %%esp, %0"
    : "=r"((*pctx).esp) 
    :
    );

    asm ("movl %%ebp, %0"
    : "=r"((*pctx).ebp) 
    :
    );

    /* calling the function sent to try(), returning whatever it returns */
    return f(arg);
}

int throw(int r)
{
    printf("MAGIC PRINT\n");

    static int my_return = 0;
    /* ^ to avoid "an element from initialisation is not a constant" */
    my_return = r;
    /* restituting context saved in try() */
    asm ("movl %0, %%esp"
    : 
    : "r"((*pctx).esp) 
    );

    asm ("movl %0, %%ebp"
    : 
    : "r"((*pctx).ebp) 
    );

    /* this return will go back to main() since we've restored try()'s context
     so the return address is whatever called try... */
    /* my_return is static (=> stored in the heap) so it's not been corrupted,
     unlike r which is now the second parameter received from try()'s context, 
     and who knows what that might be */
    return my_return;
}

pctx 是一个指向包含两个 int 的简单结构的全局指针,f 是一个调用 throw() 并将一些返回代码 #define'd 发送到 42 的函数,而 main() 本质上是分配 pctx,result=try(f, 0)并打印结果。我们预计结果为 42。

现在,您可能已经在 throw() 中发现了 MAGIC PRINT。它在这里的原因尚不完全清楚;基本上,大多数(不是所有)学生都在 throw() 内部进行段错误;在这个函数中调用 printf() 使程序看起来正常工作,老师们认为任何系统调用都可以正常工作。

因为我并没有真正得到他们的解释,所以我尝试比较使用 gcc -S 为两个版本(有和没有 printf())生成的汇编代码,但我无法充分利用它。在 throw() 的左大括号(第 33 行)设置断点并使用 gdb 反汇编给了我这个:

没有 printf():

Breakpoint 1, throw (r=42) at main4.c:38
(gdb) disass
Dump of assembler code for function throw:
0x0804845a <throw+0>:   push   %ebp
0x0804845b <throw+1>:   mov    %esp,%ebp
0x0804845d <throw+3>:   mov    0x8(%ebp),%eax
0x08048460 <throw+6>:   mov    %eax,0x8049720
0x08048465 <throw+11>:  mov    0x8049724,%eax
0x0804846a <throw+16>:  mov    (%eax),%eax
0x0804846c <throw+18>:  mov    %eax,%esp
0x0804846e <throw+20>:  mov    0x8049724,%eax
0x08048473 <throw+25>:  mov    0x4(%eax),%eax
0x08048476 <throw+28>:  mov    %eax,%ebp
0x08048478 <throw+30>:  mov    0x8049720,%eax
0x0804847d <throw+35>:  pop    %ebp
0x0804847e <throw+36>:  ret    
End of assembler dump.
(gdb) c
Continuing.

Program received signal SIGSEGV, Segmentation fault.
0xb7e846c0 in ?? ()

使用 printf():

Breakpoint 1, throw (r=42) at main4.c:34
(gdb) disassemble 
Dump of assembler code for function throw:
0x0804845a <throw+0>:   push   %ebp
0x0804845b <throw+1>:   mov    %esp,%ebp
0x0804845d <throw+3>:   sub    $0x18,%esp
0x08048460 <throw+6>:   movl   $0x80485f0,(%esp)
0x08048467 <throw+13>:  call   0x8048364 <puts@plt>
0x0804846c <throw+18>:  mov    0x8(%ebp),%eax
0x0804846f <throw+21>:  mov    %eax,0x804973c
0x08048474 <throw+26>:  mov    0x8049740,%eax
0x08048479 <throw+31>:  mov    (%eax),%eax
0x0804847b <throw+33>:  mov    %eax,%esp
0x0804847d <throw+35>:  mov    0x8049740,%eax
0x08048482 <throw+40>:  mov    0x4(%eax),%eax
0x08048485 <throw+43>:  mov    %eax,%ebp
0x08048487 <throw+45>:  mov    0x804973c,%eax
0x0804848c <throw+50>:  leave  
0x0804848d <throw+51>:  ret    
End of assembler dump.
(gdb) c
Continuing.
MAGIC PRINT
result = 42

Program exited normally.

我真的不知道该怎么做。显然事情的发生方式不同,但我发现在这两种情况下都很难理解发生了什么......所以我的问题本质上是:调用 printf 如何使 throw 不是段错误?

4

2 回答 2

1

您正在“恢复”ESP到保存在另一个函数中的值。在这里可能不是一个有用的价值。

与“魔术”代码的区别在于它使编译器在throw函数中保存和恢复堆栈帧。

最后的一条leave指令相当于

mov    %ebp, %esp
pop    %ebp

这可能会使堆栈指针回到函数入口处的位置。

于 2012-10-07T14:03:20.937 回答
1

好的,这是一个有点松散的分析,因为我看不到 try 部分,但从标准调用约定来看,包含 try 的方法将保存%esp%ebp,减少%esp为局部变量腾出空间并运行你的“try”代码保存%esp%ebp

通常,当一个函数退出时,它会通过使用leavebefore return 来恢复这些更改。离开将恢复%ebp%esp,弹出%ebp并返回。这可以确保%esp在为局部变量保留空间之前将其恢复到原来的位置。

没有版本的问题printf是它不使用leavewhich pops%ebp而不首先将其内容恢复到%esp. 该ret指令将弹出一个局部变量并返回该变量。不是最好的结果。

我的怀疑是,由于您的函数没有局部变量,编译器认为没有理由%esp%ebp. 由于在堆栈上保留空间,编译器知道在返回之前必须恢复printf的那个版本。%esp

如果要测试理论,编译成汇编,替换即可;

0x0804847d <throw+35>:  pop    %ebp

带有离开指令并组装结果。它应该也能正常工作。

或者,我怀疑您可以在您的 asm 指令中指示 gcc%esp已被破坏,从而使其产生休假。

编辑:显然标记%esp为 clobbered 本质上是 gcc 中的 NOOP :-/

于 2012-10-07T14:03:33.683 回答