当caller调用callee时,callee对应的栈帧就会被开辟,当调用结束返回caller时,callee对应的栈帧就会被销毁。 下图展示了栈帧的结构。在32位程序中,寄存器ebp指向栈帧的底部,用来存储当前栈帧的基址,在函数运行过程中不变,可以用来索引函数参数和局部变量的位置。寄存器esp指向栈帧的顶部,当栈生长时,esp的值减少(向低...
由于被调用者允许使用EAX、ECX和EDX寄存器,所以如果调用者希望保存这些寄存器的值,就必须在调用子函数之前显式地把它们保存在栈中。另一方面,如果除了上面提到的几个寄存器,被调用者还想使用别的寄存器,比如EBX、ESI和EDI,那么,被调用者就必须在栈中保存这些被额外允许使用的寄存器,并在调用返回前恢复它们。也就是说...
为了更好的理解c函数调用过程,利用gdb查看了函数调用栈帧中内存的详细情况。 c程序源代码: #include<stdio.h> int func(int a, int b) { int temp = a+ b; return temp; } int main() { int x = 10; int y…
另外一个比较重要的知识点是函数调用过程中与堆栈相关的寄存器RSP和RBP,两个寄存器主要实现对栈位置的记录,具体作用如下: RSP:栈指针寄存器(reextended stack pointer),其内存放着一个指针,该指针永远指向系统栈最上面一个栈帧的栈顶。 RBP:基址指针寄存器(reextended base pointer),其内存放着一个指针,该指针永远...
首先,main把EAX,ECX和EDX压栈。这是一个可选的步骤,只在这三个寄存器内容需要保留的时候执行此步骤。 接着,main把传递给foo的参数一一进栈,最后的参数最先进栈。例如,我们的函数调用是: a= foo(12,15,18); 相应的汇编语言指令是: pushdword18
栈: 在函数调用时,第一个进栈的是主函数中函数调用后的下一条指令(函数调用语句的下一条可执行语句)的地址,然后是函数的各个参数,在大多数的C编译器中,参数是由右往左入栈的,然后是函数中的局部变量。注意静态变量是不入栈的。 当本次函数调用结束后,局部变量先出栈,然后是参数,最后栈顶指针指向最开始存的...
函数调用栈的典型内存布局如下图所示: 函数调用栈的典型内存布局 图中给出主调函数(caller)和被调函数(callee)的栈帧布局,"m(%ebp)"表示以EBP为基地址、偏移量为m字节的内存空间(中的内容)。该图基于两个假设: 函数返回值不是结构体或联合体,否则第一个参数将位于"12(%ebp)" 处; ...
当一个C函数被调用时,函数的参数如何传递、堆栈指针如何变化、栈帧是如何被建立以及如何被消除的,一直缺乏系统性的理解,因此决定花时间学习下函数调用时整个调用机制并总结成文,以便加深理解。本文将从汇编的角度讲解函数调用时,堆栈的变化,参数的传递方式、以及栈帧的建立和消除等方面知识。
图1是一个典型的栈帧,图中,栈顶在上,地址空间往下增长。 这是如下一个函数调用时的栈的内容: int foo(int arg1, int arg2, int arg3); 1. 并且,foo有两个局部的int变量(4个字节)。在这个简化的场景中,main调用foo,而程序的控制仍在foo中。这里,main是调用者(caller),foo是被调用者(callee)。
最后,main用call指令调用子函数: 代码语言:javascript 复制 call foo 当call指令执行的时候,EIP指令指针寄存器的内容被压入栈中。因为EIP寄存器是指向main中的下一条指令,所以现在返回地址就在栈顶了。在call指令执行完之后,下一个执行周期将从名为foo的标记处开始。 图2展示了call指令完成后栈的内容。图2及后续图...