您好,登錄后才能下訂單哦!
今天終于有時間來研究一下一個很大很大的工程編譯成一個exe和若干dll后,程序是如果執行它的第一條指令的?操作系統以什么規則來找到應該執行的第一條指令(或說如何找到第一個入口函數的)?
我們以前寫windows控制臺程序時,都是先寫個main()函數,寫windows窗口程序時,首先要寫winmain()函數,然后再寫自己的邏輯;然后編譯,然后點擊exe就能運行我們的程序了;并且認為main或winmain是程序中第一個運行的程序,也是必須存在的函數,但深入了解window的編程就會發現,main或winmain函數不是第一個運行的函數,在他們之前首先會運行另一個函數,會對全局變量進行初始化和資源的分配,以及程序結束后會對資源的釋放。
我們以前寫的程序在編譯器編譯成為一個模塊(可能是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++ 運行時庫代碼在靜態非局部變量上調用構造函數和析構函數。
設有一個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 庫。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。