81、【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 示例:r7 寄存器
【OS】【Nuttx】【启动】caller-saved 和 callee-saved 示例:栈空间对齐
分析了栈指针和帧指针的一些概念,演示了栈帧的操作,解释了为什么选择 r7 寄存器作为帧指针,并分析了栈空间对齐,下面最后再补充点压栈内容细节
压栈内容
之前 blog 【OS】【Nuttx】【启动】caller-saved 和 callee-saved 示例:栈指针和帧指针(下) 分析了,参数寄存器只有 4 个,当输入参数超过 4 个时,需要将参数进行压栈
下面来看下参数超过 4 个的情况,代码如下
// main.c
static int add_func(int a, int b, int c, int d, int e) {return (a + b + c + d + e);
}int main(void) {int c = add_func(1, 2, 3, 4, 5);return 0;
}
终端 bash 中输入编译命令
arm-none-eabi-gcc -S -mcpu=cortex-m4 -mthumb main.c -o main.s
得到汇编文件 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 = 4, pretend = 0, frame = 16@ frame_needed = 1, uses_anonymous_args = 0@ link register save eliminated.push {r7}sub sp, sp, #20add r7, sp, #0str r0, [r7, #12]str r1, [r7, #8]str r2, [r7, #4]str r3, [r7]ldr r2, [r7, #12]ldr r3, [r7, #8]add r2, r2, r3ldr r3, [r7, #4]add r2, r2, r3ldr r3, [r7]add r2, r2, r3ldr r3, [r7, #24]add r3, r3, r2mov r0, r3adds r7, r7, #20mov 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, #16add r7, sp, #8movs r3, #5str r3, [sp]movs r3, #4movs r2, #3movs 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 函数
- 56 行:将帧指针 r7 和返回地址 lr 进行压栈,保存好之前的上下文状态
- 57 行:sp 减去 16 字节,为 main 函数留出 16 字节的栈空间,注意,之前 add_func 只有 2 个输入参数的时候,sp 减去的是 8 字节,之所以这里多了 8 字节,是因为现在 add_func 有 5 个输入参数,其中 4 个参数由 R0-R3 参数寄存器保存,还剩下一个参数,需要保存到栈上
- 这个多出来的参数是 int 类型,占 4 个字节,之前 blog 【OS】【Nuttx】【启动】caller-saved 和 callee-saved 示例:栈空间对齐 提到过,函数入口处,栈空间需要 8 字节对齐,而这个参数不属于 main 的栈空间,和 main 里面那些局部变量不属于一个系统,不能和栈空间里面的局部变量进行合并,所以这里单独占了 8 字节的入参进栈空间(caller-saved 原则),加上原先栈空间已对齐的 8 字节空间,所以是 8 + 8 = 16 字节空间
- 58 行:将帧指针 r7 加上 8 字节,这 8 字节属于 main 入参进栈保存的空间,不属于局部变量所属的栈空间,r7 帧指针不能操作读取
- 59-60 行: 将多出来的一个入参保存到栈上
- 61-64 行:剩下 4 个参数分别保存到 R0-R3 参数寄存器上
- 65 行:跳转到 add_func
add_func
下面来看 add_func
-
24 行:将帧指针 r7 压入栈中,之前 blog 【OS】【Nuttx】【启动】caller-saved 和 callee-saved 示例:叶子函数 有介绍过,为什么不把 lr 返回地址也压栈
-
25 行:为 add_func 申请 20 个字节的栈空间,用于存放局部变量,有 5 个 int 类型的入参,再加上前面压栈的 r7 寄存器,刚好 5 * 4 + 4 = 24 字节,已实现了 8 字节对齐
-
26 行:不需要额外申请 add_func 入参进栈空间,这里 sp 和 fp 相等
-
27-30 行:将 R0-R3 参数寄存器的值保存到栈上空间
-
31-37 行:将前 4 个入参进行相加
-
38 行:将第 5 个入参从栈上取出,这里偏移 24 个字节,刚好到了 main 入参进栈的空间
-
39-40 行:将第 5 个入参相加,结果保存到 R0 寄存器
至此,压栈内容分析完毕,后面继续开始栈溢出内容分析