阳江核电站多少人:【嵌入式学习】Arm-elf-gcc编译器函数调用规则

来源:百度文库 编辑:九乡新闻网 时间:2024/04/29 22:09:50

【嵌入式学习】Arm-elf-gcc编译器函数调用规则

嵌入式 2009-05-05 20:00:31 阅读451 评论0   字号: 订阅

今天深入研究了arm-elf-gcc编译器的C函数调用规则。说调用规则无非说的是如下几个问题:

  • 哪些寄存器是默认压栈的,以及它们的压栈顺序是怎样的
  • 函数调用涉及的三种数据:参数是怎样传递给被调用者的;怎样访问局部变量;最后的返回值又是怎样传递给调用者的
  • 所涉及的栈的操作是由调用者还是被调用者负责的

为了观察arm-elf-gcc编译器的调用规则,我设计了一个典型C程序:

int caller()

{

callee(1,2,3,4,5);

}

int callee(int a,int b,int c,int d,int e)

{

int f = 2;

a = f + e;

return a;

}

然后用arm-elf-gcc -S test.c汇编后,得到test.s文件,然后打开查看汇编码:

下面就可以从那三个方面来观察调用规则了:

  • 可以看到,不管是caller还是callee,开始时都要压栈的寄存器是FP,IP,LR及PC,压栈后的栈空间如下图:很明显,FP随后将作为基址寄存器来访问局部变量及参数,而IP对应的位置为进入此函数之前的栈,其内容归属caller,后面再说。还有点要注意,在x86体系中,返回地址CS:IP也是要入栈的(由Call指令负责),但是在arm体系中不需要,因为BL指令是将返回地址保存到了LR寄存器中的。

  • 现在来看三种数据的空间开辟和访问:在callee中看到,有sub sp,sp,#20语句,它就是为参数和局部变量而在栈中开辟空间的,参数有a,b,c,d,e局部变量有f,根据下面的赋值语句可以知道此时栈变为如下左图状态,比较奇怪的是e哪里去了呢?通过对e的访问语句ldr r2,[fp,#4]可以发现,原来e被保存在了IP位置,这是符合ATPCS的,即前4个参数由R0-R3传递,而第4个之后的参数将由栈来传递,这通过str ro,[fp,#-16]保存a也可以看出。最后由mov r0,r3语句可以看到,32位以下返回值是由r0传递回调用者的,这也是ATPCS的标准。

  • 最后再结合caller的代码可以看出,参数的传递是由caller负责的:在bl callee前,前4个参数要保存在r0-r3中,第4个后的参数要压栈(str r3,[sp, #0])。而保存上下文相关的压栈操作,如FP,IP,LR及PC压栈则是由callee负责的,另外在callee真正执行有用前,还要为参数和局部变量开辟空间和赋值。

最后我在思考的问题是,之前一直说的arm可以用r0-r3传递参数,可以提高调用函数的速度,其实这只是针对调用函数为汇编过程而言,对于上面讨论的C函数来说,反正都是要将参数都压栈再访问,不同的是以前x86是由caller将参数全部先压栈(上右图),后面callee来访问,而arm是前4个参数由callee自己压栈再访问(上左图),反正都是要涉及load和store操作,运行速度不会提高。但是在汇编callee中,可以不将r0-r3压栈,而直接访问它们,也就是直接访问参数,这样就提高了速度。