今天终于有时间来研究一下一个很大很大的工程编译成一个exe和若干dll后,程序是如果执行它的第一条指令的?操作系统以什么规则来找到应该执行的第一条指令(或说如何找到第一个入口函数的)?

我们以前写windows程序时,都是先写个main()函数,然后再写自己的逻辑;然后编译,然后点击exe就能运行我们的程序了;如果我们用VS2005工具生成一个非空工程,工程会为我们提供一个int _tmain(int argc, _TCHAR* argv[])或WinMain()函数的入口,然后我们在里面添加程序等等。我们上学时这是这样做了,但是很多人这时理所当然的,很少人会去问为什么会这样?

我读了MSDN里面的讲解才弄出点眉目了,其实我们以前所写的以main()函数开始的程序都是一个半成品,剩下的也是与系统息息相关的工作由编译器帮我们代劳了。怎么回事呢?编译器是如何帮我们代劳的呢?那么程序被系统加载时,准确的说是被系统中的加载器加载时又是如何知道编译器在我们写的程序上做了手脚呢?难道编译器和加载器之间有什么协定吗?这一些列的问题,做为刚入行的你是否在心里问过自己没有!?

我们以前写的程序在编译器编译成为一个模块(可能是obj文件或其他形式),然后连接器会将一些所需要的库文件和刚才编译器生成的文件进行连接,最终生成一个exe文件,在所连接的库文件中就包含CRT运行时库,这就是我们今天谈论的主角。在运行时库里面有好一个已经定义如下的函数函数:

(1)mainCRTStartup(或 wmainCRTStartup)             //使用 /SUBSYSTEM:CONSOLE 的应用程序

(2)WinMainCRTStartup(或 wWinMainCRTStartup)//使用 /SUBSYSTEM:WINDOWS 的应用程序

(3)_DllMainCRTStartup                   //调用 DllMain(如果存在),DllMain 必须用 __stdcall 来定义

其中w开头的函数时unicode版本的,分割符‘//’后面的是入口点函数匹配的subsystem属性设置。

如果未指定 /DLL 或 /SUBSYSTEM (也就是subsystem选项)选项,则链接器将根据是否定义了 main 或 WinMain 来选择子系统和入口点。 函数 main、WinMain 和 DllMain 是三种用户定义的入口点形式。

在默认情况下,如果你的程序中使用的是main()或_main()函数,这连接器会将你的使用(1)中的函数连接到你的exe中;如果你的函数是以WinWain()函数开始的则连接器使用(2)中的函数连接进exe中;如果我们写的是DLL程序这连接进DLL的是(3)中的函数。

用我们写的程序最终生成的exe执行时,一开始执行的就是上面的函数之一,而不是我们程序所写的main或WinMain等。那么连接器为什么要这样做呢?这就是因为我们写的程序必须要使用到各种各样的运行时库函数才能正常工作,所有在执行我们自己写程序之前必须要先准备好所需要的一切库,噢,明白了吧,之所以要连接它们是因为他们肩负着很重要的使命,就是初始化好运行时库,准备我们的程序执行时调用。

那么这些函数具体做了什么呢?通过MSDN我们可以知道---它们会去进一步调用其他函数,使得C/C++ 运行时库代码在静态非局部变量上调用构造函数和析构函数。

先摘录一段msdn的解释如下:

When you link your image, you either explicitly or implicitly specify an entry point that the operating system will call into after loading the image. For a DLL, the default entry point is DllMainCRTStartup. For an EXE, it isWinMainCRTStartup. You can override the default with the /ENTRY linker option. The CRT provides an implementation for DllMainCRTStartup, WinMainCRTStartup, and wWinMainCRTStartup (the Unicode entry point for an EXE). These CRT-provided entry points call constructors on global objects and initialize other data structures      that are used by some CRT functions. This startup code adds about 25K to your image if it is linked statically. If it is linked dynamically, most of the code is in the DLL, so your image size stays small.

大家看到了吧,上面我用红色标志了吗,我们可以使用连接器的链接选择来设置我们的函数入口点,但是最好不要这样做,原因就是我用蓝色标志的地方,如果我们重新设置入口点函数,则必须要在入口点函数中自己写上有关的初始化工作,这样岂不麻烦,所有我们最好用默认的入口点函数。

