首页 > 其他分享 >mainCRTStartup 函数解析

mainCRTStartup 函数解析

时间:2024-03-03 19:12:19浏览次数:22  
标签:__ main scrt 函数 mainCRTStartup 析构 const 解析

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

相关文章

  • scrapy—图片解析(图片懒加载)
    笔记-图片数据爬取之ImagesPipeline-基于scrapy爬取字符串类型的数据和爬取图片类型的数据区别?-字符串:只需要基于xpth进行解析且提交管道进行持久化存储-图片:xpath解析出图片src属性值。单独的对图片地址发起请求获取图片二进制类型的数据-Imag......
  • 在嵌入式设备中用多项式快速计算三角函数和方根
    惯性传感器的倾角计算要用到三角函数.在MCS-51,CortexM0,M3之类的芯片上编程时,能使用的资源是非常有限,通常只有两位数KB的Flash,个位数KB的RAM.如果要使用三角函数和开方就要引入math.h,会消耗掉10KB以上的Flash空间.在很多情况下受硬件资源限制无法使用math.h,......
  • C++ 函数调用运算符 () 重载
    函数调用运算符()可以被重载用于类的对象。当重载()时,您不是创造了一种新的调用函数的方式,相反地,这是创建一个可以传递任意数目参数的运算符函数。1#include<iostream>2usingnamespacestd;3classDistance4{5private:6intfeet;/......
  • Glide源码解析四(解码和转码)
    本文基于Glide4.11.0Glide加载过程有一个解码过程,比如将url加载为inputStream后,要将inputStream解码为Bitmap。 从Glide源码解析一我们大致知道了Glide加载的过程,所以我们可以直接从这里看起,在这个过程中我们以从文件中加载bitmap为例:DecodeJob的一个方法:privatevoiddec......
  • 常见的数据库语句解析
    创建表的时候,一般都会在结尾写上这些代码:ENGINE=InnoDBAUTO_INCREMENT=70defaultcharset=utf8mb3collate=utf8mb4_bincomment='';我经过学习后简单了解了这些语句的作用:ENGINE=InnoDB:指定了表的存储引擎为InnoDB,InnoDB是MySQL的一种存储引擎,提供了事务处理和外......
  • 各种类型json解析
    usingNewtonsoft.Json;usingSystem;usingSystem.Collections.Generic;usingSystem.Linq;usingSystem.Text;usingSystem.Threading.Tasks;usingSystem.Web.Script.Serialization;namespaceAnalysisJson{classProgram{staticvoidMain(string[]args......
  • Java引用类型解析:掌握强引用、软引用、弱引用和幻象引用的妙用
     概述:Java中的引用分为强引用、软引用、弱引用和幻象引用。强引用是最常见的,不会被垃圾回收;软引用在内存不足时才被回收;弱引用在下一次垃圾回收时回收;幻象引用用于检测对象是否已被回收。它们各自适用于不同场景,帮助开发者更灵活地管理对象生命周期,避免内存泄漏。在Java中,引......
  • ThreadLocal解析
    ThreadLocal解析目录ThreadLocal解析1.两大使用场景——ThreadLocal的用途典型场景1:每个线程需要一个独享的对象(通常是工具类,典型需要使用的类有SimpleDateFormat和Random)典型场景2:每个线程内需要保存全局变量(例如在拦截器中获取用户信息),可以让不同方法直接使用,避免参数传递的麻......
  • 蓝图函数库的使用
    作用函数库可以将预先写好的函数整合起来,方便别的蓝图对其进行调用;函数分为纯函数和函数纯函数是只调用的函数纯函数的设置方法函数:具有输入和输出......
  • C++ 重载运算符和重载函数 二元运算符重载
    C++允许在同一作用域中的某个函数和运算符指定多个定义,分别称为函数重载和运算符重载。重载声明是指一个与之前已经在该作用域内声明过的函数或方法具有相同名称的声明,但是它们的参数列表和定义(实现)不相同。当您调用一个重载函数或重载运算符时,编译器通过把您所使用的参数类型......