1. 程式人生 > >ARM函式呼叫約定

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