虛擬記憶體到實體記憶體(32位)

查志強發表於2015-01-07

【原文:http://www.cnblogs.com/DylanWind/archive/2010/09/05/1818685.html

部分出處(http://blog.sina.com.cn/wyw1976) 和 http://www.cnblogs.com/Winston/archive/2009/04/12/1434225.html

我們通過一個實際的例子,窺看作業系統是如何完成從邏輯地址到實體地址的轉換的。

(1)執行計算器程式(calc.exe),隨便輸入任意數字,如下圖所示:

image

(2)執行WinDbg, File-->Attach to a process, 選擇calc.exe

(3)輸入命令"x calc!g*", 列出calc中所有以g開頭的符號,如圖

image

其中的gpszNum存放的就是使用者輸入的數值

(4)輸入命令 "db poi(calc!gpszNum)", 因為gpszNum是一個指標變數,因此用取值操作poi,結果如下:

 image 
我們看到了我們在介面上輸入的數字。

(5)另一種方法:既然gpszNum是一個指標,那就意味著gpszNum中存放的是一個地址,而該地址指向的記憶體中包含的才是我們要找的值,如下圖(dd 命令用於讀取指定記憶體地址中的值):

image

dd gpszNum 也可以直接 dd 01014db0 
我們仍然找到了字串“888652”,它所在的邏輯地址是000af360, 這顯然是一個使用者態地址,因為在Windows平臺上,每個程式的地址空間是0~4G, 其中0~2G是使用者態,而2G~4G是核心態。那它對應的實體地址是多少呢?這就涉及到邏輯地址到實體地址的轉換。

Virtual Address(虛地址)

對於一個32bit的virtual address,其格式如下所示:

image

其中高10bit為Page Directory中的index項,中間10Bit為Page Table的index項,最低12bit為頁內偏移地址。注意,它們記錄的都是“偏移”量。

從virtual address向physical address轉換的過程如下:

Step1: 通過CR3暫存器定位到頁目錄的起始位置(DirBase), 故CR3 Regesiter又稱為頁目錄基地址暫存器; !Process 0 0可以看到每個程式的DirBase

Step2: 取virtual address的高10bit作為index,在PD中查詢相應的PDE. 每個PDE的高20位代表該PDE所指向頁表起始實體地址的高20位; 一般都是000000000,所以取!DirBase的第一項作為PDE;

Step3: 根據PDE中的頁表基地址(即PDE的高20bit)定位到Page Table(頁表); 一般step3和step4一起 !(PDE前20位 + 10-21bit索引*4),得到PTE

Step4:  取virtual address中第10-21bit作為索引,選取頁表中的一個PTE(頁表項),每個PTE的高20位代表的是4KB記憶體頁的起始實體地址的的高20位

Step5: 取PTE中的記憶體頁表基地址(高20bit)+ virtual address中的低12位offset,即可以得到實際的physical address 了; !PTE前20位+低12位offset

image

有關Paging的二級定址, 
總是為每個程式分配一個頁目錄page directory, 如果系統中有多個程式,記憶體中就會有多個頁目錄,每個page directory本身佔用4K。 
先找page directory,有1024專案,每一項對應一個page directory entry(通過線性地址的高十位) 
再從page table中找PTE,每個PTE對應一個頁面檔案 4K(有4k,2M, 4M)。( 通過線性地址的中10位)每個page table本身佔用4K 
所以一張Page table對應的記憶體空間是 4K * 1024 = 4M , 因而Page directory對應大小是1024 * 4M = 4G ,虛擬記憶體大小4G, 其中0~2G是使用者態,2G~4G是核心態

有關邏輯地址,線性地址和實體地址 
程式中變數或函式的邏輯地址是在程式編譯確定的,實際上就是段內的偏移。 
邏輯地址加上段地址,就形成了線性地址。 

在Windows平臺上,邏輯地址就是線性地址。現在我們通過WinDbg來驗證。

(1)開啟WinDbg, 點選File----->Attach to a Process...,關聯任何一個程式,WinDbg會顯示段暫存器的值。

(2)執行“dg + 段選擇子”, 可以檢視段的詳細資訊,如下圖所示: 
image

其中base指定了段的基地址,可以看到程式碼段CS, 資料段DS的基地址是0,這也就是意味著邏輯地址+0=線性地址,因此邏輯地址==線性地址。也就是Windows在淡化段地址,真正其作用的是頁地址。 

另外,上圖中還有一個fs==0038是什麼意思呢?在Windows平臺上,該暫存器存放的是當前執行緒的TEB(執行緒環境塊),而它的基地址是非零的,驗證如下:

(1)執行"dg 0038", 可見其base為7ffdc000

(2)執行"!teb",顯示當前執行緒的TEB資訊,可以看到其存放地址確實是7ffdc000,如下圖所示:

image 
32位線性地址分為3段:

31----22:高10位指定了頁目錄偏移,系統在載入每個程式的時候都有為該程式分配記憶體,而記憶體的管理是通過三級頁表的方式,但並不是所有記憶體都一步分配到位的。首先,總是為每個程式分配一個頁目錄,它的大小是1024,高10位就是指定了1024項中的一項,每一項其實也是一個記憶體地址,該地址開始的1024個空間代表一個頁表。只要程式是活著的,這個頁目錄就一直存在記憶體中,如果系統中有多個程式,記憶體中就會有多個頁目錄在每個程式的程式環境塊(PEB)中會記錄頁目錄的地址,在程式切換的時候,該地址會傳遞給CPU的CR3暫存器以便CPU進行地址轉換。在WinDbg中,我們可以看到該資訊:

(1)開啟WinDbg, 點選File---->Kernel Debuging...,選擇Local, 進入核心除錯模式

(2)在WinDbg視窗中執行".symfix c:\111",以及“.reload”, 載入符號表

(3)執行"!process 0 0", 列出了當前系統中所有程式,其中的DirBase就是頁目錄地址。

 image 

21----12:中10位指定了頁表偏移通過高10位可以得到一個頁表,該頁表也有1024項,通過中10位在這1024項中選擇1項,每一項包含一個地址,該地址開始的4096個位元組就是一頁。頁表要佔用記憶體,而且隨著程式所需記憶體的變化而變化。

11----0:低12位指定了頁內偏移通過中/高兩項,我們可以定位一個頁,每頁大小為4096,是一個連續空間,低12位就是具體地址了4096中的一項,也就是具體的一個實體記憶體了。

一般的,地址轉換的步驟是邏輯地址---->線性地址---->實體地址

 

從最後一步開始,執行du, 然後執行".format", 這條命令將顯示該地址的二進位制形式,然後我們按照線性地址的組成,分為10-10-12的形式, 如下如所示:

 image 
頁表項偏移: 00000000,  即第一項

頁表偏移:  0010101111,  即為0xAF

頁內偏移:  0011 01100000,即為0x360

(2) 再啟動一個WinDbg, 點選File--->Kernel Debug..., 選擇Local, 進入核心調除錯模式

(3) 執行“!process 0 0”, 找到計算器程式,如下圖所示,其DirBase=3f74a000, 這就是頁目錄所在的地址。

image

(4) 執行“!dd 3f74000”, 讀取頁目錄, 
image 
因為線性地址的頁表項為0, 就對應的第一項,即64db6867, (注意, 頁表項的高20位即64db6000代表頁表地址,低12位為屬性位)

(5)頁表地址64db6000代表一個頁表的起始地址。從該地址開始的記憶體中的每一項(4個位元組)對應一個PTE,因為指標佔用是4個位元組,所以要用偏移量乘以4。因此該起始地址加上頁表偏移(0xAF),就可以找到PTE。執行“!dd 64db6000 + AF*4”,如下圖所示:

image

(6)上圖中的1589867的高20位即(0x15894000)就是物理頁的其實地址,因為每頁的大小是4K,所以頁地址的低12位應該為0,之所以不為0,是充分利用這些位存放flag,例如標誌是否可讀,是否在記憶體中等等。頁的其實地址加上頁內偏移(360),就是實體地址,從下圖可以看出,該地址存放的就是我們要找的字串。

 image 
至此,我們完成了從邏輯地址到實體地址的轉換。

很清晰的過程,但是我沒用試驗成功,我的環境如下: 
image

image

作者:DylanWind
出處:http://www.cnblogs.com/
本文版權歸作者和部落格園共有,歡迎轉載,但未經作者同意必須保留此段宣告,且在文章頁面明顯位置給出原文連線,否則保留追究法律責任的權利。

分類: Debug

相關文章