一、实验名称:ARM主程序调用ARM/C语言子程序
二、实验目的:
- 了解ARM应用程序框架。
- 了解ARM汇编程序函数和C语言程序函数相互调用时,遵循的ATPCS标准;
- 了解和掌握ARM汇编程序调用C语言程序函数的基本方法;
- 了解和掌握ARM汇编程序调用C语言程序函数的参数传递过程。
三、实验原理:
- ARM工程
由于C语言便于理解,有大量的支持库,所以它是当前ARM程序设计所使用的主要编程语言。
对硬件系统的初始化、CPU状态设定、中断使能、主频设定 以及RAM控制参数初始化等C程序力所不能及的底层操作,还是要由汇编语言程序 来完成。
ARM工程 的各种源文件之间的关系,以及最后形成可执行文件的过程如下图1所示:
图1 汇编语言和C语言混合编译链接示意图
在应用系统的程序设计中,若所有的编程任务均用汇编语言 来完成,其工作量是可想而知的,这样做也不利于系统升级或应用软件移植。
通常汇编语言部分完成系统硬件的初始化;高级语言部分完成用户的应用。
执行时,首先执行初始化部分,然后再跳转到C/C++部分。整个程序结构显得清晰明了,容易理解。为方便工程开发,ARM公司的开发环境ARM ADS为用户提供了一个可以选用的应用程序框架。该框架把为用户程序做准备工作的程序分成了: 启动代码 和 应用程序初始化 两部分。
用于硬件初始化的汇编语言部分叫做 启动代码;用于应用程序初始化的C部分叫做初始化部分。整个程序如下图2所示:
图2 ARM应用程序框架
- 过程调用标准ATPCS
在ARM工程中,C程序调用汇编函数和汇编程序调用C函数是经常发生的事情。为此人们制定了ARM-Thumb过程调用标准ATPCS(ARM-Thumb Procedure Call Standard)。
- ATPCS规定,ARM的数据堆栈为FD型堆栈,即递减满堆栈。
- ATPCS标准规定,对于参数个数不多于4的函数,编译器必须按参数在列表中的顺序,自左向右为它们分配寄存器R0~R3。其中函数返回时,R0还被用来存放函数的返回值。
- 如果函数的参数多于4个,那么多余的参数则按自右向左的顺序压入数据堆栈,即参数入栈顺序与参数顺序相反。
- 根据ATPCS的C语言程序调用汇编函数,参数由左向右依次传递给寄存器R0~R3的规则。
- 子程序的调用与返回
人们把可以多次反复调用的、能完成指定功能的程序段称为“子程序”。把调用子程序的程序称为“主程序”。
为进行识别,子程序的 第1条指令 之前必须赋予一个 标号,以便其他程序可以用这个标号调用子程序。
在 ARM 汇编语言程序中,主程序一般通过 BL 指令来调用子程序。该指令在执行时完成如下操作:将子程序的返回地址存放在连接寄存器 LR 中,同时将程序计数器 PC 指向子程序的入口点。
为使子程序执行完毕能 返回 主程序的调用处,子程序末尾 处应有 MOV、LDMFD 等指令,并在指令中将返回地址重新复制到 PC 中。
在调用子程序的同时,也可以使用 R0~R3 来进行 参数的传递 和从子程序返回 运算结果。
四、实验内容:
- ARM指令主程序调用ARM指令子程序;
- ARM指令主程序调用C语言子程序,输入的6个参数为1、2、3、4、5、6;
- 子程序的参数个数要求至少6个, C语言子程序实现的功能为:(i1+i2+i3+i4)*i5-i6。
- 分析通过反汇编得到的C程序的ARM指令代码段,了解参数传递过程。
五、实验器材(设备、元器件):
- PC机一台;
- Keil MDK-ARM uVision4开发工具。
六、实验步骤:
- 打开Keil MDK-ARM uVision4开发工具;
- 新建一个工程文件;
- 在新建的工程文件中,添加新的源程序文件
- 编写代码
- 选择“Build target”菜单对编写好的工程文件进行编译链接。
- 点击““Start/Stop Debug Section””按键,对程序进行跟踪调试,在调试界面,单步执行,对CPU各寄存器的值的变化、以及相关内存的变化进行分析比较,判断程序的执行是否符合预期要求。
七、实验结果与分析(含重要数据结果分析或核心代码流程分析)
- ARM指令主程序调用ARM指令子程序;程序代码
(1)程序代码如代码1所示:
AREA lab3,CODE,READONLY
ENTRY
MOV R0,#0x01
MOV R1,#0x02
BL arm_func ;调用ARM子程序
B final
arm_func ;ARM子程序用来实现将R0和R1的值相加保存在R6中
ADD R6,R0,R1
BX LR
final
END
(2)运行过程及结果界面截图
图1 ARM指令主程序调用ARM指令子程序运行结果截图
(3)实验结果分析
执行程序后,首先将R0,R1分别初始化为0x01、0x02,之后调用ARM子程序arm_func,将R0跟R1的内容相加送到R6,此时可以看到R6的内容为0x03结果正确。
(4)实验结论
实验结果与期望结果一致,ARM子程序执行正确。
2.ARM指令主程序调用C语言子程序;
- 程序代码
ARM指令主程序如代码2所示
代码2 ARM指令主程序调用C语言子程序的RM指令主程序
PRESERVE8
IMPORT c_func ;声明c_func为外部引用符号
AREA lab3,CODE,READONLY
ENTRY
LDR SP,=0x40000100 ;初始化堆栈指针SP
MOV R0,#0x01
MOV R1,#0x02
MOV R2,#0x03
MOV R3,#0x04
MOV R4,#0x05
MOV R5,#0x06
STMFD SP!,{R4,R5} ;多余两个参数压栈
BL c_func ;调用c程序
MOV R0,R0
END
C语言子程序代码如代码3所示
代码3 RM指令主程序调用C语言子程序的C语言子程序
#include<stdio.h>
int c_func(int i1,int i2,int i3,int i4,int i5,int i6){
return (i1 + i2 + i3 + i4) * i5 - i6;
}
- 运行过程及结果界面截图
图2是程序即将调用C子程序的截图,将R0-R5分别初始化为0x01、0x02、0x03、0x04、0x05、0x06,由于参数个多余四个,因此将多余的两个参数压栈。
图2 程序即将调用C子程序的截图
图3是程序跟踪进入C语言子程序的截图,可以看到函数已经接收到了六个参数值。
图3 程序跟踪进入C语言子程序的截图
图4是调用C程序返回后的截图,函数返回值0x2C保存到R0中。
图4 程序跟踪进入C语言子程序的截图
- 实验结论
函数的接收到的六个参数分别为0x01、0x02、0x03、0x04、0x05、0x06,经(i1+i2+i3+i4)*i5-i6运算后结果为0x2C并返回,从R0的值为0x2C可以看出,ARM程序成功接收到C语言函数返回值。
七、总结及心得体会:
(1)心得体会:通过本次实验我进一步了解了ARM应用程序框架,掌握了在ARM汇编程序函数和C语言程序函数相互调用时需要遵循的ATPCS标准;同时我也了解和掌握ARM汇编程序调用C语言程序函数的基本方法以及ARM汇编程序调用C语言程序函数的参数传递过程。
(2)思考题:
1)在ARM汇编程序中,参数多余4个(比如为6个)时,多余的参数会按照从右向左的顺序压栈。
2)调用C语言函数前SP寄存器的值以及C函数返回后SP寄存器的值没有发生变化,进入C语言函数时,会先进行现场保护将会用到的寄存器压栈保存,此时SP的值变小,C语言函数执行完之后,会进行恢复现场的出栈操作,SP值变大,恢复到调用C语言函数前的值。
3)如果参数多余4个的话,前四个参数通过R0-R3寄存器传递,剩下的参数通过LDR指令从栈中依次取出;在C语言函数入口使用STMDB指令将函数中所用到的其他寄存器压栈保存依次来保护现场,之后C语言函数出口,使用LDMIA指令进行出栈进行恢复现场。