1. 程式人生 > >Windows 中一個應用程式的啟動過程

Windows 中一個應用程式的啟動過程

轉載自:https://blog.csdn.net/cpp_mybest/article/details/80194158

1. Explorer.exe 

    Windows 能夠流行起來,很大一個原因是它有友好的使用者圖形介面,操作方便簡單,容易上手。在Windows環境下開啟一個程式,只要雙擊軟體的圖示就行了,那麼它是如何啟動起來的? 

    當我們啟動電腦進入桌面時,系統會建立 Explorer.exe 程序。Explorer.exe是Windows程式管理器 或者叫 檔案資源管理器,用於管理Windows圖形殼,刪除該程式會導致 Windows 圖形介面無法使用。所以,如果有時候我們電腦的桌面空白了,或者藍屏,可以通過 Alt+Ctrl+delete(或者在dos中輸入 taskmgr 命令) 開啟工作管理員, 點選“檔案”-> “新建任務”,輸入 "explorer.exe",就可以找回我們的桌面了。

    當雙擊某個圖示時,Explorer.exe程序的一個執行緒會偵測到這個操作,它根據登錄檔中的資訊取得檔名,然後Explorer.exe 以這個檔名呼叫 CreateProcess 函式。登錄檔中有相關的項儲存著雙擊操作的資訊,如 exe 檔案關聯、啟動 exe 的 Shell 是哪個。PC中的大多其它的程序都是 Explorer.exe 的子程序,因為它們都是由Explorer.exe 程序建立的。

2. CreateProcess 函式的執行過程

(1)CreateProcess 實際上是通過 NtCreateProcess 函式實現的, 此時,系統會建立一個程序核心物件。程序核心物件可以看作是作業系統用來管理程序的小的資料結構,它是在核心堆區分配的一個結構體,是系統用來存放關於程序統計資訊的地方。程序核心物件維護了一個控制代碼表的結構,當程序被初始化之後,其控制代碼表是空的。當程序內的一個執行緒通過指定的函式建立了一個核心物件時,核心會為物件分配一塊記憶體區域並初始化這塊區域,然後核心會在程序的控制代碼表中查詢一個空的入口,找到之後會初始化控制代碼表的以索引定位的區域。初始化的主要過程就是填充控制代碼表的一個單元,包括指定核心物件地址,指定訪問碼,指定標記等。

(2)程序核心物件建立後,它的引用計數被置為1。然後系統為剛剛建立的程序分配的程序虛擬地址空間。要注意了,之所以稱為虛擬地址空間,就是因為這塊地址空間並不在記憶體之中,它只是在硬碟上劃分的被稱為“頁”的檔案。每個程序都有自己的虛擬地址空間,在程序初始化的時候,其所有的程式和資料會被載入到這個地址空間中。等到真正執行的時候,系統為每個程序配置的頁表會把虛擬地址對映為真正的實體地址(這個過程,我會在後面的部落格中詳細介紹如何對映)。

 

(3)初始化虛擬地址空間。程序地址空間建立後,Windows的裝載器(loader,也稱為PE裝載器)開始工作,Loader會讀取EXE檔案的資訊(PE檔案)。此時 loader 會檢查PE檔案的有效性,如果PE檔案有錯誤,程序也就無法啟動了。如果PE檔案沒有錯誤,裝載器就把PE檔案的內容(二進位制程式碼)對映到程序的地址空間

中,然後讀取 PE檔案的匯入地址表(Import Table),這裡存放有exe檔案需要匯入的模組檔案(DLL),系統會一一載入這些DLL到程序的地址空間中,具體做法是呼叫 LoadLibrary 函式載入程式程式碼到某個地址,然後系統會對映這些程式碼到程序的地址空間中,要知道DLL只需載入一次就可對映到所有程序的地址空間(對映過程我會在後面詳細闡述)中,併為每個DLL維護一個引用計數,當引用計數為 0 時,DLL就從記憶體中解除安裝,釋放佔用的記憶體。DLL裡面可能又引用了其它的DLL,因此載入DLL時是遞迴形式的,直到載入完Import Table 裡描述的所有DLL模組,此時程序初始化部分完成。

(4)建立程序的主執行緒。當程序的初始化完成後,開始建立程序的主執行緒,一個程序至少要有一個主執行緒才能執行,可以說程序只是充當一個容器的作用,而執行緒才是執行程式碼的載體。執行緒是用 CreateThread 這個函式建立的。建立執行緒時,也和程序相似,系統會建立執行緒核心物件,初始化執行緒堆疊。執行緒堆疊有兩個,一個是核心堆疊,由核心態維護;另一個是使用者堆疊,執行在使用者態下。同樣的,執行緒的引用計數也置為1。

(5)C/C++執行期庫初始化。當程序的主執行緒初始化完成後,並且執行緒得到了CPU時間片,CPU把CS:IP指向程式入口(OEP),這個地址相當重要,因為這是程式執行時第一條指令所在的地址(我們可以使用一些PE輔助工具來檢視PE檔案的地址資訊,注意真實地址==偏移地址 + 基址)。其實,CS:IP指向的地址處是一條JMP指令,它跳轉到程式真正的入口函式,入口函式有以下4種形式:

mainCRTStartup  (用於 ANSI 版本的控制檯應用程式 )

wmainCRTStartup ( 用於 Unicode 版本的控制檯應用程式 )

WinMainCRTStartup ( 用於 ANSI 版本的視窗應用程式 )

wWinMainCRTStartup ( 用於 Unicode 版本的視窗應用程式)

下面再看看入口函式的原始碼:

int XXXCRTStartup(void)                   //XXX表示不同Windows版本

{   __security_init_cookie();       // 完成安全方面的初始化

    return__tmainCRTStartup();

}

在__tmainCRTStartup中首先呼叫了GetStartupInfoW函式取得父程序建立本程序時的啟動資訊, 然後又是一系列的初始化,其中包括C++建構函式的呼叫,還有靜態變數,全域性變數的初始化,這些操作是在_initterm這個函式中完成的。接著,我們的 (w)WinMain / (w)main 函式會被呼叫,到這時,使用者程式程式碼才開始被執行。