修改入口点方法:proerties->Linker->Advanced->EntryPoint

如果函数与连接器的SubSystem的属性要一致的:

proerties->Linker->System->SubSystem

如果未指定 /DLL 或 /SUBSYSTEM 选项,则链接器将根据是否定义了 main 或 WinMain 来选择子系统和入口点。 函数 main、WinMain 和 DllMain 是三种用户定义的入口点形式。

通过上面的分析就知道,在微软系统中原来操作系统中的加载器与连接器之间是有协议的,要不然在加载运行程序时不可能成功的,比如你将windows程序放到apple系统上运行,就会无法运行,因为apple的加载程序根本不知道加载windows的exe的协议。

【注意】

/***   
  *mainCRTStartup(void)   
  *wmainCRTStartup(void)   
  *WinMainCRTStartup(void)   
  *wWinMainCRTStartup(void)   
  *   
  *Purpose:   
  *               These   routines   do   the   C   runtime   initialization,   call   the   appropriate   
  *               user   entry   function,   and   handle   termination   cleanup.     For   a   managed   
  *               app,   they   then   return   the   exit   code   back   to   the   calling   routine,   which   
  *               is   the   managed   startup   code.     For   an   unmanaged   app,   they   call   exit   and   
  *               never   return.   
  *   
  *               Function:                               User   entry   called:   
  *               mainCRTStartup                     main   
  *               wmainCRTStartup                   wmain   
  *               WinMainCRTStartup               WinMain   
  *               wWinMainCRTStartup             wWinMain   
  *   
  *   
  *******************************************************************************/

给出一篇博文,该博文讲的比较好:

Trackback: http://tb.blog.csdn.net/TrackBack.aspx?PostId=455591

设有一个Win32下的可执行文件MyApp.exe,这是一个Win32应用程序,符合标准的PE格式。MyApp.exe的主要执行代码都集中在其源文件MyApp.cpp中,该文件第一个被执行的函数是WinMain。初学者会认为程序就是首先从这个WinMain函数开始执行,其实不然。

在WinMain函数被执行之前,有一系列复杂的加载动作,还要执行一大段启动代码。运行程序MyApp.exe时,操作系统的加载程序首先为进程分配一个4GB的虚拟地址空间,然后把程序MyApp.exe所占用的磁盘空间作为虚拟内存映射到这个4GB的虚拟地址空间中。一般情况下,会映射到虚拟地址空间中0X00400000的位置。加载一个应用程序的时间比一般人所设想的要少,因为加载一个PE文件并不是把这个文件整个一次性的从磁盘读到内存中,而是简单的做一个内存映射,映射一个大文件和映射一个小文件所花费的时间相差无几。当然,真正执行文件中的代码时,操作系统还是要把存在于磁盘上的虚拟内存中的代码交换到物理内存(RAM)中。但是,这种交换也不是把整个文件所占用的虚拟地址空间一次性的全部从磁盘交换到物理内存中,操作系统会根据需要和内存占用情况交换一页或多页。当然,这种交换是双向的,即存在于物理内存中的一部分当前没有被使用的页也可能被交换到磁盘中。

接着,系统在内核中创建进程对象和主线程对象以及其它内容。

然后操作系统的加载程序搜索PE文件中的引入表,加载所有应用程序所使用的动态链接库。对动态链接库的加载与对应用程序的加载完全类似。

再接着,操作系统执行PE文件首部所指定地址处的代码,开始应用程序主线程的执行。首先被执行的代码并不是MyApp中的WinMain函数,而是被称为C Runtime startup code的WinMainCRTStartup函数,该函数是连接时由连接程序附加到文件MyApp.exe中的。该函数得到新进程的全部命令行指针和环境变量的指针,完成一些C运行时全局变量以及C运行时内存分配函数的初始化工作。如果使用C++编程,还要执行全局类对象的构造函数。最后,WinMainCRTStartup函数调用WinMain函数。

