mainCRTStartup函数解析
操作系统装载应用程序后,做完初始化工作就转到程序的入口点执行。程序的默认入口点由连接程序设置, 不同的连接器选择的入口函数也不尽相同。在VC++下,连接器对控制台程序设置的入口函数是 mainCRTStartup,mainCRTStartup 再调用main 函数
mainCRTStartup->main
利用VS2019的栈回溯功能查看mainCRTStartup源码:
选择菜单“调试”→“窗口”→“调用堆栈”,打开出栈窗口(快捷键:Ctrl+Alt+C),双击mainCRTStartup
mainCRTStartup 函数源码如下:
extern "C" int mainCRTStartup() { return __scrt_common_main(); } static __forceinline int __cdecl __scrt_common_main() { //初始化缓冲区溢出全局变量,在函数中检查缓冲区是否溢出 __security_init_cookie(); return __scrt_common_main_seh(); }
static __declspec(noinline) int __cdecl __scrt_common_main_seh() { if (!__scrt_initialize_crt(__scrt_module_type::exe)) __scrt_fastfail(FAST_FAIL_FATAL_APP_EXIT);
bool has_cctor = false; __try { bool const is_nested = __scrt_acquire_startup_lock();
if (__scrt_current_native_startup_state == __scrt_native_startup_state::initializing) { __scrt_fastfail(FAST_FAIL_FATAL_APP_EXIT); } else if (__scrt_current_native_startup_state == __scrt_native_startup_state::uninitialized) { __scrt_current_native_startup_state = __scrt_native_startup_state::initializing; //用于初始化C语法中的全局数据 if (_initterm_e(__xi_a, __xi_z) != 0) return 255; //用于初始化C++语法中的全局数据 _initterm(__xc_a, __xc_z);
__scrt_current_native_startup_state = __scrt_native_startup_state::initialized; } else { has_cctor = true; }
__scrt_release_startup_lock(is_nested);
//初始化线程局部存储变量 _tls_callback_type const* const tls_init_callback = __scrt_get_dyn_tls_init_callback(); if (*tls_init_callback != nullptr && __scrt_is_nonwritable_in_current_image(tls_init_callback)) { (*tls_init_callback)(nullptr, DLL_THREAD_ATTACH, nullptr); }
_tls_callback_type const * const tls_dtor_callback = __scrt_get_dyn_tls_dtor_callback(); if (*tls_dtor_callback != nullptr && __scrt_is_nonwritable_in_current_image(tls_dtor_callback)) { _register_thread_local_exe_atexit_callback(*tls_dtor_callback); } //初始化完成调用main()函数 int const main_result = invoke_main(); //main()函数返回执行析构函数或atexit注册的函数指针,并结束程序 if (!__scrt_is_managed_app()) exit(main_result);
if (!has_cctor) _cexit(); // Finally, we terminate the CRT: __scrt_uninitialize_crt(true, false); return main_result; } __except (_seh_filter_exe(GetExceptionCode(), GetExceptionInformation())) { // Note: We should never reach this except clause. int const main_result = GetExceptionCode();
if (!__scrt_is_managed_app()) _exit(main_result);
if (!has_cctor) _c_exit();
return main_result; } }
static int __cdecl invoke_main() { //调用main函数,传递命令行参数信息 return main(__argc, __argv,_get_initial_narrow_environment()); } |
函数流程:
1.初始化缓冲区溢出全局变量,用于在函数中检查缓冲区是否溢出->__security_init_cookie
2.初始化C语法中的全局数据->_initterm_e
3.初始化C++语法中的全局数据,涉及全局对象或静态对象初始化函数->_initterm
4.线程局部存储变量->__scrt_get_dyn_tls_init_callback
5.注册线程局部存储析构函数->__scrt_get_dyn_tls_dtor_callback
6.初始化完成,调用main()函数->invoke_main
7.main()函数返回执行析构函数或atexit注册的函数指针,并结束程序->exit(main_result)
函数解析:
1._initterm_e函数:用于全局数据和浮点寄存器的初始化
原型:int __cdecl _initterm_e(_PIFV* const first, _PIFV* const last)
first:函数指针数组的起始地址
last:结束地址
返回值:如果初始化失败,返回非0值,程序终止运行
_PIFV *:一个函数指针数组,保留了每个初始化函数的地址。初始化函数的类型为_PIFV:typedef int (__cdecl* _PIFV)(void);
源码:
extern "C" int __cdecl _initterm_e(_PIFV* const first, _PIFV* const last)
{
for (_PIFV* it = first; it != last; ++it)
{
if (*it == nullptr)
continue;
int const result = (**it)();
if (result != 0)
return result;
}
return 0;
}
2._initterm函数:C++全局对象和IO流等的初始化都是通过这个函数实现的,可以利用_initterm函数进行数据链初始化。
原型:void __cdecl _initterm(_PVFV* const first, _PVFV* const last)
first:函数指针数组的起始地址
last:结束地址
返回值:如果初始化失败,返回非0值,程序终止运行
_PVFV:typedef void (_cdecl *_PVFV)(void); 无参数也无返回值,用来代理调用构造函数
源码:
extern "C" void __cdecl _initterm(_PVFV* const first, _PVFV* const last)
{
for (_PVFV* it = first; it != last; ++it)
{
if (*it == nullptr)
continue;
(**it)();
}
}
当it不为NULL时,执行(**it)();后并不会进入全局对象的构造函数,而是进入编译器提供的构造代理函数,由一个负责全局对象的构造代理函数完成调用全局构造函数
编译器将为每个全局对象生成一段传递this指针和参数的代码,然后使用无参代理函数调用构造函数
参考:全局对象构造代理函数的分析
c++示例代码:
#include <stdio.h> #include <string.h> class Person { public: Person() { printf("Person()"); } ~Person(){ printf("~Person()"); } }; Person g_person1; //定义全局对象 Person g_person2; //定义全局对象 int main(int argc, char* argv[]) { printf("main"); return 0; } |
汇编标识:
//x86_vs对应汇编代码讲解 0040149D push offset dword_412120 ;参数2,代码析构函数数组终止地址 004014A2 push offset dword_412110 ;参数1,代理析构函数数组起始地址 004014A7 call __initterm ;遍历调用代理析构函数数组
00412110 dd 0 ;代理析构函数数组 00412114 dd 0040141E 00412118 dd 00401000 ;g_person1代理析构函数 0041211C dd 00401020 ;g_person2代理析构函数 00412120 dd 0
00401000 push ebp ;g_person1代理析构函数 00401001 mov ebp, esp 00401003 mov ecx, offset unk_4198B8 ;ecx=&g_person1 00401008 call sub_401060 ;调用构造函数 0040100D push 00411660 00401012 call _atexit ;注册g_person1析构代理函数 00401017 add esp, 4 0040101A pop ebp 0040101B retn
00401020 push ebp ;g_person2代理析构函数 00401021 mov ebp, esp 00401023 mov ecx, offset unk_4198B9 ;ecx=&g_person2 00401028 call sub_401060 ;调用构造函数 0040102D push 00411670 00401032 call _atexit ;注册g_person2析构代理函数 00401037 add esp, 4 0040103A pop ebp 0040103B retn |
3.__scrt_get_dyn_tls_init_callback函数:获取线程局部存储(TLS)变量的回调函数,用于初始化使用__declspec(thread)定义的变量。
4.__scrt_get_dyn_tls_dtor_callback函数:获取线程局部存储变量的析构回调函数,用于注册析构回调函数。
5.invoke_main函数:该函数获取main函数所需的3个参数信息之后,当调用main函数时,便可以将_argc、_argv、env这3个全局变量作为参数,传递到main函数中。
6.exit函数:执行析构函数或atexit注册的函数指针,并结束程序
mainCRTStartup 函数在调用main函数结束后使用了exit用来终止程序,全局对象的析构函数的调用也在其中,由exit函数内的_execute_onexit_table实现
_PVFV* saved_first = first;
_PVFV* saved_last = last;
for (;;)
{
//从后向前依次释放全局对象
_PVFV const function = __crt_fast_decode_pointer(*last);
*last = encoded_nullptr;
//调用保存的函数指着
function();
}
调用__crt_fast_decode_pointer函数可以获取保存各类资源释放函数的首地址
标签:__,main,scrt,函数,mainCRTStartup,析构,const,解析 From: https://www.cnblogs.com/XiuzhuKirakira/p/18050481