0%

分析c语言函数调用时的汇编代码,进出函数时CPU做了什么?

示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
struct foo {int a; int b; int c; int d; int e;} g_stTest;

volatile struct foo testFun2(int a, int b, int c, int d, int e)
{
struct foo stTest;
stTest.a = a;
stTest.b = b;
stTest.c = c;
stTest.d = d;
stTest.e = e;
return stTest;
};

volatile void testFun1(void)
{
g_stTest = testFun2(1, 2, 3, 4, 5);
}

反汇编

用gcc工具链编译s32k144平台的代码,通过ozone分析elf得到反汇编如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
volatile void testFun1(void)
{
// 0002C74C PUSH {R4-R5, R7, LR}
// 0002C74E SUB SP, SP, #32
// 0002C750 ADD R7, SP, #8
g_stTest = testFun2(1, 2, 3, 4, 5);
// 0002C752 LDR R4, =g_stTest ; [PC, #40] [0x0002C77C] =0x20002318
// 0002C754 MOV R2, R7
// 0002C756 MOVS R3, #4
// 0002C758 STR R3, [SP, #0]
// 0002C75A MOVS R3, #5
// 0002C75C STR R3, [SP, #4]
// 0002C75E MOV R0, R2
// 0002C760 MOVS R1, #1
// 0002C762 MOVS R2, #2
// 0002C764 MOVS R3, #3
// 0002C766 BL testFun2 ; 0x0002C710
// 0002C76A MOV R5, R4
// 0002C76C MOV R4, R7
// 0002C76E LDM R4!, {R0-R3}
// 0002C770 STM R5!, {R0-R3}
// 0002C772 LDR R3, [R4]
// 0002C774 STR R3, [R5]
}
// 0002C776 ADDS R7, #24
// 0002C778 MOV SP, R7
// 0002C77A POP {R4-R5, R7, PC}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
volatile struct foo testFun2(int a, int b, int c, int d, int e)
{
// 0002C710 PUSH {R4-R5, R7}
// 0002C712 SUB SP, SP, #44
// 0002C714 ADD R7, SP, #0
// 0002C716 STR R0, [R7, #12]
// 0002C718 STR R1, [R7, #8]
// 0002C71A STR R2, [R7, #4]
// 0002C71C STR R3, [R7]
struct foo stTest;
stTest.a = a;
// 0002C71E LDR R3, [R7, #8]
// 0002C720 STR R3, [R7, #20]
stTest.b = b;
// 0002C722 LDR R3, [R7, #4]
// 0002C724 STR R3, [R7, #24]
stTest.c = c;
// 0002C726 LDR R3, [R7]
// 0002C728 STR R3, [R7, #28]
stTest.d = d;
// 0002C72A LDR R3, [R7, #56]
// 0002C72C STR R3, [R7, #32]
stTest.e = e;
// 0002C72E LDR R3, [R7, #60]
// 0002C730 STR R3, [R7, #36]
return stTest;
// 0002C732 LDR R3, [R7, #12]
// 0002C734 MOV R5, R3
// 0002C736 ADD.W R4, R7, #20
// 0002C73A LDM R4!, {R0-R3}
// 0002C73C STM R5!, {R0-R3}
// 0002C73E LDR R3, [R4]
// 0002C740 STR R3, [R5]
};
// 0002C742 LDR R0, [R7, #12]
// 0002C744 ADDS R7, #44
// 0002C746 MOV SP, R7
// 0002C748 POP {R4-R5, R7}
// 0002C74A BX LR

分析

  1. 在进入函数后,首先会将R4, R5, R7寄存器压栈,使用PUSH指令后SP会自动减去压栈空间的大小。
  2. 然后是SP减去一个直接数,是在给局部变量、形参和参数传递开辟栈区空间。
  3. R7 = SP + 偏移,R7是局部变量所在栈区的地址,偏移的大小是函数内调用函数时,参数传递所需要的空间大小。
  4. g_stTest = testFun2(1, 2, 3, 4, 5);反汇编中,参数4, 5通过栈传递,参数1, 2, 3通过R1~R3寄存器传递。
  5. testFun2函数进入后,STR R0, [R7, #12]等指令,会将R0~R3寄存器传递的参数存入(局部变量)栈中,为什么传参的还有R0?
  6. 重新分析g_stTest = testFun2(1, 2, 3, 4, 5);反汇编,R0传入的是R7(局部变量地址),在testFun2函数return stTest;时,将stTest写入R0传入的地址区域。
  7. 在退出函数时,如果函数有return参数,会将return参数写入R0,若大于4字节会将进入函数时R0传入的地址返回。
  8. 在退出函数时,会将SP改回进入函数前的SP,并使用POP指令出栈(POP指令会增加SP),最后跳转出函数(还可以直接POP取出PC跳转)。