原文链接: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=;
int managedapp;
#ifdef _WINMAIN_
_TUCHAR *lpszCommandLine;
STARTUPINFOW StartupInfo; GetStartupInfoW( &StartupInfo );
#endif /* _WINMAIN_ */ #ifdef _M_IX86
// 对于32位程序,设置为如果检测到堆败坏则则自动结束进程
// 64位程序默认就设置了这个行为
if (!_NoHeapEnableTerminationOnCorruption)
{
HeapSetInformation(NULL, HeapEnableTerminationOnCorruption, NULL, );
}
#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() < ) /* initialize lowio */
_amsg_exit(_RT_LOWIOINIT);
// 获取命令行参数
/* get wide cmd line info */
_tcmdln = (_TSCHAR *)GetCommandLineT();
// 获取环境变量参数
_tenvptr = (_TSCHAR *)GetEnvironmentStringsT();
// 解析并设置命令行参数
if ( _tsetargv() < )
_amsg_exit(_RT_SPACEARG);
// 解析并设置环境变量参数
if ( _tsetenvp() < )
_amsg_exit(_RT_SPACEENV);
// 初始化全局数据和浮点寄存器
initret = _cinit(TRUE); /* do C data initialize */
if (initret != )
_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 != )
return initret; // C++数据初始化
_initterm( __xc_a, __xc_z ); return ;
}
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) ? - : ;
}

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

void __cdecl exit (
int code
)
{
doexit(code, , ); /* 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 ;
}
Copyed From 程序人生
Home Page:http://www.programlife.net
Source URL:http://www.programlife.net/msvc-crt-startup.html

输出如下:

MSVC CRT运行库启动代码分析的更多相关文章

  1. STM32启动代码分析 IAR 比较好

    stm32启动代码分析 (2012-06-12 09:43:31) 转载▼     最近开始使用ST的stm32w108芯片(也是一款zigbee芯片).开始看他的启动代码看的晕晕呼呼呼的. 还好在c ...

  2. Linux内核启动代码分析二之开发板相关驱动程序加载分析

    Linux内核启动代码分析二之开发板相关驱动程序加载分析 1 从linux开始启动的函数start_kernel开始分析,该函数位于linux-2.6.22/init/main.c  start_ke ...

  3. Cortex-M0(NXP LPC11C14)启动代码分析

    作者:刘老师,华清远见嵌入式学院讲师. 启动代码的一般作用 1.堆和栈的初始化: 2.向量表定义: 3.地址重映射及中断向量表的转移: 4.初始化有特殊要求的断口: 5.处理器模式: 6.进入C应用程 ...

  4. Android 4.2启动代码分析(一)

    Android系统启动过程分析 Android系统的框架架构图如下(来自网上):   Linux内核启动之后----->就到Android的Init进程 ----->进而启动Android ...

  5. ARM Linux启动代码分析

    前言 在学习.分析之前首先要弄明白一个问题:为什么要分析启动代码? 因为启动代码绝大部分都是用汇编语言写的,对于没学过或者不熟悉汇编语言的同学确实有一定难度,但是如果你想真正深入地学习Linux,那么 ...

  6. STM32启动代码分析

    STM32启动文件简单分析(STM32F10x.s适用范围)定时器, 型号, 名字在<<STM32不完全手册里面>>,我们所有的例程都采用了一个叫STM32F10x.s的启动文 ...

  7. STM32启动代码分析及其汇编学习-ARM

    STM32 启动代码 Author By YuCloud 边看启动文件边学汇编 汇编 see ARM: Assembler User Guide see: https://blog.csdn.net/ ...

  8. S3C6410的启动代码分析&nbsp;一

    本文开始第一篇,启动代码的编写,注意,仅仅是启动代码,并不是bootloader,因为只有boot,没有loader. 第一要明确:CPU上电之后,会从某个固定地址执行指令.ARM结构的CPU从地址0 ...

  9. KEA128单片机启动代码分析

    ;/*****************************************************************************; * @file: startup_SK ...

随机推荐

  1. SVN常见问题处理

    1.SVN 提交时报错:Path is not a working copy directory   (1)报这个错通常是因为这个文件夹或其上层文件夹不是checkout或update出来的. 使用S ...

  2. windows phone 8 开发系列(三)程序清单说明与配置

    一 清单文件内容介绍 当我们先建了一个项目之后,我们可以看到vs自动会为我们创建了很多文件,正常人都会先一个个去翻看下每个文件都是干啥的,都主要写了些啥,在这些文件中,在Properies目录下面,我 ...

  3. Factory_Method

    class Product { public: virtual ~Product() {} ; }; class ProductA : public Product { public: Product ...

  4. .Net 将一个DataTable分解成多个DataTable

    这两天遇到一个问题,我们所接触 的一个系统在导出数据到Excel的时候,产生了内存溢出的错误.原因在于数据过大,它导出是将所有数据存放在一个DataSet的一个表中,再将这个数 据集放入session ...

  5. MongoDB五种树形结构表示法

    MongoDB五种树形结构表示法 第一种:父链接结构 db.categories.insert( { _id: "MongoDB", parent: "Databases ...

  6. javascript 创建对象及对象原型链属性介绍

    我们知道javascript里定义一个普通对象的方法,如: let obj = {}; obj.num = 1; obj.string = 'string'; obj.func = function( ...

  7. scp实现mac与linux服务器之间文件传输

    1.mac上传文件到linux服务器 scp 文件名 用户名@服务器ip:目标路径如:scp /Users/test/testFile test@xxx.xxx.xxx.xxx:/test/ 2.ma ...

  8. mysql rand随机查询记录效率

    一直以为mysql随机查询几条数据,就用 SELECT * FROM `table` ORDER BY RAND() LIMIT 5 就可以了. 但是真正测试一下才发现这样效率非常低.一个15万余条的 ...

  9. spring boot单元测试(转)

    Junit这种老技术,现在又拿出来说,不为别的,某种程度上来说,更是为了要说明它在项目中的重要性.凭本人的感觉和经验来说,在项目中完全按标准都写Junit用例覆盖大部分业务代码的,应该不会超过一半. ...

  10. delphi图形图像开发相关

    ①delphi的图形处理(doc) http://wenku.baidu.com/view/519df09951e79b89680226ee.html ②delphi的图形图像处理(ppt) http ...