函数调用过程再分析

缘起:

  前天读《链接器和装载器》 第二章,看到有提起“过程调用”一节,仔细想来还有些细节不清晰,一时兴起,做个了断,以下是记录……

 

正文:

  1,先解释一下:

    A:当进入到函数的时候,机器执行的一般步骤:

      1)ebp压栈;
      2)将esp赋给ebp,现在ebp就是当前的栈顶了;
      3)将esp减去一个偏移值,这个偏移值就是为该过程所留的栈空间,这时esp指向了虚拟的栈顶;
      4)从栈底,即esp所指向的地址开始(包括这个地址)给局部变量分配空间,一般第一个局部变量的地址就是esp的值,依次往下分配;
      5)当这些局部变量用完之后,将ebp重新赋值给esp,这时候esp重新指向栈顶;
      6)出栈操作,也就是将旧的ebp值返回给ebp;
      7)ret,过程返回,即将eip(指令寄存器)的值赋值为当前栈顶的值;

    B:使用的测试环境:

      1)vs2008,VC++ win32控制台项目;

      2)编译选项:去掉/RTC1,改/ZI为/Zi(为了去掉vs2008产生供Edit+Continue使用的附加代码);

 

  2,上图(手工绘制,比较粗糙):

 

图1 调用函数时堆栈的变化情况

  3,一段实例:

    A:C源代码(仅作为测试之用,没有任何意义)

代码
#include <stdio.h>

int a_plus_b(int a, int b)
{
// 只为了解释用,没有任何意义
int c1 = 1;
int c3 = a+b-c1;
return c3;
}

int main()
{
int a = 1, b = 2;

int c = a_plus_b(a, b);

printf(
"%d + %d = %d", a, b, c);
}

    B:反汇编后的代码(部分带有注释,请参考之)

代码
1 #include <stdio.h>
2
3  int a_plus_b(int a, int b)
4 {
5 ;=============================================
6 ;保留当前的栈顶到ebp中,原始ebp存放到栈空间中
7 00401CB0 push ebp
8 00401CB1 mov ebp,esp
9 ;=============================================
10 ;在栈上预留8个字节的局部变量空间
11 00401CB3 sub esp,8
12 ;=============================================
13 int c1 = 1;
14 ;=============================================
15 ;c1的地址就是预留栈空间的第一个int空间处
16 00401CB6 mov dword ptr [c1],1
17 ;=============================================
18 int c3 = a+b-c1;
19 00401CBD mov eax,dword ptr [a]
20 00401CC0 add eax,dword ptr [b]
21 00401CC3 sub eax,dword ptr [c1]
22 ;=============================================
23 ;c3的地址是预留栈空间的第二个int空间处
24 00401CC6 mov dword ptr [c3],eax
25 ;=============================================
26 return c3;
27 00401CC9 mov eax,dword ptr [c3]
28 }
29 ;=============================================
30 ;将ebp中保存的原来刚进入该过程的栈顶位置重新赋值到esp中
31 ;称之为还原栈顶
32 00401CCC mov esp,ebp
33 ;=============================================
34 ;还原ebp
35 00401CCE pop ebp
36 ;=============================================
37 ;将栈顶保存的返回地址,弹出到eip中
38 00401CCF ret
39 ;=============================================
40
41  int main()
42 {
43 00401CD0 push ebp
44 00401CD1 mov ebp,esp
45 00401CD3 sub esp,0Ch
46 int a = 1, b = 2;
47 00401CD6 mov dword ptr [a],1
48 00401CDD mov dword ptr [b],2
49 //const char *cpc = "hello, world";
50  
51 int c = a_plus_b(a, b);
52
53 ;=============================================
54 ;传递过程的参数
55 00401CE4 mov eax,dword ptr [b]
56 00401CE7 push eax
57 00401CE8 mov ecx,dword ptr [a]
58 00401CEB push ecx
59 ;=============================================
60 ;开始调用,注意这条语句之后的地址是:00401CF1
61 ;这个地址将要存放到当前栈顶
62 00401CEC call @ILT+0(_a_plus_b) (401005h)
63 ;=============================================
64 00401CF1 add esp,8
65 00401CF4 mov dword ptr [c],eax
66
67 printf("%d + %d = %d", a, b, c);
68 00401CF7 mov edx,dword ptr [c]
69 00401CFA push edx
70 00401CFB mov eax,dword ptr [b]
71 00401CFE push eax
72 00401CFF mov ecx,dword ptr [a]
73 00401D02 push ecx
74 00401D03 push offset ___globallocalestatus-10h (405000h)
75 00401D08 call dword ptr [__imp__printf (406230h)]
76 00401D0E add esp,10h
77 }
78 00401D11 xor eax,eax
79 00401D13 mov esp,ebp
80 00401D15 pop ebp

 

    希望配合图片和注释各位能看的清楚!

 

结语:

  1,清晰了C中的函数调用过程,关键是:参数传递、返回值、跳转等细节部分;

  2,学会了这种预留栈空间的技巧,说不定什么时候就能用在别的地方了;

  3,还比较幼稚,希望以后有更深入的思考和总结,不足的地方各位海涵,不吝赐教,谢谢!

posted @ 2010-03-20 11:04  学问  阅读(2632)  评论(3编辑  收藏  举报