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=;
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运行库启动代码分析的更多相关文章
- STM32启动代码分析 IAR 比较好
stm32启动代码分析 (2012-06-12 09:43:31) 转载▼ 最近开始使用ST的stm32w108芯片(也是一款zigbee芯片).开始看他的启动代码看的晕晕呼呼呼的. 还好在c ...
- Linux内核启动代码分析二之开发板相关驱动程序加载分析
Linux内核启动代码分析二之开发板相关驱动程序加载分析 1 从linux开始启动的函数start_kernel开始分析,该函数位于linux-2.6.22/init/main.c start_ke ...
- Cortex-M0(NXP LPC11C14)启动代码分析
作者:刘老师,华清远见嵌入式学院讲师. 启动代码的一般作用 1.堆和栈的初始化: 2.向量表定义: 3.地址重映射及中断向量表的转移: 4.初始化有特殊要求的断口: 5.处理器模式: 6.进入C应用程 ...
- Android 4.2启动代码分析(一)
Android系统启动过程分析 Android系统的框架架构图如下(来自网上): Linux内核启动之后----->就到Android的Init进程 ----->进而启动Android ...
- ARM Linux启动代码分析
前言 在学习.分析之前首先要弄明白一个问题:为什么要分析启动代码? 因为启动代码绝大部分都是用汇编语言写的,对于没学过或者不熟悉汇编语言的同学确实有一定难度,但是如果你想真正深入地学习Linux,那么 ...
- STM32启动代码分析
STM32启动文件简单分析(STM32F10x.s适用范围)定时器, 型号, 名字在<<STM32不完全手册里面>>,我们所有的例程都采用了一个叫STM32F10x.s的启动文 ...
- STM32启动代码分析及其汇编学习-ARM
STM32 启动代码 Author By YuCloud 边看启动文件边学汇编 汇编 see ARM: Assembler User Guide see: https://blog.csdn.net/ ...
- S3C6410的启动代码分析 一
本文开始第一篇,启动代码的编写,注意,仅仅是启动代码,并不是bootloader,因为只有boot,没有loader. 第一要明确:CPU上电之后,会从某个固定地址执行指令.ARM结构的CPU从地址0 ...
- KEA128单片机启动代码分析
;/*****************************************************************************; * @file: startup_SK ...
随机推荐
- Android四大组件之一:BroadCastReceiver(广播接收者)
广播接受者是(BroadCastReceiver)是Android中的地大组件之一,之前学习了一些关于BroadCastReceiver方面的知识,今天回过头来发现已经快忘记的差不多了,毕竟现在是刚开 ...
- OpenGL第12-14讲小结
首先要为自己为什么没有写第10讲的控制3D场景和第11讲的红旗飘飘呢?因为没看啊~哈哈哈,而且我尝试着运行红旗飘飘的时候电脑蓝屏了(可能不是它的锅),暂时跳过了. 恩,12到14主要了解了这么些东西, ...
- Windows Phone 中查找可视化树中的某个类型的元素
private void StackPanel_Tap(object sender, TappedRoutedEventArgs e) { //获取到的对象是ListBoxItem ListBoxIt ...
- (转)Jmeter内存溢出处理方式记录
方法一: 使用jmeter进行压力测试时 遇到一段时间后报内存溢 出outfmenmory错误,导致jmeter卡死了,先尝试在jmeter.bat中增加了JVM_ARGS="- Xmx20 ...
- Linux大量TIME_WAIT的解决办法
发布:theboy 来源:net [大 中 小] 根据TCP协议定义的3次握手断开连接规定,发起socket主动关闭的一方 socket将进入TIME_WAIT状态,TIME_WAIT状态将持 ...
- select标签用法
<select name="type" class="textarea" onchange='bbbb(this.value)' > <opt ...
- Gartner2014年魔力象限(商业智能和分析平台)
- [ios]ios的延迟执行方法
1.最直接的方法performSelector:withObject:afterDelay: 这种方法的缺点:每次要为延时写一个方法 2.使用类别,用BOLCK执行 [代码]c#/cpp/oc代码 ...
- mac下wget用来仿站
wget -c -r -np -k -L -p http://www.domain.com 参考 http://www.v2ex.com/t/147870
- asp.net 多个域名重定向,在web.Config中配置
一个网站有多个域名,但是需要在访问其中某个域名之后跳转到另一域名. Web.config 中配置 </system.webServer> <!--重定向 域名 开始--> &l ...