ReactOS 图形系统分析(23):引擎内存管理 --- mem.c
1. 概述
mem.c 位于 win32ss/gdi/eng/mem.c(文件头自述 "GDI Driver Memory Management Functions"),实现 GDI 引擎的内存管理服务族 ------既是引擎内部各模块的通用分配入口,也是 DDI 契约中供显示/字体/打印驱动调用的内存服务(win32k.sys 导出)。
按能力分三类:
┌─────────────────────────────────────────────────────────────┐
│ gdi/eng/mem.c(本分析对象) │
│ A. 内核池分配 │
│ EngAllocMem(Paged/NonPaged + 清零)/ EngFreeMem │
│ B. 用户虚拟内存分配 │
│ EngAllocUserMem / EngFreeUserMem(ZwAllocateVirtMem) │
│ C. 用户内存安全锁定(防内核使用中被用户释放) │
│ EngSecureMem / EngSecureMemForRead / EngUnsecureMem │
│ + HackSecureVirtualMemory / HackUnsecureVirtualMemory │
│ (MDL 探测+锁定,供内核访问用户缓冲区) │
├─────────────────────────────────────────────────────────────┤
│ 底层:ExAllocatePoolWithTag / Zw(Alloc|Free)VirtualMemory / │
│ MmSecureVirtualMemory / IoAllocateMdl+MmProbeAndLock │
└─────────────────────────────────────────────────────────────┘
重要观察: EngAllocMem 是全引擎最通用的分配服务------全树 32 个文件、65 处调用(eng/ntgdi/字体驱动/显示驱动/DirectX 全在用),是《分析_4》2.x 各对象(SURFACE/EBRUSHOBJ/EXLATEOBJ 等)与《分析_18/19》等文件分配内存的共同底层。
2. 内核池分配 --- EngAllocMem / EngFreeMem
c
PVOID EngAllocMem(ULONG fl, ULONG cjMemSize, ULONG ulTag)
{
pvBaseAddress = ExAllocatePoolWithTag((fl & FL_NONPAGED_MEMORY) ?
NonPagedPool : PagedPool,
cjMemSize, ulTag);
if (pvBaseAddress == NULL) return NULL;
if (fl & FL_ZERO_MEMORY) RtlZeroMemory(pvBaseAddress, cjMemSize);
return pvBaseAddress;
}
VOID EngFreeMem(PVOID pvBaseAddress)
{
if (pvBaseAddress) // Windows 允许传 NULL
ExFreePoolWithTag(pvBaseAddress, 0); // tag 0 == ExFreePool(不校验 tag)
}
标志(winddi.h(file:///d:/reactos/sdk/include/psdk/winddi.h#L1358-L1359)):
| 标志 | 含义 |
|---|---|
FL_ZERO_MEMORY (0x1) |
分配后清零(RtlZeroMemory) |
FL_NONPAGED_MEMORY (0x2) |
从 NonPagedPool 分配(缺省为 PagedPool) |
设计要点:
- 默认分页池 (大部分 GDI 对象可分页换出);需要锁路径/中断上下文时显式
FL_NONPAGED_MEMORY(如信号量 ERESOURCE,见《分析_20》)。 ulTag用于池标签调试(WinDbg!pool可看归属);EngFreeMem用 tag 0 等价ExFreePool,不校验标签。- 与
EngAllocSectionMem(《分析_21》会话空间大块共享分配)形成"小对象用池 / 大共享用节"的分层。
3. 用户虚拟内存分配 --- EngAllocUserMem / EngFreeUserMem
c
PVOID EngAllocUserMem(SIZE_T cjMemSize, ULONG ulTag)
{
Status = ZwAllocateVirtualMemory(NtCurrentProcess(), &pvBaseAddress, 0,
&cjMemSize, MEM_COMMIT | MEM_RESERVE,
PAGE_READWRITE);
...
/* TODO: Add allocation info to AVL tree (stored inside W32PROCESS) */
}
VOID EngFreeUserMem(PVOID pvBaseAddress)
{
ZwFreeVirtualMemory(NtCurrentProcess(), &pvBaseAddress, &cjSize, MEM_RELEASE);
}
语义: 在当前进程的用户地址空间 一次性"保留+提交"一段读写内存(用于内核替用户分配 GDI 位图缓冲等)。树内消费者:
| 消费者 | 用途 |
|---|---|
| surface.c(file:///d:/reactos/win32ss/gdi/eng/surface.c#L238) | BMF_USERMEM 位图的像素内存(CreateBitmap 用户内存模式) |
| dibobj.c(file:///d:/reactos/win32ss/gdi/ntgdi/dibobj.c#L2017) | 位图信息查询/转换的临时用户缓冲(bm.bmBits) |
已知 TODO(源码标注): 分配信息应记入 W32PROCESS 的 AVL 树以便进程退出时清理,当前未实现(若进程中途退出,该用户内存可能泄漏/由 VAD 回收)。
4. 用户内存安全锁定 --- EngSecureMem / EngSecureMemForRead / EngUnsecureMem
Windows 语义: 当内核正在使用一块用户模式内存 (如 DIB Section 位图、系统调用传入的缓冲)时,必须"锁定"它------否则用户线程可能并发释放该内存(VirtualFree/退出进程),内核继续写就崩溃。EngSecureMem 返回不透明句柄,用毕 EngUnsecureMem 解锁。
ReactOS 现状(关键): 三个函数的真实现被 {// HACK!!! 块短路,真正的 MmSecureVirtualMemory 分支不可达:
c
HANDLE EngSecureMem(PVOID Address, ULONG Length)
{
{// HACK!!!
_SEH2_TRY { ProbeForWrite(Address, Length, 1); } // 仅做可写性探测
_SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER) { _SEH2_YIELD(return NULL); }
_SEH2_END;
return (HANDLE)-1; // ← 恒返回 -1,假句柄!
}
return MmSecureVirtualMemory(Address, Length, PAGE_READWRITE); // 不可达
}
VOID EngUnsecureMem(HANDLE Mem)
{
if (Mem == (HANDLE)-1) return; // 假句柄直接跳过
MmUnsecureVirtualMemory((PVOID)Mem);
}
EngSecureMem只做ProbeForWrite校验后返回假句柄-1(无真正锁定);EngSecureMemForRead类似(逐页读探测)。EngUnsecureMem遇-1直接返回------成对调用下行为等价"只探测不锁定"。- 这意味着 ReactOS 当前没有真正防止用户释放正在使用内存的机制(属安全缺口/简化)。
消费者: dibobj.c(file:///d:/reactos/win32ss/gdi/ntgdi/dibobj.c#L1166)(DIB 位图锁定)、mapping.c(file:///d:/reactos/win32ss/gdi/eng/mapping.c#L58)(EngMapSectionView 后锁定视图,见《分析_21》)。
5. MDL 锁定 --- HackSecureVirtualMemory / HackUnsecureVirtualMemory
供内核访问用户缓冲区(如 DIB 引擎读写用户位图)的 MDL 探测+映射辅助:
c
PVOID HackSecureVirtualMemory(PVOID Address, SIZE_T Size,
ULONG ProbeMode, PVOID *SafeAddress)
{
pmdl = IoAllocateMdl(Address, Size, FALSE, TRUE, NULL);
MmProbeAndLockPages(pmdl, UserMode, Operation); // 探测+锁定用户页
*SafeAddress = MmGetSystemAddressForMdlSafe(pmdl, NormalPagePriority);
return pmdl; // 返回 MDL 作为"句柄"
}
VOID HackUnsecureVirtualMemory(PVOID SecureHandle)
{
MmUnlockPages(pmdl); // 解锁
IoFreeMdl(pmdl); // 释放 MDL
}
- 流程:
IoAllocateMdl→MmProbeAndLockPages(SEH 保护,探测失败返回 NULL)→MmGetSystemAddressForMdlSafe得到系统空间映射地址,内核即可直接读写用户缓冲; - 是 4.3 节"只探测不锁定"的安全替代方案(真正的页锁定 + 系统地址映射),声明于 ntgdi/misc.h(file:///d:/reactos/win32ss/gdi/ntgdi/misc.h#L29-L30)。
6. 导出与消费路径
6.1 win32k.spec 导出(win32k.spec(file:///d:/reactos/win32ss/win32k.spec))
@ stdcall EngAllocMem(long long long) # L9
@ stdcall EngAllocPrivateUserMem(ptr ptr long) # L10(本文件未实现,spec 保留)
@ stdcall EngAllocUserMem(ptr long) # L12
@ stdcall EngFreeMem(ptr) # L57
@ stdcall EngFreePrivateUserMem(ptr ptr) # L59(本文件未实现)
@ stdcall EngFreeUserMem(ptr) # L61
@ stdcall EngSecureMem(ptr long) # L118
@ stdcall EngUnsecureMem(ptr) # L140
6.2 其他导出路径
| 路径 | 内容 |
|---|---|
| dxgthk.spec(file:///d:/reactos/win32ss/reactx/dxgthk/dxgthk.spec#L3-L14) | 重新导出 EngAllocUserMem/EngFreeUserMem(用户态 DirectX 驱动用) |
| win32u/umpdstubs(file:///d:/reactos/win32ss/gdi/eng/umpdstubs.c#L9) | NtGdiUMPDEngFreeUserMem(UMPD 用户内存释放) |
6.3 调用链示例
#mermaid-svg-uhc0aKtKrEtMDVuE{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-uhc0aKtKrEtMDVuE .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-uhc0aKtKrEtMDVuE .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-uhc0aKtKrEtMDVuE .error-icon{fill:#552222;}#mermaid-svg-uhc0aKtKrEtMDVuE .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-uhc0aKtKrEtMDVuE .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-uhc0aKtKrEtMDVuE .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-uhc0aKtKrEtMDVuE .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-uhc0aKtKrEtMDVuE .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-uhc0aKtKrEtMDVuE .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-uhc0aKtKrEtMDVuE .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-uhc0aKtKrEtMDVuE .marker{fill:#333333;stroke:#333333;}#mermaid-svg-uhc0aKtKrEtMDVuE .marker.cross{stroke:#333333;}#mermaid-svg-uhc0aKtKrEtMDVuE svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-uhc0aKtKrEtMDVuE p{margin:0;}#mermaid-svg-uhc0aKtKrEtMDVuE .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-uhc0aKtKrEtMDVuE .cluster-label text{fill:#333;}#mermaid-svg-uhc0aKtKrEtMDVuE .cluster-label span{color:#333;}#mermaid-svg-uhc0aKtKrEtMDVuE .cluster-label span p{background-color:transparent;}#mermaid-svg-uhc0aKtKrEtMDVuE .label text,#mermaid-svg-uhc0aKtKrEtMDVuE span{fill:#333;color:#333;}#mermaid-svg-uhc0aKtKrEtMDVuE .node rect,#mermaid-svg-uhc0aKtKrEtMDVuE .node circle,#mermaid-svg-uhc0aKtKrEtMDVuE .node ellipse,#mermaid-svg-uhc0aKtKrEtMDVuE .node polygon,#mermaid-svg-uhc0aKtKrEtMDVuE .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-uhc0aKtKrEtMDVuE .rough-node .label text,#mermaid-svg-uhc0aKtKrEtMDVuE .node .label text,#mermaid-svg-uhc0aKtKrEtMDVuE .image-shape .label,#mermaid-svg-uhc0aKtKrEtMDVuE .icon-shape .label{text-anchor:middle;}#mermaid-svg-uhc0aKtKrEtMDVuE .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-uhc0aKtKrEtMDVuE .rough-node .label,#mermaid-svg-uhc0aKtKrEtMDVuE .node .label,#mermaid-svg-uhc0aKtKrEtMDVuE .image-shape .label,#mermaid-svg-uhc0aKtKrEtMDVuE .icon-shape .label{text-align:center;}#mermaid-svg-uhc0aKtKrEtMDVuE .node.clickable{cursor:pointer;}#mermaid-svg-uhc0aKtKrEtMDVuE .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-uhc0aKtKrEtMDVuE .arrowheadPath{fill:#333333;}#mermaid-svg-uhc0aKtKrEtMDVuE .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-uhc0aKtKrEtMDVuE .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-uhc0aKtKrEtMDVuE .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-uhc0aKtKrEtMDVuE .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-uhc0aKtKrEtMDVuE .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-uhc0aKtKrEtMDVuE .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-uhc0aKtKrEtMDVuE .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-uhc0aKtKrEtMDVuE .cluster text{fill:#333;}#mermaid-svg-uhc0aKtKrEtMDVuE .cluster span{color:#333;}#mermaid-svg-uhc0aKtKrEtMDVuE div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-uhc0aKtKrEtMDVuE .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-uhc0aKtKrEtMDVuE rect.text{fill:none;stroke-width:0;}#mermaid-svg-uhc0aKtKrEtMDVuE .icon-shape,#mermaid-svg-uhc0aKtKrEtMDVuE .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-uhc0aKtKrEtMDVuE .icon-shape p,#mermaid-svg-uhc0aKtKrEtMDVuE .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-uhc0aKtKrEtMDVuE .icon-shape .label rect,#mermaid-svg-uhc0aKtKrEtMDVuE .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-uhc0aKtKrEtMDVuE .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-uhc0aKtKrEtMDVuE .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-uhc0aKtKrEtMDVuE :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} EngAllocMem(65 处调用:eng/ntgdi/驱动)
ExAllocatePoolWithTag
Paged/NonPaged
EngAllocUserMem
ZwAllocateVirtualMemory
用户地址空间
EngSecureMem
ProbeForWrite + 假句柄-1
(真实锁定未启用)
HackSecureVirtualMemory
MDL 探测锁定
- 系统地址映射
7. 与《分析_4》标注对照
| 标注 | 核对 |
|---|---|
| 《_4》第 43 行:mem.c =「引擎内存分配」 | 准确。补充:三类能力(内核池/用户内存/安全锁定);EngAllocMem 为全引擎通用分配入口;EngSecureMem 当前为"只探测不锁定"的 HACK 简化 |
8. 源码索引
| 文件 | 关键内容 |
|---|---|
| mem.c(file:///d:/reactos/win32ss/gdi/eng/mem.c) | EngAllocMem/FreeMem、EngAllocUserMem/FreeUserMem、EngSecureMem(ForRead)/UnsecureMem、HackSecure/UnsecureVirtualMemory |
| winddi.h(file:///d:/reactos/sdk/include/psdk/winddi.h#L1358-L1359) | FL_ZERO_MEMORY / FL_NONPAGED_MEMORY |
| misc.h(file:///d:/reactos/win32ss/gdi/ntgdi/misc.h#L29-L30) | Hack* 声明 |
| surface.c(file:///d:/reactos/win32ss/gdi/eng/surface.c#L238) | BMF_USERMEM 位图用 EngAllocUserMem |
| mapping.c(file:///d:/reactos/win32ss/gdi/eng/mapping.c#L58) | EngMapSectionView 用 EngSecureMem(《分析_21》) |
| dibobj.c(file:///d:/reactos/win32ss/gdi/ntgdi/dibobj.c#L1166) | DIB 位图锁定 / 用户缓冲分配 |
关联模块:
- 大块共享分配:
EngAllocSectionMem(《分析_21》mapping.c,会话空间) - 分配对象:SURFACE/EBRUSHOBJ/EXLATEOBJ/PATHOBJ 等(《分析_4/17/18/19》)
- 内存标签:GDITAG_* 系列(ExAllocatePoolWithTag)
9. 各函数使用说明
9.1 EngAllocMem --- 分配内核池内存(最常用)
c
PVOID EngAllocMem(ULONG fl, ULONG cjMemSize, ULONG ulTag);
| 参数 | 说明 |
|---|---|
fl |
标志:FL_ZERO_MEMORY(清零)/ FL_NONPAGED_MEMORY(非分页池);可 OR 组合 |
cjMemSize |
请求字节数 |
ulTag |
4 字节池标签(如 'rusG'),用于调试 !pool 归类 |
使用要点:
- 默认是分页池 ------只有锁路径/中断/DMA 等必须常驻的场景才加
FL_NONPAGED_MEMORY(例:信号量 ERESOURCE 见《分析_20》)。 - 返回 NULL 表示分配失败,必须判空再使用。
- 要求清零内存时传
FL_ZERO_MEMORY,省去手动memset。 - 释放必须用
EngFreeMem(配 tag 0),不要直接用ExFreePool(会丢失"允许 NULL"的语义约定)。
典型用法:
c
PBRUSH pbr = EngAllocMem(FL_ZERO_MEMORY, sizeof(BRUSH), GDITAG_BRUSH);
if (!pbr) return NULL; // 失败处理
...
EngFreeMem(pbr); // 释放(NULL 安全)
9.2 EngFreeMem --- 释放 EngAllocMem 分配的内存
c
VOID EngFreeMem(PVOID pvBaseAddress);
使用要点:
- 允许传 NULL(与 Windows 一致),无需外层判空。
- 内部用 tag 0 调用
ExFreePoolWithTag,等价ExFreePool,不校验标签。 - 只能释放
EngAllocMem(或驱动直接用ExAllocatePoolWithTag)分配的内核池内存;不能释放用户内存或节映射内存。
9.3 EngAllocUserMem --- 在当前进程用户空间分配内存
c
PVOID EngAllocUserMem(SIZE_T cjMemSize, ULONG ulTag);
使用要点:
- 分配在当前进程的用户地址空间 (
ZwAllocateVirtualMemory,MEM_COMMIT|MEM_RESERVE,PAGE_READWRITE),内核可直接写,用户态可读。 - 典型用途:为
BMF_USERMEM位图提供像素缓冲、为位图信息查询提供临时缓冲。 - 释放必须用
EngFreeUserMem。 - 已知局限(源码 TODO):分配记录未纳入 W32PROCESS 的 AVL 树------若分配后进程异常退出,该内存靠进程 VAD 回收,引擎不主动跟踪。
典型用法:
c
pvBits = EngAllocUserMem(cjBits, 0);
if (!pvBits) return FALSE;
... /* 内核填像素 */
EngFreeUserMem(pvBits); /* 用毕释放 */
9.4 EngFreeUserMem --- 释放用户空间内存
c
VOID EngFreeUserMem(PVOID pvBaseAddress);
使用要点:
- 对应
EngAllocUserMem的逆操作(ZwFreeVirtualMemory,MEM_RELEASE)。 - 注意:
EngFreeUserMem不判 NULL (与EngFreeMem不同)------调用前需自行确认指针有效。 - 只释放"由内核替当前进程分配"的用户内存,不要用来释放应用自己
VirtualAlloc的内存。
9.5 EngSecureMem --- 锁定用户内存(当前为 HACK 简化版)
c
HANDLE EngSecureMem(PVOID Address, ULONG Length);
使用要点:
- 语义:防止内核使用期间用户并发释放该内存;返回不透明句柄,用毕
EngUnsecureMem解锁。 - ReactOS 现状 :只做
ProbeForWrite可写性探测,恒返回假句柄(HANDLE)-1;真正的MmSecureVirtualMemory被{// HACK!!!块短路,没有真正锁定。 - 因此安全校验逻辑要自己写 :若后续代码依赖"内存已被锁定",当前实现并不保证------需自行加锁或改用
HackSecureVirtualMemory。 - 返回值判 NULL/
-1均表示失败(探测不通过)。
典型用法:
c
hSecure = EngSecureMem(pjBits, cjMaxBits);
if (!hSecure) return FALSE; /* 探测失败 */
... /* 使用 pjBits */
EngUnsecureMem(hSecure); /* 假句柄 -1 会被直接跳过 */
9.6 EngSecureMemForRead --- 锁定只读用户内存
c
HANDLE EngSecureMemForRead(PVOID Address, ULONG Length);
使用要点:
- 与
EngSecureMem同构,但做的是逐页读探测 (ProbeForRead+ 每页读一次),语义为"内核将只读该缓冲"。 - 同样返回假句柄
-1,真实锁定未启用(HACK)。 - 使用场景:只读用户缓冲(如 GetBitmapBits 的输入参数校验)。
9.7 EngUnsecureMem --- 解除锁定
c
VOID EngUnsecureMem(HANDLE Mem);
使用要点:
- 与
EngSecureMem/EngSecureMemForRead成对使用。 - 遇假句柄
(HANDLE)-1直接返回(HACK 配套逻辑);真实句柄才调用MmUnsecureVirtualMemory。 - 不要对 NULL 或未加锁的地址调用(无防护),成对性由调用方保证。
9.8 HackSecureVirtualMemory --- 用 MDL 锁定用户缓冲并映射到系统空间
c
PVOID HackSecureVirtualMemory(PVOID Address, SIZE_T Size,
ULONG ProbeMode, PVOID *SafeAddress);
| 参数 | 说明 |
|---|---|
Address / Size |
用户缓冲地址与大小 |
ProbeMode |
PAGE_READONLY(读锁)/ PAGE_READWRITE(读写锁) |
SafeAddress |
输出:可被内核直接访问的系统空间映射地址 |
使用要点:
- 返回值为 PMDL (作为后续释放的句柄),不是映射地址------映射地址在
SafeAddress输出。 - 流程:
IoAllocateMdl→MmProbeAndLockPages(SEH 保护,失败返回 NULL)→MmGetSystemAddressForMdlSafe。 - 用毕必须调用
HackUnsecureVirtualMemory释放(解锁 + 释放 MDL)。 - 这是当前引擎里真正能锁定用户页的方案(优于 9.5 的探测式 HACK),适合 DIB 引擎需要长时间读写用户位图的场景。
典型用法:
c
pmdl = HackSecureVirtualMemory(pvUserBits, cbSize, PAGE_READWRITE, &pvSafe);
if (!pmdl) return FALSE;
memcpy(pvSafe, ...); /* 内核通过系统地址访问 */
HackUnsecureVirtualMemory(pmdl);
9.9 HackUnsecureVirtualMemory --- 释放 MDL 锁定
c
VOID HackUnsecureVirtualMemory(PVOID SecureHandle);
使用要点:
- 参数是
HackSecureVirtualMemory返回的 PMDL。 - 依次
MmUnlockPages+IoFreeMdl,成对释放。 - 必须与 9.8 一一对应,过早释放会导致内核访问已解锁页面的错误。
本文档基于 ReactOS 源代码
win32ss/gdi/eng/mem.c及关联模块分析(2026 年 8 月)