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會使用暫存器來儲存區域性變數,如果被呼叫函式隨便使用這些暫存器,就會破壞源呼叫函式的區域性變數。所以在被呼叫函式中,應該首先將本函式有可能使用到的暫存器壓棧,在返回之前,又將這些暫存器出棧,以保證函式呼叫之間不會破壞原來函式的上下文。