• 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 

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

  • 相关阅读:
    记录ci框架中定时任务的执行
    2019 年MySQL面试题及答案
    Net线程问题解答(转)
    vs2005 Team System的版本
    ASP.NET 安全认证(如何运用 Form 表单认证)
    .net调用存储过程时的输出函数
    在服务器执行js脚本
    简单的批量更新(小技巧)
    UNION 和UNION ALL 的区别
    ServerVariable(环境变量)
  • 原文地址:https://www.cnblogs.com/panweishadow/p/3390615.html
Copyright © 2020-2023  润新知