转载于飞天舞者 http://www.cnblogs.com/winston/archive/2009/04/12/1434225.html
我们知道,从386开始,IA-32 CPU开始支持Paging。在启用Paging之后,OS将线性地址空间划分为固定大小的Page(通常为4KB或4MB)。
本文演示了如何通过WinDbg展示windows paging中的virtual address 向physical address转换过程。
在现代OS中,涉及到Paging的几个概念如下(以32-bit IA CPU&Microsoft Windows OS为例):
Page Directory (页目录):
Page Directory 是用来存放Page-Directory Entry(PDE)(页目录表项)的线性表。每个页目录占一个4kb的内存页(page),PD中的每个PDE长度为32Bit(其中高20bit为PDE所指向的页表的起始地址,低12bit为该PDE的属性)。因此每个PD中最多包含1024 个PDE。
对于内存页大小为4KB的页目录地址格式:
其实际上只有高20Bit有用,其低12bit固定为0
对于内存页大小为4MB的页目录地址格式:
其实际上只有高10Bit有用,其低12bit固定为0
其高20(10)为该页目录的起始地址。
Page Table (页表)
Page Table是用来存在页表项(Page Table Entry, PTE)的线性表。同样,每个PT占用一个内存页,每个PTE的长度为32bit(其中高20bit为PT所指向的物理页的起始地址,低12bit为该物理页的属性), 故每个PT中最多可以包含1024个PTE
Virtual Address(虚地址)
对于一个32bit的virtual address,其格式如下所示:
其中高10bit为Page Directory中的index项,中间10Bit为Page Table的index项,最低12bit为页内偏移地址。
从virtual address向physical address转换的过程如下:
Step1:
通过CR3寄存器定位到页目录的起始位置(DirBase), 故CR3 Regesiter又称为页目录基地址寄存器
Step2:
取virtual address的高10bit作为index,在PD中查找相应的PDE.
Step3:
根据PDE中的页表基地址(即PDE的高20bit)定位到Page Table(页表)
Step4:
取virtual address中第10-21bit作为索引,选取页表中的一个PTE(页表项)
Step5:
取PTE中的内存页表基地址(高20bit)+ virtual address中的低12位offset,即可以得到实际的physical address 了。
图示如下:
下面用windbg展示地址转换过程
以windows计算器(calc.exe)为例
1.打开calc.exe,输入数字,如123456, 然后在user mode下开始调试calc.exe
0:002> x calc!g*
01014f08 calc!ghwndTimeOutDlg = <no type information>
01014d9c calc!g_fHighContrast = <no type information>
0100514d calc!GetKeyColor = <no type information>
01014ef8 calc!gfExiting = <no type information>
0100518d calc!GetHelpID = <no type information>
01014c70 calc!ghnoPrecNum = <no type information>
01014c08 calc!ghnoParNum = <no type information>
01014038 calc!gszSep = <no type information>
01014eec calc!ghcurOld = <no type information>
01014d38 calc!g_ahnoChopNumbers = <no type information>
01014f00 calc!ghCalcDone = <no type information>
01014db0 calc!gpszNum = <no type information>
01014f0c calc!gnPendingError = <no type information>
01014000 calc!gnDecGrouping = <no type information>
01014dc0 calc!gcio = <no type information>
01014d98 calc!ghnoLastNum = <no type information>
01014f04 calc!ghDogThread = <no type information>
01014d80 calc!g_hDecMenu = <no type information>
01014f48 calc!gbinexact = <no type information>
01014d7c calc!g_hHexMenu = <no type information>
01014efc calc!ghCalcStart = <no type information>
01014da0 calc!g_fLayoutRTL = <no type information>
01014db8 calc!gbRecord = <no type information>
010149d8 calc!gcIntDigits = <no type information>
01014d6c calc!g_hwndDlg = <no type information>
01014d4c calc!gbUseSep = <no type information>
01014d94 calc!ghnoMem = <no type information>
010044b4 calc!GroupDigits = <no type information>
01014f4c calc!gllfact = <no type information>
01014d90 calc!ghnoNum = <no type information>
01014064 calc!gldPrevious = <no type information>
0:002> dd 01014db0
01014db0 000b2ee0 00000000 00000001 00000000
01014dc0 00000000 ffffffff 00000000 00000000
01014dd0 00000006 00320031 00340033 00360035
01014de0 00000000 00000000 00000000 00000000
01014df0 00000000 00000000 00000000 00000000
01014e00 00000000 00000000 00000000 00000000
01014e10 00000000 00000000 00000000 00000000
01014e20 00000000 00000000 00000000 00000000
我们可以看看000b2ee0附近 连续内存空间里面的内容:
0:002> dd 000b2ee0
000b2ee0 00320031 00340033 00360035 0000002e
000b2ef0 00030025 0008013d 000b3060 000b2f14
000b2f00 00000000 00000000 00000000 00000000
000b2f10 00000000 5443534d 614d2e46 61687372
000b2f20 746e496c 61667265 462e6563 4d656c69
000b2f30 412e7061 462e4c4d 4545482e 00464945
000b2f40 00000000 00000000 00000000 00000000
000b2f50 00000000 00000000 00000000 00000000
输入字符串变量的地址就在000b2ee0内存里面
0:002> du 000b2ee0
000b2ee0 "123456."
该地址000b2ee0 里面存放的正是我们所输入的数字:123456。而000b2ee0 为 virtual address。那么其physical address 到底是什么呢?根据前面的介绍,一个32bit的virtual address由3部分组成,我们可以具体看看每一部分的值
0:002> .formats 000b2ee0
Evaluate expression:
Hex: 000b2ee0
Decimal: 732896
Octal: 00002627340
Binary: 00000000 00001011 00101110 11100000
Chars: ....
Time: Fri Jan 09 03:34:56 1970
Float: low 1.02701e-039 high 0
Double: 3.62099e-318
由以上可以看出,其PDE index为高10 :0, PTE index(中间10bit): B2, 页内偏移地址:EE0
下面找出该虚地址的absolute address
再启动一个kernal model debug -> local
lkd> !process 0 0
......
PROCESS 85185288 SessionId: 0 Cid: 0b8c Peb: 7ffde000 ParentCid: 021c
DirBase: 1d386000 ObjectTable: e1ff17e0 HandleCount: 187.
Image: dllhost.exe
PROCESS 84f10da0 SessionId: 0 Cid: 049c Peb: 7ffde000 ParentCid: 0508
DirBase: 093ee000 ObjectTable: e2ace720 HandleCount: 49.
Image: calc.exe
PROCESS 847e6220 SessionId: 0 Cid: 0840 Peb: 7ffde000 ParentCid: 02c4
DirBase: 15297000 ObjectTable: e2b1fe30 HandleCount: 161.
Image: msmsgs.exe
其中DirBase所指向的地址高20位 即为该进程calc.exe的页目录基地址:093ee000(低12固定为0)
下面来看PD中具体的PDE
lkd> !dd 093ee000 (显示指定地址的页目录表项内容)
# 93ee000 093fb067 0c765067 1803b067 00000000
# 93ee010 14240067 00000000 00000000 00000000
# 93ee020 00000000 00000000 00000000 00000000
# 93ee030 00000000 00000000 00000000 00000000
# 93ee040 00000000 00000000 00000000 00000000
# 93ee050 00000000 00000000 00000000 00000000
# 93ee060 00000000 00000000 00000000 00000000
# 93ee070 00000000 00000000 00000000 00000000
根据virtual address中的 PDE index:0, 故其在PD中查找PDT的index为0, 即为第1个PDE :093fb067 。
在该093fb067 地址里面, 其高20bit (即093fb000)为其page table(页表)起始地址,低12bit为页表属性,至于每个Bit代表什么属性,在此不作赘述。
页表起始地址 + 页表项在页表内的索引,即可以得到该PTE的物理地址。从以上我们已经知道,该virtual address在PT中的索引值为B2,故PTE的地址为:
093fb000 + B2*4 (因为每个表项占4个byte)
lkd> !dd 093fb000 + B2 * 4
# 93fb2c8 105eb067 148ec886 18aed886 0dcee886
# 93fb2d8 00000080 00000000 00000000 00000000
# 93fb2e8 00000000 00000000 00000000 00000000
# 93fb2f8 00000000 00000000 00000000 00000000
# 93fb308 00000000 00000000 00000000 00000000
# 93fb318 00000000 00000000 00000000 00000000
# 93fb328 00000000 00000000 00000000 00000000
# 93fb338 00000000 00000000 00000000 00000000
可以得到该地址内的PTE地址为105eb067 ,而该地址的高20bit( 105eb000 )为所在物理内存页的起始地址,低12bit为内存页属性。
得到该物理页的起始地址后 + virtual address的offset,即可得到其物理地址
即105eb000 + EE0 =105ebee0
下面我们看看该地址内的内容是什么:
lkd> !dd 105ebee0
#105ebee0 00320031 00340033 00360035 0000002e
#105ebef0 00030025 0008013d 000b3060 000b2f14
#105ebf00 00000000 00000000 00000000 00000000
#105ebf10 00000000 5443534d 614d2e46 61687372
#105ebf20 746e496c 61667265 462e6563 4d656c69
#105ebf30 412e7061 462e4c4d 4545482e 00464945
#105ebf40 00000000 00000000 00000000 00000000
#105ebf50 00000000 00000000 00000000 00000000
lkd> !du 105ebee0
#105ebee0 "123456."
比对user mode下的dd 000b2ee0 ,我们可以看到, 其内容是完全相同的。
也就是说我们找到了virtual address : 000b2ee0 的准确物理地址:105ebee0。
只不过我们在user mode下使用dd时,这个转换过程自动完成了。