MSVC CRT运行库启动代码分析
来源:互联网 发布:炉石传说淘宝金币账号 编辑:程序博客网 时间:2024/05/07 04:33
在程序进入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;}
输出如下:
- MSVC CRT运行库启动代码分析
- 运行库:Windows下MSVC CRT运行库封装fread()函数解析
- MSVC与CRT默认库链接冲突问题
- MSVC与CRT默认库链接冲突问题
- MSVC与CRT默认库链接冲突问题
- MSVC與CRT的恩怨情仇
- MSVC與CRT的恩怨情仇
- glibc与MSVC CRT,crt编译错误及解决
- C运行时(CRT)库
- CRT,C++运行时库详解
- Xen从启动到运行的调度相关代码分析
- Xen从启动到运行的调度相关代码分析
- MSVC CRT的全局构造和析构(1)
- MSVC 与 CRT 之间的恩怨情仇
- MSVC CRT的全局构造和析构
- VC6启动代码分析
- Eboot启动代码分析
- S3C44B0X启动代码分析
- 往Android SDCard中写入数据
- android 回调 activity向viewpager中的fragment传值
- 运行时java对象在内存中是什么样子的?
- Eclipse+GIMP 揪出占用内存的那个图片
- vim recording功能介绍
- MSVC CRT运行库启动代码分析
- sphinx配置文件
- 教你如何开发VR游戏系列教程二:VR SDK介绍及开发流程介绍
- 杭电ACM1568——Fibonacci
- jquery获取各种鼠标位置
- PSPICE中的各种库文件说明
- Mac安装非app store下载的软件
- 获取android SDCard存储大小
- mybatis使用经验