stack和stack frame

首先,我们先来了解下栈帧概念:
  • 栈帧也常被称为“活动记录”(activation record),是编译器用来实现过程/函数调用的一种数据结构。
  • 从逻辑上讲,栈帧就是一个函数执行的环境,包含所有与函数调用相关的数据:主要包括函数参数、函数中的局部变量、函数执行完后的返回地址,被函数修改的需要恢复的任何寄存器的副本。
另外,需要注意的是: 栈是从高地址向低地址延伸的。每个函数的每次调用,都有它自己独立的一个栈帧,这个栈帧中维持着所需要的各种信息。寄存器ebp用来指向当前的栈帧的底部(高地址),寄存器esp用来指向当前的栈帧的顶部(低地址), 在函数调用执行过程中,寻找所需参数或变量信息时使用寄存器ebp来寻址,因为寄存器esp的值是经常变化的,而寄存器ebp的值对一个函数的栈帧来讲是不变的。
  有了上面的基础知识,让我们以一个实际的例子来讲解一下。
int Add(int a, int b)
{
    int c;
    c=a+b;
    return c;
}
Main()
{
    Add(10, 5);
    printf("hello world!");
}

程序从main()函数开始执行,首先将main()函数压入系统调用栈(call stack)(下面如无特殊说明,用代指系统调用栈(call stack)),并给它分配一个栈帧用以保存所需信息。然后执行main函数中第一条语句,这是一条函数调用语句,在实际调用执行Add函数前需要做一些准备工作。

  首先将5和10两个参数压入栈,同时更新ESP指针的值,如下图所示:

 
  注意,参数5和10压入栈的顺序,应该是从右向左依次压入系统调用栈(call stack)。
  接下来我们需要搞清楚的是:当Add函数调用执行完毕之后,我们需通过某种方式返回到main函数中继续执行下面的指令,在本例中也就是执行print函数。解决这个问题的方式就是将下一条指令的地址压入栈中。
  以上准备工作就绪,下面开始调用执行Add函数。
  首先,我们需要将main函数用来寻址参数或变量信息的EBP寄存器值压入栈中保存,以便于从Add函数返回之后,从栈中取出EBP的值赋给EBP寄存器让main函数用来寻址参数或变量信息,同时更新ESP的值。为了Add函数能够寻址到所需信息,将此时的ESP寄存器的值赋值给EBP寄存器(图中的EBP-new)。此时将接着执行int c语句,为变量c开辟一段内存空间压入栈中,同时更新ESP的值。接下来执行c=a+b,然后返回c,但main函数中并没有声明变量来存储该返回值,故该返回值丢失。 函数返回时将ESP更新为EBP-new,接着将EBP-old弹出赋值给EBP寄存器,让main函数拿来寻址所需信息,此时就从Add函数的栈帧恢复到了main函数的栈帧。接着弹出RET(Add函数的返回地址)跳转到main函数的下一条指令即print函数处继续执行。print函数调用执行过程中压栈出栈过程与Add函数类似,不再细说。
 

猜你喜欢

转载自www.cnblogs.com/lian--ying/p/9159636.html