main函数之前:程序入口与启动函数

写在前面

本文内容总结自《c++反汇编与逆向分析技术揭秘》

程序真正的入口

main函数是语法规定的入口,而不是应用程序入口

应用程序需要被系统加载到内存单元后才执行入口代码,入口代码通常是mainCRTStartupwmainCRTStartupWinMainCRTStartupwWinMainCRTStartup。这里解释一下,以上四个函数名称中的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_argvenv这3个全局变量作为参数,传递到main函数中。

static int __cdecl invoke_main()
{
    return main(__argc, __argv, _get_initial_narrow_environment());
}

这样,我们写的代码便运行起来了。

posted @ 2025-05-11 00:21  XueZhou  阅读(10)  评论(0)    收藏  举报