-
-
[原创]Windows虚拟地址转物理地址(原理+源码实现,附简单小工具)
-
发表于:
2015-8-21 09:44
13084
-
[原创]Windows虚拟地址转物理地址(原理+源码实现,附简单小工具)
Windows虚拟地址转物理地址(原理+源码实现,附简单小工具)
上个月就想写了,一直没时间...网上大概搜了一下,原理与操作倒是一大堆,一直没看到源码实现,总得有人动手,这回轮到我了。东西写得很烂,请大牛勿喷。一直觉得靠源码的方式驱动学习是非常好的一种学习方法,比较直观!声明一下,本教程只有讨论开启PAE与关闭PAE两种,至于PSE是否开启没有管...我的虚拟机默认PSE貌似是开启滴?不知是不是写的小工具有问题....对于x64下的等我有时间再写吧。
东西都上传在压缩包中了,Codes文件夹下是工程源码,Demo文件夹下是测试案例,Tool文件夹放的是小工具的Demo和源码。
我的环境:开发环境(win7 sp1 x64 + vs2013社区版 update5 + wdk8.1)
测试环境(vm10 + win7 sp1 x86)
一、先说说未开启PAE的情况,祭出intel手册的经典图例:
这幅图就是虚拟地址转为物理地址的原理图(4k页面),看图说话,用伪代码描述一下:
1.Directory Entry(PDE) = PDBR[Directory];
2.Page-Table Entry(PTE) = PDE + Table * 4;
3.Physical Address = PTE + Offset;
由上可知,Linear Address(线性地址)中的Directory和Table其实就是个索引,在未开启PAE的情况下,PDE、PTE均是32bit(4字节,所以要Table*4),以上只是原理上的描述,实际上,PDE、PTE的后3位是属性值,所以需要把后3位抹掉。
下边上关键代码,基本都步骤都写了注释了,有需要的可以封装成函数。此外,本段代码只是测试用,写的很不规范,比如,在调用MmMapIoSpace应该调用MmUnMapIoSpace释放内存。
// 得到ring3传入的虚拟地址
size_t* pOutAddress = (size_t*)MmGetSystemAddressForMdlSafe(pIrp->MdlAddress, NormalPagePriority);
VIRTUAL_ADDRESS virtualAddress = { 0 };
virtualAddress.ulVirtualAddress = *pOutAddress;
ULONG pdbr;
_asm{
mov eax, cr3;
mov pdbr, eax;
}
PHYSICAL_ADDRESS phyAddress = { 0 };
phyAddress.LowPart = pdbr;
PULONG pPdbr = (PULONG)MmMapIoSpace(phyAddress, sizeof(PHYSICAL_ADDRESS), MmNonCached);
KdPrint(("pdbr = 0x%08X, 映射后的地址0x%p\n", pdbr, pPdbr));
// pPdbr[ulDirBaseIdx] 页目录项
ULONG ulDirBaseIdx = virtualAddress.stVirtualAddress.dirBaseIndex;
ULONG ulDirIdx = virtualAddress.stVirtualAddress.dirIndex;
KdPrint(("第一级,已找到页目录所在项:pPdbr[%d]:0x%08X", ulDirBaseIdx,pPdbr[ulDirBaseIdx]));
ULONG ulDir = pPdbr[ulDirBaseIdx] & 0xFFFFF000; // 抹去后3位得到真正的页目录项
ULONG ulDirPlus = ulDir + ulDirIdx * 4; // 页表项
phyAddress.LowPart = ulDirPlus;
PULONG pDirPlus = (PULONG)MmMapIoSpace(phyAddress, sizeof(PHYSICAL_ADDRESS), MmNonCached);
KdPrint(("第二级,已找到页表项:ulDirPlus = 0x%08X, 映射后的地址0x%p\n", ulDirPlus, pDirPlus));
ULONG ulPageTable = *pDirPlus & 0xFFFFF000; // 抹去后3位得到真正的页表项
// 得到物理地址
ULONG ulPhyAddress = ulPageTable + virtualAddress.stVirtualAddress.offset;
// 映射为虚拟地址,获取其值进行验证
phyAddress.LowPart = ulPhyAddress;
PWCHAR pPhyAddress = (PWCHAR)MmMapIoSpace(phyAddress, sizeof(PHYSICAL_ADDRESS), MmNonCached);
KdPrint(("虚拟地址:0x%08X, 对应物理地址:0x%08X, Value:%S\n", *pOutAddress, ulPhyAddress, pPhyAddress));
// 传出对应物理地址
*pOutAddress = ulPhyAddress;
[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!