概述
上一篇介绍了有关页的概念,以及线性地址转换物理地址过程。受于篇幅限制,抱歉我把实验部分放在了这里。如果你觉得上一篇看完还是晕晕的,那这里的实验希望你能好好做一下。
实验内容很简单,用 malloc 申请一段内存,然后往这段内存写点数据,顺便查看下申请的这段内存的地址是多少,接着,中断到 WinDbg 中,使用线性地址转换方法,找到这个线性地址对应的物理地址,并验证这段物理地址中保存的数据就是你在程序中写入的。
实验代码
// 文件名:LinearAddr.cpp
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int main(int argc, char* argv[])
{
char buf[] = "hello world! I'm in linear address convertion experiment!";
char *p = (char*)malloc(strlen(buf)+1);
strcpy(p, buf);
printf("%s\n", p);
printf("%08x\n", p);
getchar();
free(p);
return 0;
}
实验步骤
- 编译并运行
在虚拟机中编译以上代码,运行。这时候会在 getchar()
处停下来,然后不要回车,在 WinDbg 中中断下来。
图1 运行,然后中断到 WinDbg
- 转换线性地址
图1中看到的线性地址是 003729a0,按照10-10-12拆分,得到的值是 0000 0000 00—-11 0111 0010—-1001 1010 0000 即000-372-9a0. 以后,我会把这种拆分后的地址称为三段式结构。
- 其中第一段是一级页表索引号,它的值是 0,一级页表英文简称是 PDT
- 第二段是二级页表索引号,它的值是 372(注意这是16进制),二级页表的简称是 PTT。
- 第三段是普通页的页内偏移。
首先我们需要知道 PDT 的基址。在 WinDbg 中使用 !process 0 0
查看。
图2 使用 !process 0 0 查看DirBase
然后一直找到最后一个位置,LinearAddr.exe 进程。见图3.
图3 进程 LinearAddr.exe 的页目录基址是 19656000
所以 PDT=0x19656000.
接下来,就是查找 PDT[0] 的值。因为每个元素大小是4字节,PDT[0] 的值就是地址 PDT + 0*4 处的值。
图4 查看 PDT[0] 的值
我们知道,一级页表 PDT 中的每个元素存储了二级页表的编号,那么这里看到的二级页表编号是否就是 1410d067 呢?还记得我说过吗,页表编号只需要用 20 bit 来存储,想起来了吗?
页表中的每个元素的高20位是用来存储页表编号的,所以这里二级页表的编号应该是 1410d,后面的 067 只是 1410d 号页面的属性。根据运算规则,可以计算出二级页表的基址为 1410d000(因为每个页大小是 4KB)。
故有 PTT=1410d000.
然后我们拿着线性地址的第二段,它是一个索引号,它的值是16进制0x372. 所以我们需要去查看 PTT[0x372] 是多少。因为每个元素的大小是4字节,所以PTT[0x372]的值就是地址PTT+0x372*4处的值。
图5 查看 PTT[372] 的值
最终我们得到 PTT[0x372]=0736c067,依据规则,我们得到该保存的页面编号的值是 0736c。于是计算得到这个页的页基址是 0736c000,这只是一个普通页,用来保存数据的。
我们还有线性地址的第 3 段没有使用,它记录了普通页的页内偏移值。所以,最终的物理地址就是 0736c000+9a0 = 0736c9a0
在WinDbg中使用命令!db 0736c9a0
就可以按字节方式查看该地址存储的数据。
图6 查看最终的物理地址处的数据
如果最后你能看到右侧有 hello world! I'm in ...
的字样,恭喜你,完成了该实验。
总结
本篇使用 WinDbg + VM 虚拟机双机调试,实现手工线性地址转换实验,以加深对10-10-12线性地址转换的理解。
实验中使用了 !process 0 0
查看进程的页目录基址,每个进程都有属于自己的页目录基址,当CPU执行某个进程的时候,就会把这个进程的页目录基址加载的 CR3 寄存器,因为每个进程的页目录基址都不一样,所以使用的一套页目录页表也是不同的。这也就是为什么不同进程之间的地址空间是隔离的。
如果你感兴趣的话,可以尝试着修改一个进程的页表,来修改另一个进程空间地址的值。甚至,你可以读写地址为0的内存单元的值。