AArch64 学习(二) 函数调用 (Function Call Convention)
本系列的第一篇 中介绍了 AArch64 的基础指令、进程内存布局以及基础栈操作 等. 本文该系列的第二篇, 主要聊聊函数调用, 涉及到的就是 Function Call Convention. 初衷还是尽可能 “浅入深出” 地 got 到语言背后的本质, 这不是一个手册, 所以不是完备的.
1. 我们在聊函数调用的时候在聊什么?
至少我们应该把函数调用的几个问题搞清楚:
- 函数在汇编层是怎么调用的, 本质是什么?
- 函数的参数怎么传?
- 返回值写到哪里? 怎么传给 caller?
- 调用完之后, 怎么返回到原来的位置?
Function Call Convention 其实就是回答这些问题的, 接下里我们一一找到答案.
1.1. 函数调用本质是什么?
汇编层是没有函数的概念的, 我们需要把函数映射到汇编层来, 这样我们就知道了它的本质. 其实执行一个程序, 在汇编层来看就是不断的执行 CPU 指令, 都执行完了, 进程就结束了. 从第一篇的例子其实可以看出, 一个函数就是一个label, 等于代码段中该函数第一条指令的位置. 其实本质上函数调用, 就是程序从代码段的某一条指令, 跳转到另外一个地址上的指令去执行. 稍微复杂点的 C 程序都不是从头执行到尾就结束了, 会有条件判断, 函数调用. 函数调用和普通跳转不同的地方在于要处理传参、返回、以及寄存器的 backup 和恢复.
AArch64 提供给我们了一个 bl (branch with link) 指令, 用来执行指定的函数. 第一篇里, 我们介绍了 cmp 以及 b.le/b.ge 等, ‘b’ 在这两处都是 branch 跳转的意思.
只不过 bl 是跳转的函数地址上, bl 内部实现是这样的:
- 跳转之前会把函数调用后面地址(也就是bl的下一条指令的地址) 存放到 LR (Link register) 中
- PC 被 bl 的参数替换, 就是 PC 指向了 bl 的参数, 通常是一个函数 label, 对应着一个地址
- 目标函数开始执行
- 目标函数执行完, 调用 ret 指令, ret 会把 LR copy 回 PC
- 程序执行 PC, 也就是执行原来 bl 下一条指令了