本文主要探讨 Visual C++ 程序如何从编译到执行,在这个过程中,VC 生成的程序如何与操作系统交互,初始化,运行以及结束.同时还讨论了一些Visual C++ Runtime Library 的内在机制,包括内存管理等等.
要知道一个程序怎么从编译到执行,首先得了解一下 PE 文件格式,也就是我们常用的 Windows Executable 的格式。(事实上这种格式并不只限于 Windows Exectuable)。关于这个 topic 最好的文章是 Matt Pietrek写的An In-Depth Look into the Win32 Portable Executable File Format,网上 google 一下即得。简单来讲,PE格式的文件 Header里包含下面的结构:
IMAGE_DOS_HEADER
IMAGE_NT_HEADERS
如果你用Hex editor打开一个executable文件,最前面是IMAGE_DOS_HEADER,这个一般不太重要.紧跟其后的就是IMAGE_NT_HEADERS.
这些结构定义在 <winnt.h>. IMAGE_NT_HEADERS定义如下 (以32bit为例):
typedef struct _IMAGE_NT_HEADERS {
DWORD Signature;
IMAGE_FILE_HEADER FileHeader;
IMAGE_OPTIONAL_HEADER32 OptionalHeader;
} IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;
其中,IMAGE_OPTIONAL_HEADER32定义如下:
typedef struct _IMAGE_OPTIONAL_HEADER {
WORD Magic;
BYTE MajorLinkerVersion;
BYTE MinorLinkerVersion;
DWORD SizeOfCode;
DWORD SizeOfInitializedData;
DWORD SizeOfUninitializedData;
DWORD AddressOfEntryPoint; //Entry Point
DWORD BaseOfCode;
DWORD BaseOfData;
DWORD ImageBase;
DWORD SectionAlignment;
DWORD FileAlignment;
...
DWORD SizeOfImage;
DWORD SizeOfHeaders;
DWORD CheckSum;
WORD Subsystem;
...
IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
} IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;
IMAGE_OPTIONAL_HEADER32中, AddressOfEntryPoint定义了程序的入口点, 即当Windows系统完成Loading之后, 从哪个Virtual Address开始把控制转给executable本身. 不过这个入口函数既不是Windows启动一个Process时开始执行的第一个函数, 也不是C++程序里面的main函数. 关于Windows Kernel 如何完成Process loading的, 在Mark E. Russinovich, David A. Solomon的Microsoft Windows Internals, Fourth Edition: Microsoft Windows Server(TM) 2003, Windows XP, and Windows 2000 里面讲得比较清楚. 我们就考虑系统把控制转到EntryPoint之后吧.
Visual C++的编译器的executable的入口函数是mainCRTStartup, 或wmainCRTStartup if compiled as UNICODE, 以上是编译Console Executable.如果是Windows程序, 则是 (w)WinMainCRTStartup. 函数定义在 crt0.c 里可以找到. 通常在main函数里设个断点, 停下来后在 Callstack里就可以点到mainCRTStartup里去看看. 你也可以定义自己的入口函数, 然后用Linker选项: /ENTRYR指定自己的入口函数. 但是因为入口函数干了很多初始化 C++ Library之类的工作, 所以如果你自己的入口函数没有做类似的初始化的话, 多半程序没法运行. Linker 编译的时候, 确定了入口函数的地址, 就把这个地址写入PE Header中.
要知道一个程序怎么从编译到执行,首先得了解一下 PE 文件格式,也就是我们常用的 Windows Executable 的格式。(事实上这种格式并不只限于 Windows Exectuable)。关于这个 topic 最好的文章是 Matt Pietrek写的An In-Depth Look into the Win32 Portable Executable File Format,网上 google 一下即得。简单来讲,PE格式的文件 Header里包含下面的结构:
IMAGE_DOS_HEADER
IMAGE_NT_HEADERS
如果你用Hex editor打开一个executable文件,最前面是IMAGE_DOS_HEADER,这个一般不太重要.紧跟其后的就是IMAGE_NT_HEADERS.
这些结构定义在 <winnt.h>. IMAGE_NT_HEADERS定义如下 (以32bit为例):
typedef struct _IMAGE_NT_HEADERS {
DWORD Signature;
IMAGE_FILE_HEADER FileHeader;
IMAGE_OPTIONAL_HEADER32 OptionalHeader;
} IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;
其中,IMAGE_OPTIONAL_HEADER32定义如下:
typedef struct _IMAGE_OPTIONAL_HEADER {
WORD Magic;
BYTE MajorLinkerVersion;
BYTE MinorLinkerVersion;
DWORD SizeOfCode;
DWORD SizeOfInitializedData;
DWORD SizeOfUninitializedData;
DWORD AddressOfEntryPoint; //Entry Point
DWORD BaseOfCode;
DWORD BaseOfData;
DWORD ImageBase;
DWORD SectionAlignment;
DWORD FileAlignment;
...
DWORD SizeOfImage;
DWORD SizeOfHeaders;
DWORD CheckSum;
WORD Subsystem;
...
IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
} IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;
IMAGE_OPTIONAL_HEADER32中, AddressOfEntryPoint定义了程序的入口点, 即当Windows系统完成Loading之后, 从哪个Virtual Address开始把控制转给executable本身. 不过这个入口函数既不是Windows启动一个Process时开始执行的第一个函数, 也不是C++程序里面的main函数. 关于Windows Kernel 如何完成Process loading的, 在Mark E. Russinovich, David A. Solomon的Microsoft Windows Internals, Fourth Edition: Microsoft Windows Server(TM) 2003, Windows XP, and Windows 2000 里面讲得比较清楚. 我们就考虑系统把控制转到EntryPoint之后吧.
Visual C++的编译器的executable的入口函数是mainCRTStartup, 或wmainCRTStartup if compiled as UNICODE, 以上是编译Console Executable.如果是Windows程序, 则是 (w)WinMainCRTStartup. 函数定义在 crt0.c 里可以找到. 通常在main函数里设个断点, 停下来后在 Callstack里就可以点到mainCRTStartup里去看看. 你也可以定义自己的入口函数, 然后用Linker选项: /ENTRYR指定自己的入口函数. 但是因为入口函数干了很多初始化 C++ Library之类的工作, 所以如果你自己的入口函数没有做类似的初始化的话, 多半程序没法运行. Linker 编译的时候, 确定了入口函数的地址, 就把这个地址写入PE Header中.