75、【OS】【Nuttx】【启动】caller-saved 和 callee-saved 示例
【声明】本博客所有内容均为个人业余时间创作,所述技术案例均来自公开开源项目(如Github,Apache基金会),不涉及任何企业机密或未公开技术,如有侵权请联系删除
背景
接之前 blog
【OS】【Nuttx】【启动】深入理解 caller-saved 和 callee-saved(上)
【OS】【Nuttx】【启动】深入理解 caller-saved 和 callee-saved(下)
详细分析了 AAPCS(ARM Architecture Procedure Call Standard)中的 caller-saved 和 callee-saved 的原理,下面编写几个简单函数,来看下这其中的过程
caller-saved 和 callee-saved 示例
编写一个简单的加法函数如下
// main.c
static int add_func(int a, int b) {return (a + b);
}int main(void) {int c = add_func(1, 2);return 0;
}
在 bash 中输入如下命令,将 main.c 编译成汇编语言
arm-none-eabi-gcc -S -mcpu=cortex-m4 -mthumb main.c -o main.s
可以得到如下汇编文件
.cpu cortex-m4.arch armv7e-m.fpu softvfp.eabi_attribute 20, 1.eabi_attribute 21, 1.eabi_attribute 23, 3.eabi_attribute 24, 1.eabi_attribute 25, 1.eabi_attribute 26, 1.eabi_attribute 30, 6.eabi_attribute 34, 1.eabi_attribute 18, 4.file "main.c".text.align 1.syntax unified.thumb.thumb_func.type add_func, %function
add_func:@ args = 0, pretend = 0, frame = 8@ frame_needed = 1, uses_anonymous_args = 0@ link register save eliminated.push {r7}sub sp, sp, #12add r7, sp, #0str r0, [r7, #4]str r1, [r7]ldr r2, [r7, #4]ldr r3, [r7]add r3, r3, r2mov r0, r3adds r7, r7, #12mov sp, r7@ sp neededpop {r7}bx lr.size add_func, .-add_func.align 1.global main.syntax unified.thumb.thumb_func.type main, %function
main:@ args = 0, pretend = 0, frame = 8@ frame_needed = 1, uses_anonymous_args = 0push {r7, lr}sub sp, sp, #8add r7, sp, #0movs r1, #2movs r0, #1bl add_funcstr r0, [r7, #4]movs r3, #0mov r0, r3adds r7, r7, #8mov sp, r7@ sp neededpop {r7, pc}.size main, .-main.ident "GCC: (15:13.2.rel1-2) 13.2.1 20231009"
main 分析
下面来分析下这个汇编文件,首先是 main 函数入口
- 48行:main 函数入口,首先将 r7 和 lr 寄存器压栈,这里其实就体现了 callee-saved 规则,因为 main 函数相对于之前的调用者(比如启动函数),属于被调用函数,而 main 函数这里由于要使用 r7,r7 作为非易失性寄存器,在 main 函数返回之后,需要恢复原状,所以这里进行了压栈处理
- 49行:将当前的栈指针 sp 减去 8 字节,意味着 main 函数这里需要用到 8 字节的栈空间
- 50行:将栈指针 sp 的值赋给 r7 寄存器,申请完栈空间后,函数在使用时,是不会去修改 sp 栈指针的,sp 栈指针作为路标(用来标识当前函数栈地址,是不会轻易修改的),此时可以将 sp 栈指针赋值给 r7,通过 r7 寄存器去访问栈空间里面的内容
- 51-53行:这里就体现了 caller-saved 规则,r0 和 r1 作为易失性寄存器,将立即数 1 和 2 赋值给 r0 和 r1,然后被调用函数 add_func 就可以通过 r0 和 r1 获得了入参
add_func 分析
main 分析完后,再来看下被调用函数 add_func
- 24行:同样这里体现了 callee-saved 规则,add_func 作为被调用函数,本身也有使用 r7 寄存器的需求(需要用 r7 寄存器去访问栈空间),而 r7 作为非易失性寄存器,需要做压栈处理
- 25行:将当前的栈指针 sp 减去 12 字节,为 add_func 预留出 12 字节的栈空间
- 26行:和前面 main 函数一样,将 sp 栈指针赋值给 r7,后面就可以通过 r7 去访问申请的 12 字节栈空间
- 27-28行:通过 r7 去访问栈空间,具体的操作是把 r0 和 r1 两个寄存器的内容放到栈上,前面 main 函数说过,r0 和 r1 里面存放了入参 a 和 b
- 29-30行:将栈上刚存放的 a 和 b 参数值再赋给 r2 和 r3,这里其实也是 caller-saved 规则的体现,因为 r2 和 r3 也作为易失性寄存器,被调用函数可以随便用,不用对它的调用者负责
- 31-32行:将 r2 和 r3 寄存器内容相加,并最终赋值给 r0,之前 blog 【OS】【Nuttx】【启动】深入理解 caller-saved 和 callee-saved(下) 提过,r0 寄存器存放被调用函数的返回值
- 33-34行:回收 add_func 的栈空间
- 36行:将 r7 压在栈上的值弹出(存放着之前 main 函数使用的 r7 值)
- 37行:返回 main 函数
先分析到这里,下篇继续