gpt4 book ai didi

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

转载 作者:行者123 更新时间:2023-12-04 01:58:22 27 4
gpt4 key购买 nike

我已经编写了一些代码来了解调用堆栈。我已经使用一些内联程序集完成了此操作,用于在堆栈上传递参数。我用 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/

27 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com