mainCRTStartup 函数解析

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函数可以获取保存各类资源释放函数的首地址

posted @ 2024-03-03 19:00  修竹Kirakira  阅读(56)  评论(0编辑  收藏  举报