Windows虚拟地址转物理地址(原理+源码实现,附简单小工具)

                                                                                                           By Lthis

上个月就想写了,一直没时间...网上大概搜了一下,原理与操作倒是一大堆,一直没看到源码实现,总得有人动手,这回轮到我了。东西写得很烂,请大牛勿喷。一直觉得靠源码的方式驱动学习是非常好的一种学习方法,比较直观!声明一下,本教程只有讨论开启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;

二、开启PAE的情况

同样是4k页面的,伪代码描述如下:

1.Dir.Pointer Entry(PDPTE)  = PDPTR[Directory Pointer];

2.Director Entry(PDE)  = PDPTE + Directory * 0x8;

3.Page-Table Entry(PTE)  = PDE + Table * 0x8;

4.Physical Address  = PTE+Offset;

在开启PAE的情况下,PDE、PTE均是64bit(8字节,所以要*8),同样PDE、PTE的后3位是属性值,所以需要把后3位抹掉。

关键代码如下:

            // 得到传入的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));

            // 定位页目录指针表并获取页目录表物理页地址
            // ulDirAddress 为页目录表物理页地址
            ULONG ulPointerIdx = virtualAddress.stVirtualAddress.dirPointer;
            ULONG ulDirBaseAddress = pPdbr[ulPointerIdx];
            ulDirBaseAddress &= 0xFFFFF000;            // 中间物理地址

            // 定位页表项
            ULONG ulDirAddress = ulDirBaseAddress + virtualAddress.stVirtualAddress.dirIndex * 0x8;
            phyAddress.LowPart = ulDirAddress;
            PULONG pPageTable = (PULONG)MmMapIoSpace(phyAddress, sizeof(PHYSICAL_ADDRESS), MmNonCached);
            ULONG ulPageTable = *pPageTable;
            ulPageTable &= 0xFFFFF000;                 // 中间物理地址

            // 定位物理页面
            ulPageTable += virtualAddress.stVirtualAddress.tableIndex * 0x8;
            phyAddress.LowPart = ulPageTable;
            PULONG pPageBase = (PULONG)MmMapIoSpace(phyAddress, sizeof(PHYSICAL_ADDRESS), MmNonCached);
            ULONG ulPageBase = *pPageBase;
            ulPageBase &= 0xFFFFF000;

            // 得到物理地址
            ULONG ulPhyAddress = ulPageBase + 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;

            pIrp->IoStatus.Information = cout;

以上代码步骤是参考安于此生的文章写的,看不懂的可以先看看安于此生的文章《启用PAE后虚拟地址到物理地址的转换

另附上小工具源码,该工具用于检测系统是否开启PAE、PSE等。

#define BUFFERSIZE    0x3000
char g_szMemInfo[BUFFERSIZE] = { 0 };