WinMainCRTStartup函数传给WinMain函数的4个参数分别为:hInstance、hPrevInstance、lpCmdline、nCmdShow。

hInstance:该进程所对应的应用程序当前实例的句柄。WinMainCRTStartup函数通过调用GetStartupInfo函数获得该参数的值。该参数实际上是应用程序被加载到进程虚拟地址空间的地址,通常情况下,对于大多数进程,该参数总是0X00400000。

hPrevInstance:应用程序前一实例的句柄。由于Win32应用程序的每一个实例总是运行在自己的独立的进程地址空间中,因此,对于Win32应用程序,WinMainCRTStartup函数传给该参数的值总是NULL。如果应用程序希望知道是否有另一个实例在运行,可以通过线程同步技术,创建一个具有唯一名称的互斥量,通过检测这个互斥量是否存在可以知道是否有另一个实例在运行。

lpCmdline:命令行参数的指针。该指针指向一个以0结尾的字符串,该字符串不包括应用程序名。

nCmdShow:指定如何显示应用程序窗口。如果该程序通过在资源管理器中双击图标运行,WinMainCRTStartup函数传给该参数的值为SW_SHOWNORMAL。如果通过在另一个应用程序中调用CreatProcess函数运行,该参数由CreatProcess函数的参数lpStartupInfo(STARTUPINFO.wShowWindow)指定。

----------------------------------------------------------------------------------------

操作系统装载应用程序后,做完初始化工作就转到程序的入口点执行。程序的默认入口点由连接程序设置, 不同的连接器选择的入口函数也不尽相同。在VC++下,连接器对控制台程序设置的入口函数是 mainCRTStartup,mainCRTStartup 再调用main 函数;对图形用户界面(GUI)程序设置的入口函数是 WinMainCRTStartup,WinMainCRTStartup 调用你自己写的 WinMain 函数。具体设置哪个入口点是由连接器的“/subsystem:”选项确定的,它告诉操作系统如何运行编译生成的.EXE文件。可以指定四种方式:CONSOLE|WINDOWS|NATIVE|POSIX。如果这个选项参数的值为 WINDOWS,则表示该应用程序运行时不需要控制台,有关连接器参数选项的详细说明请参考 MSDN 库。

以下四种组合,可以实现console和windows模式的混合,可以达到不弹出DOS窗口的效果,也可以达到在Windows程序中向控制台输出printf信息的目的。
#pragma comment( linker, "/subsystem:windows /entry:WinMainCRTStartup" )
#pragma comment( linker, "/subsystem:windows /entry:mainCRTStartup" )
#pragma comment( linker, "/subsystem:console /entry:mainCRTStartup" )
#pragma comment( linker, "/subsystem:console /entry:WinMainCRTStartup" )

int APIENTRY WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR      lpCmdLine,
int        nCmdShow)
{
      // ...
}

int main(void)
{
      // ...
}

http://blog.csdn.net/xhhjin/article/details/6659028

http://blog.csdn.net/xhhjin/article/details/6658958

