当前位置:首页 > 芯闻号 > 充电吧
[导读]1、函数调用约定主要涉及参数如何传递、返回值如何传递、返回地址如何保存以及不要破坏调用函数的上下文。那么在ARM中,这些约定规则是什么样呢? 2、测试程序如下:static int fun_a(uin

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

本站声明: 本文章由作者或相关机构授权发布,目的在于传递更多信息,并不代表本站赞同其观点,本站亦不保证或承诺内容真实性等。需要转载请联系该专栏作者,如若文章内容侵犯您的权益,请及时联系本站删除。
关闭
关闭