VS下EXE可执行文件启动代码剖析

    当我们在学C/C++语言编程的时候,老师总是跟我们说程序是从Main函数开始的,然而当我第一次用OD分析一个可以执行文件EXE的时候,找这个Main函数就花了我好长时间,后来才知道在Windows下不同的编译器都会在生产最后的可执行PE文件的时候,在我们写的启动函数之前加入一些编译器附带的启动代码,来做一些初始化工作,比如初始化全局变量,运行全局类对象的构造函数,初始化C运行时库等等。对于有(知其然知其所以然)强迫症的我,为此在网上爬了不少帖,也自己调试分析了好几天VS的启动代码,所以贴出来备忘。

    在程序进入main/WinMain函数之前,通过在Visual Studio中调试,通过栈回溯可以找到位于crt0.c中的_tmainCRTStartup函数,这个函数负责进行一些初始化操作,因为C运行库代码又有两个版本,如果是静态编译的话代码位于crt0.c之中,如果是动态编译的话代码位于crtexe.c之中,这里可以通过项目属性的“配置属性”——“C/C++”——“代码生成”——“运行库”的MT和MD进行设置。

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

static
int
__tmainCRTStartup(
         void
         );

#ifdef _WINMAIN_

#ifdef WPRFLAG
int wWinMainCRTStartup(
#else  /* WPRFLAG */
int WinMainCRTStartup(
#endif  /* WPRFLAG */

#else  /* _WINMAIN_ */

#ifdef WPRFLAG
int wmainCRTStartup(
#else  /* WPRFLAG */
int mainCRTStartup(
#endif  /* WPRFLAG */

#endif  /* _WINMAIN_ */
        void
        )
{
        /*
         * The /GS security cookie must be initialized before any exception
         * handling targetting the current image is registered.  No function
         * using exception handling can be called in the current image until
         * after __security_init_cookie has been called.
         */
        __security_init_cookie();

        return __tmainCRTStartup();
}


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

先来看下使用静态C运行库(/MT /MTd)时的代码

位于crt0.c

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头中的标志,判断是否为.net托管程序
        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;
} 

先来看下_Heap_Init
int __cdecl _heap_init (void)
{
        ULONG HeapType = 2;

        //  Initialize the "big-block" heap first.
        if ( (_crtheap = HeapCreate(0, BYTES_PER_PAGE, 0)) == NULL )  _crtheap  全局堆句柄  mallco 函数使用该句柄
            return 0;

#ifdef _WIN64
        // Enable the Low Fragmentation Heap by default on Windows XP and
        // Windows Server 2003.  It's the 8 byte overhead heap, and has
        // generally better performance charateristics than standard heap,
        // particularly for apps that perform lots of small allocations.

        if (LOBYTE(GetVersion()) < 6)
        {
            HeapSetInformation(_crtheap, HeapCompatibilityInformation,
                               &HeapType, sizeof(HeapType));
        }
#endif  /* _WIN64 */
        return 1;
}

这个函数用HeapCreate创建一个私有堆供标准C函数malloc使用来分配内存,它的第三个参数为0表示堆可以自动增长。因为是静态链接C运行库,所以在C运行库代码是嵌入到应用程序里的,因此_crtheap为应用程序私有,如果该应该程序调用一个同样使用静态运行库的DLL,那么该DLL同样会创建属于该DLL的_crtheap并调用HeapCreate为其获取句柄标识。

来看来malloc的实现位于 smallheap.c文件中

void * __cdecl malloc (
        size_t size
        )
{
        return _nh_malloc( size, _newmode );
}
void __cdecl free (
        void * pblock
        )
{
        if ( pblock == NULL )
            return;

        HeapFree(_crtheap, 0, pblock);
}


_nh_malloc同样位于这个文件中

void * __cdecl _nh_malloc (
        size_t size,
        int nhFlag
        )
{
        void * retp;

        for (;;) {

            retp = HeapAlloc( _crtheap, 0, size );  //使用全局句柄来申请空间

            /*
             * if successful allocation, return pointer to memory
             * if new handling turned off altogether, return NULL
             */

            if (retp || nhFlag == 0)
                return retp;

            /* call installed new handler */
            if (!_callnewh(size))
                return NULL;

            /* new handler was successful -- try to allocate again */
        }
}


在这个C文件中也有一个初始化堆的函数

int __cdecl _heap_init (
        void
        )
{
        if ( (_crtheap = HeapCreate( 0, BYTES_PER_PAGE, 0 )) == NULL )
            return 0;

        return 1;
}

通过翻代码可以找到它在dllcrt0.c中和crtlib.c被调用


dllcrt0.c

BOOL WINAPI _CRT_INIT(       DLL使用静态运行库时被调用
        HANDLE  hDllHandle,
        DWORD   dwReason,
        LPVOID  lpreserved
        )
{
        /*
         * Start-up code only gets executed when the process is initialized
         */

        if ( dwReason == DLL_PROCESS_ATTACH )
        {
            if ( !_heap_init() )    /* initialize heap */
                return FALSE;  



crtlib.c

__CRTDLL_INIT(               //使用动态运行库时被调用
        HANDLE  hDllHandle,
        DWORD   dwReason,
        LPVOID  lpreserved
        )
{
        if ( dwReason == DLL_PROCESS_ATTACH ) {
            if ( !_heap_init() )    /* initialize heap */


上面使用静态C运行库的DLL程序的启动代码的一部分,和使用动态运行库msvcrxx.dll时被调用的一部分,因此不难理解,当我们调用一个使用静态C运行库的DLL,或者一个使用动态C运行库的DLL, 他们分别创建和使用自己的私有堆,并用各自的_crtheap全局句柄来标识。如果这个DLL有个导出的函数,该函数使用malloc申请了一块内存并返回指针。

当我们编写一个使用静态运行库的EXE来调用这个DLL导出的该函数的时候,用Free来释放已申请的内存,会导致程序崩溃就不难理解了,相应的如果EXE用动态运行库来编写,那么DLL使用静态库时报错,使用动态运行库时候工作正常,因为它们调用的都是msvcrxx.DLL中的malloc 和 free,自然引用的是同一个_crtheap全局句柄,该句柄在mscrcxx.dll中。



由此我们不难看到在编写Windows程序的时候如果大量使用C标准库函数,应该使用动态运行库来避免一些不必要的错误。

使用C++的 new关键字来为对象分配空间的时候同样是使用_crtheap为标识 使用HeapAlloc来申请堆内存的。因此应该在同一个模块中使用NEW 和Delete来申请和释放堆对象


                
  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值