在编写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>
|