OS-virtual-memory 之 linear page table

virtual memory

例子分析:
page size = 4K
address space = 16K
physical memroy = 64K
page table entry = 4Bytes 假定 valid bit(1bit) + PFN(31bits)

address translation 图解

Linear page table

在这里插入图片描述

virtual address space(16K)
  1. bits(virtual address space) = log2(virtual memory address space) = log216K = 14
  2. 总页数 = virtual address space / page size = 16K / 4K = 4,可知需要 2bits 表示页数(Virtual Page Number)
  3. offset = log2(page size) = log2(4K) = 12
Virtual Page Number bitsoffset bits
212
physical memory (64K)
  1. bits(virtual address space) = log2(virtual memory address space) = log264K = 16
  2. 总页数 = virtual address space / page size = 64K / 4K = 16,可知需要 4bits 表示页数(Physical Frame Number)
  3. offset = log2(page size) = log2(4K) = 12
Physical Frame Number bitsoffset bits
412
linear page table 记录啥?

记录从 VPNPFN 的映射。那么记录(Page Table Entry)格式是怎样的呢?
该示例使用的记录格式是:

valid bitPFN bits
131

valid bit = 1 该 entry 是一个有效的 VPN -> PFN 映射。
valid bit = 0 该 entry 是一个无效的 VPN -> PFN 映射。

linear page table 就是一维数组,数组 index 作为 VPN。数组每一个元素就是一个 Page Table Entry。

实例 Address Translation

在这里插入图片描述

Page table 例子
Page Table (from entry 0 down to the max size)
  [       0]  0x8000000c
  [       1]  0x00000000
  [       2]  0x00000000
  [       3]  0x80000006
virtual address 例子
Virtual Address Trace
  VA 0x00003229 (decimal:    12841) --> PA or invalid address? (1)
  VA 0x00001369 (decimal:     4969) --> PA or invalid address? (2)
  VA 0x00001e80 (decimal:     7808) --> PA or invalid address? (3)
  VA 0x00002556 (decimal:     9558) --> PA or invalid address? (4)
  VA 0x00003a1e (decimal:    14878) --> PA or invalid address? (5)

(1) VA 0x00003229 (decimal: 12841) 虚拟地址翻译过程。

  1. 计算 VPN = VA / page size = 12841 / 4K = 3,即是线性页表的 index
  2. 计算 offset = VA % page size = 553
  3. 查看 Page Table[VPN] = Page Table[3] = 0x80000006
  4. 判断该 Page Table Entry 是否有效,该 VPN -> PFN 是有效映射。根据 page table entry 格式可以得到 PFN = 6。
  5. 计算物理地址:PFN * page size + offset = 6 * 4K + 553 = 25129。虚拟地址翻译完毕。
    综上步骤可知:地址翻译主要是找到 VPN 到 PFN 的过程。这个关系存储在 Linear Page Table 之中。

References

  1. decimal-to-binary
  2. vm-paging