// 以下code在 DriverEntry 中

    DWORD dwPE  = 0;                // Protection Enable    cr0[0]
    DWORD dwWP  = 0;                // Write Protect        cr0[16]
    DWORD dwPG  = 0;                // Paging                cr0[31]
    DWORD dwPAE = 0;                // 物理地址扩展            cr4[5]
    DWORD dwPSE = 0;                // Page Size Extension    cr4[4]
    DWORD dwCr0 = 0;
    DWORD dwCr4 = 0;

    // 注册卸载函数
    pDriverObj->DriverUnload = driverUnload;

    _asm{
        pushad;
        mov eax, cr0;
        mov dwCr0, eax;

        // PE标志位
        and eax, 0x01;
        mov dwPE, eax;
        mov eax, cr0;

        // WP标志位
        and eax, 0x10000;
        mov dwWP, eax;
        mov eax, cr0;

        // PG标志位
        and eax, 0x80000000;
        mov dwPG, eax;

        // PAE
        //mov eax, cr4; 机器码如下
        _emit 0x0F;
        _emit 0x20;
        _emit 0xE0;
        mov dwCr4, eax;
        and eax, 0x20;
        mov dwPAE, eax;

        // PSE
        _emit 0x0F;
        _emit 0x20;
        _emit 0xE0;
        and eax, 0x10;
        mov dwPSE, eax;

        popad;
    }

    KdPrint(("PE  = 0x%08X\r\n",dwPE));
    KdPrint(("WP  = 0x%08X\r\n",dwWP));
    KdPrint(("PG  = 0x%08X\r\n",dwPG));
    KdPrint(("PAE = 0x%08X\r\n",dwPAE));
    KdPrint(("PSE = 0x%08X\r\n",dwPSE));
    KdPrint(("Cr0 = 0x%08X\r\n",dwCr0));
    KdPrint(("Cr4 = 0x%08X\r\n",dwCr4));

    //----------------------------------------------------------------------------
    // PE标志位
    if (0 != dwPE){
        RtlStringCchCatNA(
            g_szMemInfo,
            BUFFERSIZE,
            "----------------------保护模式(PE=1)-------------------\r\n",
            BUFFERSIZE - sizeof("----------------------保护模式(PE=1)-------------------\r\n"));
    }
    else{
        RtlStringCchCatNA(
            g_szMemInfo,
            BUFFERSIZE ,
            "----------------------实地址模式(PE=0)-------------------\r\n",
            BUFFERSIZE - sizeof("----------------------实地址模式(PE=0)-------------------\r\n"));
    }

    //----------------------------------------------------------------------------
    // WP标志位
    if (0 != dwWP){
        RtlStringCchCatA(
            g_szMemInfo,
            BUFFERSIZE,
            "内存写保护(WP)开启...\r\n"
            );
    }
    else{
        RtlStringCchCatA(
            g_szMemInfo,
            BUFFERSIZE,
            "内存写保护(WP)禁止...\r\n"
            );
    }

    //----------------------------------------------------------------------------
    // PG标志位
    if (0 != dwPG){
        RtlStringCchCatA(
            g_szMemInfo,
            BUFFERSIZE,
            "页机制(PG)启用\r\n"
            );
    }
    else{
        RtlStringCchCatA(
            g_szMemInfo,
            BUFFERSIZE,
            "页机制(PG)禁止\r\n"
            );
    }

    //----------------------------------------------------------------------------
    // PAE标志位
    if (0 != dwPAE){
        RtlStringCchCatA(
            g_szMemInfo,
            BUFFERSIZE,
            "物理地址扩展(PAE)已开启\r\n"
            );
    }
    else{
        RtlStringCchCatA(
            g_szMemInfo,
            BUFFERSIZE,
            "物理地址扩展(PAE)未启用\r\n"
            );
    }

    //----------------------------------------------------------------------------
    // PSE标志位
    if (0 != dwPSE){
        RtlStringCchCatA(
            g_szMemInfo,
            BUFFERSIZE,
            "页面大小扩展(PSE)已开启\r\n"
            );
    }
    else{
        RtlStringCchCatA(
            g_szMemInfo,
            BUFFERSIZE,
            "页面大小扩展(PSE)未启用\r\n"
            );
    }

    KdPrint(("%s\r\n", g_szMemInfo));

最后,看看效果运行图。Demo是在ring3层定义一个Unicoe字符串:“Lthis”,然后将其虚拟地址传入ring0层,ring0解析后传出对应的物理地址。

开启PAE下运行的效果:

未开启PAE的运行效果:

附件地址:链接:http://pan.baidu.com/s/1kTENdnL 密码:g5j7

时间: 2024-08-24 06:06:59

Windows虚拟地址转物理地址(原理+源码实现,附简单小工具)的相关文章

[hadoop]Windows下eclipse导入hadoop源码,编译WordCount

