Menu

StructuredExceptionHandling

Christoph Schwarz Jiří Hruška
Attachments
seh-win32-v.png (60873 bytes)
seh-win32.png (40591 bytes)

Structured Exception Handling

Exception handling on Win32/WOW64

The following diagram shows a high-level picture how exceptions work on 32-bit Windows:
SEH on Win32/WOW64

Hardware exceptions in client code

If something bad happens, e.g. an invalid memory access, the hardware exception is caught by the kernel, which resumes user level execution in ntdll!KiUserExceptionDispatcher. This function is hooked by Valgrind, so the exception can be analyzed etc. The rest of the dispatcher is then ran on the emulated CPU (necessary mostly because RtlDispatchException() can call application code), which is done by longjumping back to the scheduler with updated EIP and stack.

Then, if a viable exception handler has been found, NtContinue syscall is issued and caught by Valgrind, which copies the updated context values to the VEX context structures and executes the syscall with a dummy CONTEXT structure in order to call it, but effectively just return to the syscall PRE-wrapper, which sets SsComplere and SfDontWriteResult and the scheduler resumes at the updated location.

If no useful exception handler could be found, the syscall issued is NtRaiseException, with the FirstChance argument set to FALSE. This is intercepted by Valgrind and the process is terminated, as that would happen if the syscall was passed to the kernel anyway. (To be precise, UnhandledExceptionFilter would be called. Maybe we should call it too if the application uses its own routine?)

Software exceptions in client code

The client code can call RaiseException() to throw an exception at any time. This is used by MSVS C++ exceptions, internally by OutputDebugString() etc. In this case, the process is exactly the same, except the exception does not originate from generated code but by stopping emulation and calling a syscall NtRaiseException.

The syscall is let through, so after kernel does its job, the execution gets to the hooked KiUserExceptionDispatcher again.

Exceptions in Valgrind/tool code

Although Valgrind itself does not use exceptions and will not throw any through the RaiseException() API, there is a possibility of crash in V/tool code because of bugs. Such exceptions will take the same route as hardware exceptions originating from client code, with the difference that VG_(in_generated_code) will be false in the KiUserExceptionDispatcher hook. So this case can be easily discerned and a helpful report about bug in Valgrind shown to the user instead. Then the process is terminated.

SetUnhandledExceptionFilter() is also called early during startup to catch similarly any problems during initialization.

Valgrind's handling of Win32/WOW64 exceptions

Valgrind's SEH processing on Win32/WOW64

Exception handling on Win64

TBD

Hooks

There are two entry points in ntdll.dll, which the kernel uses when dealing with exceptions -- KiUserExceptionDispatcher and KiRaiseUserExceptionDispatcher. The latter is used only when kernel wants to raise an exception and essentially just calls RtlRaiseException(), which is not very interesting. KiUserExceptionDispatcher is much more useful, as it is the function called when an exception has occurred and needs to be handled.

This section describes how Valgrind for Windows intercepts KiUserExceptionDispatcher. In general:

  • Valgrind modifies (overwrites a part of) the ntdll!!KiUserExceptionDispatcher code to call an assembler stub.
  • The assembler stub calls a C function for further handling with all necessary information passed in its arguments.
  • The C function
    • for first-chance exceptions from client code: sets up the client state and transfers control to the virtual CPU in order to dispatch the exception,
    • for second-chance exceptions and those originating from Valgrind itself: prints some helpful messages and terminates.

Related files:

  • assembler stub (WIN32/WOW64): VGASM_(catch_user_exception_dispatcher) in coregrind/m_syswrap/syscall-x86-windows.S
  • assembler stub (WIN64): VGASM_(catch_user_exception_dispatcher) in coregrind/m_syswrap/syscall-amd64-windows.S
  • C function: VG_(win_sc_user_exception_dispatch) in coregrind/m_windows/win-fault.c

WIN32

Original ntdll!KiUserExceptionDispatcher Hooked ntdll!KiUserExceptionDispatcher
0000 8b4c2404 mov ecx, \[esp+4] 0004 8b1c24 mov ebx, \[esp] 0007 51 push ecx 0008 53 push ebx 0009 e8xxxxxxxx call RtlDispatchException 000e 0ac0 or al, al 0010 740c je .001e 0012 5b pop ebx 0013 59 pop ecx 0014 6a00 push 0 0016 51 push ecx 0017 e8xxxxxxxx call NtContinue 001c eb0b jmps .0029 001e 5b pop ebx 001f 59 pop ecx 0020 6a00 push 0 0022 51 push ecx 0023 53 push ebx 0024 e8xxxxxxxx call NtRaiseException 0029 83c4ec add esp, -0x14 002c 890424 mov \[esp], eax 002f c744240401000000 mov dword ptr \[esp+4], 1 0037 895c2408 mov \[esp+8], ebx 003b c744241000000000 mov \[esp+0x10], 0 0043 54 push esp 0044 e8xxxxxxxx call RtlRaiseException 0049 c20800 retn 8 0000 ff15xxxxxxxx call d,\[user_exception_dispatcher_ptr\] 0006 90 nop 0007 51 push ecx 0008 53 push ebx 0009 e8xxxxxxxx call RtlDispatchException 000e 0ac0 or al, al 0010 740c je .001e 0012 5b pop ebx 0013 59 pop ecx 0014 6a00 push 0 0016 51 push ecx 0017 e8xxxxxxxx call NtContinue 001c eb0b jmps .0029 001e 5b pop ebx 001f 59 pop ecx 0020 6a00 push 0 0022 51 push ecx 0023 53 push ebx 0024 e8xxxxxxxx call NtRaiseException 0029 83c4ec add esp, -0x14 002c 890424 mov \[esp], eax 002f c744240401000000 mov dword ptr \[esp+4], 1 0037 895c2408 mov \[esp+8], ebx 003b c744241000000000 mov \[esp+0x10], 0 0043 54 push esp 0044 e8xxxxxxxx call RtlRaiseException 0049 c20800 retn 8
Stack layout at the entry time of
KiUserExceptionDispatcher
Stack layout at the entry time of
VG_(win_sc_user_exception_dispatch)
      ESP+0x00 EXCEPTION_RECORD* ExceptionRecord +0x04 CONTEXT* ContextRecord +0x08 EXCEPTION_RECORD +0x58 CONTEXT ... ... ESP+0x00 return address (into VG_(catch_user_exception_dispatcher)) +0x04 real sizeof(CONTEXT) +0x08 return address (into original KiUserExceptionDispatcher) +0x0c EXCEPTION_RECORD* ExceptionRecord +0x10 CONTEXT* ContextRecord +0x14 EXCEPTION_RECORD +0x54 CONTEXT ... ...

 

WOW64

The function is exactly the same, except for one "cld" instruction at the start. For safety reasons, the hook call is patched after this instruction (in the end, it makes the hooking code simpler too).

Original ntdll!KiUserExceptionDispatcher Hooked ntdll!KiUserExceptionDispatcher
0000 fc cld 0001 8b4c2404 mov ecx, \[esp+4] 0005 8b1c24 mov ebx, \[esp] 0008 51 push ecx 0009 53 push ebx 000a e8xxxxxxxx call RtlDispatchException ... ... ... 0000 fc cld 0001 ff15xxxxxxxx call d,\[user_exception_dispatcher_ptr] 0007 90 nop 0008 51 push ecx 0009 53 push ebx 000a e8xxxxxxxx call RtlDispatchException ... ... ...

WIN64

TBD


Related

Wiki: Home

Want the latest updates on software, tech news, and AI?
Get latest updates about software, tech news, and AI from SourceForge directly in your inbox once a month.