为了使单独编译的c文件和汇编文件之间能够互相调用,需要制定一系列的规则,AAPCS就是ARM程序和Thumb程序中子程序调用的基本规则。
ATPCS规定了子程序调用过程中寄存器的使用规程、数据站的使用规则、参数的传递规则。为了适应一些特殊的需求,对这些规则进行改动可以得到几种不同的子程序调用规则,具体包括:
支持数据栈限制检查的ATPCS
支持只读段位置无关(ROPI)的ATPCS
支持可读写段位置无关(RWPI)的ATPCS
支持ARM程序和Thumb程序混合使用的ATPCS
处理浮点运算的ATPCS
相关调用程序必须遵守同一种ATPCS,编译器/汇编器在ELF格式的目标文件中设置相应的属性,标识用户选定 ATPSC类型。不同类型的ATPCS规则,有对应的C语言库,连接器根据用户的指定ATPCS类型链接相应的c库。
C语言的编译器编译的C子程序能够满足用户指定的ATPCS类型。而对于汇编语言,需要用户来满足子程序间的ATPCS类型。汇编子程序必须满足如下三个条件:
子程序编写时必须遵守相应的ATPCS规则
数据栈的使用要遵守相应的ATPCS规则
汇编器中使用-apcs选项
下面是keil中,C语言配置的选项。
下面是汇编器的配置。
基本的ATPCS规则包括下面三个方面内容:
各寄存器的使用规则及其相应的名称
数据栈的使用规则
参数传递的规则
满足基本类型的ATPCS程序运行速度更快,占用内存更少,但不支持以下功能:
ARM程序和Thumb程序的互相调用(注:在Cortex-M3中使用的是Thumb2指令,已经不区分ARM、Thumb指令了,《Cortex-M3权威手册》对此有论述。因此,该条存疑,本文参考的资料也较老,新版可能会更改,待验证。。。。)
数据以及代码的位置无关的支持
子程序的可重入性
数据栈检查的支持
派生的几种ATPCS规则是在基本的ATPCS基础上添加额外规则形成的,目的就是提供上述功能。
寄存器的使用必须满足如下规则:
子程序间通过R0--R3传递参数,被调用的子程序在返回前无需恢复寄存器R0--R3的内容。
在子程序中,使用R4--R11保存局部变量,如果在子程序中使用到了寄存器R4--R11中的某些寄存器,子程序进入前需要保存这些寄存器的值,在返回时需要恢复。未用到,不处理。在Thumb程序中,通常只能使用寄存器R4--R7来保存局部变量。
寄存器R12用作子程序间scratch寄存器,记作ip。在子程序间的连接代码段中常用这种使用规则。
寄存器R13用作数据栈指针,记作sp。在子程序中sp不能用作其它用途,进入子程序时的sp值和退出子程序时的sp值必须相等。
R14称为连接寄存器,记作lr。用于保存子程序的返回地址,如果在子程序中保存了返回地址,R14可以用作其它的用途。
R15是程序计数器,记作pc,不能用于其它的用途。
ATPCS中各寄存器的使用规则及其名称
寄存器 | 别名 | 特殊名称 | 使用规则 |
R15 | Pc | 程序计数器 | |
R14 | Lr | 连接寄存器 | |
R13 | Sp | 数据栈指针 | |
R12 | Ip | 子程序内部调用的scratch寄存器 | |
R11 | V8 | 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 寄存器4 | |
R2 | A3 | 参数/结果/scratch 寄存器3 | |
R1 | A2 | 参数/结果/scratch 寄存器2 | |
R0 | A1 | 参数/结果/scratch 寄存器1 |
栈指针通常可以指向不同的位置。当栈指针指向栈顶元素(即最后一个入栈的数据元素)时,称为FULL栈;当栈指针指向与栈顶指针(即最后一个入栈的数据元素)相邻的一个可用数据单元时,称为EMPTY栈。
栈的增长方式也可以不同,向内存地址减小的方向增长时,称为DESCENDING栈,向地址增长的方向增长时,称为ASCENDING栈。故,有四种栈:
ATPCS规定数据栈为FD栈,并且数据栈的操作时8 Byte对齐。下面时数据栈的示例和名词解释。
异常中断的处理程序可以使用被中断程序的数据栈,这时用户要保证中断的程序的数据栈足够大。
数据栈的示意图
在ARMv5RE(很老的版本了,cortex-m3式ARMv7版本)中,批量传输指令LDRD/STRD要求的数据栈是8 Byte对齐,以提高数据的传输速度。用ADS编译器产生的目标文件中,外部接口的数据栈都是8 Byte对齐的,并且编译器告诉连接器:本目标文件的数据栈是8字节对齐的,对于汇编程序来说,如果目标文件中包含了外部调用,则必须满足下列条件:
根据参数个数是否固定可以将子程序分为参数个数固定的子程序和参数个数可变的子程序,二者的参数传递规则不同。
当参数不超过4个时,可以使用寄存器R0--R3来传递参数,超过4个,还可以使用数据栈来传递参数。
参数传递时,将所有参数看作时存放在连续的内存字单元中的字数据,然后,依次将各字数据传送到寄存器R0--R3中,如果大于4个,将剩余的字数据传送到数据栈中,入栈顺序与参数顺序相反。
按照上面的规则,一个浮点数参数可以通过寄存器传递,也可以通过数据栈传递,也可能一半通过寄存器传递,另一半通过数据栈传递。
如果系统包含了浮点运算的硬件部件,浮点参数将按照下面的规则传递:
第一个整数参数,通过R0--R3传输,其余的参数按照数据栈传输。
子程序中结果返回的规则如下:
几种特定的ATPCS是在基本的ATPCS基础上增加一些规则形成的:
支持数据栈限制检查的ATPCS
支持只读段位置无关(ROPI)的ATPCS
支持可读写段位置无关(RWPI)的ATPCS
支持ARM程序和Thumb程序混合使用的ATPCS
处理浮点运算的ATPCS
如果在程序设计期间能够准确计算程序用到的内存大小,就不需要进行数据栈检查,但这在一般情况下很难做到,这时就需要进行数据栈的检查。
在进行数据栈的检查时,使用R10(又记作sl)作为数据栈限制指针。用户在程序中不能使用该寄存器。支持数据栈限制检查的ATPCS要满足下面的规则:
与支持数据栈限制检查的ATPCS相关的编译/汇编选项有下面几种:
对于C或者C++来说,如果在编译时指定swst选项,生成的目标代码将遵守支持数据栈限制检查的ATPCS。
对于汇编程序来说,如果要遵守支持数据栈限制检查的ATPCS,用户在编写程序时,必须满足支持数据栈限制检查的ATPCS所要求的规则。然后在汇编时指定选项swst。
位置无关的只读段可能为位置无关的代码段,也可能是只读数据段。使用 支持只读段位置无关的ATPCS可以避免必须将程序存放到特定的位置。常用于:
如果程序遵守支持只读段位置无关的ATPCS,需要满足如下规则:
如果一个程序中所有的可读写段都是位置无关,则称该程序遵守支持可读写段位置无关(RWPI)的ATPCS。使用支持可读写段位置无关的ATPCS可以避免必须将程序放到特定的位置。这时,R9通常用作静态基址寄存器,记作sb。可重入的子程序可以再内存中同时有多个实例,各个实例拥有独立的可读写段。在生成一个新的实例时,sb指向该实例的可读写段。RWPI段中的符号的计算方法为:连接器首先计算出该符号相对于RWPI段中某一特定位置的偏移量,通常该特定位置选为RWPI段的第一个字节处:在程序运行时,将该偏移量加上sb即可生成该符号的地址。
据《Cortex-M3权威手册》中所述,Cortex-M3已经不再区分ARM指令和Thumb指令,M3中使用的时Thumb2指令集,Thumb2指令集兼容32位和16位指令,因此,该部分在目前已经用不到,就不再叙述。
ATPCS支持VFP体系和FPA体系两种不同的浮点硬件体系和指令集,两种体系对应的代码不兼容。
相应的,ADS的编译器和汇编器有下面6种与浮点数相关的选项。
当系统种包含浮点运算部件时,可以选择上述选项。在M3内核种不包含浮点数运算单元,但M4内核中包含一个可选的浮点数运算单元(FPU),FPU符合IEEE 754标准(未实现完整的内容)。FPU为单精度浮点单元,浮点数据和计算基于IEEE Std 754-2008,IEEE二进制浮点运算标准,是VFP体系。
本篇文章参考了《ARM体系结构与编程》(杜春雷 著)和《Cortex-M3权威指南》,属于学习笔记性质。参考资料链接:https://gitee.com/zichuanning520/htq_library