zoukankan      html  css  js  c++  java
  • MSVC CRT运行库启动代码分析

    原文链接:http://www.programlife.net/msvc-crt-startup.html

    在程序进入main/WinMain函数之前,需要先进行C运行库的初始化操作,通过在Visual Studio中调试,通过栈回溯可以找到位于crt0.c中的_tmainCRTStartup函数,这个函数负责进行一些初始化操作,_tmainCRTStartup的上一层调用来自kernel32.dll。这里简单分析一下crt0.c的代码。

    实际上,C运行库代码又有两个版本,如果是静态编译的话代码位于crt0.c之中,如果是动态编译的话代码位于crtexe.c之中,这里可以通过项目属性的“配置属性”——“C/C++”——“代码生成”——“运行库”的MT和MD进行设置。

    根据工程的类型的不同(Win32工程和Console工程),以及工程编码的不同(Unicode与多字节),实际的入口函数会有四种不同的可能,_tmainCRTStartup被设置为一个红,根据工程的设置,实际的名字选取其中的一种:

    #ifdef _WINMAIN_
     
    #ifdef WPRFLAG
    #define _tmainCRTStartup    wWinMainCRTStartup
    #else  /* WPRFLAG */
    #define _tmainCRTStartup    WinMainCRTStartup
    #endif  /* WPRFLAG */
     
    #else  /* _WINMAIN_ */
     
    #ifdef WPRFLAG
    #define _tmainCRTStartup    wmainCRTStartup
    #else  /* WPRFLAG */
    #define _tmainCRTStartup    mainCRTStartup
    #endif  /* WPRFLAG */
     
    #endif  /* _WINMAIN_ */
    Copyed From 程序人生 
    Home Page:http://www.programlife.net 
    Source URL:http://www.programlife.net/msvc-crt-startup.html 

    _tmainCRTStartup实际上是__tmainCRTStartup的一个包装函数,在调用后者之前,对cookie进行了初始化操作,如果设置了/GS选项的话,在函数调用过程中,建立栈帧的时候会设置一个cookie,函数返回之前会校验cookie是否一致,简单的判断是否发出缓冲区溢出。

    int
    _tmainCRTStartup(
            void
            )
    {
            __security_init_cookie();
     
            return __tmainCRTStartup();
    }
    Copyed From 程序人生 
    Home Page:http://www.programlife.net 
    Source URL:http://www.programlife.net/msvc-crt-startup.html 

    我的测试环境是Visual Studio 2010,__tmainCRTStartup函数的代码感觉和VC6的还是有一定差距的,《C++反汇编与逆向分析》和《程序员的自我修养》都是以VC6的代码作为例子讲解的。__tmainCRTStartup的基本流程为:堆初始化、多线程初始化、IO初始化、命令行参数解析、环境变量参数解析、全局数据和浮点数寄存器初始化、main函数调用、返回。分析如下

    int
    __tmainCRTStartup(
             void
             )
    {
            int initret;
            int mainret=0;
            int managedapp;
    #ifdef _WINMAIN_
            _TUCHAR *lpszCommandLine;
            STARTUPINFOW StartupInfo;
     
            GetStartupInfoW( &StartupInfo );
    #endif  /* _WINMAIN_ */
     
    #ifdef _M_IX86
            // 对于32位程序,设置为如果检测到堆败坏则则自动结束进程
            // 64位程序默认就设置了这个行为
            if (!_NoHeapEnableTerminationOnCorruption)
            {
                HeapSetInformation(NULL, HeapEnableTerminationOnCorruption, NULL, 0);
            }
    #endif  /* _M_IX86 */
     
            // 检测PE头中的标志
            managedapp = check_managed_app();
            // ======================================================
            // 堆初始化操作
            // 对于32位程序而言,_heap_init通过CreateHeap创建一个堆
            // ======================================================
            if ( !_heap_init() )                /* initialize heap */
                fast_error_exit(_RT_HEAPINIT);  /* write message and die */
            // 初始化多线程环境,暂时不做分析
            if( !_mtinit() )                    /* initialize multi-thread */
                fast_error_exit(_RT_THREAD);    /* write message and die */
     
            _CrtSetCheckCount(TRUE);
     
    #ifdef _RTC
            _RTC_Initialize();
    #endif  /* _RTC */
     
            __try {
                // I/O初始化,暂时不做分析
                if ( _ioinit() < 0 )            /* initialize lowio */
                    _amsg_exit(_RT_LOWIOINIT);
                // 获取命令行参数
                /* get wide cmd line info */
                _tcmdln = (_TSCHAR *)GetCommandLineT();
                // 获取环境变量参数
                _tenvptr = (_TSCHAR *)GetEnvironmentStringsT();
                // 解析并设置命令行参数
                if ( _tsetargv() < 0 )
                    _amsg_exit(_RT_SPACEARG);
                // 解析并设置环境变量参数
                if ( _tsetenvp() < 0 )
                    _amsg_exit(_RT_SPACEENV);
                // 初始化全局数据和浮点寄存器
                initret = _cinit(TRUE);                  /* do C data initialize */
                if (initret != 0)
                    _amsg_exit(initret);
                // 进入(w)WinMain或者(w)main函数
    #ifdef _WINMAIN_
                lpszCommandLine = _twincmdln();
                mainret = _tWinMain( (HINSTANCE)&__ImageBase,
                                     NULL,
                                     lpszCommandLine,
                                     StartupInfo.dwFlags & STARTF_USESHOWWINDOW
                                          ? StartupInfo.wShowWindow
                                          : SW_SHOWDEFAULT
                                    );
    #else  /* _WINMAIN_ */
                _tinitenv = _tenviron;
                mainret = _tmain(__argc, _targv, _tenviron);
    #endif  /* _WINMAIN_ */
     
                if ( !managedapp )
                    exit(mainret);
     
                _cexit();
     
            }
            // 异常处理
            __except ( _XcptFilter(GetExceptionCode(), GetExceptionInformation()) )
            {
                /*
                 * Should never reach here
                 */
     
                mainret = GetExceptionCode();
     
                if ( !managedapp )
                    _exit(mainret);
     
                _c_exit();
     
            } /* end of try - except */
     
            return mainret;
    } 
    
    Copyed From 程序人生 
    Home Page:http://www.programlife.net 
    Source URL:http://www.programlife.net/msvc-crt-startup.html 

    下面分析一下_cinit函数。阉割之后的代码大致是这样的:

    int __cdecl _cinit (
            int initFloatingPrecision
            )
    {
        int initret;
        // 浮点寄存器初始化
        if (_FPinit != NULL &&
            _IsNonwritableInCurrentImage((PBYTE)&_FPinit))
        {
            (*_FPinit)(initFloatingPrecision);
        }
     
        // C语言数据初始化
        initret = _initterm_e( __xi_a, __xi_z );
        if ( initret != 0 )
            return initret;
     
        // C++数据初始化
        _initterm( __xc_a, __xc_z );
     
        return 0;
    }
    Copyed From 程序人生 
    Home Page:http://www.programlife.net 
    Source URL:http://www.programlife.net/msvc-crt-startup.html 

    首先进行浮点寄存器初始化操作,之后进行C语言数据初始化和C++数据初始化。_initterm_e函数和_initterm函数的代码差不多,差别不过是一个返回void,一个返回int。

    typedef void (__cdecl *_PVFV)(void);
     
    void __cdecl _initterm (
        _PVFV * pfbegin,
        _PVFV * pfend
        )
    {
        while ( pfbegin < pfend )
        {
            if ( *pfbegin != NULL )
                (**pfbegin)();
            ++pfbegin;
        }
    }
    Copyed From 程序人生 
    Home Page:http://www.programlife.net 
    Source URL:http://www.programlife.net/msvc-crt-startup.html 

    _PVFV是一个函数指针类型,_initterm就是遍历pfbegin到pfend(不包括pfend)之间不为NULL的函数指针并进行调用。C++全局类的构造函数就是在这个地方进行调用的,编译器会对注册函数进行预处理,填充到pfbegin和pfend之间的指针。在调用函数的时候,进行了两次解引用操作:(**pfbegin)()。这里只解引用一次就够了,或者如果你愿意,解引用N次也行:(***************pfbegin)()。对于C++全局类,调用(**pfbegin)()在调用构造函数的同时,通过atexit函数对析构函数进行了注册,使得在main返回之后析构函数能够调用:

    int __cdecl atexit (
        _PVFV func
        )
    {
        return (_onexit((_onexit_t)func) == NULL) ? -1 : 0;
    }

    析构函数的调用将程序退出之前:

    void __cdecl exit (
        int code
        )
    {
        doexit(code, 0, 0); /* full term, kill process */
    }
     
    static void __cdecl doexit (
        int code,
        int quick,
        int retcaller
        )
    {
        // ......部分代码省略
        _initterm(__xp_a, __xp_z);
     
        // ......部分代码省略
        _initterm(__xt_a, __xt_z);
     
        // ......部分代码省略
        __crtExitProcess(code);
     
        // ......部分代码省略
    }

    可以看到,还是通过调用_initterm来执行析构函数相关的代码。

    关于函数指针解引用,由编译器隐式转换成指向函数的指针。所以无论进行多少次解引用都可以,不解引用也可以。

    #include <stdio.h>
     
    typedef void (__cdecl *FN)(void);
     
    void TestFun()
    {
        printf("TestFun()
    ");
    }
     
    int main(int argc, char **argv)
    {
        FN pFn = reinterpret_cast<FN>(TestFun);
        printf("%08X
    ", pFn);
        printf("%08X
    ", *pFn);
        printf("%08X
    ", **pFn);
        pFn();    // 不解引用,直接使用函数指针
     
        return 0;
    }
    Copyed From 程序人生 
    Home Page:http://www.programlife.net 
    Source URL:http://www.programlife.net/msvc-crt-startup.html 

    输出如下:
    函数指针解引用

  • 相关阅读:
    Codeforces 787D. Legacy 线段树优化建图+最短路
    Codeforces 1051E. Vasya and Big Integers
    BZOJ3261 最大异或和
    BZOJ3531 SDOI2014 旅行
    洛谷P2468 SDOI 2010 粟粟的书架
    2018 ICPC 焦作网络赛 E.Jiu Yuan Wants to Eat
    HDU6280 From Tree to Graph
    HDU5985 Lucky Coins 概率dp
    (HDU)1334 -- Perfect Cubes (完美立方)
    (HDU)1330 -- Deck (覆盖物)
  • 原文地址:https://www.cnblogs.com/panweishadow/p/3390615.html
Copyright © 2011-2022 走看看