C 入口不是main?知乎上打起來了!
時(shí)間:2021-10-14 16:59:01
手機(jī)看文章
掃描二維碼
隨時(shí)隨地手機(jī)看文章
[導(dǎo)讀]出處:編程技術(shù)宇宙(ID:xuanyuancoding)知乎上居然有人為了C的入口函數(shù)到底是什么打了起來!至于打的有多激烈我就不知道了,我們來關(guān)注這個(gè)問題本身。你說main函數(shù)是入口,那main是被誰調(diào)用的呢?他說mainCRTStartup是入口,那mainCRTStartup...
出處:編程技術(shù)宇宙(ID:xuanyuancoding)
知乎上居然有人為了C 的入口函數(shù)到底是什么打了起來!至于打的有多激烈我就不知道了,我們來關(guān)注這個(gè)問題本身。你說main函數(shù)是入口,那main是被誰調(diào)用的呢?他說mainCRTStartup是入口,那mainCRTStartup又是被誰調(diào)用的呢?
知乎上居然有人為了C 的入口函數(shù)到底是什么打了起來!至于打的有多激烈我就不知道了,我們來關(guān)注這個(gè)問題本身。你說main函數(shù)是入口,那main是被誰調(diào)用的呢?他說mainCRTStartup是入口,那mainCRTStartup又是被誰調(diào)用的呢?
從進(jìn)程創(chuàng)建說起
一切的一切,讓我們從創(chuàng)建進(jìn)程開始說起。進(jìn)程創(chuàng)建完成后,接著會(huì)創(chuàng)建主線程,這是進(jìn)程中第一個(gè)開始執(zhí)行代碼的線程。主線程創(chuàng)建后,就得到了時(shí)間片,開始參與系統(tǒng)的線程調(diào)度,那么程序從哪里開始執(zhí)行呢?在Windows平臺(tái),C 代碼編譯后的可執(zhí)行文件叫PE文件。PE文件中有一個(gè)叫OEP的術(shù)語便是指的程序入口點(diǎn)。所謂入口點(diǎn)顧名思義就是主線程最開始執(zhí)行的地方,許多病毒加殼技術(shù)其中一點(diǎn)就是對(duì)這個(gè)OEP進(jìn)行處理。現(xiàn)在,我們來使用工具PEID來看一個(gè)程序(VC8.0編譯)的OEP如圖:0x00011078乃是RVA(相對(duì)虛擬地址),要看在進(jìn)程地址空間中真正的起始地址,還得加上PE文件的映射基址,默認(rèn)為0x00400000,不過,可以通過編譯器選項(xiàng)進(jìn)行調(diào)整。不知道也沒關(guān)系,將程序放入OllyDbg,在內(nèi)存映射中可以看到程序的映射基址:由圖看到映射基址是0x00400000。那么由前面所述,程序執(zhí)行的第一條指令應(yīng)該位于0x00400000 0x00011078 = 0x00411078。沒錯(cuò),就是這樣。切換到OllyDbg的主窗口,我們發(fā)現(xiàn)了,程序確實(shí)初始停在了這里,并且這里是一條jmp指令。我們到Jmp的目的地0x00411800去看看那里是什么東東?這是什么東西?先賣個(gè)關(guān)子,總之,這里是程序進(jìn)來之后真正做的第一件事。main函數(shù)被誰調(diào)用?
換個(gè)思路,我們打開VS2008寫一個(gè)簡(jiǎn)單的程序,程序做什么并不重要,我們要看它的啟動(dòng)原理。注意看調(diào)用堆棧窗口,因?yàn)槲沂鞘褂肬NICODE編碼環(huán)境,故_tmain()就是wmain(),如果是ANSI編碼就是最開始學(xué)程序時(shí)的main()函數(shù)了。以前寫程序就想過一個(gè)問題,我們寫的所有函數(shù)都會(huì)被我們自己直接或間接調(diào)用,但有一個(gè)函數(shù)例外,那就是main()函數(shù)。我們寫了它但從不會(huì)去調(diào)用它,事實(shí)上也不可能去調(diào)用它。從調(diào)用堆??吹?,我們的wmain函數(shù)是被_tmainCRTStartup函數(shù)調(diào)用的,這是個(gè)什么東西?再往前推是wmainCRTStartup調(diào)用的_tmainCRTStartup。這兩個(gè)函數(shù)是做什么的,他們之間有什么關(guān)系?雙擊調(diào)用堆棧里的項(xiàng)即可轉(zhuǎn)到對(duì)應(yīng)的源代碼,我們可以發(fā)現(xiàn),這兩個(gè)函數(shù)是在crtexe.c文件中實(shí)現(xiàn)的。閱讀源碼可以發(fā)現(xiàn),有四個(gè)啟動(dòng)函數(shù)分別是:這一點(diǎn)在《windows核心編程》中也有提到。不過我們可以更進(jìn)一步一窺它們的實(shí)現(xiàn)代碼:就這么簡(jiǎn)單,先調(diào)用了__security_init_cookie(),然后是我們前面看到的_tmainCRTStartup()。第一個(gè)函數(shù)是做什么的呢?這個(gè)是微軟在VS2003后引入的防止緩沖區(qū)溢出攻擊的技術(shù)。簡(jiǎn)單的說就是在調(diào)用函數(shù)的時(shí)候在棧里安裝一個(gè)隨機(jī)的cookie值,這一cookie值在內(nèi)存的一個(gè)地方有備份,函數(shù)調(diào)用完成后需要檢測(cè)這個(gè)cookie和備份的一不一致,以此來判斷有沒有棧溢出發(fā)生。那么,這個(gè)函數(shù)就是來初始化這個(gè)備份區(qū)域的數(shù)據(jù)的。然后第二個(gè)函數(shù)調(diào)用_initterm()進(jìn)行全局變量、對(duì)象初始化。之后,我們可以看到才是真正調(diào)用了我們的main()/wmain()/WinMain()/wWinMain()的地方。饒了一大圈,回答了開始的疑問了。這兩個(gè)函數(shù)是編譯器在生成可執(zhí)行文件的時(shí)候給我們鏈接進(jìn)來的。至此,我們來看看第一個(gè)函數(shù)wmainCRTStartup的匯編代碼。如圖:請(qǐng)注意和我們前面使用OllyDbg調(diào)試時(shí)的圖對(duì)比:發(fā)現(xiàn)沒有?一樣的!我們之前留的那個(gè)問題的答案想必已經(jīng)出來了:程序一進(jìn)來從OEP處執(zhí)行了jmp指令,這條指令轉(zhuǎn)向了wmainCRTStartup開始了程序真正的起點(diǎn)!
- mainCRTStartup() ANSI 控制臺(tái)程序
- wmainCRTStartup() UNICODE 控制臺(tái)程序
- WinMainCRTStartup() ANSI GUI程序
- wWinMainCRTStartup() UNICODE GUI程序
結(jié)論
編譯生成的exe文件,雙擊運(yùn)行后,建立新進(jìn)程的地址空間,然后主線程開始運(yùn)行。程序一進(jìn)來通過jmp指令來到前面列出的四個(gè)啟動(dòng)函數(shù),它們?cè)僬{(diào)用最終的啟動(dòng)器_tmainCRTStartup。這個(gè)啟動(dòng)器干了幾件大事,分別是,使用GetStartupInfo獲取進(jìn)程啟動(dòng)信息,然后使用_inititem初始化全局變量和對(duì)象,最后調(diào)用我們main、wmain、WinMain、wWinMain進(jìn)入我們的程序。。。所以,從編程語言的角度來說,main函數(shù)就是入口函數(shù),這一點(diǎn)毋庸置疑。至于mainCRTStartup,則是VC 這個(gè)編譯器額外增加的包含C/C 運(yùn)行時(shí)庫初始化操作在內(nèi)的封裝函數(shù),可以算可執(zhí)行文件的入口函數(shù)。說明:這里談到的是使用VC2008編譯器生成的exe文件形態(tài)(不同的VC版本可能情況有所不同),至于Linux上的ELF文件,情況則更不一樣。最后給大家留一個(gè)思考題:進(jìn)程創(chuàng)建后,又是從哪里進(jìn)入到OEP的呢?前面我們說了,OEP是程序運(yùn)行的入口,是一切的起點(diǎn)。那在進(jìn)入入口之前,進(jìn)程又在干什么?這個(gè)問題有點(diǎn)類似于:在宇宙大爆炸之前,世界是怎么樣的?歡迎大家留言交流,告訴我你的想法!