调用方法/函数时汇编语言会发生什么?
如果我有一个C ++ / C程序(语言无关紧要,只需说明一个概念):
#include void foo() { printf("in foo"); } int main() { foo(); return 0; }
assembly中会发生什么? 我实际上并不是在寻找汇编代码,因为我还没有那么远,但基本原理是什么?
一般来说,这是发生的事情:
- 函数的参数存储在堆栈中。 按平台特定顺序。
- 返回值的位置在堆栈上“已分配”
- 该函数的返回地址也存储在堆栈或专用CPU寄存器中。
- 通过CPU特定的
call
指令或通过正常的jmp
或br
指令(跳转/分支)call
函数(或实际上,函数的地址) - 该函数从堆栈中读取参数(如果有)并运行函数代码
- 函数返回值存储在指定位置(堆栈或专用CPU寄存器)
- 执行跳转回调用者并清除堆栈(通过将堆栈指针恢复为其初始值)。
上述细节因平台而异,甚至从编译器到编译器也有所不同(参见例如STDCALL与CDECL调用约定)。 例如,在某些情况下,使用CPU寄存器而不是在堆栈上存储东西。 一般的想法是相同的
你可以自己看看:
在Linux下’编译’您的程序:
gcc -S myprogram.c
并且您将获得汇编程序(myprogram.s)中的程序列表。
当然你应该对汇编程序有一点了解它(但它值得学习,因为它有助于理解你的计算机是如何工作的)。 调用函数(在x86架构上)基本上是:
- 把变量放在堆栈上
- 将变量b放在堆栈上
- 把变量n放在堆栈上
- 跳转到该函数的地址
- 从堆栈加载变量
- 做function的东西
- 干净的堆栈
- 跳回主要
assembly中会发生什么?
简要说明:保存当前堆栈状态,创建新堆栈并加载并运行要执行的函数的代码。 这会给微处理器的一些寄存器带来不便,一些来回读取/写入内存,一旦完成,就会恢复调用函数的堆栈状态。
参数被推入堆栈并进行“调用”指令
调用是一个简单的“jmp”,将指令的地址压入堆栈(“ret”在弹出它并跳过它的方法的末尾)
我想你想看一下调用堆栈,以便更好地了解函数调用期间发生的事情: http : //en.wikipedia.org/wiki/Call_stack
怎么了? 在x86中,main函数的第一行可能如下所示:
call foo
call
指令将推送堆栈上的返回地址,然后jmp
到foo的位置。
一个很好的例子: http : //www.cs.uleth.ca/~holzmann/C/system/memorylayout.pdf
怎么了?
C模仿assembly中会发生什么……
它离机器很近,你可以意识到会发生什么
void foo() { printf("in foo"); /* db mystring 'in foo' mov eax, dword ptr mystring mov edx , dword ptr _printf push eax call edx add esp, 8 ret //thats it */ } int main() { foo(); return 0; }
1-在堆栈上建立调用上下文
2-参数被推入堆栈
3-对该方法执行“调用”
一般的想法是你需要
- 保存当前的本地状态
- 将参数传递给函数
- 调用实际function。 这涉及将返回地址放在某处,以便
RET
指令知道继续的位置。
具体情况因建筑而异。 更具体的细节可能因各种语言而异。 虽然通常有一些方法可以在一定程度上控制它,以允许不同语言之间的互操作性。
关于调用约定的维基百科文章是一个非常有用的起点。 例如,在x86上,堆栈几乎总是用于将参数传递给函数。 然而,在许多RISC架构中,主要使用寄存器,而在特殊情况下仅需要堆栈。
常见的想法是,调用方法中使用的寄存器被压入堆栈(堆栈指针位于ESP
寄存器中),此过程称为“推送寄存器”。 有时他们也被归零,但这取决于。 汇编程序员倾向于释放更多寄存器,然后使用通用4( EAX
, EBX
, ECX
和EDX
)以在函数内提供更多可能性。
当函数结束时,反过来也会发生这种情况:堆栈恢复到调用之前的状态。 这称为“弹出寄存器”。
更新:此过程不一定必须发生。 编译器可以优化它并内联您的function。
更新:通常函数的参数以相反的顺序被推入堆栈,当从堆栈中检索它们时,它们看起来好像是正常的顺序。 C不保证此订单。(参考:Rick Booth的Inner Loops
)