怀德维宁

大邦维屏,大宗维翰。怀德维宁,宗子维城。

0%

windbg标准调试技巧-读写内存(5):将虚拟地址转换为物理地址

大多数调试器命令都是用虚拟地址而非物理地址作为输出及输出参数。然而,也同时存在物理地址起作用的场景。

这里有两种方式将虚拟地址转换为物理地址:使用!vtop扩展或使用!pte扩展。

#1 使用!vtop进行地址转换

  1. 确保在16禁止之下进行操作。如果必要的话,通过N 16指令设置当前环境为16进制;

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

  3. 通过使用!process扩展确定地址的字典进制:

     kd> !process 0 0
     **** NT ACTIVE PROCESS DUMP ****
     ....
     PROCESS ff779190  SessionId: 0  Cid: 04fcPeb: 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)

最后一行中展示的第二个数字是物理页的起始物理地址。

  1. 在页的开始出加上地址的字节索引: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。

#2 使用!pte进行地址转换

假设客户正在调查属于MyApp.exe进程的虚拟地址0x0012F980。在使用!pte扩展指令获取其对应的物理地址过程中,操作如下:

  1. 确保子啊16进制下进行运算。如果有必要,通过N 16指令设置当前环境为16进制;

  2. 获取地址的字节索引。该数字等同于虚拟地址的低12位。因此,虚拟地址0x0012f980的字节索引为0x980;

  3. 将进程上下文环境设置到目标进程中:

     kd> !process 0 0
     **** NT ACTIVE PROCESS DUMP ****
     ....
     PROCESS ff779190  SessionId: 0  Cid: 04fcPeb: 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指令时以虚拟地址作为参数。输出信息以两列形式展示出来。左边的一列描述了地址对应的页目录条目(page directory entry,pe),右边列展示了页表条目(page table entry,pte):

     kd> !pte 12f980
        VA 0012f980
     PDE at   C0300000PTE at C00004BC
     contains 0BA58067  contains 09DE9067
     pfn ba58 ---DA--UWVpfn 9de9 ---DA--UWV
    
  5. 查看右边列的最后一行。符号”pfn 9de9”出现了。pte的页框号(page frame number,pfn)是0x9de9.页框号乘以0x1000(例如,左移12位)。结果0x09DE9000就是内存也的起始物理地址;

  6. 在页的开始出加上地址的字节索引:0x09DE9000 + 0x980 = 0x09DE9980。这就是目标物理地址;

与之前的方法得到了相同的结果。

#3 手动进行地址转换

尽管!ptov和pte指令提供了将虚拟地址转换为物理地址的最快方式,但是也可以人工完成这一转换过程。对该过程的描述将阐明虚拟内存体系结构的一些细节。

内存结构因其处理器和硬件配置的不同而会在大小方面发生变化。例子来源于一个没有启用物理地址扩展(physical address extension,pae)功能的x86系统。

使用0x0012F980作为虚拟地址,首先需要将该地址转换为2进制,可以手动转换,也可以使用.formats(dhow number 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

虚拟地址有3个字段组成。第0位到第11位是字节索引。第12位到第21位是页表索引。第22位到第31位是页目录索引。将对应字段进行拆分,实现如下:

0x0012F980  =  0y  00000000 00   010010 1111   1001 10000000

导出虚拟地址的3个字段:

  • 页目录索引=0y0000000000=0x0
  • 页表索引=0y0100101111=0x12F
  • 字节索引=0y100110000000=0x980

之后系统需要3个额外的信息:

  • 每一个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位的双字变量。其内容如下:

kd> dd 0xc00004bc L1
c00004bc  09de9067

pte数值是0x09DE9067。其由两个字段组成。

  • pte的低12位是状态标志(status flags)。在这种情况下,这些标志位等于0x067–或者二进制的0y000001100111.对于状态标志位的解释,可以查看!pte指令参考页。
  • pte的高20位等于pte的页框号pfn。在这种情况下,pfn是0x09DE9.

物理页上的第一个物理地址是pfn乘以0x1000(即左移12位)。字节索引就是页上的偏移。因此,查找的物理地址就是0x09DE9000+0x980=0x09DE9980。与之前的计算方式获取的结果一致。