[ARM-Cortex-M3/4] ATPCS子程序调用规则(部分参考AAPCS最新调用规则
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:数据栈,指数据栈中,为子程序(函数)分配的用来保存寄存器和局部变量的区别
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
2
3
4
基于上述规则,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 字节的复合类型,或者其大小不能由调用者和被调用者静态确定,存储在内存中的地址处,该地址在调用函数时作为额外参数传递。用于结果的内存可以在函数调用期间的任何时候修改。
参考:
AAPCS
ARM的FP寄存器
也谈栈和栈帧
END
————————————————
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/kouxi1/article/details/123039106
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· winform 绘制太阳,地球,月球 运作规律
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)