将虚拟地址转换为物理地址

大多数调试器命令使用虚拟地址(而不是物理地址)作为其输入和输出。 但是,有时拥有物理地址可能很有用。

可通过两种方式将虚拟地址转换为物理地址:使用 !vtop 扩展和 !pte 扩展。

有关 Windows 中虚拟地址的概述,请参阅 虚拟地址空间

使用 !vtop 进行地址转换

假设要调试运行 MyApp.exe 进程的目标计算机,并且想要调查虚拟地址0x0012F980。 下面是用于 !vtop 扩展以确定相应物理地址的过程。

使用 !vtop 将虚拟地址转换为物理地址

  1. 请确保以十六进制格式工作。 如有必要,请使用 N 16 命令设置当前基数。

  2. 确定地址的 字节索引 。 此数字等于虚拟地址的最低 12 位。 因此,虚拟地址0x0012F980的字节索引为0x980。

  3. 使用 !process 扩展确定地址的目录基

    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
    
  4. 确定目录基 页的页码 。 这只是没有三个尾随十六进制零的目录基。 在此示例中,目录基为0x098FD000,因此页帧编号0x098FD。

  5. 使用 !vtop 扩展。 此扩展的第一个参数应该是页帧编号。 !vtop 的第二个参数应该是有问题的虚拟地址:

    kd> !vtop 98fd 12f980
    Pdi 0 Pti 12f
    0012f980 09de9000 pfn(09de9)
    

    最后一行中显示的第二个数字是物理页开头的物理地址。

  6. 将字节索引添加到页面开头的地址: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* (Display Memory) 命令使用虚拟地址作为其参数:

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 将虚拟地址转换为物理地址

  1. 请确保以十六进制格式工作。 如有必要,请使用 N 16 命令设置当前基数。

  2. 确定地址的 字节索引 。 此数字等于虚拟地址的最低 12 位。 因此,虚拟地址0x0012F980的字节索引为0x980。

  3. 进程上下文 设置为所需的进程:

    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
    
  4. !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
    
  5. 查看右列的最后一行。 此时会显示表示法“pfn 9de9”。 0x9DE9编号是此 PTE) (PFN 的页码 。 将页帧编号乘以0x1000 (例如,将页码左移 12 位) 。 结果(0x09DE9000)是页面开头的物理地址。

  6. 将字节索引添加到页面开头的地址:0x09DE9000 + 0x980 = 0x09DE9980。 这是所需的物理地址。

这与之前方法获取的结果相同。

手动转换地址

尽管 !ptovpte 扩展提供了将虚拟地址转换为物理地址的最快方法,但也可以手动进行此转换。 此过程的说明将阐明虚拟内存体系结构的一些细节。

内存结构的大小因处理器和硬件配置而异。 此示例取自未启用物理地址扩展 (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

这会公开虚拟地址的三个部分:

  • 页目录索引 = 0y00000000000 = 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 位是 状态标志。 在这种情况下,这些标志等于 0x067 -- 或二进制 0y0000011100111。 有关状态标志的说明,请参阅 !pte 参考页。

  • PTE 的高 20 位等于 PTE (PFN) 页帧数 。 在这种情况下,PFN 是0x09DE9。

物理页上的第一个物理地址是 PFN 乘以0x1000 (移左 12 位) 。 字节索引是此页上的偏移量。 因此,要查找的物理地址为 0x09DE9000 + 0x980 = 0x09DE9980。 这与之前的方法获取的结果相同。