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 ...
随机推荐
- C# 网卡IP(网上资料整理)
//设置对外访问所使用网卡的IP string sendingIp = "192.168.0.1"; //设置对外访问所使用的端口 ; Uri uri = new Uri(&quo ...
- Winform ListView根据条件定位到指定行
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; usin ...
- 初测WIN10
WIN10已经发布,通过百度直通车把WIN7升级成了WIN10,改变较大,不太习惯,用着不是很顺手. 吐槽几个问题 1.微软的Visual Studio 2015 Community版本,宣布是免费的 ...
- 纯js分页代码(简洁实用)
纯js写的分页代码. 复制代码代码如下: //每页显示字数 PageSize=5000; //分页模式 flag=2;//1:根据字数自动分页 2:根据[NextPage]分页 //默认页 start ...
- liunx下vi命令详解
vi编辑器是所有Unix及Linux系统下标准的编辑器,它的强大不逊色于任何最新的文本编辑器,这里只是简单地介绍一下它的用法和一小部分指令.由于对Unix及Linux系统的任何版本,vi编辑器是完全相 ...
- linux前景到底怎么样啊?
我就不长篇大论,举四个例子你看看. 1.目下最热最潮最流行的云计算技术的背后是虚拟化和网格技术,而虚拟化和网格技术基本是Linux的天下,目前虚拟化的三大家:Vmware,Xen,Hyper-V中,市 ...
- session 测试用例详解
http协议是WEB服务器与客户端(浏览器)相互通信的协议,它是一种无状态协议.所谓无状态,指的是不会维护http请求数据,http请求是独立的,非持久的.而越来越复杂的WEB应用,需要保存一些用户状 ...
- HTTP 错误 404.3 - Not Found
在使用win2012服务器上的IIS发布网页的时候,出现下面的错误 解决办法: 将应用程序开发下的所有功能都安装. 如果上面的方法没解决问题的话,那么看看下图中的这些安装没,没有的话就继续安装.
- java_Thread生产者与消费者 Demo
package com.bjsxt.Thread.Demo; public class ProducerConsumer { /** * 生产者与消费者 * @param args */ public ...
- ios开发之数据的持久化存储机制
IOS中数据的持久化保存这块内容,类似于Android中文件的几种常见的存储方式. 对于数据的持久化存储,ios中一般提供了4种不同的机制. 1.属性列表 2.对象归档 3.数据库存储(SQLite3 ...