ARM-Thumb子程序调用规则—ATPCS
能使C语言程序和汇编程序之间能够互相调用,必须为子程序间的调用制定规则。在ARM处理器中,这个规则被称为ATPCS:ARM程序和Thumb程序中子程序调用的规则。
基本的ATPCS规则包括寄存器使用规则、数据栈使用规则、参数传递规则
1.寄存器使用规则
ARM处理器中有r0-r15共16个寄存器,它们的用途是有一些约定的习惯的,并依据这些用途定义了别名。
1)子程序间通过寄存器r0-r3来传递参数,这时可以使用它们的别名a1-a4,被调用的子程序返回前无需回复r0-r3的内容。
2)在子程序中,使用r4-r11来保存局部变量,这时可以使用它们的别名v1-v8,如果在子程序中使用了它们的某些寄存器,子程序进入时要保存这些寄存器的值,返回时再次恢复它们;
对于子程序中没有使用到的寄存器,则不必进行这些操作,在Thumb指令中,通常只能使用寄存器r4-r7来保存局部变量。
3)寄存器r12用作子程序间scratch寄存器,别名为ip。
4)寄存器r13用作数据栈指针,别名为sp,在子程序中寄存器r13不能用作它用,它的值在进入、退出子程序时必须相等。
5)寄存器r14称为连接寄存器,别名为lr,它用于保存子程序的返回地址。
如果在子程序中保存了返回地址(比如将lr值保存到数据栈中),r14可用作它用。
6)寄存器r15是程序计数器,别名pc
2.数据栈使用规则
数据栈有两个增长方向;向内存地址减小的方向增长时,称为DESCENDING栈;向内存地址增加的方向增长时,称为ASCENDING栈。
所谓数据栈的增长就是移动栈指针。当栈指针指向栈顶元素(最后一个入栈的数据)时,称为FULL栈;当栈指针指向栈顶元素相邻的一个空的数据单元时,称为EMPTY栈。
综合这两个特点,数据栈可以分为以下4种:
1)FD: Full Descending,满递减。
2)ED:Empty Descending,空递减。
3)FA:Full Ascending,满递增。
4)EA:Empty Ascending,满递增。
ATPCS规定数据栈为FD类型,并且对数据栈的操作是8字节对齐的。使用stmdb/ldmia批量内存访问指令来操作FD数据栈。
使用stmdb命令往数据栈中保存内容时,先递减sp指针,再保存数据,
使用ldmia命令从数据栈中恢复命令时,先获得数据,再递增sp指针,sp指针总是指向栈顶元素,这刚好是FD栈的定义。
stmdb和ldmia指令一般配对使用,stmdb用于将寄存器压栈,ldmia用于将寄存器弹出栈,作用是保存使用到的寄存器
https://blog.csdn.net/minsophia/article/details/53080183
3.参数传递规则
一般来说,当参数个数不超过4个时,使用r0-r3这4个寄存器来传递参数;如果参数个数超过4个,剩余的参数通过数据栈来传递。
同样,对于一般的返回结果,通常使用a0-a3来传递。
例程:
假设CopyCode2SDRAM函数使用C语言实现的,数据原型为:
int CopyCode2SDRAM(unsigned char *buf,unsigned long StartAddr,int size)
在汇编代码中,使用下面的代码调用,同时判断返回值:
1 ldr r0,=0x30000000 @ 1、目标地址=0x30000000,这是SDRAM的起始地址 2 3 mov r1,#0 @ 2、源地址=0 4 5 mov r2,#16*1024 @ 3、复制长度=16K 6 7 bl CopyCode2SDRAM @ 4、调用C函数CopyCode2SDRAM 8 9 cmp a0,#0 @ 5、进行判断
参考链接: https://blog.csdn.net/wangpengqi/article/details/8443462
https://blog.csdn.net/chun_1959/article/details/46604979
LDR指令格式:
LDR{条件} 目的寄存器 <存储器地址>
作用:将 存储器地址 所指地址处连续的4个字节(1个字)的数据传送到目的寄存器中。
LDR指令的寻址方式比较灵活,实例如下:
LDR R0,[R1] ;将存储器地址为R1的字数据读入寄存器R0
LDR R0,[R1,R2] ;将存储器地址为R1+R2的字数据读入寄存器R0。
LDR R0,[R1,#8] ;将存储器地址为R1+8的字数据读入寄存器R0。
LDR R0,[R1],R2 ;将存储器地址为R1的字数据读入寄存器R0,然后R1=R1+8。
LDR R0,[R1],#8 ;将存储器地址为R1的字数据读入寄存器R0,并将R1+8的值存入R1。
LDR R0,[R1,R2]! ;将存储器地址为R1+R2的字数据读入寄存器R0,并将R1+R2的值存入R1。
LDR R0,[R1,LSL #3] ;将存储器地址为R18的字数据读入寄存器R0。
LDR R0,[R1,R2,LSL #2] ;将存储器地址为R1+R24的字数据读入寄存器R0。
LDR R0,[R1,,R2,LSL #2]! ;将存储器地址为R1+R24的字数据读入寄存器R0,并将R1+R24的值存入R1。
LDR R0,[R1],R2,LSL #2 ;将存储器地址为R1的字数据读入寄存器R0,并将R1+R2*4的值存入R1。
LDR R0,Label ;Label为程序标号,Label必须是当前指令的-4~4KB范围内。
要注意的是
LDR Rd,[Rn],#0x04 ;这里Rd不允许是R15。
另外LDRB 的指令格式与LDR相似,只不过它是将存储器地址中的8位(1个字节)读到目的寄存器中。
LDRH的指令格式也与LDR相似,它是将内存中的16位(半字)读到目的寄存器中。
LDR R0,=0xff
这里的LDR不是arm指令,而是伪指令。这个时候与MOVE很相似,只不过MOV指令后的立即数是有限制的。这个立即数必须是0X00-0XFF范围内的数经过偶数次右移得到的数,所以MOV用起来比较麻烦,因为有些数不那么容易看出来是否合法。
LDR R,label 和 LDR R,=label的区别
LDR 是ARM中的指令,也是伪指令。
当用 LDR r, =imd // r 为寄存器, imd为立即数
LDR 是一条伪指令。编译器会根据 立即数的大小,决定用 ldr 指令或者是mov或mvn指令。
当imd能用mov或者mvn操作时,就将它翻译成一条mov或mvn指令。当imd大于mov或mvn能够操作的数时,编译器会将imd存在一个内存单元中,然后再用一条ldr指令加载这个内存单元的的值到寄存器中。
LDR r, label 和 LDR r, =label的区别:
LDR r, =label 会把label表示的值加载到寄存器中,而LDR r, label会把label当做地址,把label指向的地址中的值加载到寄存器中。
譬如 label的值是 0x8000, LDR r, =label会将 0x8000加载到寄存器中,而LDR r, label则会将内存0x8000处的值加载到寄存器中。