一、在C函数中调用汇编函数
1.1 调用不带参数的汇编函数
关于建立MDK工程的具体操作,可参考我的另一篇博文: 基于MDK创建纯汇编语言的STM32工程
- 新建一个main.c文件
#include <stdio.h>
extern void Init_1(void);
int main()
{
Init_1();
return 0;
}
- 新建一个func.s文件
AREA My_Function,CODE,READONLY
EXPORT Init_1
Init_1
MOV R1,#0
MOV R2,#0
LOOP
CMP R1,#10
BHS LOOP_END
ADD R2,#1
ADD R1,#1
B LOOP
LOOP_END
NOP
END
关于上述汇编代码的解释:
EXPORT :将c文件中定义的函数相关联;Init_1 、LOOP 、LOOP_END :程序段名,是跳转程序的参照;MOV R1,#0 、MOV R2,#0 :将R1,R2寄存器的初值设置为0;CMP R1,#10 、BHS LOOP_END :将R1的值与10相比较,若R1大于10,就调到LOOP_END ;ADD R2,#1 、ADD R1,#1 :R1,R2寄存器加1;B LOOP :再次进入循环的标志;END :程序结束。
关于汇编代码格式的注意事项:
- 开头部分:
AREA My_Function ,CODE,READONLY 这个指令一定不要顶格写! - 中间部分:程序名必须顶格写;
- 末尾部分:必须空格后再写END,不然会被认为是段名,表示程序结束;
- 仿真调试
使用单步调试,可以看到R1从0变到9,再变为A(10在16进制下为A)时 ,跳出汇编程序,具体过程如下所示: 初始状态: R1变为0: R1变为1: R1变为2: 一直执行,到9: 最后到A: 跳出循环:
1.2调用带形参的汇编函数
- 修改代码
将原汇编语言 Init_1函数的类型改为 int Init_1(init) ,此函数功能修改为 传入一个整型数x,函数运行后返回整型数 x+100;
#include <stdio.h>
extern int Init_1(int a);
int main()
{
Init_1(5);
return 0;
}
Init_1
ADD R0,R0,
BX LR ;跳转的LR的地址执行,在这里,LR记录main函数调用子函数的返回地址
之所以让R0加100,是因为在arm编程里,函数调用过程中,子函数的参数值传递按顺序存放在R0、R1、R2、R3里,超过4个参数值传递放栈帧里,所以我们给定的参数值默认是放在R0中的,要想实现x+100,就要对R0寄存器加100。
- 进入调试:
可以看到R0初始值为十六进制下的78,进入单步运行后,R0变为5: 再次点击进入汇编文件,R0加了100,变为16进制下的105为69
二、在汇编函数中调用c函数
在c文件中写入函数Init_1,将主函数放入汇编文件当中,可以发现,将以前的导入函数EXPORT改为了导入INPORT
2.1 代码文件
- main.c
#include<stdio.h>
extern int Init_1();
int Init_1()
{
int x=5;
return x+100;
}
- func.s
IMPORT Init_1
AREA MYCODE, CODE
EXPORT __main
__main
BL Init_1
END
注意:ARM汇编指令不支持顶格写,否则不能识别;声明变量时不要有空格,不然会出现奇奇怪怪的错误。
2.2 仿真调试
调试方法依旧如上,编译无误后进入仿真调试 初始状态:
按F5跳入主函数的断点处
再经过单步调试后,可以看到寄存器R0变为16进制的105,即69 ,证明试验无误。
三、汇编函数与c函数混合调用
3.1 代码文件
extern int SUM_ASM(void);
int sum(int a,int b)
{
int c;
a=100;
b=200;
c=a+b;
return c;
}
int main(void)
{
SUM_ASM();
return 0;
}
AREA My_Function ,CODE,READONLY
IMPORT sum
EXPORT SUM_ASM
ENTRY
SUM_ASM
LDR R0,=0X3
LDR R1,=0X4
BL sum
MOV PC,LR
END
3.2 仿真调试
初始状态: 运行结果:
四、总结C语言与汇编语言混合编程的规则
4.1寄存器的使用规则
- R1~R3通常用来传递函数参数,当参数个数多于4个时,使用堆栈来传递参数,此时R0-R3可记作A1-A4;
- R4~R11用来保存程序运算的中间结果或函数的局部变量,因此当进行子程序调用时要注意对这些寄存器的保存和恢复,此时R4-R11可记作V1-V8;
- R12通常用来作为函数调用过程中的临时寄存器,用于保存堆栈指针SP,当子程序返回时使用该寄存器出栈,记作IP;
- R13寄存器是堆栈寄存器(SP),用来保存堆栈的当前指针
- R14寄存器是链接寄存器(LR),用来保存函数的返回地址
- R15寄存器是程序寄存器(PC),指向程序当前的地址
4.2 堆栈的使用规则
ATPCS规定堆栈采用满递减类型(FD,Full Descending),即堆栈通过减小存储器地址而向下增长,堆栈指针指向内含有效数据项的最低地址。
4.3 参数的传递规则
- C语言在ARM中函数调用时,如果形参个数少于或等于4,则形参由R0,R1,R2,R3四个寄存器进行传递;若形参个数大于4,大于4的部分必须通过堆栈进行传递,且压入堆栈的顺序是与函数中形参的顺序相反。
- 对于X86平台,32位程序使用栈传递,而64位程序则根据参数的个数而不同。当参数少于6,使用寄存器传递参数;当参数大于6,多出来的参数使用栈传递。
- 子程序的返回结果为一个32位整数时,通过R0返回;返回结果为一个64位整数时,通过R0和R1返回;依此类推。结果为浮点数时,通过浮点运算部件的寄存器F0、D0或者S0返回。
五、总结
无论是在汇编程序中调用c程序,还是c程序中调用汇编程序,或者是二者相互嵌套,都涉及到子程序的调用、参数的传递、子程序的返回等问题,以及寄存器、堆栈的存储规则。在了解这些具体调用规则后,再通过设计程序进行仿真试验,更能理解每一步的执行过程,掌握其运行原理。
|