函数调用栈callstack

 

一、栈知识

栈既可以向下增长(向内存低地址)也可以向上增长, 这依赖于具体的实现。在我们的例子中, 堆栈是向下增长的。堆栈指针(SP)也是依赖于具体实现的。它可以指向堆栈的最后地址,或者指向堆栈之后的下一个空闲可用地址。在我们的讨论当中, SP指向堆栈的最后地址。

栈从高地址向低地址增长,栈底为一个固定的低地址。栈由栈帧(stack frame)组成,当调用函数时逻辑堆栈帧被压入栈中, 当函数返回时逻辑堆栈帧被从栈中弹出。栈帧包括函数的参数, 函数地局部变量, 以及恢复前一个堆栈帧所需要的数据, 其中包括在函数调用时指令指针(IP)的值。

SP(stack pointer): 栈指针,指向栈顶。

FP(frame pointer):帧指针,指向帧内固定地址的指针。也叫局部基指针(LB-local base pointer)。

EBP(extended base pointer):它包含了帧指针

二、调用栈

当一个例程被调用时所必须做的第一件事是保存前一个FP(这样当例程退出时就可以恢复)。 然后它把SP复制到FP, 创建新的FP, 把SP向前移动为局部变量保留空间,这称为例程的序幕(prolog)工作。当例程退出时, 堆栈必须被清除干净, 这称为例程的收尾(epilog)工作。

一个简单的例子
void function(int a, int b, int c) {
  char buffer1[5];
  char buffer2[10];
}

void main() {
  function(1,2,3);


通过查看汇编语言输出, 我们看到对function()的调用被翻译成:
        pushl $3
        pushl $2
        pushl $1
        call function
 
    以从后往前的顺序将function的三个参数压入栈中, 然后调用function()。 指令CALL会把指令指针(EIP)也压入栈中,我们把这被保存的IP称为返回地址(RET),而在执行RET指令时,IP重新被弹入至EIP寄存器中,继续执行下一条指令。在函数中所做的第一件事情是例程的序幕工作:
        pushl %ebp
        movl %esp,%ebp 
        subl $20,%esp

    将帧指针EBP压入栈中。 然后把当前的SP复制到EBP, 使其成为新的帧指针。 我们把这个被保存的FP叫做SFP。 接下来将SP的值减小, 为局部变量保留空间。

    内存只能以字为单位寻址。 一个字是4个字节, 32位。因此5字节的缓冲区会占用8个字节(2个字)的内存空间, 而10个字节的缓冲区会占用12个字节(3个字)的内存空间。这就是为什么SP要减掉20的原因。这样我们就可以想象function()被调用时堆栈的模样(每个空格代表一个字节):
内存低地址                                            内存高地址
          buffer2      buffer1  sfp  ret  a    b    c
<------  [            ][        ][    ][    ][    ][    ][    ]
堆栈顶部                                                堆栈底部

 

你可能感兴趣的:(工作,汇编,function,buffer,语言,FP)