UnhandledExceptionFilter 未被调用分析一例

本文详细介绍了在空间打开的情况下,为何unhandledexceptionfilter未能被调用,并提供了排查方法及解决方案。通过分析kernel32!UnhandledExceptionFilter的实现,发现其在检测到不存在调试器时会调用注册的处理函数。进一步研究发现,SetUnhandledExceptionFilter函数实际上使用了RtlEncodePointer进行函数地址加密,导致在某些情况下无法正确触发自定义异常处理函数。为了解决这一问题,作者提出了一种在手动触发异常前进行断言的方法,确保自定义处理函数能够正常执行。此外,还展示了如何通过打补丁的方式绕过SetUnhandledExceptionFilter的正常执行,以确保自定义处理函数始终被调用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

有时候会遇到 crash 的时候没有生成 dump file. 因为 unhandledexceptionfilter 没有被调用. 而没有被调用的原因, 文档上说有时候的确不会调用. 比如在处理异常的时候再次发生异常.

最近发现在 space 打开的情况下, unhandledexceptionfilter 不被调用, 此时, 会出现系统默认的 crash 对话框. 这个对话框的出现意味着 kernel32!UnhandledExceptionFilter 被调用了, 虽然在 2000, xp, vista 里, 出现对话框的具体实现都不一样, 但都是从 kernel32!UnhandledExceptionFilter 开始的.

这个函数并不长. 它会调用 IsDebugPortPresent(), 如果存在调试器, 则直接返回, 让调试器处理异常. 否则调用用户通过 SetUnhandledExceptionFilter() 注册的处理函数. 如果要调试自己的处理函数,可以在 IsDebugPortPresent() 函数返回之后, 将 eax 置 0.

bp 772b5a38 "r eax=0;g";bp connect!_UnhandledExceptionFilter

 

地址 772b5a38 是在 vista enterprise build 6000 中反汇编 kernel32!UnhandledExceptionFilter 得到的. 当然, 随着 windows 版本的不同地址是不一样的.

在 space 打开时, 触发异常, 的确没有进到 connect!_UnhandledExceptionFilter 中. 怀疑是不是我们的处理函数被替换了. SetUnhandledExceptionFilter() 将用户注册的函数地址存到全局变量 kernel32!BasepCurrentTopLevelFilter 中, 查看该变量的值, 却发现不是一个函数地址. 查看 SetUnhandledExceptionFilter() 的代码, 才知道它会将函数地址用 RtlEncodePointer() 加密. 而在调用这个函数时会使用 RtlDecodePointer() 解密. 既然每次调用 SetUnhandledExceptionFilter() 都会返回之前的处理函数的地址. 于是在手动触发异常之前先来一个 ASSERT:

 	ATLASSERT(_UnhandledExceptionFilter == SetUnhandledExceptionFilter(_UnhandledExceptionFilter));

 

运行程序, 居然真的 ASSERT 了. 有其他的代码在调用 SetUnhandledExceptionFilter(), 遂在其上加断点, 不看不知道, 一看吓一跳. 原来很多的 dll 都会设置自己的异常处理函数.

为了确保我们的处理函数能够调用, 过河拆桥, 在我们调用 SetUnhandledExceptionFilter() 之后, 给 SetUnhandledExceptionFilter() 打一个补丁, 以后再有调用, 则直接返回. 打补丁的代码很简单:

void PatchSetUnhandledExceptionFilter()
{
	static BYTE RETURN_CODE[] = { 0xc2, 0x04, 0x00}; // __asm ret 4
	MEMORY_BASIC_INFORMATION   mbi;
	DWORD dwOldProtect = 0;
	DWORD pfnSetFilter =(DWORD)GetProcAddress(GetModuleHandleW(L"kernel32.dll"), "SetUnhandledExceptionFilter");
	VirtualQuery((void *)pfnSetFilter, &mbi, sizeof(mbi) );
	VirtualProtect( (void *)pfnSetFilter, sizeof(RETURN_CODE), PAGE_READWRITE, &dwOldProtect);

	WriteProcessMemory(GetCurrentProcess(),	(void *)pfnSetFilter,	RETURN_CODE,	sizeof(RETURN_CODE), NULL);
	VirtualProtect((void *)pfnSetFilter, sizeof(RETURN_CODE), mbi.Protect, 0);
	FlushInstructionCache(GetCurrentProcess(), (void *)pfnSetFilter, sizeof(RETURN_CODE));
}

 

在 kernel32!UnhandledExceptionFilter() 的起始处直接写上 __asm ret 4.

测试通过.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值