可执行程序的入口点在那里?(强化概念:程序真正的入口是mainCRTstartup)的更多相关文章

  1. 在纯C工程的main函数之前跑代码(手工找到程序入口点, 替换为我们自己的函数)

    在main函数之前跑代码的方法 方法: 手工找到程序入口点, 替换为我们自己的函数 写测试程序 // test.cpp : Defines the entry point for the consol ...

  2. 逆向课程第二讲,寻找main入口点

    逆向课程第二讲,寻找main入口点 一丶识别各个程序的入口点 入门知识,识别各个应用程序的入口点 (举例识别VC 编译器生成,以及VS编译生成的Debug版本以及Release版本) 1.识别VC6. ...

  3. Windows 程序启动性能优化(先载入EXE,后载入DLL,只取有限的代码载入内存,将CPU的IP指向程序的入口点)

    一.重定位链接时重定位:目标文件一般由多个节组成,编译器在编译每个目标文件时一般都是从0地址开始生成代码.当多个代码节合成一个代码段时,需要根据其在最终代码段中的位置做出调整.同时,链接器需要对已经解 ...

  4. 模块shimgvw.dll已加载,但找不到入口点DllRegisterServer

    [环境]Windows 7 / Windows Server 2008 [现象]在文件夹浏览器中不能显示图片缩略图. [错误信息]查系统日志,有如下消息:“模块shimgvw.dll已加载,但找不到入 ...

  5. 不包含适合于入口点的静态"Main"方法

    学习新建项目.此问题做为笔记. 错误 1 程序“admin.exe”不包含适合于入口点的静态“Main”方法 原因:原来创建项目的时候,用的是“空项目”,我以为这样就会生成类库,实际上,一开始准备运行 ...

  6. WPF编译时提示“...不包含适合于入口点的静态‘Main’方法 ...”

    今天看了一下wpf的Application类方面的知识,一个windows应用程序由一个Application类的实例表示,该类跟踪在应用程序中打开的所有窗口,决定何时关闭应用程序(属性 Shutdo ...

  7. (转) MFC的入口点与消息循环,消息映射

    博文分析的很不错,尤其是替换默认窗口过程的这块,本人觉得,所有的这些都可以参阅侯杰的<深入浅出MFC >. 来自:http://blog.csdn.net/sryan/article/de ...

  8. C#调用C++函数入口点的问题 z

    C++使用 void extern __declspec(dllexport) 函数名()定义的输出函数, 在C#中调用时, 如前文所述, 使用 [DllImport("D:\VS2005P ...

  9. Android程序的入口点和全局变量设置--application

    首先看看 application的官方文档 我之前一直以为Android程序的入口点就是带MAIN和LAUNCHER的Activity的onCreate方法,看来我是错了~  原来真正的入口点是 Ap ...

随机推荐

  1. [置顶] mybatis批量新增系列之有主键的表的批量新增

    前面介绍了无主键的表的批量插入,文章地址:http://blog.csdn.net/zhouxiaoyun0228/article/details/9980181 但是在开发中往往许多的表是需要主键的 ...

  2. 第二种:NSObject

    - (void)viewDidLoad { [super viewDidLoad]; /** * 开启子线程的方式之一:NSObject */ // 第一个参数:selector // 第二个参数:方 ...

  3. js面向对象继承

    前言 最近看到js面向对象这章节了,主要学习了原型和面向对象继承关系,为了梳理自己的知识逻辑,特此记录. js的面向对象 先说说我目前了解的js创建对象方法 1.写一个函数,然后通过new创建对象 2 ...

  4. Cocos2dx项目移植Android平台

    链接地址:http://blog.csdn.net/iuncle/article/details/24772183 版权声明:本文为博主原创文章,未经博主允许不得转载. 1.Classes目录下存放. ...

  5. iOS 开发设计常用软件及工具整理

    1, xCode 2, AppCode 3, Skech 原型设计软件 4, Hype 动画设计工具 5, fontawsome 免费图表 6, Prepo icon, images.catlog 生 ...

  6. POJ 1861 Network (模版kruskal算法)

    Network Time Limit: 1000MS Memory Limit: 30000K Total Submissions: Accepted: Special Judge Descripti ...

  7. 【每日一摩斯】-Shared Pool优化和Library Cache Latch冲突优化 (1523934.1)-系列3

    减轻Shared Pool负载 Parse一次并执行多次        在OLTP类型的应用中,最好的方法是只让一个语句被解析一次,然后保持这个cursor的打开状态,在需要的时候重复执行它.这样做的 ...

  8. HDU 4738 双连通模版题

    九野的博客,转载请注明出处:http://blog.csdn.net/acmmmm/article/details/11711577 题意:给定n个点,m条无向边 下面m行表示u , v ,边权值 求 ...

  9. sql server 深入使用 总结 part1

    1   OUTPUT  应用场景 : 1.1.对于INSERT,可以引用inserted表以查询新行的属性.         insert into [表名] (a) OUTPUT Inserted. ...

  10. saltstack:使用教程之一安装及客户端返回写入MySQL

    saltstack使用教程: 1.安装: 需要epel的yum源,没有的话把下面的复制并新建个文件 /etc/yum.repos.d/epel.repo 粘贴即可: [epel] name=Extra ...