在编写Win32应用程序时,都必须在源码里实现一个WinMain函数。但Windows程序执行并不是从WinMain函数开始的,首先被执行的是启动函数相关代码,这段代码是编译器生成的。启动代码完成初始化进程,再调用WinMain函数。
对于Visual C++程序来说,它调用的是C/C++运行时启动函数,该函数负责对C/C++运行库进行初始化。Visual C++配有C运行库的源代码,可以在crtsrccrt0.c文件中找到启动函数的源代码(安装时Visual C++必须选取安装源代码选项);而用于控制台程序的启动代码存放在crtsrcwincmdln.c文件中。
所有的C/C++运行时启动函数的作用基本都是相同的:检索指向新进程的命令行指针,检索指向新进程的环境变量指针,全局变量初始化,内存堆栈初始化等。当所有的初始化操作完毕后,启动函数就调用应用程序的进入点函数。调用WinMain函数如下所示:
1
2
3
|
<span style
=
"font-size: 13.86px;"
>GetStartupInfo (&StartupInfo);
Int
nMainRetVal
=
WinMain(GetModuleHandle(NULL),NULL,pszCommandLineAnsi,(StartupInfo.dwFlags&STARTF_USESHOWWINDOW)?StartupInfo.wShowWindow:SW__SHOWDEFAULT);
<
/
span>
|
当进入点返回时,启动函数便调用C运行库的exit函数,将返回值(nMainRetVal)传递给它,进行一些必要处理,最后调用系统函数ExitProcess退出。
下面是一个Visual C++编译的程序,程序启动代码的汇编代码如下:
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
|
<span style
=
"font-size: 13.86px;"
>
00401180
push ebp
00401181
mov ebp, esp
00401183
push FFFFFFFF
00401185
push
004040D0
0040118A
push
00401CB4
0040118F
mov eax, dword ptr fs:[
00000000
]
00401195
push eax
00401196
mov dword ptr fs:[
00000000
], esp
0040119D
sub esp,
00000058
004011A0
push ebx
004011A1
push esi
004011A2
push edi
004011A3
mov dword ptr [ebp
-
18
], esp
004011A6
Call KERNEL32.GetVersion ; 确定Windows系统版本
……
004011F4
Call KERNEL32.GetCommandLineA ; 指向进程的完整命令行的指针
……
0040121E
push eax
0040121F
Call KERNEL32.GetStartupInfoA ; 获取一个进程的启动信息
……
00401241
push esi
00401242
Call KERNEL32.GetModuleHandleA ; 返回进程地址空间执行文件基地址
00401248
push eax
00401249
call
00401000
; 调用用户编写的进入点函数WinMain
; 分析程序时,直接跳到
401000
即可
0040124E
mov dword ptr [ebp
-
60
], eax
00401251
push eax
00401252
call
004012EC
; 退出程序
……
0040126A
ret
<
/
span>
|