#include <ntifs.h> #include <ntddk.h> // 全局物理内存区段句柄 HANDLE g_phys_mem_handle = NULL; // 页表索引宏定义 #define PML4_INDEX(va) ((va >> 39) & 0x1FF) #define PDPT_INDEX(va) ((va >> 30) & 0x1FF) #define PD_INDEX(va) ((va >> 21) & 0x1FF) #define PT_INDEX(va) ((va >> 12) & 0x1FF) #define PAGE_OFFSET(va) (va & 0xFFF) // 初始化物理内存区段 NTSTATUS init_physical_memory_section() { OBJECT_ATTRIBUTES objAttr; UNICODE_STRING physMemName; RtlInitUnicodeString(&physMemName, L"\\Device\\PhysicalMemory"); InitializeObjectAttributes(&objAttr, &physMemName, OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, NULL, NULL); return ZwOpenSection(&g_phys_mem_handle, SECTION_MAP_READ | SECTION_MAP_WRITE, &objAttr); } // 映射物理页到虚拟地址 PVOID map_physical_page(ULONG64 physical_address) { if (!g_phys_mem_handle) return NULL; LARGE_INTEGER sectionOffset; sectionOffset.QuadPart = physical_address; PVOID baseAddress = NULL; SIZE_T viewSize = PAGE_SIZE; NTSTATUS status = ZwMapViewOfSection( g_phys_mem_handle, NtCurrentProcess(), &baseAddress, 0L, PAGE_SIZE, &sectionOffset, &viewSize, ViewShare, 0, PAGE_READWRITE ); return NT_SUCCESS(status) ? baseAddress : NULL; } // 取消映射 void unmap_physical_page(PVOID base_address) { if (base_address) { ZwUnmapViewOfSection(NtCurrentProcess(), base_address); } } // 获取进程的CR3值 ULONG64 get_process_cr3(HANDLE pid) { PEPROCESS process; if (NT_SUCCESS(PsLookupProcessByProcessId(pid, &process))) { // Windows 10 10240 EPROCESS中CR3的偏移为0x28 ULONG64 cr3 = *(ULONG64*)((PUCHAR)process + 0x28); ObDereferenceObject(process); return cr3; } return 0; } // 主函数:映射线性地址到物理空间 PVOID get_pyse_map_space(HANDLE pid, ULONG64 linear_address) { // 1. 获取目标进程CR3 ULONG64 cr3 = get_process_cr3(pid); if (!cr3) return NULL; // 2. 计算页表索引 ULONG64 pml4_index = PML4_INDEX(linear_address); ULONG64 pdpt_index = PDPT_INDEX(linear_address); ULONG64 pd_index = PD_INDEX(linear_address); ULONG64 pt_index = PT_INDEX(linear_address); ULONG64 offset = PAGE_OFFSET(linear_address); // 3. 递归查询页表 ULONG64 current_phys = cr3 & ~0xFFF; // 清除低12位标志 // PML4 -> PDPT PVOID mapped_page = map_physical_page(current_phys); if (!mapped_page) return NULL; ULONG64* pml4_entry = (ULONG64*)mapped_page + pml4_index; if (!(*pml4_entry & 1)) { // 检查有效位 unmap_physical_page(mapped_page); return NULL; } current_phys = *pml4_entry & 0x000FFFFFFFFFF000; unmap_physical_page(mapped_page); // PDPT -> PD mapped_page = map_physical_page(current_phys); if (!mapped_page) return NULL; ULONG64* pdpt_entry = (ULONG64*)mapped_page + pdpt_index; if (!(*pdpt_entry & 1)) { unmap_physical_page(mapped_page); return NULL; } // 检查2MB大页 if (*pdpt_entry & 0x80) { ULONG64 large_page_base = *pdpt_entry & 0x000FFFFFFFFFF000; unmap_physical_page(mapped_page); return (PVOID)((ULONG64)map_physical_page(large_page_base) + offset); } current_phys = *pdpt_entry & 0x000FFFFFFFFFF000; unmap_physical_page(mapped_page); // PD -> PT mapped_page = map_physical_page(current_phys); if (!mapped_page) return NULL; ULONG64* pd_entry = (ULONG64*)mapped_page + pd_index; if (!(*pd_entry & 1)) { unmap_physical_page(mapped_page); return NULL; } // 检查1GB大页 if (*pd_entry & 0x80) { ULONG64 large_page_base = *pd_entry & 0x000FFFFFFFFFF000; unmap_physical_page(mapped_page); return (PVOID)((ULONG64)map_physical_page(large_page_base) + offset); } current_phys = *pd_entry & 0x000FFFFFFFFFF000; unmap_physical_page(mapped_page); // PT -> 物理页 mapped_page = map_physical_page(current_phys); if (!mapped_page) return NULL; ULONG64* pt_entry = (ULONG64*)mapped_page + pt_index; if (!(*pt_entry & 1)) { unmap_physical_page(mapped_page); return NULL; } ULONG64 target_phys = *pt_entry & 0x000FFFFFFFFFF000; unmap_physical_page(mapped_page); // 4. 映射目标物理页 PVOID result = map_physical_page(target_phys); return result ? (PVOID)((ULONG64)result + offset) : NULL; } // 驱动卸载清理 VOID DriverUnload(PDRIVER_OBJECT DriverObject) { if (g_phys_mem_handle) { ZwClose(g_phys_mem_handle); g_phys_mem_handle = NULL; } } // 驱动入口 extern "C" NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath) { DriverObject->DriverUnload = DriverUnload; NTSTATUS status = init_physical_memory_section(); if (!NT_SUCCESS(status)) { KdPrint(("Failed to open physical memory section: 0x%X\n", status)); return status; } ULONG64 linear_addr = 0xEA46DD02AC; PVOID mapped_addr = get_pyse_map_space((HANDLE)8496, linear_addr); DbgPrintEx(77,0,"数据:%x",mapped_addr); return STATUS_SUCCESS; } [TRACE] The DiagOutputDir folder is accessible [TRACE] The DiagOutputDir folder is accessible Invalid parameter passed to C runtime function. Invalid parameter passed to C runtime function. Invalid parameter passed to C runtime function. Invalid parameter passed to C runtime function. Invalid parameter passed to C runtime function. Invalid parameter passed to C runtime function. Invalid parameter passed to C runtime function. Invalid parameter passed to C runtime function. Failed to locate Mobile Store key, so we're not running on a mobile device. [HRESULT = 0x80070002 - ]数据:0Info: LogSession: Starting a new log session at [C:\Windows\Logs\WinREAgent]
最新发布
07-22
<think>我们正在处理一个Windows内核驱动程序的问题,该程序用于通过页表遍历将线性地址映射到物理内存。用户遇到了运行时参数错误和日志信息。我们需要帮助用户调试驱动程序,理解错误来源,并可能优化代码。 首先,我们需要理解页表遍历(Page Table Walk)的基本原理。在x86/x64架构中,虚拟地址(线性地址)到物理地址的转换是通过多级页表实现的。对于64位系统,通常使用4级页表(PML4, PDP, PD, PT)。驱动程序需要读取这些页表结构来转换地址。 用户遇到的错误是“无效参数”(invalid parameter)运行时错误。这通常意味着在调用某个内核函数时传递了不合法的参数。此外,日志输出可能提供了更多线索。 由于我们无法直接看到用户的代码,但根据描述,代码中包含了NT驱动程序框架、物理内存访问和页表解析,我们可以推测一些常见的问题点: 1. **参数验证失败**:在调用内核API时,传递的参数可能无效。例如,访问内存时地址不正确,或者传递的指针无效。 2. **页表遍历逻辑错误**:在解析页表的过程中,可能没有正确处理页表项(PTE)中的标志位(如存在位、大页标志等),导致访问了无效的地址。 3. **内存访问权限**:在读取物理内存时,可能需要使用正确的函数(如`MmGetPhysicalAddress`和`MmMapIoSpace`),并确保访问的内存是有效的。 4. **同步问题**:在多处理器环境下,页表可能被修改,因此需要确保在遍历页表时持有正确的锁(如禁用中断或使用CR3寄存器)。 为了帮助用户调试,我们可以建议以下步骤: 1. **检查日志输出**:仔细查看驱动程序输出的日志信息,确定错误发生的具体位置和参数值。 2. **使用调试器(WinDbg)**:通过内核调试器(如WinDbg)附加到目标系统,检查错误发生时的调用栈和寄存器状态。 3. **验证参数**:在代码中添加更多的日志输出,特别是在调用内核API之前,打印出参数的值,确保它们符合要求。 4. **页表遍历算法**:确保页表遍历算法正确,特别是处理大页(Large Page)和保留位等情况。 下面是一个简单的页表遍历(64位系统,4级分页)的伪代码: ``` 1. 获取目标进程的CR3(页表基址)或通过进程的目录表基址(DirectoryTableBase)获取。 2. 将线性地址拆分为多个部分(PML4索引、PDP索引、PD索引、PT索引、页内偏移)。 3. 从CR3开始,读取PML4表,根据PML4索引获取PDP表基址。 4. 读取PDP表,根据PDP索引获取PD表基址。 5. 读取PD表,根据PD索引获取PT表基址(如果大页标志被设置,则直接得到1GB页的基址)。 6. 读取PT表,根据PT索引获取物理页基址(如果大页标志被设置,则得到2MB页的基址)。 7. 将物理页基址加上页内偏移得到物理地址。 ``` 注意:在读取物理内存时,需要将物理地址映射到系统空间。可以使用`MmMapIoSpace`函数,但要注意映射的大小(通常为4KB)。 在Windows内核中,访问另一个进程的页表需要特别小心,因为页表可能被换出。因此,通常需要附加到目标进程(通过`KeStackAttachProcess`)或使用进程的CR3。 现在,针对用户的问题,我们生成相关问题: §§相关问题§§ 1. 在Windows内核驱动中,如何安全地读取物理内存? 2. 页表遍历过程中如何处理大页(Large Page)? 3. 如何获取目标进程的页表基址(CR3)? 4. 在驱动程序开发中,常见的无效参数错误有哪些原因? 5. 如何在内核驱动程序中输出详细的调试日志? 6. 在Windows内核中,如何正确地映射物理地址到虚拟地址以读取内容?</think>### Windows内核驱动实现线性地址到物理地址映射的问题分析 在Windows内核驱动中通过页表遍历实现线性地址到物理地址映射时遇到"无效参数"错误,通常涉及以下关键环节: 1. **页表遍历基础原理** 在x64架构下,地址转换需遍历4级页表结构: $$ \text{CR3} \rightarrow \text{PML4E} \rightarrow \text{PDPTE} \rightarrow \text{PDE} \rightarrow \text{PTE} $$ 每个表项占用8字节(64位),索引计算方式为: $ \text{Index} = (\text{VirtualAddr} \gg \text{Shift}) \ \&\ \text{Mask} $ 2. **常见错误原因**: - **无效地址输入**:未验证用户态传入的线性地址范围 ```c // 正确验证示例 if (VirtualAddress >= MmUserProbeAddress) { DbgPrint("用户地址越界: 0x%p", VirtualAddress); return STATUS_ACCESS_VIOLATION; } ``` - **物理内存映射错误**:使用`MmMapIoSpace`时未对齐或超出范围 - **页表项标志位处理**:忽略`NX`/`PAT`等标志位导致访问冲突 - **大页处理缺失**:未检测`PS=1`的2MB/1GB大页情况 - **CR3寄存器访问**:直接读取CR3需在0环执行`__readcr3()` 3. **调试建议**: - **日志增强**:在每级页表遍历时输出关键信息 ```c DbgPrint("PML4[%d] = 0x%llx (Present=%d)", pml4_index, pPml4e->Value, pPml4e->Present); ``` - **使用`!pte`命令**:在WinDbg中验证页表结构 - **内存屏障**:访问页表前添加`_mm_sfence()`防止乱序执行 - **错误注入测试**:故意传递非法地址测试异常处理 4. **物理地址访问优化**: ```c PHYSICAL_ADDRESS pa = MmGetPhysicalAddress(pVirtual); PVOID pMapped = MmMapIoSpaceEx(pa, PAGE_SIZE, PAGE_READWRITE); if (!pMapped) return STATUS_INSUFFICIENT_RESOURCES; // 访问物理内存... MmUnmapIoSpace(pMapped, PAGE_SIZE); ``` 5. **安全注意事项**: - 必须使用`ProbeForRead`验证用户缓冲区 - 禁用SMAP/SMEP时需设置CR4寄存器 - 大页处理需特殊路径(PDE.PS=1时直接计算物理地址) [^1]: 内核访问物理内存需通过`MmMapIoSpace`建立映射,直接访问物理地址将触发异常
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值