前几天和同事扯淡,说调试驱动时访问无效内存会蓝屏,好麻烦,应该让windbg自动建立一个有效的页面,这样就不用蓝屏重启了。虽然说这是扯淡,但仔细想想貌似也不是不可能实现。
首先回顾一下OS解决内存缺页时简单步骤:1.搜索物理页面;2.修改虚拟地址对应的pte表的值;3.重新执行访存失败的指令。基于这样的思路,我也试了一下在windbg中手工建立内存映射(环境:win7 x32)
1.在内核空间中找一个无效地址。先枚举内核中所有已加载的模块,各模块之间的内存间隙应该有不少无效地址.
kd> lm
start end module name
82a06000 82a3d000 hal (deferred)
82a3d000 82e4d000 nt (pdb symbols)
kd> dd 82a06000-0x1000
82a05000 ???????? ???????? ???????? ????????
kd> ed 82a05000 0x00
^ Memory access error in 'ed 82a05000 0x00'
模块nt加载在0x82a0d000,从这开始是模块nt的PE头/text区...要在这些位置中找到一段无效内存还是有点难度的,但是往前搜索一个内存页,说不定能找到无效内存。而windbg以????的形式显示无效地址。果然,我在0x82a05000处找到一块无效内存。往这块内存写入值就报出了错误"^ Memory access error in 'ed 82a05000 0x00'"
2.找一块物理地址,用于建立映射到虚拟内存上。我猜想模块nt的PE头应该是有效内存.验证一下:
kd> !pte 82a06000
VA 82a06000
PDE at C06020A8 PTE at C0415030
contains 00000000001D1063 contains 0000000002A06963
pfn 1d1 ---DA--KWEV pfn 2a06 -G-DA--KWEV
kd> dd 82a06000 L4 ;虚拟页面的内容
82a06000 00905a4d 00000003 00000004 0000ffff
kd> !dd 2A06000 ;物理页面的内容
# 2a06000 00905a4d 00000003 00000004 0000ffff
nt的PE头位于0x82a06000,通过!pte命令查看其页表项,我们知道0x82a06000所在的页面是有效页面,页面物理地址是0x2a06000。另外,虚拟页面的内容和其所映射的物理页面的内容是一致的。接下来,我会用这个物理页面映射到虚拟地址0x82a05000上。(移花接木之术)
3.定位虚拟地址0x82a05000的pte,然后修改pte中存储的值为物理页面0x2a06000。
kd> !pte 82a05000
VA 82a05000
PDE at C06020A8 PTE at C0415028
contains 00000000001D1063 contains 0000000000000000
pfn 1d1 ---DA--KWEV not valid
kd> ed C0415028 2A06963
kd> !pte 82a05000
VA 82a05000
PDE at C06020A8 PTE at C0415028
contains 00000000001D1063 contains 0000000002A06963
pfn 1d1 ---DA--KWEV pfn 2a06 -G-DA--KWEV
修改前,0x82a05000对应的pte (位于C0415028)是无效pte;修改后,变成了有效pte
4.看看修改的结果。
kd> dd 82a05000
82a05000 00905a4d 00000003 00000004 0000ffff
82a05010 000000b8 00000000 00000040 00000000
82a05020 00000000 00000000 00000000 00000000
以前0x82a05000处是无效内存,结果页面映射,现在已经有除????以外的数值。恩,页面映射成功
------------------------------------------------------------------------------
后记:
这次修改有2个有意思的地方:
1.修改后,有时候执行
dd 0x82a05000,还是会看到????,不要以为这是映射失败。要经过一段时间或者编辑一下内存才能看到内存值被修改了。我问过群里的大神,他们的回复这种现象是由于Cpu TLB(快表)的存在,要使TLB中的页表失效,才能重新获得新的页表
2.现在虚拟地址0x82a05000和0x82a06000两个页面的确指向同一块物理页面。理论上,修改其中一个页面的值,势必会影响另一个页面的值,然而,调试过程中并非如此:
我尝试修改页面0x82a05000的值,却并没有影响到页面0x82a06000的值。开始时,我以为发生了写时复制,可是查看0x82a05000的页表项,其值并没有改变。
kd> ed 82a05000 90909090 ;修改虚拟内存的内容
kd> dd 82a05000 L4
82a05000 90909090 00000003 00000004 0000ffff
kd> dd 82a06000 L4 ;查看nt模块的pe头,值没有改变
82a06000 00905a4d 00000003 00000004 0000ffff
kd> !pte 82a05000 ;查看两个虚拟地址对应的pte,也没有发生改变,仍然指向相同的物理地址
VA 82a05000
PDE at C06020A8 PTE at C0415028
contains 00000000001D1063 contains 0000000002A06963
pfn 1d1 ---DA--KWEV pfn 2a06 -G-DA--KWEV
kd> !pte 82a06000
VA 82a06000
PDE at C06020A8 PTE at C0415030
contains 00000000001D1063 contains 0000000002A06963
pfn 1d1 ---DA--KWEV pfn 2a06 -G-DA--KWEV
kd> !dd 2A06000 ;前面修改82a05000的值已经成功,物理页面的内容已经被改变
# 2a06000 90909090 00000003 00000004 0000ffff
对于这个现象,我实在无法解释,只能等路过的大神帮忙解答了