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会使用寄存器来保存局部变量,如果被调用函数随便使用这些寄存器,就会破坏源调用函数的局部变量。所以在被调用函数中,应该首先将本函数有可能使用到的寄存器压栈,在返回之前,又将这些寄存器出栈,以保证函数调用之间不会破坏原来函数的上下文。