大多数调试器命令的输入参数和输出结果使用虚拟地址,而不使用物理地址。不过,有时候可能用得上物理地址。
有两个方法将一个虚拟地址转换成一个物理地址:使用 !vtop 扩展和使用 !pte 扩展。在Windows NT 4.0中还可以使用 !vpdd 扩展。
使用 !vtop 进行地址转换
假设你正在调试一台正在运行MyApp.exe进程的目标计算机,而且你想要调查虚拟地址0x0012F980。使用 !vtop 扩展确定对应的物理地址,步骤如下。
使用 !vtop 将虚拟地址转换成物理地址
确保你是工作在十六进制中。如若不然,用 N 16 命令设定当前基数。
确定地址的字节索引byte index。这个数值等于虚拟地址的最低12位。因此,虚拟地址 0x0012F980 的字节索引是0x980。
使用 !process 扩展来确定该地址的目录基址directory base:
kd> !process 0 0
**** NT ACTIVE PROCESS DUMP ****
....
PROCESS ff779190 SessionId: 0 Cid: 04fc Peb: 7ffdf000 ParentCid: 0394
DirBase: 098fd000 ObjectTable: e1646b30 TableSize: 8.
Image: MyApp.exe
确定目录基址的页框架号page frame number。只须把目录基址去掉尾部三个十六进制零就是了。本例中,目录基址是 0x098FD000 ,因此页框架号是 0x098FD。
使用 !vtop 扩展。这个扩展的第一个参数应该是页框架号。 !vtop 的第二个参数应该是正被讨论的虚拟地址:
kd> !vtop 98fd 12f980
Pdi 0 Pti 12f
0012f980 09de9000 pfn(09de9)
最后一行显示的第二个数值是物理页开始的物理地址。
把字节索引加上页的开始地址:0x09DE9000 + 0x980 = 0x09DE9980。即是所求的物理地址。
你能够通过显示每个地址的内存来验证这个计算结果是正确的。!d* 扩展显示一个指定物理地址上的内存:
kd> !dc 9de9980
# 9de9980 6d206e49 726f6d65 00120079 0012f9f4 In memory.......
# 9de9990 0012f9f8 77e57119 77e8e618 ffffffff .....q.w...w....
# 9de99a0 77e727e0 77f6f13e 77f747e0 ffffffff .'.w>..w.G.w....
# 9de99b0 .....
d* (显示内存) 命令以一个虚拟地址作为它的参数:
kd> dc 12f980
0012f980 6d206e49 726f6d65 00120079 0012f9f4 In memory.......
0012f990 0012f9f8 77e57119 77e8e618 ffffffff .....q.w...w....
0012f9a0 77e727e0 77f6f13e 77f747e0 ffffffff .'.w>..w.G.w....
0012f9b0 .....
结果是相同的,所以这表明物理地址 0x09DE9980 的确对应虚拟地址 0x0012F980。
使用 !pte 进行地址转换
再次假定你正在调查属于MyApp.exe进程的虚拟地址0x0012F980。使用 !pte 扩展确定对应的物理地址,步骤如下:
使用 !pte 将虚拟地址转换成物理地址
确保你是工作在十六进制中。如若不然,用 N 16 命令设定当前基数。
确定地址的byte index。这个数值等于虚拟地址的最低12位。因此,虚拟地址0x0012F980的字节索引是0x980。
把需要的进程设定为进程上下文环境process context:
kd> !process 0 0
**** NT ACTIVE PROCESS DUMP ****
....
PROCESS ff779190 SessionId: 0 Cid: 04fc Peb: 7ffdf000 ParentCid: 0394
DirBase: 098fd000 ObjectTable: e1646b30 TableSize: 8.
Image: MyApp.exe
kd> .process /p ff779190
Implicit process is now ff779190
.cache forcedecodeuser done
以虚拟地址作为参数使用 !pte 扩展。显示两个栏的信息。左栏描述这个地址的页目录项(PDE),右栏描述它的页表项(PTE):
kd> !pte 12f980
VA 0012f980
PDE at C0300000 PTE at C00004BC
contains 0BA58067 contains 09DE9067
pfn ba58 ---DA--UWV pfn 9de9 ---DA--UWV
在右栏最后一列看到记号“pfn 9de9”出现。数值0x9DE9是这个PTE的page frame number (PFN)。把页框架号乘以0x1000 (例如,把它左移12位)。所得乘积0x09DE9000是页开始的物理地址。
把字节索引加上页开始的地址:0x09DE9000 + 0x980 = 0x09DE9980。即是所求的物理地址。
这与前一个方法获得的结果相同。
手动转换地址
虽然 !ptov 和 pte 扩展提供最快速的方法将虚拟地址转换成物理地址,但我们也可以手动做这个转换。对这一过程的描述会让你搞清楚虚拟存储结构的某些细节。
存储结构因处理器和硬件配置而有不同大小。这里以一个不启用物理地址扩展(PAE)的x86系统为例。
还是用0x0012F980作为虚拟地址,首先你需要将它转换成二进制,手动或使用 .formats (显示格式化数值) 命令均可:
kd> .formats 12f980
Evaluate expression:
Hex: 0012f980
Decimal: 1243520
Octal: 00004574600
Binary: 00000000 00010010 11111001 10000000
Chars: ....
Time: Thu Jan 15 01:25:20 1970
Float: low 1.74254e-039 high 0
Double: 6.14381e-318
这个虚拟地址是三个域的组合。位 0 到 11 是字节索引。位 12 到 21 是页表索引。位 22 到 31 是页目录索引。分开这些域,得:
0x0012F980 = 0y 00000000 00 010010 1111 1001 10000000
这展现该虚拟地址的三个部份:
页目录索引 = 0y0000000000 = 0x0
页表索引 = 0y0100101111 = 0x12F
字节索引 = 0y100110000000 = 0x980
然后你需要关于你的系统的另外三个的信息。
每个PTE的尺寸。在非PAE的x86系统上它是4个字节。
页尺寸。它是0x1000个字节。
PTE_BASE虚拟地址。在一个非PAE系统上,它是0xC0000000。
使用这数据,你能够计算PTE本身的地址:
PTE address = PTE_BASE
+ (page directory index) * PAGE_SIZE
+ (page table index) * sizeof(MMPTE)
= 0xc0000000
+ 0x0 * 0x1000
+ 0x12F * 4
= 0xC00004BC
这是PTE的地址。PTE是个32位双字节DWORD。调查它的内容:
kd> dd 0xc00004bc L1
c00004bc 09de9067
这个PTE的值是0x09DE9067。它由两个域组成:
该PTE的低12位是状态标记status flags。这里,这些标记等于0x067 - 或者用二进制表示为0y000001100111。关于状态标记的说明,请看 !pte 参考页。
该PTE的高20位等于该PTE的page frame number (PFN)。这里,该PFN是0x09DE9。
该物理页上的第一个物理地址是该PFN乘以0x1000 (左移12位)。字节索引是在该页上的偏移量。因此,你所求的物理地址是0x09DE9000 + 0x980 = 0x09DE9980。这跟以上方法获得的结果相同。