MSVC CRT运行库启动代码分析

转载 2015年11月19日 15:40:50

在程序进入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_ */

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

int
_tmainCRTStartup(
        void
        )
{
        __security_init_cookie();
 
        return __tmainCRTStartup();
}

我的测试环境是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;
}

首先进行浮点寄存器初始化操作,之后进行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;
    }
}

_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()\n");
}
 
int main(int argc, char **argv)
{
	FN pFn = reinterpret_cast<FN>(TestFun);
	printf("%08X\n", pFn);
	printf("%08X\n", *pFn);
	printf("%08X\n", **pFn);
	pFn();    // 不解引用,直接使用函数指针
 
	return 0;
}

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

程序员的自我修养---C/C++运行库

来源:http://linux.ctocio.com.cn/470/8881970.shtml 作者:俞甲子        任何一个C程序,它的背后都有一套庞大的代码来进行支撑,以使得该程序能够正...
  • chence19871
  • chence19871
  • 2012年05月04日 11:40
  • 1281

[读书笔记]程序员的自我修养 chp13 一个简单的CRT 运行库实现

0. 前言 目标:实现一个小型的 CRT 运行库 本质: 利用系统提供的api接口, 实现一个通用的 CRT 函数接口, 使得 C 语言程序可以自由的运行在各个不同的系统上 项目工程地址: https...
  • zhyh1435589631
  • zhyh1435589631
  • 2016年12月22日 22:03
  • 499

运行库与多线程

11.3  运行库与多线程 11.3.1  CRT的多线程困扰 线程的访问权限 线程的访问能力非常自由,它可以访问进程内存里的所有数据,甚至包括其他线程的堆栈(如果它知道其他线程的堆...
  • wangpengk7788
  • wangpengk7788
  • 2016年12月31日 21:37
  • 319

MSVC与CRT默认库链接冲突问题

我在vc2008上编译一个小程序,在该程序中连接的另一个lib(使我自定义的)。报如下错误: 1>Linking... 1>msvcrtd.lib(MSVCR90D.dll) : error ...
  • u012377333
  • u012377333
  • 2014年11月04日 15:50
  • 1148

运行库:Windows下MSVC CRT运行库封装fread()函数解析

在介绍运行库的过程中,强调过运行库是具体语言实现的程序和操作系统之间的抽象层。经验表明,任何系统级的软件工程,IO功能的封装历来是最具有挑战性的。以下以Windows下MSVC CRT运行库中封装的文...
  • roger_ranger
  • roger_ranger
  • 2017年10月18日 23:25
  • 67

cmake设置msvc的运行库(runtime library)塈指定openjpeg使用静态库

cmake这个跨平台的make工具功能已经很强大了,但它也有不足的地方,就是本文的标题。 在用cmake生成NMake的Makefile或visual studio的.sln时,如何指定运行库(/M...
  • 10km
  • 10km
  • 2016年06月22日 17:04
  • 2087

运行库:Linux下glibc和Windows下MSVC CRT对比

任何一个C程序要想要得到实现,都离不开背后的一套庞大的代码来进行支持(至少包括入口函数,以及其所依赖的函数所构成的函数集合等),这样一套背后代码被称为运行库,C语言的运行库叫做CRT(Runtime ...
  • roger_ranger
  • roger_ranger
  • 2017年12月28日 13:45
  • 77

[Windows]_[初级]_[/MD、/MT、/LD(使用运行库)]

原文链接: http://msdn.microsoft.com/zh-cn/library/2kzt1wy3.aspx 指示多线程模块是否为 DLL,并指定运行库的零售版本或调试版本。 ...
  • infoworld
  • infoworld
  • 2014年12月03日 09:42
  • 1107

解决vs2013性能分析工具报verfermon.exe 无法启动的错误

在vs2013下性能分析工具默认是没有安装的  需要在安装盘或者安装文件夹下找到Standalone Profiler文件夹里找到安装文件安装即可...
  • qbook
  • qbook
  • 2015年02月11日 09:20
  • 2937

VS项目属性中C/C++运行库 、MT /MTd /MD /MDd

from :http://blog.csdn.net/ithzhang/article/details/20160009 周五晚,小雨,少见的未加班。无聊,遂准备写一篇博客,介绍一下C和C+...
  • u010059658
  • u010059658
  • 2016年03月31日 15:03
  • 2024
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:MSVC CRT运行库启动代码分析
举报原因:
原因补充:

(最多只允许输入30个字)