如何验证一个地址可否使用—— MmIsAddressValid函数分析
又是一篇内核函数分析的博文,我个人觉得Windows的内核是最好的老师,当你想实现一个功能之前可以看看Windows内核是怎么做的,说不定就有灵感呢:)
首先看下官方的注释说明:
/*++
Routine Description:
For a given virtual address this function returns TRUE if no page fault
will occur for a read operation on the address, FALSE otherwise.
Note that after this routine was called, if appropriate locks are not
held, a non-faulting address could fault.
Arguments:
VirtualAddress - Supplies the virtual address to check.
Return Value:
TRUE if no page fault would be generated reading the virtual address,
FALSE otherwise.
Environment:
Kernel mode.
--*/
WDK文档中给出的功能描述是这样的:The MmIsAddressValid routine checks whether a page fault will occur for a read or write operation at a given virtual address.根据描述来看这个函数的功能只是去检查读写操作会不会触发一个页错误,但是作为一个常用函数,我们常常用这个函数来检查地址合不合法,这次就在源码里看下具体的流程,主要目的是搞清楚这个函数是怎么判断一个函数会不会触发页错误的。
BOOLEAN
MiIsAddressValid (
IN PVOID VirtualAddress,
IN LOGICAL UseForceIfPossible
)
{
PMMPTE PointerPte; //
// If the address is not canonical then return FALSE as the caller (which
// may be the kernel debugger) is not expecting to get an unimplemented
// address bit fault.
// if (MI_RESERVED_BITS_CANONICAL(VirtualAddress) == FALSE) {
return FALSE;
} PointerPte = MiGetPdeAddress (VirtualAddress);
if (PointerPte->u.Hard.Valid == ) {
return FALSE;
} if (MI_PDE_MAPS_LARGE_PAGE (PointerPte)) {
return TRUE;
} PointerPte = MiGetPteAddress (VirtualAddress);
if (PointerPte->u.Hard.Valid == ) {
return FALSE;
} //
// Make sure we're not treating a page directory as a page table here for
// the case where the page directory is mapping a large page. This is
// because the large page bit is valid in PDE formats, but reserved in
// PTE formats and will cause a trap. A virtual address like c0200000 (on
// x86) triggers this case.
// if (MI_PDE_MAPS_LARGE_PAGE (PointerPte)) {
return FALSE;
} return TRUE;
}
代码出人意外的简单,很明显,这是利用了分页机制去查询。先查下页目录项是否为空,然后再看一下页表项是否为空。至于28、29行应该是判断是不是直接使用PDE作为一级表吧,但是现在应该没有这么用的吧。
if (MI_PDE_MAPS_LARGE_PAGE (PointerPte))
{
return TRUE;
}
如上就是一个判断。
而MiGetPdeAddress和MiGetPteAddress 其实是两个宏,这个宏我们也可以拿来用。
#define MiGetPdeAddress(va) \
((PMMPTE)(((((ULONG_PTR)(va) & VIRTUAL_ADDRESS_MASK) >> PDI_SHIFT) << PTE_SHIFT) + PDE_BASE))
#define MiGetPteAddress(va) \
((PMMPTE)(((((ULONG_PTR)(va) & VIRTUAL_ADDRESS_MASK) >> PTI_SHIFT) << PTE_SHIFT) + PTE_BASE))
#define VIRTUAL_ADDRESS_BITS 48
#define VIRTUAL_ADDRESS_MASK ((((ULONG_PTR)1) << VIRTUAL_ADDRESS_BITS) - 1)
注意,每个进程都有自己的进程页表和页目录但是内核在分配一个进程的地址空间时会把PD给复制一份,以便于访问。
由以上的分析可以看出并没有所谓的验证地址是否可读写的功能,我们有时候会把它和ProbeForRead(),ProbeForWrite()这两个函数相混淆。这两个函数才是来验证地址是否可读写的函数,但是仅限于用户地址空间的地址。从WDK中可以看到如下的描述:The ProbeForWrite routine checks that a user-mode buffer actually resides in the user-mode portion of the address space, is writable, and is correctly aligned.我们来看下这个函数的实现。
VOID
ProbeForWrite (
__inout_bcount(Length) PVOID Address,
__in SIZE_T Length,
__in ULONG Alignment
) /*++ Routine Description: This function probes a structure for write accessibility and ensures
correct alignment of the structure. If the structure is not accessible
or has incorrect alignment, then an exception is raised. Arguments: Address - Supplies a pointer to the structure to be probed. Length - Supplies the length of the structure. Alignment - Supplies the required alignment of the structure expressed
as the number of bytes in the primitive datatype (e.g., 1 for char,
2 for short, 4 for long, and 8 for quad). Return Value: None. --*/ { ULONG_PTR EndAddress;
ULONG_PTR StartAddress; #define PageSize PAGE_SIZE //
// If the structure has zero length, then do not probe the structure for
// write accessibility or alignment.
// if (Length != ) { //
// If the structure is not properly aligned, then raise a data
// misalignment exception.
// ASSERT((Alignment == ) || (Alignment == ) ||
(Alignment == ) || (Alignment == ) ||
(Alignment == )); StartAddress = (ULONG_PTR)Address;
if ((StartAddress & (Alignment - )) == ) { //
// Compute the ending address of the structure and probe for
// write accessibility.
// EndAddress = StartAddress + Length - ;
if ((StartAddress <= EndAddress) &&
(EndAddress < MM_USER_PROBE_ADDRESS)) { //
// N.B. Only the contents of the buffer may be probed.
// Therefore the starting byte is probed for the
// first page, and then the first byte in the page
// for each succeeding page.
//
// If this is a Wow64 process, then the native page is 4K, which
// could be smaller than the native page size/
// EndAddress = (EndAddress & ~(PageSize - )) + PageSize;
do {
*(volatile CHAR *)StartAddress = *(volatile CHAR *)StartAddress;
StartAddress = (StartAddress & ~(PageSize - )) + PageSize;
} while (StartAddress != EndAddress); return; } else {
ExRaiseAccessViolation();
} } else {
ExRaiseDatatypeMisalignment();
}
} return;
}
VOID
ProbeForRead(
__in_bcount(Length) VOID *Address,
__in SIZE_T Length,
__in ULONG Alignment
) /*++ Routine Description: This function probes a structure for read accessibility and ensures
correct alignment of the structure. If the structure is not accessible
or has incorrect alignment, then an exception is raised. Arguments: Address - Supplies a pointer to the structure to be probed. Length - Supplies the length of the structure. Alignment - Supplies the required alignment of the structure expressed
as the number of bytes in the primitive datatype (e.g., 1 for char,
2 for short, 4 for long, and 8 for quad). Return Value: None. --*/ { PAGED_CODE(); ASSERT((Alignment == ) || (Alignment == ) ||
(Alignment == ) || (Alignment == ) ||
(Alignment == )); if (Length != ) {
if (((ULONG_PTR)Address & (Alignment - )) != ) {
ExRaiseDatatypeMisalignment(); } else if ((((ULONG_PTR)Address + Length) > (ULONG_PTR)MM_USER_PROBE_ADDRESS) ||
(((ULONG_PTR)Address + Length) < (ULONG_PTR)Address)) { *(volatile UCHAR * const)MM_USER_PROBE_ADDRESS = ;
}
}
}
以分页来管理内存,以页为单位,如果一个内存页的第一个字节可写,那么整个内存页就可写,所以就验证页的一个字节就可以了。(xxx & ~(PageSize - 1)) + PageSize就是以页为单位移动。这里也可以看到字节对齐Alignment仅仅是起到了一个验证的作用,而读写验证也只是一个简单的指针操作*(volatile CHAR *)StartAddress = *(volatile CHAR *)StartAddress;
如何验证一个地址可否使用—— MmIsAddressValid函数分析的更多相关文章
- 笔记——malloc、free、不同数据类型操作、.pyc文件、python安装第三方包、验证一个网站的所有链接有效性
C — malloc( ) and free( ) C 语言中使用malloc( )函数申请的内存空间,为什么一定要使用free释放? **malloc()函数功能:是从堆区申请一段连续的空间,函数结 ...
- Java实现 LeetCode 468 验证IP地址
468. 验证IP地址 编写一个函数来验证输入的字符串是否是有效的 IPv4 或 IPv6 地址. IPv4 地址由十进制数和点来表示,每个地址包含4个十进制数,其范围为 0 - 255, 用(&qu ...
- 微信公众号开发(一)--验证服务器地址的Java实现
现在主流上都用php写微信公众号后台,其实作为后端语言之一的java也可以实现. 这篇文章将对验证服务器地址这一步做出实现. 参考资料:1.慕课网-<初识java微信公众号开发>,2.微信 ...
- 类虚函数表原理实现分析(当我们将虚表地址[n]中的函数替换,那么虚函数的实现就由我们来控制了)
原理分析 当调用一个虚函数时, 编译器生成的代码会调用 虚表地址[0](param1, param2)这样的函数. 已经不是在调用函数名了. 当我们将虚表地址[n]中的函数实现改为另外的函数, 虚函数 ...
- 如何验证 Email 地址:SMTP 协议入门教程
http://www.ruanyifeng.com/blog/2017/06/smtp-protocol.html 作者: 阮一峰 日期: 2017年6月25日 Email 是最常用的用户识别手段 ...
- C++11标准 STL正则表达式 验证电子邮件地址
转自:http://www.cnblogs.com/yejianfei/archive/2012/10/07/2713715.html 我们最经常遇到的验证,就是电子邮件地址验证.网站上常见.各种网页 ...
- C++ Daily 《3》----构造函数可否是虚函数
C++ 中构造函数可否是虚函数? 绝不要!! 而且,在构造函数中调用虚函数也是不提倡的行为,因为会引发预想不到的结果. 因为,在 derived class 对象构造的过程中,首先调用的是基类的构造函 ...
- 此集合已经采用方案 http 的地址。此集合中每个方案中最多只能包含一个地址。
错误信息:此集合已经采用方案 http 的地址.此集合中每个方案中最多只能包含一个地址.如果服务承载于 IIS 中,则可以通过将“system.serviceModel/serviceHostingE ...
- 不定参数函数原理以及实现一个属于自己的printf函数
一.不定参数函数原理 二.实现一个属于自己的printf函数 参考博文:王爽汇编语言综合研究-函数如何接收不定数量的参数
随机推荐
- C++命名规则 (转载仅作参考)
如果想要有效的管理一个稍微复杂一点的体系,针对其中事物的一套统一.带层次结构.清晰明了的命名准则就是必不可少而且非常好用的工具. 活跃在生物学.化学.军队.监狱.黑社会.恐怖组织等各个领域内的大量有识 ...
- php 性能优化
php 性能测试工具 ab(Apache Benchmark) ab 是由 Apache 提供的压力测试软件.安装 apache 服务器时会自带该压测软件. 如何使用: ab -n1000 -c100 ...
- linux kill 掉所有匹配到名字的进程
如,要 kill 掉 swoole 相关的进程 ps aux | grep swoole | awk '{print $2}' | xargs kill -9 ps 列出所有进程, 参数: a - ...
- App统计指标定义
度量(指标) 定义 活跃用户 指启动应用的用户(去重,即1台设备打开多次会被计为1个活跃用户). 是衡量一个应用运营情况最基础的一个指标,用以表示用户规模.通常根据不同的时间限定,有日活跃用户.周活跃 ...
- Python【经典类与新式类】
经典类多继承的调用方法顺序是:深度优先查询,如下图: 新式类多继承的调用方法顺序是:广度优先查询,如下图: 可以使用下面的代码进行实验验证: #经典类class A: def __init__ ...
- NATS_03:NATS发布/订阅机制
概念 发布/订阅(Publish/subscribe 或pub/sub)是一种消息范式,消息的发送者(发布者)不是计划发送其消息给特定的接收者(订阅者).而是发布的消息分为不同的类别,而不需要知道什么 ...
- jquery 格式化数字字符串(小数位)
用于页面上格式化数字字符串,此代码为工作时所需,留作笔记,比较常用. /** * author: xg君 * 描述: 格式化数字字符串,格式化小数位 * obj为需要格式的对象(例如:input标签) ...
- SourceTree使用SSH克隆码云项目
SourceTree使用SSH克隆码云项目 觉得有用的话,欢迎一起讨论相互学习~Follow Me SourceTree使用SSH克隆码云项目 参考文献 https://blog.csdn.net/q ...
- Java并发编程原理与实战二十二:Condition的使用
Condition的使用 Condition用于实现条件锁,可以唤醒指定的阻塞线程.下面来实现一个多线程顺序打印a,b,c的例子. 先来看用wait和notify的实现: public class D ...
- Java并发编程原理与实战十七:AQS实现重入锁
一.什么是重入锁 可重入锁就是当前持有锁的线程能够多次获取该锁,无需等待 二.什么是AQS AQS是JDK1.5提供的一个基于FIFO等待队列实现的一个用于实现同步器的基础框架,这个基础框架的重要性可 ...