卸载NativeAot编译DLL后KiUserExceptionDispatch调用已卸载模块致访问违例求助
NativeAot编译的DLL在加载时,会向Windows系统注册向量异常处理程序(Vectored Exception Handler,VEH),用于处理.NET相关的异常逻辑。当你通过非常规hack方法卸载DLL时,这些注册的VEH并没有从系统的异常处理链中被移除。Windows异常调度器在处理异常时,仍然会遍历调用所有已注册的VEH,此时原本属于NativeAot DLL的VEH所在内存已经被释放/卸载,就会触发访问违例。
从堆栈跟踪也能佐证这一点:ntdll.dll!RtlpCallVectoredHandlers()正是系统调用向量异常处理程序的环节,调用到了已卸载模块的无效内存地址。
1. 手动移除NativeAot注册的VEH(推荐)
Windows提供了RemoveVectoredExceptionHandler API来移除已注册的向量异常处理程序,但需要知道注册时返回的PVOID句柄。由于NativeAot内部注册的VEH无法直接获取句柄,可以通过以下hack方式枚举并移除:
- 使用
NtQueryInformationProcess结合ProcessVectoredExceptionHandlers信息类,枚举当前进程中所有注册的VEH。 - 遍历这些VEH,检查处理函数的地址是否落在已卸载的NativeAot DLL原本的内存范围内(可通过
LoadLibrary时记录的模块基址和大小判断)。 - 对匹配到的VEH调用
RemoveVectoredExceptionHandler移除。
示例伪代码:
// 记录加载时的模块基址和大小 HMODULE hMod = LoadLibraryA("Aot.dll"); MODULEINFO modInfo; GetModuleInformation(GetCurrentProcess(), hMod, &modInfo, sizeof(modInfo)); LPVOID modBase = modInfo.lpBaseOfDll; SIZE_T modSize = modInfo.SizeOfImage; // 枚举并移除属于目标DLL的VEH PVOID* handlers = ...; // 通过NtQueryInformationProcess获取VEH列表 for (int i = 0; handlers[i]; i++) { FARPROC handlerAddr = (FARPROC)handlers[i]; if ((handlerAddr >= (FARPROC)modBase) && (handlerAddr < (FARPROC)((BYTE*)modBase + modSize))) { RemoveVectoredExceptionHandler(handlers[i]); } } // 执行卸载DLL的操作
注意:NtQueryInformationProcess属于未公开API,需要自行声明或从ntdll.dll中动态获取,使用时需注意系统兼容性。
2. 修改NativeAot DLL的卸载逻辑(若有权限修改源码)
如果可以修改NativeAot DLL的代码,在DllMain的DLL_PROCESS_DETACH分支中,主动清理所有NativeAot注册的VEH。NativeAot运行时内部会维护注册的VEH句柄,找到对应逻辑添加移除代码即可。
3. 拦截异常处理链的应急Hack
如果上述方法无法实现,可尝试在卸载NativeAot DLL后,注册一个优先级更高的VEH,让它先于残留的无效VEH被调用,并且在处理函数中返回EXCEPTION_CONTINUE_SEARCH,避免系统调用到无效地址。但这种方法治标不治本,且可能影响其他异常处理逻辑,仅作为应急方案。
- 非常规的DLL卸载方法本身存在风险,NativeAot运行时并未设计支持动态卸载,即使移除了VEH,仍可能存在线程、内存分配等残留资源导致潜在问题。
- 测试时需覆盖各类异常场景,确保移除VEH后系统异常处理逻辑恢复正常。
内容的提问来源于stack exchange,提问作者Sardelka

