main函数之前:程序入口与启动函数
写在前面
本文内容总结自《c++反汇编与逆向分析技术揭秘》
程序真正的入口
main函数是语法规定的入口,而不是应用程序入口。
应用程序需要被系统加载到内存单元后才执行入口代码,入口代码通常是mainCRTStartup
、wmainCRTStartup
、WinMainCRTStartup
或wWinMainCRTStartup
。这里解释一下,以上四个函数名称中的CRT就是C运行时库的意思。
我们写一个十分简单的程序来讨论这个问题。
int main()
{
return 0;
}
在return 0;
处下一个断点,并启动调试。
了解启动函数
vs c++控制台应用程序的启动函数为mainCRTStartup
,由系统库kernel32.dll负责调用。在mainCRTStartup
中再调用main函数。
从图中我们可以发现,真正的程序入口是mainCRTStartup(void * __formal)
接下来我们看看__scrt_common_main
函数的源代码,为了方便说明,我把代码直接复制粘贴过来了。
static __forceinline int __cdecl __scrt_common_main()
{
__security_init_cookie();
return __scrt_common_main_seh();
}
__forceinline
关键字是定义内联函数的关键字,关于这个关键字的详细解释,参考(这个链接)[https://learn.microsoft.com/zh-cn/cpp/cpp/inline-functions-cpp?view=msvc-170]。__cdecl
关键字指定了函数的调用约定。__security_init_cookie
函数用于初始化缓冲区溢出全局变量,用于在函数中检查缓冲区是否溢出。随后__scrt_common_main
函数调用了__scrt_common_main_seh
函数。
接下来看看__scrt_common_main_seh
这个函数的代码。由于该函数代码较多们这里选取关键部分。
_initterm_e
用于C语言语法的全局数据和浮点寄存器的初始化,参数为函数数组
_initterm
用于初始化C++全局对象和IO流等,调用对象的构造函数,这就是我们说的全局对象在main
函数之前就初始化的原因。
者两个函数声明如下:
#ifndef _M_CEE
_ACRTIMP void __cdecl _initterm(
_In_reads_(_Last - _First) _In_ _PVFV* _First,
_In_ _PVFV* _Last
);
_ACRTIMP int __cdecl _initterm_e(
_In_reads_(_Last - _First) _PIFV* _First,
_In_ _PIFV* _Last
);
#endif
随后__scrt_common_main_seh
中调用了invoke_main
函数
在invoke_main
函数实现如下,它获取main函数所需的3个参数信息之后,当调用main函数时,便可以将_argc
、_argv
、env
这3个全局变量作为参数,传递到main函数中。
static int __cdecl invoke_main()
{
return main(__argc, __argv, _get_initial_narrow_environment());
}
这样,我们写的代码便运行起来了。