1 基本概念
ATPCS (ARM-Thumb Procedure Call Standard)规定了在子程序调用时的一些基本规则:
- 各寄存器的使用规则及其相应的名称
- 数据栈的使用规则
- 参数传递的规则
以上是基本ATPCS规定的规则,为适应一些特定需要,在此基础上再添加其他规则形成了几种特定的ATPCS:
- 支持数据栈限制检查的ATPCS
- 支持只读段位置无关(ROPI)的ATPCS
- 支持可读写段位置无关(RWPI)的ATPCS
- 支持ARM程序和Thumb程序混合使用的ATPCS
- 处理浮点运算的ATPCS
有调用关系的子程序必须遵守同一种ATPCS,相对特定类型的ATPCS,满足基本ATPCS的程序执行速度更快,所占内存更少,但它不能:
- ARM程序和Thumb程序相互调用
- 数据以及代码的位置无关的支持
- 子程序的可重入性
- 数据栈检查的支持
07年ARM推出了AAPCS (ARM Archtecture Procedure Call Standard),是ATPCS的改进版,目前, AAPCS和ATPCS都是可用的标准。
2 寄存器的使用规则
| 寄存器 | 别名 | 特殊名称 | 使用规则 |
|---|---|---|---|
| r15 | PC | 程序计数器 | |
| r14 | LR | 连接寄存器 | |
| r13 | SP | 数据栈指针 | |
| r12 | IP | 子程序间调用的暂存寄存器scratch register. | |
| r11 | v8 | FP | ARM状态局部变量寄存器8 / 栈帧指针 |
| r10 | v7 | SL | ARM状态局部变量寄存器7 在支持数据栈检查的ATPCS中为数据栈限制指针 |
| r9 | v6 | SB | ARM状态局部变量寄存器6 在支持RWPI的ATPCS中为静态基址寄存器 |
| r8 | v5 | ARM状态局部变量寄存器5 | |
| r7 | v4 | WR | 局部变量寄存器4 Thumb状态工作寄存器 |
| r6 | v3 | 局部变量寄存器3 | |
| r5 | v2 | 局部变量寄存器2 | |
| r4 | v1 | 局部变量寄存器1 | |
| r3 | a4 | 参数/结果/暂存寄存器scratch register 4 | |
| r2 | a3 | 参数/结果/暂存寄存器scratch register 3 | |
| r1 | a2 | 参数/结果/暂存寄存器scratch register 2 | |
| r0 | a1 | 参数/结果/暂存寄存器scratch register 1 |
- 子程序间通过寄存器R0~R3来传递
参数。这时,寄存器R0~R3可记作a0~a3。被调用的子程序在返回前无需恢复寄存器R0~R3的内容。 - 在子程序中,使用寄存器R4~R11来保存
局部变量。这时,寄存器R4~R11可以记作v1~v8。如果在子程序中使用了寄存器v1~v8中的某些寄存器,则子程序进入时必须保存这些寄存器的值,在返回前必须恢复这些寄存器的值。在Thumb程序中,通常只能使用寄存器R4~R7来保存局部变量(Cortex-M系列)。 - 寄存器R12用作过程调用中间临时寄存器,记作IP。在子程序之间的连接代码段中常常有这种使用规则。
- 寄存器R13用作堆栈指针,记作SP。在子程序中寄存器R13不能用作其他用途。寄存器SP在进入子程序时的值和退出子程序时的值必须相等。
- 寄存器R14称为连接寄存器,记作LR。它用于保存子程序的
返回地址。如果在子程序中保存了返回地址,寄存器R14则可以用作其他用途。 - 寄存器R15是程序计数器,记作PC。它不能用作其它用途。
AAPCS中的寄存器使用:
| 寄存器 | 别名 | 特殊名称 | 使用规则 |
|---|---|---|---|
| r15 | PC | The Program Counter. | |
| r14 | LR | The Link Register. | |
| r13 | SP | The Stack Pointer. | |
| r12 | IP | The Intra-Procedure-call scratch register. | |
| r11 | v8 | FP | Frame Pointer or Variable-register 8. |
| r10 | v7 | Variable-register 7. | |
| r9 | v6/SB/TR | Platform register. The meaning of this register is defined by the platform standard. | |
| r8 | v5 | Variable-register 5. | |
| r7 | v4 | Variable-register 4. | |
| r6 | v3 | Variable-register 3. | |
| r5 | v2 | Variable-register 2. | |
| r4 | v1 | Variable-register 1. | |
| r3 | a4 | Argument / scratch register 4. | |
| r2 | a3 | Argument / scratch register 3. | |
| r1 | a2 | Argument / result / scratch register 2. | |
| r0 | a1 | Argument / result / scratch register 1. |
3 数据栈的使用规则
3.1 数据栈分类
根据栈指针指向位置分:
FULL栈:栈指针指向栈顶元素,即最后一个入栈的元素EMPTY栈:栈指针指向与栈顶元素相邻的一个可用数据单元
根据栈的增长方向分:
DESCENDING栈:数据栈向内存地址减小的方向增长ASCENDING栈:数据栈向内存地址增加的方向增长
因此,可有以下4种数据帧:
- FD:FULL DESCENDING ( ATPCS规定 )
- ED:EMPTY DESCENDING
- FA: FULL ASCENDING
- EA:EMPTY ASCENDING
ATPCS规定数据栈为FD类型,即向下生长的满栈,并且对数据栈的操作是8字节对齐,
3.2 数据栈(FD类型)的相关名词
Stack Pointer:数据栈指针,最后一个入栈数据的内存地址Stack Base:数据栈基地址,指栈的最高地址,最早入栈数据占据的内存单元是基地址的下一个内存单元Stack Limit:数据栈界限,指数据栈可以使用的最低内存单元的地址Used Stack:已占用的数据帧,指数据栈的基地址和栈指针间的区域(包括栈指针对应的内存单元,但不包括数据栈的基地址对应的内存单元)Unused Stack:未占用的数据帧,指栈指针与数据栈界限间的区域(不包括栈指针对应的内存单元,但包括数据栈界限对应的内存单元)Stack Frames:数据栈,指数据栈中,为子程序(函数)分配的用来保存寄存器和局部变量的区别
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jC9s3oMK-1645380363442)(img/image-20220221001055480.png)]](/https://i-blog.csdnimg.cn/blog_migrate/fe81de5547b6000685602cf2bbfcbf34.png)
3.3 栈和栈帧
栈(stack)相对整个系统而言,调用栈(Call stack)相对某个进程而言,栈帧(stack frame)则是相对某个函数而言。
- 调用栈:正在使用的栈空间,由多个嵌套调用函数所使用的栈帧组成,存放某个程序的正在运行的函数的信息。
- 栈帧:每次调用一个函数,都要为该次调用的函数实例分配栈空间(保存寄存器信息、返回结果、局部变量、参数等),为单个函数分配的那部分栈空间就叫做 stack frame。

上图为ATPCS标准下ARM的栈帧布局方式,main stack frame为调用函数的栈帧,func1 stack frame为被调函数的栈帧,所有函数的stack frame串起来就组成了一个完整的栈。栈帧的两端由帧指针FP和栈指针SP限定,FP指向被调函数的栈帧起始地址(基地址);SP则是被调函数的栈指针,它指向当前栈顶位置,在函数执行过程中,栈指针SP会随着数据的入栈和出栈而移动。
函数调用时,依次压入当前函数的PC指针、返回指针LR、栈指针SP、帧指针FP、传入参数个数及指针、本地变量和临时变量。如果函数准备调用另一个函数,跳转之前临时变量区先要保存另一个函数的参数。
backtrace机制:在程序执行过程中(通常是发生了某种意外情况而需要进行调试),通过SP和FP所限定的stack frame,就可以得到调用函数的SP和FP,从而得到调用函数的stack frame(PC,LR,SP,FP会在函数调用的第一时间压栈),以此追溯,即可得到所有函数的调用顺序。
MDK debug backtrace:

4 参数传递规则
根据参数个数是否固定,可以将子程序分为参数个数固定的子程序和参数个数可变化的子程序,它们规则不同。
4.1 参数个数可变子程序参数传递规则
在传递参数时,将所有参数看作是存放在连续的内存字单元的字数据(4字节)。然后,依次将各字数据传递到寄存器R0~R3中。如果参数多于4个,则将剩余的字数据传递到数据栈中。入栈的顺序与参数传递顺序相反,即最后一个字数据先入栈。如:
int sum(int a, int b) // b先压入栈中,然后是a
{
return a + b;
}
基于上述规则,1个浮点数可以:
- 只通过寄存器传递
- 一半通过寄存器传递,一半通过数据栈传递
- 只通过数据栈传递
4.2 参数个数固定子程序参数传递规则
非浮点运算规则与参数个数可变规则一样,参数个数固定浮点数传递规则如下:
- 各浮点参数按顺序处理
- 为每个浮点数分配FP寄存器,分配方法:满足该浮点参数需要的且编号最小的一组连续的FP寄存器
- 第一个整数参数可通过
R0~R3传递,其他参数通过数据栈传递
注意:如果系统不包含浮点运算的硬件部件,浮点参数会通过相应的规则转换成整数参数。
5 子程序结果返回规则(AAPCS)
-
r0的最低有效 16 位中返回半精度浮点类型 -
小于 4 字节的基本数据类型以零或符号扩展为一个字并在
r0中返回 -
在
r0中返回一个字长的基本数据类型(int、float) -
在
r0和r1中返回双字大小的基本数据类型(double、long long) -
在
r0~r3中返回一个 128 位的容器化向量containerized vector -
在
r0中返回一个不大于4字节的复合类型Composite Type。这种格式就好像结果存储在内存中的字对齐地址,然后用LDR指令加载到r0中一样。r0中任何超出结果边界的位都有未指定的值 -
大于 4 字节的复合类型,或者其大小不能由调用者和被调用者静态确定,存储在
内存中的地址处,该地址在调用函数时作为额外参数传递。用于结果的内存可以在函数调用期间的任何时候修改。
参考:
END
本文详细介绍了ARM Cortex-M3/4处理器的寄存器规则,包括寄存器分配、数据栈的分类与操作、参数传递策略以及AAPCS与ATPCS的区别。重点讲解了栈帧结构、参数传递方式和子程序返回规则,适合理解嵌入式系统调用规范。
&spm=1001.2101.3001.5002&articleId=123039106&d=1&t=3&u=f0e9e09c41af4a0898f3fde88750aeed)
1742

被折叠的 条评论
为什么被折叠?



