当前位置: 首页 > 科技观察

C++入门是不是main?知乎在战斗!

时间:2023-03-13 05:23:13 科技观察

知乎居然为了什么C++入口函数吵起来了!不知道打的有多激烈,还是先看问题本身吧。你说main函数是入口,那谁调用main呢?他说mainCRTStartup是入口,那么谁来调用mainCRTStartup呢?从流程创建开始,让我们从创建流程开始。创建进程后,创建主线程,它是进程中第一个开始执行代码的线程。主线程创建后,得到一个时间片,开始参与系统的线程调度,那么程序从哪里开始执行呢?在Windows平台上,C++代码编译后的可执行文件称为PE文件。PE文件中有一个术语叫做OEP,指的是程序入口点。所谓入口点,顾名思义,就是主线程最先执行的地方。许多病毒打包技术之一就是处理这个OEP。下面我们用PEID这个工具来看一个程序(VC8.0编译的)的OEP如图:0x00011078是RVA(相对虚拟地址),取决于进程地址空间中真正的起始地址,PE必须加上文件的映射基址,默认为0x00400000,不过可以通过编译器选项调整。不知道也没关系,把程序放到OllyDbg中,在内存映射中就可以看到程序的映射基地址:如图所示,映射基地址为0x00400000。那么,如上所述,程序执行的第一条指令应该位于0x00400000+0x00011078=0x00411078。没错,就是这样。切换到OllyDbg的主窗口,我们发现程序最初确实停在了这里,这里是一条jmp指令。我们去jmp的目的地0x00411800看看有什么?这是什么?先开个玩笑吧。总之,这是程序真正进来后做的第一件事,谁调用了main函数?换个思路,我们打开VS2008,写一个简单的程序。不管程序做什么,我们都要看它的启动原理。注意调用栈窗口,因为我使用的是UNICODE编码环境,所以_tmain()就是wmain()。如果是ANSI编码,就是刚学程序时的main()函数。在写程序之前我想过一个问题。我们写的所有函数都会被我们自己直接或间接调用,但是有一个例外,就是main()函数。我们写它但从不调用它,实际上调用它是不可能的。从调用栈可以看出,我们的wmain函数是被_tmainCRTStartup函数调用的。这是什么?往前推就是wmainCRTStartup调用的_tmainCRTStartup。这两个函数有什么作用,它们之间有什么关系?双击调用堆栈中的项目以转到相应的源代码。我们可以发现这两个函数都是在crtexe.c文件中实现的。阅读源码可以发现有四个启动函数:mainCRTStartup()ANSI+consoleprogramwmainCRTStartup()UNICODE+consoleprogramWinMainCRTStartup()ANSI+GUIprogramwWinMainCRTStartup()UNICODE+GUIprogram这是在《windows核心编程》也提及。不过我们可以仔细看看他们的实现代码:就这么简单,先调用__security_init_cookie(),然后再调用前面看到的_tmainCRTStartup()。第一个函数是做什么的?这是微软在VS2003之后推出的防止缓冲区溢出攻击的技术。简单来说,就是调用函数时在栈上安装了一个随机的cookie值。这个cookie值被备份在内存中的某个地方。不会发生堆栈溢出。那么,这个函数就是对这个备份区的数据进行初始化。然后第二个函数调用_initterm()来初始化全局变量和对象。之后,我们可以看到我们的main()/wmain()/WinMain()/wWinMain()确实被调用了。过了很久,我回答了最初的问题。这两个函数由编译器在生成可执行文件时链接进来。至此,让我们看一下第一个函数wmainCRTStartup的汇编代码。如图:请注意我们使用OllyDbg调试时的图片对比:找到了吗?相同!我们之前留下的问题的答案肯定已经出来了:程序一进来就执行了OEP的jmp指令,这条转向WmainCRTStartup的指令开始了程序真正的起点!结束语编译生成exe文件后,双击运行后,新进程的地址空间就建立好了,然后主线程开始运行。程序一进来,就通过jmp指令来到上面列出的四个启动函数,它们调用了最后的启动器_tmainCRTStartup。这个launcher主要做了几件事情,就是用GetStartupInfo获取进程启动信息,然后用_inititem初始化全局变量和对象,最后调用我们的main,wmain,WinMain,wWinMain进入我们的程序。..因此,从编程语言的角度看,main函数无疑是入口函数。至于mainCRTStartup,是VC++编译器额外增加的封装函数,包括C/C++运行时库的初始化操作,可以看作是可执行文件的入口函数。说明:这里说的是使用VC2008编译器生成的exe文件的形式(不同的VC版本可能不同),至于Linux上的ELF文件,情况就更不一样了。最后留给大家一个思考问题:进程创建后,从哪里进入OEP?前面我们说过,OEP是程序运行的入口,是一切的起点。那么进入入口之前进程在做什么呢?这个问题有点类似于:大爆炸之前的世界是什么样的?本文转载自微信公众号“编程技术宇宙”,可通过以下二维码关注。转载本文请联系编程技术宇宙公众号。