我们有一个功能:
int caller()
{
int arg1 = 1;
int arg2 = 2
int a = test(&arg1, &arg2)
}
test(int *a, int *b)
{
...
}
所以我不明白为什么&arg1和&arg2也必须像这样被压入堆栈
我知道我们可以使用来获取被调用方中arg1和arg2的地址
movl 8(%ebp), %edx
movl 12(%ebp), %ecx
但是如果我们不将这两个压入堆栈, 我们还可以使用以下方式找到他们的地址:
leal 8(%ebp), %edx
leal 12(%ebp), %ecx
那为什么还要麻烦将&arg1和&arg2压入堆栈呢?
答案 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)
如果直接访问arg1
和arg2
,则意味着您正在访问不属于该功能的堆栈部分。当有人使用buffer overflow attack从调用堆栈访问其他数据时,会发生这种情况。
当调用包含参数时,参数将被推入堆栈(在您的情况下为&arg1
和&arg2
)中,函数可以将它们用作此函数的有效参数列表。