# 栈、栈帧与函数调用

## 栈

栈是一个先入后出队列。关于算法栈，请自行搜索或参考：<https://zh.wikipedia.org/wiki/堆栈> 。\
在操作系统中，一般用栈来保存函数的状态和局部变量。

Linux的栈位于程序内存空间的末端，从**高地址向低地址**增长。\
在x86架构中，使用esp寄存器指向栈顶的内存地址；在x64架构中，使用rsp寄存器指向栈顶。一般可以简称为sp。

## 栈帧

栈帧是指函数在被调用时，所拥有的一块独立的用于存放函数所使用的状态和变量的栈空间。

每个函数都对应有一个栈帧。同一个函数多次进入，每次可能会分配到不同的栈帧。整个栈的内容在同一个时刻可以看作是由许多栈帧依序“堆叠”组成的。

对于一个运行中的函数，其使用的栈帧区域被sp和bp寄存器限定（对于x86，sp等价esp，bp等价rsp；对于x64，sp等价rsp，bp等价rbp）。bp指向栈帧的底部，sp指向栈帧的顶部。

![](/files/-LjOgn4Pl-EzpIs0RCEc)\
（图片来源：<http://witmax.cn/c-function-heap-stack.html>）

在函数中使用的所有变量（本地变量、实参），一般使用bp定位。设N为整型字节数，bp+2N是第一个实参的地址，bp-N是第一个本地变量的地址。

## 函数调用

一个函数调用，一般需要以下步骤

1. 保存函数的实参
2. 保存子函数结束后，需要返回的地址（返回到哪里）
3. 保存父函数的栈帧信息
4. 在栈上开辟空间供局部变量使用
5. 执行函数实现的功能
6. 释放局部变量使用的空间
7. 根据保存的父函数栈帧信息，恢复父函数栈帧
8. 根据保存的返回地址，恢复父函数执行流，一般是函数调用指令后的下一条指令

共有多种函数调用方式，这里介绍一种：`stdcall`。stdcall是标准调用约定，还有其他调用约定：cdecl、PASCAL、fastcall、thiscall，可以自行搜索。

在stdcall中，调用一个函数func(a,b,c)会有一些比较固定的代码（x86架构）。

父函数调用时，会把参数从右至左入栈，实现保存函数实参的功能：

```
push c  
push b  
push a
```

然后执行call指令：

```
call func
```

这里call指令内部实际上做了两个工作，一个是将这个call指令的下一条语句入栈，实现返回地址的保存。然后把执行流跳转到函数里。所以一个call指令从功能上可以拆分为以下两个指令：

```
push 本call指令下一条指令的地址  
jmp func
```

执行流到了func函数内部，会先进行父函数栈帧信息的保存。此时esp和ebp依然维持父函数的栈帧。\
当前子函数所有的栈中变量被释放后，esp会回到函数调用前的状态，因此无需保存esp，只要保存ebp的信息即可。

```
push ebp
```

此时，子函数的栈帧底部变到esp处：

```
mov ebp, esp
```

栈帧底部设置完毕后，可以为局部变量开辟空间，这里开辟了一个32（0x20）字节的栈空间：

```
sub esp, 20h
```

注意栈是从高地址到低地址增长的，故这里做减法。

然后就可以开始进行函数的操作，通过ebp定位函数的参数和局部变量空间，并将函数返回值放在eax寄存器中。

在函数的功能代码全部执行完毕后，释放之前开辟的栈空间：

```
add esp, 20h
```

此时esp恢复到压入ebp之后的状态，这时栈顶为父函数的ebp值，可以依据这个信息恢复父函数的ebp，进而恢复栈帧：

```
pop ebp
```

当前栈顶为返回地址，这时父函数的栈信息已经恢复，只要根据这个返回地址更改执行流，回到父函数call func指令的下一条指令即可。

```
retn
```

至此，一个函数的调用流程结束，栈的状态和调用前完全一致，子函数的返回值被存在eax寄存器中。


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://ctfbook.ph0en1x.com/reverse/zhan-3001-zhan-zheng-yu-han-shu-diao-yong.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
