将局部变量的地址压入堆栈(程序集)的目的是什么

时间:2018-09-18 05:31:38

标签: c assembly stack instruction-set

我们有一个功能:

int caller()
{
   int arg1 = 1;
   int arg2 = 2
   int a = test(&arg1, &arg2)
}
test(int *a, int *b)
{
    ...
}

所以我不明白为什么&arg1和&arg2也必须像这样被压入堆栈

enter image description here

我知道我们可以使用来获取被调用方中arg1和arg2的地址

movl  8(%ebp), %edx
movl  12(%ebp), %ecx

但是如果我们不将这两个压入堆栈, 我们还可以使用以下方式找到他们的地址:

leal 8(%ebp), %edx
leal 12(%ebp), %ecx 

那为什么还要麻烦将&arg1和&arg2压入堆栈呢?

2 个答案:

答案 0 :(得分:5)

在一般情况下,test必须在向其传递任意指针(包括指向extern int global_var或其他任何指针)时起作用。然后main必须根据ABI /调用约定对其进行调用。

因此test的asm定义不能假设int *a指向何处,例如它指向调用者的堆栈框架。

(或者您可以将其视为优化本地引用调用中的地址,因此调用方必须将指向对象放置在arg传递槽中,并在返回时返回栈的这两个双字内存中包含*a*b的潜在更新值。)

您在禁用优化的情况下进行了编译。特别是在调用方将指针传递给本地的特殊情况下,此问题的解决方案是内联整个函数,启用优化后编译器将执行此操作。

允许编译器 进行test的私有克隆,该克隆按值,在寄存器中或采用编译器要使用的任何自定义调用约定来获取其args。不过,大多数编译器实际上并没有这样做,而是依靠内联而不是私有函数的自定义调用约定来消除arg传递开销。

或者如果已声明为static test,则编译器将已经知道它是私有的,并且理论上可以使用它想要的任何自定义调用约定,而无需使用test.clone1234这样的名称进行克隆。 gcc有时确实会这样做,以便不断传播,例如如果调用者传递了编译时常量,但gcc选择不内联。 (或者不能因为您使用了__attribute__((noinline)) static test() {}


然后顺便说一句,具有良好的register-args调用约定,例如x86-64 System V ,调用者将执行lea 12(%rsp), %rdi / lea 8(%rsp), %rsi / call test或的东西。 i386 System V的调用约定过时且效率低下,将堆栈上的所有内容都传递给了存储/重新加载。

您基本上已经确定了stack-args调用约定具有较高开销且通常很烂的原因之一。

答案 1 :(得分:0)

如果直接访问arg1arg2,则意味着您正在访问不属于该功能的堆栈部分。当有人使用buffer overflow attack从调用堆栈访问其他数据时,会发生这种情况。

当调用包含参数时,参数将被推入堆栈(在您的情况下为&arg1&arg2)中,函数可以将它们用作此函数的有效参数列表。