78、【OS】【Nuttx】【启动】caller-saved 和 callee-saved 示例:栈指针和帧指针(下)
【声明】本博客所有内容均为个人业余时间创作,所述技术案例均来自公开开源项目(如Github,Apache基金会),不涉及任何企业机密或未公开技术,如有侵权请联系删除
背景
接之前 blog
【OS】【Nuttx】【启动】caller-saved 和 callee-saved 示例
【OS】【Nuttx】【启动】caller-saved 和 callee-saved 示例:叶子函数
【OS】【Nuttx】【启动】caller-saved 和 callee-saved 示例:栈指针和帧指针(上)
分析了栈指针和帧指针的一些概念,下面来补充下栈帧里面的一些细节
栈帧操作演示
1、进程中,函数 A 是一个执行的函数,程序为函数 A 开辟了一块栈空间,此时 sp 栈指针和 fp 帧指针都指向这块开辟的栈空间基址
函数 A 可以通过 fp 帧指针,尽情的访问这块为它开辟的栈空间
在函数 A 的执行过程中,调用了函数 B,此时程序需要为函数 B 开辟栈空间,那么程序需要做两件事
- 存下函数 A 的帧指针 fp(这个帧指针记录了程序为函数 A 开辟的栈帧基址,如果丢了,函数 A 就没有栈帧可用了),还要存下函数 A 的返回地址(函数 A 前面可能还有调用者,那它调用者的信息也不能丢,丢了就找不到回去的路了),到时候从函数 B 返回函数 A 时,再恢复帧指针 fp,就又能操作函数 A 的栈空间了,存下的这两个信息,也叫帧记录,上篇 blog 介绍过的 【OS】【Nuttx】【启动】caller-saved 和 callee-saved 示例:栈指针和帧指针(上)
- 跳转到这个为函数 B 新开辟的栈空间,此时 sp 栈指针和 fp 帧指针更新,重新指向这个新开辟的栈帧空间
此时函数 B 也可以通过 fp 帧指针,尽情的访问这块为它开辟的栈空间
其实从上面的演示也能看出来,上面栈指针 sp 和帧指针的 fp 的差别,这是两个维度的概念
- 栈指针 sp 负责维护【栈】这个数据结构
- 帧指针负责维护数据块里面的内容,栈空间分配了这个数据块后,帧指针可以通过数据块基址,访问到数据块里面的内容
帧指针的选择
再说个有意思的现象,查看 AAPCS32 文档,可以看到关于寄存器在函数调用中的特殊用途
这里有两个值得注意的细节:
- r0-r3:首先 r0-r3 这四个寄存器都有 argument 和 scratch 的属性
- argument 表示它们是参数寄存器,在函数调用中,如果函数有多个参数,前四个整数或指针类型的参数会被依次放在 r0, r1, r2, r3 中,这是之前 blog 【OS】【Nuttx】【启动】深入理解 caller-saved 和 callee-saved(上) 提到过的
- scratch 表示它们还是临时的,可被函数破坏,需要调用者保存的寄存器(也就是一直在提的 caller-saved 原则),如果函数 A 调用函数 B,那么在调用之前,函数 A 必须手动保存这些寄存器的值到栈上,否则它们的内容可能会被破坏
- r0-r1:除了具有 argument 和 scratch 属性,r0 和 r1 还具有 result 属性,意味着它俩还可以用于输出返回值,一般返回值会放在 r0 中,如果是 64 位返回值(比如 long long 类型),则用 r0 和 r1 联合表示
- r11 寄存器:Frame Pointer,帧指针寄存器
有读者到这里会有疑问:什么?怎么 r11 是帧指针?在代码里看到的可是 r7 寄存器啊?
今天先分析到这儿,这个问题下篇 blog 解答