函数调用时栈在做什么?

 以一段简单的函数调用开始,看看程序的上下文是如何切换的。

工具:Keil5

平台:Cortex-M7

1. 简单函数调用

 1 int func(int a, int b, int c, int d, int e, int f)
 2 {
 3     int buf[10], i;
 4     
 5     buf[0] = 0;
 6     i = a + b + c + d + e + f;
 7     
 8     if (i > 0)    
 9     {        
10         buf[0] = 1;
11     }
12     return i + buf[0];
13 }
14 
15 int main()
16 {
17     int value = func(5, 6, 7, 8, 9, 10);
18     
19     return value;
20 }

 

 

编译成功后仿真,看看汇编里做了啥:

1. 程序执行到C代码的17行 时调用子函数,准备切换下文,首先将func()的传参从右向左扫描,依次暂存在寄存器中(见下列汇编 1-7行)。由于传参数多达6个,而寄存器R0-R3不够用(备注1),只好将末尾的两个传参压入栈中(栈指针会偏移8个字节,备注2)。

1 0x080002E8 200A      MOVS          r0,#0x0A
2 0x080002EA 2109      MOVS          r1,#0x09
3 0x080002EC 2308      MOVS          r3,#0x08
4 0x080002EE 2207      MOVS          r2,#0x07
5 0x080002F0 E9CD1000  STRD          r1,r0,[sp,#0]
6 0x080002F4 2106      MOVS          r1,#0x06
7 0x080002F6 2005      MOVS          r0,#0x05
8 0x080002F8 F7FFFFE1  BL.W          func (0x080002BE)

 

 

 2. 进入func函数内,首先将上文的R4-R7寄存器及 lr 链接地址压入栈中(备注3),然后继续准备下文。

      首先为数组buf开辟内存空间,大小为sp栈指针偏移0x28=40字节。

      接下来要将传参准备好,由于有两个传参在栈中,需要透过sp指针取出,见下列汇编 第7行,此处对sp的指针偏移了0x3C=60字节,刚好就是(0x28=40  + (R4-R7)*4 =16  +  lr*4=4)的长度,偏移回了传参压栈时的位置。

   汇编9-10行要对buf[0]赋0操作,由于buf刚在栈中分配完,且栈的生长方向向下,因此sp此时指向的地址为buf[10]的最低位,即buf[0],STR指令的sp偏移即为0x00.

   继续往下看,r1-r4,r6-r7保存了6个传参值,求和的结果最终存储在r5中

   后续对sp及r0、r5的操作大同小异,不再赘述。

 1     57: { 
 2     58:         int buf[10], i; 
 3     59:          
 4 0x080002BE B5F0      PUSH          {r4-r7,lr}
 5 0x080002C0 B08A      SUB           sp,sp,#0x28
 6 0x080002C2 4604      MOV           r4,r0
 7 0x080002C4 E9DD670F  LDRD          r6,r7,[sp,#0x3C]
 8     60:         buf[0] = 0; 
 9 0x080002C8 2000      MOVS          r0,#0x00
10 0x080002CA 9000      STR           r0,[sp,#0x00]
11     61:         i = a + b + c + d + e + f; 
12     62:          
13 0x080002CC 1860      ADDS          r0,r4,r1
14 0x080002CE 4410      ADD           r0,r0,r2
15 0x080002D0 4418      ADD           r0,r0,r3
16 0x080002D2 4430      ADD           r0,r0,r6
17 0x080002D4 19C5      ADDS          r5,r0,r7
18     63:         if (i > 0)       
19     64:         {               
20 0x080002D6 2D00      CMP           r5,#0x00
21 0x080002D8 DD01      BLE           0x080002DE
22     65:                 buf[0] = 1; 
23     66:         } 
24 0x080002DA 2001      MOVS          r0,#0x01
25 0x080002DC 9000      STR           r0,[sp,#0x00]
26     67:         return i + buf[0]; 
27 0x080002DE 9800      LDR           r0,[sp,#0x00]
28 0x080002E0 4428      ADD           r0,r0,r5
29     68: } 

 

思考

备注1. 为什么传参只用到r0-r3寄存器?

经搜索,在《The ARM-THUMB Procedure Call Standard》手册上对寄存器的使用做了如下限定:

备注2. 什么时候sp指针会移动?

 特殊的指令在执行时SP指针会移动,例如:PUSH/POP  LDR/STR 等。

 

备注3. r4-r10保持上文的信息,每次入栈的寄存器数为何不一样?

 根据本文例子1/2的观察看,不是每次都会将所有的临时寄存器r4-r11入栈,而是要看上文环境所使用到的寄存器个数而定。

 

2. 函数递归调用

若将func() 函数改造为递归调用,则栈的情况又如何呢?

 1 int func(int a, int b, int c, int d, int e, int f)
 2 {
 3     int buf[10], i;
 4     
 5     buf[0] = 0;
 6     i = a + b + c + d + e + f;
 7     
 8     if (i > 0)    
 9     {        
10         buf[0] = func(a-1, b-1, c-1, d-1, e-1, f-1);
11     }
12     return i + buf[0];
13 }
14 
15 int main()
16 {
17     int value = func(5, 6, 7, 8, 9, 10);
18     
19     return value;
20 }

 

与章节1相比,只是改动了第10行,编译下看看效果。

1. 首先是传参的扫描处理,跟章节1完全一样

1 0x0800030C 200A      MOVS          r0,#0x0A
2 0x0800030E 2109      MOVS          r1,#0x09
3 0x08000310 2308      MOVS          r3,#0x08
4 0x08000312 2207      MOVS          r2,#0x07
5 0x08000314 E9CD1000  STRD          r1,r0,[sp,#0]
6 0x08000318 2106      MOVS          r1,#0x06
7 0x0800031A 2005      MOVS          r0,#0x05
8 0x0800031C F7FFFFCF  BL.W          func (0x080002BE)

 

2. 进入函数内,首先不同的是压栈的指针数,由之前的 r4-r7 增加到 r4-r10,

    sp指针的偏移也由0x28增加到0x30,多出8个字节

 汇编第6行-20行为buf[0]及i的初始化,执行过程没有变化,除了采用了不同的寄存器组

 在if语句中,改成递归后的变化为:因为传参众多,此时寄存器r0-r10都被占用(r10存着 i的临时值),这就是为什么在递归调用本函数之前,入栈的寄存器增加到r10

 此外还记得章节1中寄存器不足,需要压栈两个传参吗,这两个传参刚好放在sp多分配出来的8个字节空间里,其它的传参依然还是在r0-r3中。

 最终递归调用的上下文环境与第一次调用时保持了一致。

  

 1     58: { 
 2     59:         int buf[10], i; 
 3     60:          
 4 0x080002BE E92D47F0  PUSH          {r4-r10,lr}
 5 0x080002C2 B08C      SUB           sp,sp,#0x30
 6 0x080002C4 4604      MOV           r4,r0
 7 0x080002C6 460D      MOV           r5,r1
 8 0x080002C8 4616      MOV           r6,r2
 9 0x080002CA 461F      MOV           r7,r3
10 0x080002CC E9DD8914  LDRD          r8,r9,[sp,#0x50]
11     61:         buf[0] = 0; 
12 0x080002D0 2000      MOVS          r0,#0x00
13 0x080002D2 9002      STR           r0,[sp,#0x08]
14     62:         i = a + b + c + d + e + f; 
15     63:          
16 0x080002D4 1960      ADDS          r0,r4,r5
17 0x080002D6 4430      ADD           r0,r0,r6
18 0x080002D8 4438      ADD           r0,r0,r7
19 0x080002DA 4440      ADD           r0,r0,r8
20 0x080002DC EB000A09  ADD           r10,r0,r9
21     64:         if (i > 0)       
22     65:         {               
23 0x080002E0 F1BA0F00  CMP           r10,#0x00
24 0x080002E4 DD0C      BLE           0x08000300
25     66:                 buf[0] = func(a-1, b-1, c-1, d-1, e-1, f-1); 
26     67:         } 
27 0x080002E6 F1A90101  SUB           r1,r9,#0x01
28 0x080002EA F1A80001  SUB           r0,r8,#0x01
29 0x080002EE 1E7B      SUBS          r3,r7,#1
30 0x080002F0 1E72      SUBS          r2,r6,#1
31 0x080002F2 E9CD0100  STRD          r0,r1,[sp,#0]
32 0x080002F6 1E69      SUBS          r1,r5,#1
33 0x080002F8 1E60      SUBS          r0,r4,#1
34 0x080002FA F7FFFFE0  BL.W          func (0x080002BE)
35 0x080002FE 9002      STR           r0,[sp,#0x08]
36     68:         return i + buf[0]; 
37 0x08000300 9802      LDR           r0,[sp,#0x08]
38 0x08000302 4450      ADD           r0,r0,r10
39     69: } 

 

posted @ 2019-06-25 22:27  ba哥  阅读(2015)  评论(0编辑  收藏  举报