ARM 编程:C语言与汇编间互相调用,参数与返回值的传递方式详解 您所在的位置:网站首页 C语言程序如何使用寄存器 ARM 编程:C语言与汇编间互相调用,参数与返回值的传递方式详解

ARM 编程:C语言与汇编间互相调用,参数与返回值的传递方式详解

2024-06-18 02:55| 来源: 网络整理| 查看: 265

汇编基础: linux AT&T格式汇编简单框架 函数调用时的规则如下: 父函数与子函数间的入口参数依次通过R0R3这4个寄存器传递。父函数在调用子函数前先将参数存入到R0R3中,若只有一个参数则使用R0传递,2个则使用R0和R1传递,依次类推,当超过4个参数时,其它参数通过栈传递。当子函数运行时,根据自身参数个数自动从R0~R3或者栈中读取参数。子函数通过R0寄存器将返回值传递给父函数。子函数返回时,将返回值存入R0,当返回到父函数时,父函数读取R0获得返回值。发生函数调用时,R0R3是传递参数的寄存器,即使是父函数没有参数需要传递,子函数也可以任意更改R0R3寄存器,无需考虑会破坏它们在父函数中保存的数值,返回父函数前无需恢复其值。AAPCS规定,发生函数调用前,由父函数将R0R3中有用的数据压栈,然后才能调用子函数,以防止父函数R0R3中的有用数据被子函数破坏。R4R11为普通的通用寄存器,若子函数需要使用这些寄存器,则需要将这些寄存器先压栈然后再使用,以免破坏了这些寄存器中保存的父函数的数值,子函数返回父函数前需要先出栈恢复其数值,然后再返回父函数。AAPCS规定,发生函数调用时,父函数无需对这些寄存器进行压栈处理,若子函数需要使用这些寄存器,则由子函数负责压栈,以防止父函数R4R11中的数据被破坏。编译器在编译时就确定了函数间的调用关系,它会使函数间的调用遵守3、4条规定。但编译器无法预知中断函数的调用,被中断的函数无法提前对R0R3进行压栈处理,因此需要在中断函数里对它所使用的R0R11压栈。对于中断函数,不遵守第3条规定,遵守第5条规定。R12寄存器在某些版本的编译器下另有它用,用户程序不能使用,因此我们在编写汇编函数时也必须对它进行压栈处理,确保它的数值不能被破坏。R13寄存器是堆栈寄存器(SP),用来保存堆栈的当前指针。R14寄存器是链接寄存器(LR),用来保存函数的返回地址。R15寄存器是程序寄存器(PC),指向程序当前的地址。 上述只介绍了本手册中使用到的情形,具体的情况在编写操作系统代码时会涉及到,其它规则请请读者自行查找资料。

接下来我们再通过几个小例子熟悉一下C函数与汇编函数的调用过程。下面的C函数TestFunc1与汇编函数TestFunc2的功能是一样的。

一:情况1,C代码与汇编代码各自的入参与返回值处理分析: unsigned int TestFunc1(void) { unsigned int arg_ret = 100; return arg_ret; } .func TestFunc2 TestFunc2: STMDB R13!, {R5 - R6, R10} @R5,R6,R10寄存器压栈 LDR R1, =1 LDR R3, =2 LDR R4, =3 LDR R5, =4 LDR R6, =5 LDR R10, =6 ADD R0, R1, R3 ADD R0, R0, R4 ADD R0, R0, R5 ADD R0, R0, R6 ADD R0, R0, R10 LDMIA R13!, {R5 - R6, R10} @R5,R6,R10寄存器出栈 .endfunc

TestFunc2函数使用了R0、R1、R3、R4、R5、R6、R10共7个寄存器,遵循AAPCS规则,在使用R0、R1和R3之前并没有对它们压栈,但对R5、R6和R10寄存器进行了压栈保存,在函数返回前又出栈还原了这3个寄存器,这样TestFunc2函数返回到它的父函数之后,R5、R6和R10寄存器的数值是没有改变的,而R0、R1和R3则分别被改写为了1、2和3。

二:情况2,C代码调用汇编代码的入参与返回值处理分析: unsigned int TestFunc3(void) { return TestFunc4(1, 2); } .func TestFunc4 TestFunc4: ADD R0, R0, R1 BX R14; .endfunc

TestFunc3函数在调用TestFunc4函数前已经将参数1和2分别存入R0和R1,并将返回地址存入到R14中,然后才跳转到TestFunc4函数,发生函数调用。这时程序将运行TestFunc4函数,它将R0和R1相加,将结果放入R0,需要通过R0将返回值返回给TestFunc3函数。此时R14中保存的就是返回TestFunc3函数的返回地址,最后TestFunc4函数跳转到R14就返回到了TestFunc3函数,TestFunc3函数从R0就可以取出TestFunc4函数计算的结果了。

下面我们再来看看汇编函数TestFunc5调用C函数TestFunc6完成1+2的运算。

三:情况3,汇编调用C代码代码的入参与返回值处理分析: .func TestFunc5 TestFunc5: MOV R0, #1 MOV R1, #2 SUB R13, R13, #4 STR R14, [R13] BL TestFunc6 LDR R14, [R13] ADD R13, R13, #4 BX R14 .endfunc unsigned int TestFunc6(U8 ucPara1, U8 ucpara2) { return ucPara1 + ucPara2; }

TestFunc5函数先将参数1和2存入R0和R1寄存器,准备调用TestFunc6函数并传递入口参数,然后将R14寄存器压栈,以防止使用BL指令时存入的R14返回地址破坏R14原有的数据,然后调用TestFunc6函数。在调用TestFunc6函数时BL指令会自动将“LDR R14, [R13]”这条指令的地址存入R14,这样就开始运行TestFunc6函数了。TestFunc6函数会自动从R0和R1寄存器中取出参数,将计算结果存入R0,通过R0将返回值返回给TestFunc5函数。TestFunc6函数跳转回TestFunc5函数后,TestFunc5函数从栈中恢复原有的R14寄存器,完成函数调用,此时R0中的数值就是TestFunc6函数的计算结果。



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有