VEH无法捕获PAGE_GUARD异常,因该异常由内核内存管理器直接处理,不经过KiUserExceptionDispatcher分发路径;应改用SEH配合VirtualProtect重置guard属性。

VEH 不能捕获 PAGE_GUARD 引发的异常
直接说结论:SetUnhandledExceptionFilter 和 AddVectoredExceptionHandler 都无法可靠捕获由 PAGE_GUARD 触发的 EXCEPTION_GUARD_PAGE。这不是用法问题,而是 Windows 内核的设计限制:Guard Page 异常在用户态 VEH 链之前就被内核特殊处理并“吞掉”了——它不走标准的 SEH/VEH 分发路径,而是由内存管理器直接回调到 VirtualAlloc 或 VirtualProtect 的调用上下文中(如果有的话),否则直接终止线程。
为什么 PAGE_GUARD 不走 VEH?
Windows 对 Guard Page 的异常处理是硬编码在内存管理路径中的。当 CPU 访问带 PAGE_GUARD 的页时,会触发 #PF(page fault),内核检查到该页有 GUARD 属性后,会:
- 清除该页的
PAGE_GUARD标志(即“触发一次就失效”) - 向当前线程发送
EXCEPTION_GUARD_PAGE,但这个异常不经过 KiUserExceptionDispatcher - 该异常只被当前线程的顶层 SEH 框架(如
__try/__except)或系统默认调试器捕获,VEH 注册的回调根本收不到
你可以用 WinDbg 验证:对 guard 页执行 mov eax, [rax] 后,.exr -1 显示 ExceptionCode: c0000005 (Access violation),但 !exchain 里看不到你的 VEH 回调地址。
替代方案:用 SEH + VirtualProtect 配合模拟
真正可行的做法是放弃 VEH,改用结构化异常处理(SEH),并在 __except 块中手动重设 guard 属性。关键点:
立即学习“C++免费学习笔记(深入)”;
-
PAGE_GUARD必须配合VirtualProtect单独设置(不能和VirtualAlloc一起用,后者不支持 GUARD) - 每次访问触发后,必须立刻用
VirtualProtect重新加回PAGE_GUARD,否则第二次访问就变成普通 access violation(EXCEPTION_ACCESS_VIOLATION) - SEH 处理器必须是
__except( GetExceptionCode() == EXCEPTION_GUARD_PAGE ),不能写成EXCEPTION_EXECUTE_HANDLER全捕获
示例片段:
char* guard_page = (char*)VirtualAlloc(nullptr, 4096, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
VirtualProtect(guard_page, 4096, PAGE_READWRITE | PAGE_GUARD, &old_prot);
<p>__try {
char x = guard_page[0]; // 触发 EXCEPTION_GUARD_PAGE
} __except( GetExceptionCode() == EXCEPTION_GUARD_PAGE ? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH ) {
printf("Guard hit at %p\n", guard_page);
VirtualProtect(guard_page, 4096, PAGE_READWRITE | PAGE_GUARD, &old_prot); // 重置
}</p>调试与验证时容易忽略的坑
实际调试中常见几个误判点:
- 用
IsDebuggerPresent()或 Visual Studio 调试器运行时,guard page 异常可能被调试器截获并静默处理,导致你“看不到异常”,建议用DebugBreak()+ WinDbg 验证 -
PAGE_GUARD只对首次访问有效,且仅作用于单个页面(4KB),跨页访问不会连锁触发 - x64 下 SEH 是基于表的(not stack-based),所以
__try/__except必须在函数栈帧内,不能放在 lambda 或静态初始化器里 - 如果你真需要类似 VEH 的全局监控,唯一办法是用
SetThreadContext+ 硬件断点(CONTEXT.Dr0-Dr3),但开销大、不兼容所有 CPU 特性,且和 guard page 机制无关
Guard Page 的设计本意就是轻量级内存访问通知,不是通用异常捕获机制。想靠 VEH 插手,本质上是在绕过 Windows 内存子系统的预期路径,结果注定不可靠。


















