ARM函数调用约定
扫描二维码
随时随地手机看文章
1、函数调用约定主要涉及参数如何传递、返回值如何传递、返回地址如何保存以及不要破坏调用函数的上下文。那么在ARM中,这些约定规则是什么样呢?
2、测试程序如下:
static int fun_a(uint32_t a, uint32_t b, uint32_t c)
{
a = b + c;
b = a + c;
c = a + b;
return fun_b(a,b,c,a,b);
}
static int fun_b(uint32_t a, uint32_t b, uint32_t c, uint32_t d, uint32_t e)
{
a = b + c;
b = a + c;
c = a + b;
d = a + b + c;
e = d + e + a;
return (int)e;
}
/*----------------------------------------------------------------------------
* Global functions
*----------------------------------------------------------------------------*/
/**
* brief Application entry point for test pmecc example.
* return Unused (ANSI-C compatibility).
*/
extern int main( void )
{
uint32_t const_a = 0x12345678;
uint32_t const_b = 0x87654321;
uint32_t const_c = 0x04;
while(1)
{
int d = fun_a(const_a, const_b, const_c);
}
}
3、在main函数中调用fun_a函数,传入3个参数,参数传递方式如下:
// 108 extern int main( void )
// 109 {
main:
PUSH {R4-R6,LR}
CFI R14 Frame(CFA, -4)
CFI R6 Frame(CFA, -8)
CFI R5 Frame(CFA, -12)
CFI R4 Frame(CFA, -16)
CFI CFA R13+16
// 110 uint32_t const_a = 0x12345678;
LDR R4,??DataTable0 ;; 0x12345678
// 111 uint32_t const_b = 0x87654321;
LDR R5,??DataTable0_1 ;; 0x87654321
// 112 uint32_t const_c = 0x04;
MOV R6,#+4
// 113
// 114 while(1)
// 115 {
// 116 int d = fun_a(const_a, const_b, const_c);
??main_0:
MOVS R2,R6
MOVS R1,R5
MOVS R0,R4
CFI FunCall fun_a
BL fun_a
B ??main_0
CFI EndBlock cfiBlock2
// 117 }
// 118 }在调用函数fun_a之前,先将R4-R6的值赋给R0-R2,由前面部分程序可以看出R4-R6就是局部变量const_a、const_b、const_c,所以我们猜测,ARM使用R0-R2来传递参数。实际上,在fun_a中,是这样去参数的:
// 82 static int fun_a(uint32_t a, uint32_t b, uint32_t c)
// 83 {
fun_a:
PUSH {R4-R6,LR}
CFI R14 Frame(CFA, -4)
CFI R6 Frame(CFA, -8)
CFI R5 Frame(CFA, -12)
CFI R4 Frame(CFA, -16)
CFI CFA R13+16
SUB SP,SP,#+8
CFI CFA R13+24
MOVS R4,R0
MOVS R5,R1
MOVS R6,R2
// 84 a = b + c;
ADDS R0,R6,R5
MOVS R4,R0
// 85 b = a + c;
ADDS R0,R6,R4
MOVS R5,R0
// 86 c = a + b;
ADDS R0,R5,R4
MOVS R6,R0
// 87 return fun_b(a,b,c,a,b);
STR R5,[SP, #+0]
MOVS R3,R4
MOVS R2,R6
MOVS R1,R5
MOVS R0,R4
CFI FunCall fun_b
BL fun_b
POP {R1,R2,R4-R6,PC} ;; return
CFI EndBlock cfiBlock0
// 88 }其中的
MOVS R4,R0
MOVS R5,R1
MOVS R6,R2
// 84 a = b + c;
ADDS R0,R6,R5
MOVS R4,R0
// 85 b = a + c;
ADDS R0,R6,R4
MOVS R5,R0
// 86 c = a + b;
ADDS R0,R5,R4
MOVS R6,R0
表明,在ARM中确实是使用R0-R2来传输3个参数。
实验中,当参数小于4个时,ARM首先会用R0-R3四个寄存器来传递参数,当参数个数大于4时,开始使用栈来传递参数。
4、ARM中使用R0作为默认的返回值
5、ARM中,在每一个函数开始部分都需要将一部分寄存器压栈,这部分寄存器必定包含LR寄存器。因为在函数调用时,使用的是BL指令,BL指令在跳转到目标函数之前,首先将一下跳指令的地址存入LR寄存器中,这就是函数的返回地址。
一个被调用函数开始时,首先要将LR、以及需要使用的部分寄存器压栈,以保护这几个寄存器。因为,在被调用函数也可能调用其他的函数,这时的LR就会受到破坏,所以需要将LR压栈,保存到内存中。这样在返回时,直接将POP原来保存的LR到PC,即可实现返回。同时还需要将本函数中使用到的几个寄存器压栈,因为ARM会使用寄存器来保存局部变量,如果被调用函数随便使用这些寄存器,就会破坏源调用函数的局部变量。所以在被调用函数中,应该首先将本函数有可能使用到的寄存器压栈,在返回之前,又将这些寄存器出栈,以保证函数调用之间不会破坏原来函数的上下文。





