Win32程序函数调用时堆栈变化情况分析

2006年1月15日09:08星期日 [VC]
在经典的汇编语言教程中,函数调用时堆栈的使用都是着重讲解的问题。如今随着高级语言的越来越完善,单纯使用汇编开发的程序已经不多了。但对函数调用时堆栈动向的了解仍有助于我们明晰程序的执行流程,从而在程序编写和调试的过程中有一个清晰的思路。

一.调用约定
在Win32中,有关函数的调用主要有两种约定。
1._stdcall
以__stdcall方式调用的函数有以下特征:
• 参数由右至左压栈
• 调用返回时,堆栈由被调函数调整
2.__cdecl
__cdecl约定是C/C++函数的默认调用约定。它有以下特征:
• 参数由右至左压栈
• 调用返回时,堆栈由调用者调整

二.Win32函数调用过程

1. 压入参数
这里依据以上的调用方式将调用者给出的参数一一压入堆栈。

2. 压入断点
当程序执行到Call指令的时候,当前语句的地址作为断点地址压入堆栈。

3. 跳转
eip的值被重新设置为被调函数的起始地址。

4. mov ebp, esp
这里ebp被用来在堆栈中寻找调用者压入的参数,同时作为调用者堆栈指针的一个备份。在此前还应该执行一条:
push ebp
把ebp中原来的数值保存。

5. sub esp,N
这里N是函数内局部变量的总字节数加上一个整数,一般为40。此后esp即为被调函数的堆栈指针了。

6. 初始化esp ~ esp-N之间的N字节空间
这是对堆栈中已分配给局部变量使用的内存空间的初始化,一般全部设置为0xcc。

7. 顺序执行函数内语句。
此时函数的堆栈位于所有局部变量的内存空间之后,二者之间一般有40字节的隔离带。

8.返回
为保障调用的正常返回,函数内应当保证规范使用堆栈,使即将返回的时候esp的值恢复为执行第一条语句前的状态。说明白点,就是每一条push都要有相应的pop。
调用返回的过程如下:
mov esp, ebp
执行后,esp恢复为调用者的堆栈指针,栈顶除断点地址外,还存有原ebp的值和调用时压入的参数。
然后依次弹出ebp的值和断点地址。如果是__cdecl约定则直接返回调用者,调用者将负责调整堆栈,丢弃调先前压入的参数。如果是__stdcall则这个工作由被调函数来执行。

程序样例如下:
……
0040B8E8 push 1 ;压入参数
0040B8EA call 00401028 ;调用函数
……
00401028 jmp 0040b7c0 ;跳转到函数入口
……
0040B7C0 push ebp ;保存ebp
0040B7C1 mov ebp,esp
0040B7C3 sub esp,44h ;设置函数的堆栈指针,此函数中有4
;字节的局部变量
0040B7C6 push ebx
0040B7C7 push esi
0040B7C8 push edi
0040B7C9 lea edi,[ebp-44h]
0040B7CC mov ecx,11h
0040B7D1 mov eax,0CCCCCCCCh
0040B7D6 rep stos dword ptr [edi] ;初始化局部变量空间
0040B7D8 mov eax,dword ptr [ebp+8]
0040B7DB mov dword ptr [ebp-4],eax
……
0040B7DE pop edi ;弹出曾压栈的数据
0040B7DF pop esi
0040B7E0 pop ebx
0040B7E1 mov esp,ebp ;恢复调用者的堆栈
0040B7E3 pop ebp ;弹出原ebp值
0040B7E4 ret 4 ;返回并将堆栈向上调整4字节。
;此处为__stdcall约定,所以由函数调
;整堆栈

相应的C代码如下:

void __stdcall fun(int);

int main(void)
{
……
fun(1);
……
return 0;
}

void __stdcall fun(int para)
{
int localpara = para;
……
}

随机日志

发表评论

0 评论.

Leave a Reply



[ Ctrl + Enter ]

*
To prove you're a person (not a spam script), type the security word shown in the picture. Click on the picture to hear an audio file of the word.
Click to hear an audio file of the anti-spam word