c - 基于ebp的寻址和esp寻址之间的区别

标签 c gcc disassembly

我已经编写了一些代码来了解调用堆栈。我已经使用一些内联程序集完成了此操作,用于在堆栈上传递参数。我用 gcc 4.1.2(在 CentOS5.4 上)编译它并且运行良好,然后我用 gcc 4.8.4(在 Ubuntu14.04.3 上)编译它并运行程序但它总是崩溃。

我发现变量的引用方式存在差异。 gcc 4.1.2(CentOS5.4)中局部变量使用EBP寄存器寻址,gcc 4.8.4(Ubuntu14.04.3)中局部变量使用ESP寄存器寻址。这似乎是它崩溃的原因。

我的问题是,如何控制 gcc 使用 EBP 还是 ESP?还有,它们有什么区别?

这是 C 代码:

double fun(double d) {
    return d;
}

int main(void) {
    double a = 1.6;
    double (*myfun)() = fun;
    asm volatile("subl $8, %esp\n"
                 "fstpl (%esp)\n");
    myfun();
    asm volatile("addl $8, %esp\n");
    return 0;
}

这是 gcc 4.1.2 中的程序集,它可以工作

int main(void) {
    **......**

double a = 1.6;
    0x080483bf <+17>:     fldl   0x80484d0
    0x080483c5 <+23>:     fstpl  -0x18(%ebp)

double (*myfun) () = fun;
    0x080483c8 <+26>:     movl   $0x8048384,-0xc(%ebp)

asm volatile("subl $8, %esp\n"
             "fstpl (%esp)\n");                 
    0x080483cf <+33>:     sub    $0x8,%esp
    0x080483d2 <+36>:     fstpl  (%esp)        

myfun();
    0x080483d5 <+39>:     mov    -0xc(%ebp),%eax
    0x080483d8 <+42>:     call   *%eax
    0x080483da <+44>:     fstp   %st(0)

asm volatile("addl $8, %esp\n");
    0x080483dc <+46>:     add    $0x8,%esp

    **......**

这是 gcc 4.8.4 中的程序集。这就是崩溃的原因:

int main(void) {
    **......**

double a = 1.6;
    0x0804840d <+9>:    fldl   0x80484d0
    0x08048413 <+15>:   fstpl  0x8(%esp)

double (*myfun)() = fun;
    0x08048417 <+19>:   movl   $0x80483ed,0x4(%esp)

asm volatile("subl $8,%esp\n"
             "fstpl (%esp)\n");
    0x0804841f <+27>:   sub    $0x8,%esp
    0x08048422 <+30>:   fstpl  (%esp)      

myfun();
    0x08048425 <+33>:   mov    0x4(%esp),%eax
    0x08048429 <+37>:   call   *%eax
    0x0804842b <+39>:   fstp   %st(0)

asm volatile("addl $8,%esp\n");
    0x0804842d <+41>:   add    $0x8,%esp
    **......**

最佳答案

使用 esp 之间没有真正的区别和 ebp , 除了 esp更改为 push , pop , call , ret ,这有时会让人很难知道某个局部变量或参数在堆栈中的位置。这就是为什么 ebp加载 esp , 以便有一个稳定的引用点来引用函数参数和局部变量。

对于这样的函数:

int foo( int arg ) {
    int a, b, c, d;
    ....
}

通常生成以下程序集:

# using Intel syntax, where `mov eax, ebx` puts the value in `ebx` into `eax`
.intel_syntax noprefix

foo:                                                          
    push ebp          # preserve
    mov ebp, esp      # remember stack
    sub esp, 16       # allocate local variables a, b, c, d

    ...

    mov esp, ebp      # de-allocate the 16 bytes
    pop ebp           # restore ebp
    ret

调用此方法 ( foo(0)) 会生成如下内容:

    pushd 0           # the value for arg; esp becomes esp-4
    call  foo         
    add   esp, 4      # free the 4 bytes of the argument 'arg'.

紧接着 call指令已执行,就在 foo 的第一条指令之前方法被执行,[esp]将保存返回地址,[esp+4] 0 arg 的值.

在方法中foo , 如果我们想加载 arg进入eax (在 ... 处) 我们可以使用:

    mov eax, [ebp + 4 + 4]

因为[ebp + 0]保留 ebp 的先前值(来自 push ebp ), 和 [ebp + 4] (esp 的原始值),保存返回地址。

但我们也可以使用 esp 引用参数:

   mov eax, [esp + 16 + 4 + 4]

我们添加16因为 sub esp, 16 , 然后 4因为 push ebp , 和另一个 4跳过返回地址,到达 arg .

同样可以通过两种方式访问​​四个局部变量:

  mov eax, [ebp -  4]
  mov eax, [ebp -  8]
  mov eax, [ebp - 12]
  mov eax, [ebp - 16]

 mov eax, [esp + 12]
 mov eax, [esp +  8]
 mov eax, [esp +  4]
 mov eax, [esp +  0]

但是,每当esp更改,这些说明也必须更改。所以,最后,是否esp并不重要。或 ebp用来。使用 esp 可能更有效因为你不必 push ebp; mov ebp, esp; ... mov esp, ebp; pop ebp .


更新

据我所知,无法保证您的内联汇编能够正常工作:Ubunty 上的 gcc 4.8.4 优化了 ebp 的使用。并用 esp 引用所有内容.它不知道你的内联汇编修改了 esp , 所以当它试图调用 myfun() ,它从 [esp + 4] 中获取它, 但它应该从 [esp + 4 + 8] 中获取它.

这里有一个变通方法:不要在使用进行堆栈操作的内联汇编的函数中使用局部变量(或参数)。绕过类型转换问题double fun(double)double fn()直接在汇编中调用函数:

void my_call() {     
    asm volatile("subl $8, %esp\n"
                 "fstpl (%esp)\n"
                 "call  fun\n"
                 "addl $8, %esp\n");
}

int main(void) {
    my_call();
    return 0;
}

您还可以放置 my_call在单独的 .s 中运行(或 .S )文件:

.text
.global my_call
my_call:
    subl  $8, %esp
    fstpl (%esp)
    call  fun
    addl  $8, %esp
    ret

在 C 中:

extern double my_call();

你也可以传递 fun作为论据:

extern double my_call( double (*myfun)() );
...
     my_call( fun );

.text
.global my_call
my_call:
    sub  $8, %esp
    fstp (%esp)
    call *12(%esp)
    add  $8, %esp
    ret

关于c - 基于ebp的寻址和esp寻址之间的区别,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/34001600/

相关文章:

c - 在 C 中查找整数中最高设置位(msb)的最快/最有效的方法是什么?

c - C 中的这个字符串比较函数有什么问题?字符串是如何比较的?

android - 为android交叉编译c程序

assembly - 有没有一个网站或工具可以解析、分解和解释 x86 字节码

c++ - 在fopen()之后和相应的fclose()之前使用system()(文件描述符泄漏)

c++ - 有人有用 C++ 包装函数的例子吗?

C 宏 : get smallest type for an integer constant

c - 您如何解释这个反汇编列表?

linux - 在不同架构的文件中使用 ndisasm

c - 溢出与信息