從win32中的寫時複製(Copy on write )機制談起
我們知道,記憶體對映檔案的物理儲存器來自磁碟上已有的檔案,而不是來自也交換檔案。系統在載入exe和dll檔案的時候使用的是記憶體對映檔案來載入並執行exe和dll,這樣大大節省了頁交換檔案的空間以及應用程式的啟動時間。所以,實際上系統載入exe檔案的時候就是利用記憶體對映檔案技術把磁碟中的exe檔案對映到記憶體(實際上,系統只是預定了一塊足夠大的地址空間來容納exe檔案,一定要注意“預定”這個詞,待預定的地址空間區域的具體位置已經在exe檔案中指定。預設情況下,exe的基地址為0x00400000.系統也會對這個地址空間的區域進行標註,表明該區域的後備物理儲存區來自磁碟上的exe檔案,而非來自系統的頁交換檔案。
以上就是,系統裝載可執行檔案的時經常被稱為“對映到地址空間”的解釋了,實際上就是建立了exe檔案對應的頁交換檔案,也就是系統為這個exe檔案的執行預定了足夠大的地址空間,在x86下為4G的地址空間。
以上是系統載入exe檔案的過程,對映到地址空間之後,系統開始執行exe檔案的啟動程式碼。完成了這個對映過程後,系統會負責所有的換頁(paging),快取(buffering)以及快取記憶體(caching)的操作。例如,如果.exe檔案中的程式碼跳到一個指令地址,但是該地址尚未載入記憶體,那麼會引發一個頁錯誤(page fault)。系統會檢測到這個錯誤並自動將該頁程式碼從檔案載入到記憶體。然後系統會把該記憶體頁對映到程序地址空間中的適合位置,並讓執行緒繼續執行,就好像該頁程式碼早就在記憶體中一樣。
從理想來分析整個裝載的過程,我們需要了解PE檔案的結構,實際上,在載入的過程PE檔案會按照磁碟中的各個頁的排列順序對映到地址空間中,也即是對映到記憶體對映檔案中。改變的只是每一個頁在磁碟中的對齊大小要按照在記憶體中的頁對齊大小來排列了。假如,在磁碟中的檔案對齊為0x1000,記憶體對齊其0x2000,那麼PE檔案的記憶體映像中每一也都將擴大1倍了,但是每一個頁的相對位置不變。這樣,對於磁碟映像到記憶體映像的雙向都可以很容易的一一對應起來,這為windows虛擬記憶體的管理提供了很好的一個機制。檔案對齊和記憶體對齊的值在PE檔案中都有指定,具體的位置在PE的頭部資訊中。
介紹了背景知識,接著我們開始來談copy on write這個機制。
假如一個應用程式已經在運行了,那麼我們為這個應用程式建立一個新的物件時,系統只不過開啟另一個記憶體映像檢視(memory-mapped view ),建立一個新的程序物件,併為主執行緒建立一個新的執行緒物件。這個新開啟的記憶體映像檢視隸屬於一個檔案對映物件(file-mapping object),後者用來標示可執行檔案的映像。系統同時給程序物件和執行緒物件分別指定新的程序ID和執行緒ID。,通過記憶體對映檔案,同一個應用程式的多個例項可以共享記憶體中的程式碼和資料。
如果應用程式的一個例項修改了資料頁面的一些全域性變數,那麼應用程式的所有例項的記憶體都將會被修改。由於這種型別的修改可能導致災難性的後果,因此避免。
系統通過記憶體管理系統的寫時複製特性來防止以上情況的發生。任何時候當應用程式檢視寫入記憶體對映檔案的時候,系統會首先截獲此類嘗試,接著為應用程式試圖寫入的記憶體對映檔案頁面分配一塊新的記憶體,然後複製頁面的額內容,最後讓應用程式寫入到剛分配的記憶體塊。最終的結果就是,應用程式的其他實力不會受到任何的影響。
下面我們來探討一個關於記憶體對映檔案的例子:
我們再使用ITA Hook的時候,需要獲取一個exe檔案的匯入表,那麼如何獲取這個程式的匯入表呢?
匯入表是PE檔案中的一個結構,可以根據PE的頭部資訊獲取得到,因為匯入錶針對的dll以及函式的填充是在裝載完成之後的,所以我們需要在程式被匯入到記憶體中之後再獲取。直接使用GetModuleHandle來獲取模組的基地址。但是假如目標程式沒有執行,那麼我們需要使用記憶體對映檔案相應的API將該程式匯入到記憶體之後再獲取匯入表的資訊。
相關的API有:
CreateFile,CreateFileMapping等操作。
關於記憶體對映檔案的操作,以後再詳細討論。