【发布时间】:2025-12-03 02:15:01
【问题描述】:
我正在阅读关于 Windows 上的 SEH 的 article。 这里是myseh.cpp的源代码
我调试了 myseh.cpp。我分别在第 24 行的 printf("Hello from an exception handler\n"); 和第 36 行的 DWORD handler = (DWORD)_except_handler; 设置了 2 个断点。
然后我运行它,它在第 36 行坏了。我看到堆栈跟踪如下。
由于mov [eax], 1,发生了AccessViolationException
然后它在第 24 行中断。我看到堆栈跟踪如下。
同一线程,但main 的框架 不见了!而不是_except_handle。而ESP从0018f6c8跳到0018ef34;0018f6c8和0018ef34之间差距很大
异常处理后。
我知道_except_handle 必须在用户模式而不是内核模式下运行。
_except_handle 返回后,线程转到 ring0,然后 windows 内核将 CONTEXT EAX 修改为 &scratch & 然后返回 ring3 。因此线程不断运行。
我很好奇windows处理异常的机制:
为什么调用main 的框架消失了?
为什么 ESP 从0018f6c8 跳转到0018ef34?(我的意思是一个很大的间距),这些 ESP 地址是否属于同一个线程的堆栈?内核是否在 ring3 中对 ESP 玩了一些花样???如果是,为什么选择0018ef34的地址作为handler回调的frame?非常感谢!
【问题讨论】:
-
是同一个线程的堆栈。内核将
CONTEXT和EXCEPTION_RECORD复制到用户线程堆栈。当然,Esp 例外 - 因为这已经严重减少了 Esp。然后从内核调用KiUserExceptionDispatcher回调,并使用指向此复制的CONTEXT和EXCEPTION_RECORD记录的指针。最后你的_except_handler打电话了。但是如果您查找ContextRecord->Esp,您会注意到它与main()中的Esp完全相同。对于真正的处理程序实现看\VC\crt\src\i386\chandler4.c和\VC\crt\src\amd64\chandler.c -
是的,我知道 CONTEXT 必须是发生异常时的硬件快照。所以,
ContextRecord->Esp==Esp in main()。事实上,我们应该归咎于调试器的默认设置,它丢失了堆栈跟踪。顺便说一句,我知道*ContextRecord的可修改性,这是内核在返回ring3 之前按照您的意愿恢复硬件上下文的平均值。但为什么是*类型的第二个参数ExceptionRecord而不是const *? -
ExceptionRecord 真的不是 const。
ExceptionFlags在异常处理期间被修改。我们两次通过堆栈走!首先我们寻找__try/__except块,直到一些不返回EXCEPTION_EXECUTE_HANDLER然后_except_handlerX调用RtlUnwindEx,它在ExceptionFlags中设置EXCEPTION_UNWIND标志,然后再次遍历__try/__finally处理程序的堆栈。在winnt.h和wdm.h中寻找IS_DISPATCHING(Flag)和IS_UNWINDING(Flag)宏 - 同时研究chandler.c注意if (IS_DISPATCHING(ExceptionRecord->ExceptionFlags))开关 -
在 x64 中也使用了
IS_TARGET_UNWIND。简单地说来自myseh.cpp的_except_handler非常原始。支持__try/__except和__try/__finally子调用的真实处理程序用于复杂且需要修改 (RtlUnwindEx)ExceptionFlags的最小值。也嵌套了ExceptionRecord