hadoop版本为hadoop1.2.1 eclipse版本为eclipse-standard-kepler-SR2-win32-x86_64 WordCount.java为hadoop-1.2.1\src\examples\org\apache\hadoop\examples\WordCount.java 1 /** 2 * Licensed under the Apache License, Version 2.0 (the "License"); 3 * you may not

防猎豹垃圾清理(实现原理+源码)

防猎豹垃圾清理(实现原理+源码) 转载请注明出处: 防猎豹垃圾清理(实现原理+源码) 前几天无意打开猎豹内存大师, 发现它的垃圾清理很强大, 效果也不错, 闲着就研究了下. 不过.. 结果貌似和我想象的不太一样.怎么说呢, 听我下文一一分析. 效果图: 从效果图, 我们可以看出它有以下几个功能: 获取设备上已安装的所有App 获取App的信息, 包括图标和名称 获取当前已用存储和可用存储 扫描App动画效果 清除所有App垃圾文件 看到这里, 你是不是也觉得很强大? 然后然后, 感叹的同时, 我

Windows/Mac跑起XDAG源码

Windows环境 1,下载github源码 https://github.com/XDagger/xdag/archive/master.zip 2,安装VS2017 3,安装openssl和pthreads http://slproweb.com/download/Win64OpenSSL-1_0_2o.exe(注:不是每个版本都可以) ftp://sourceware.org/pub/pthreads-win32/pthreads-w32-2-9-1-release.zip 4,设置全局变

Java源码转C#源码的五款最佳工具

Java源码转C#源码的五款最佳工具 作者:chszs,转载需注明.博客主页:http://blog.csdn.net/chszs 出于某些需要,你可能会遇到把Java源码转换成C#源码的任务.如果是自己一边理解源码,再一边手工翻译,那效率肯定是很低的.有鉴于此,本文推荐了五款最佳的源码转换工具,以解决你的烦恼.工具1#:Java语言转换器助手地址:http://www.microsoft.com/en-us/download/details.aspx?id=14349 Java语言转换器助手是

【小程序源码案例】微信小程序项目开发案例分享

作者:web小二本文标签: 微信小程序 小程序源码案例 小程序项目小程序的开发,并不是适合所有公司,我今天跟大家分享小程序方面的教程,主要是供大家学习使用.学习这种东西,有时候则是单纯的喜欢,没有任何目的,很单纯的为了好玩,记得很早之前学flash,没有想法,就是觉得好玩,纯娱乐爱好而已.到后来玩视频剪辑也是出于同样的原因,不图钱财名利,只是图自己个人爱好娱乐. 但是,学习,有时候则是需要有明确目的,特别是关系到自己吃饭问题的时候,你就需要非常有目的去学习,并且还需要制定好学习的计划与目标,希望

转:微信开发之使用java获取签名signature(贴源码,附工程)

微信开发之使用java获取签名signature(贴源码,附工程) 标签: 微信signature获取签名 2015-12-29 22:15 6954人阅读 评论(3) 收藏 举报  分类: 微信开发(5)  版权声明:本文为博主原创文章,转载注明出处http://blog.csdn.net/u013142781 目录(?)[+] 一.前言 微信接口调用验证最终需要用到的三个参数noncestr.timestamp.signature: 接下来将会给出获取这三个参数的详细代码 本文的环境ecli

PreferenceActivity源码分析与简单应用

· PreferenceActivity可以显示一系列Header,每一个Header可以关联一个Fragment或者Activity.此外,它还可以直接显示Preference条目. · PreferenceActivity显示Header的时候有两种模式:single pane和two panes:如果是Fragment,那么在two panes模式下,也就是大屏模式下,它可以同时显示Header和Fragment,这充分利用了屏幕的空间.而在singlepane模式下只会显示Header,

jQuery源码学习笔记:扩展工具函数

// 扩展工具函数 jQuery.extend({ // http://www.w3school.com.cn/jquery/core_noconflict.asp // 释放$的 jQuery 控制权 // 许多 JavaScript 库使用 $ 作为函数或变量名,jQuery 也一样. // 在 jQuery 中,$ 仅仅是 jQuery 的别名,因此即使不使用 $ 也能保证所有功能性. // 假如我们需要使用 jQuery 之外的另一 JavaScript 库,我们可以通过调用 $.noC

preferenceActivity源码解析与简单用例

PreferenceActivity可以显示一系列Header,每一个Header可以关联一个Fragment或者Activity.此外,它还可以直接显示Preference条目. PreferenceActivity显示Header的时候有两种模式:single pane和two panes:如果是Fragment,那么在two panes模式下,也就是大屏模式下,它可以同时显示Header和Fragment,这充分利用了屏幕的空间.而在singlepane模式下只会显示Header,无论如何