原文链接: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. 8款效果惊艳的HTML5 3D动画

    1.HTML5 WebGL水面水波荡漾特效 之前已经向各位分享过一款很逼真的HTML5水波荡漾特效,效果还算不错.今天再向大家分享一款更加给力的HTML5水波动画,画面上是一个大水池,水池底部是一颗大 ...

  2. 【转】c# yield用法

    注意事项:1.yield语句只能出现在迭代块中:2.yield return 语句不能放在 try-catch 块中的任何位置. 该语句可放在后跟 finally 块的 try 块中3.yield b ...

  3. Winform 拦截最小化、最大化、关闭事件【整理】

    const int WM_SYSCOMMAND = 0x112; //窗体关闭消息 const int SC_CLOSE = 0xf060; //窗体最小化消息 const int SC_MINIMI ...

  4. DOS环境下含包并引用第三方jar的java程序的编译及运行

    DOS环境下含包并引用第三方jar的java程序的编译及运行 1.程序目录机构 bin:class文件生成目录 lib:第三方jar包目录 src:源程序文件目录 2.程序代码: 3.程序编译 jav ...

  5. mvc涉及到input设置了disabled

    在做网站管理后台的用户修改功能时,由于当前用户修改个人信息时规定用户名不能修改,故使用了input标签的disabled属性,但是在提交数据后却发现用户名显示为空了.后来一查才知道input设置为di ...

  6. apache+tomcat整合

    一 .Apache与Tomcat的比较 apache支持静态页面,tomcat支持动态的,比如servlet等. 一般使用apache+tomcat的话,apache只是作为一个转发,对jsp的处理是 ...

  7. C# 获得手机归属地功能

    今天通过查资料了解到web的页面抓取功能,应用HttpWebRequest和HttpWebResponse功能,从http://www.showji.com网站中抓取归属地信息 应该说这个方法是从别的 ...

  8. MySQL SHOW 语句大全

    常用的mysql show命令如下: 1.show databases;        显示mysql中所有数据库 2.show tables [from databases] ;     显示当前数 ...

  9. DataGridView 使用CheckBox选中行

    在winform中使用checbox很多.上次那个项目里就用到了,写了一个不太好用,后来翻阅了一下微软提供的样码,我觉得有必要给大家分享一下. // This event handler manual ...

  10. UML中的六种关系的比较与学习

    通过不断的学习并绘制UML图,整个画图的过程中深刻体会到其核心部分还是理解事物之间的关系,总结六大关系来深入学习,主要关系有六种:继承.实现.依赖.关联.聚合.组合. 区别于联系:         1 ...