I recently spent a few hours learning about structured exception handling (SEH) in 32-bit (i686) Microsoft Windows. The canonical article documenting this seems to be A Crash Course on the Depths of Win32™ Structured Exception Handling by Matt Pietrek in 1997.
After reading the first 1600 words of the article, I came across a code sample (Figure 3) that shows the most basic way one can use structured exception handling. The code uses C++ and a little bit of inline assembly to add its own exception handler to the beginning of the linked list of exception handlers that is stored in the Thread Environment Block. It then generates an segmentation fault (by writing to address 0) in order to demonstrate that the exception handler can run, fix the problem, and tell the OS to resume and try the problematic instruction again.
This example did not compile in my favorite C++ development environment for Windows, which is MSYS2. MSYS2 comes with a mingw-w64 GCC compiler. I don't know much about x86 assembly but I managed to port the example and get it to run. The main thing I had to change was the syntax of the inline assembly. Here is the code:
/* Basic demonstration of Win32 structured exception handling (SEH).
This code is based on Figure 3 (MYSEH.CPP) from this article:
https://www.microsoft.com/msj/0197/exception/exception.aspx
Originally written by Matt Pietrek for the January 1997 Microsoft
Systems Journal.
Modified in 2016 by David E. Grayson to work with gcc.
*/
#include <windows.h>
#include <stdio.h>
DWORD scratch = 10;
EXCEPTION_DISPOSITION __cdecl
_except_handler(
struct _EXCEPTION_RECORD * ExceptionRecord,
void * EstablisherFrame,
struct _CONTEXT * ContextRecord,
void * DispatcherContext)
{
// Indicate that we made it to our exception handler.
printf("Hello from an exception handler\n"); fflush(stdout);
// Change EAX in the context record so that it points to some
// place where we can successfully write.
ContextRecord->Eax = (DWORD_PTR)&scratch;
// Tell the OS to restart the faulting instruction.
return ExceptionContinueExecution;
}
int main()
{
__asm__( // Build EXCEPTION_REGISTRATION record:
"push %0\n" // Bytes 4 to 7: Address of handler function
"push %%fs:0\n" // Bytes 0 to 3: Address of previous handler
"movl %%esp, %%fs:0\n" // Install new EXECPTION_REGISTRATION
:
: "r" ((DWORD_PTR)_except_handler)
);
__asm__(
"movl $0, %eax\n" // Zero out EAX
"movl $1, (%eax)\n" // Write to EAX to deliberately cause a fault
);
printf("After writing!\n"); fflush(stdout);
__asm__( // Remove our EXCEPTION_REGISTRATION record
"movl (%esp), %eax\n" // Get pointer to previous record
"movl %eax, %fs:0\n" // Install previous record
"addl $8, %esp\n" // Clean our EXCEPTION_REGISTRATION off stack
);
printf("scratch = %d\n", scratch);
return 0;
}
I compiled the code in a MinGW-w64 32-bit Shell, with /mingw32/bin/g++
, using the command g++ -g3 -O0 myseh.cpp
. When I run a.exe
, the output is:
Hello from an exception handler After writing! scratch = 1
So this shows that our exception handler really did work!
Using less assembly
The code above worked, but I would not use it in production because it contains inline assembly that does sketchy things to the stack and registers without telling the compiler about it. We can make it a little bitter by using functions and structs provided by Windows headers to manipulate the thread environment block (which were probably not available in 1997 when Matt Pietrek wrote his article).
/* Basic demonstration of Win32 structured exception handling (SEH).
This code is based on Figure 3 (MYSEH.CPP) from this article:
https://www.microsoft.com/msj/0197/exception/exception.aspx
Originally written by Matt Pietrek for the January 1997 Microsoft
Systems Journal.
Modified in 2016 by David E. Grayson to work with gcc and use less assembly.
*/
#include <windows.h>
#include <winternl.h>
#include <stdio.h>
DWORD scratch = 10;
EXCEPTION_DISPOSITION __cdecl
_except_handler(
struct _EXCEPTION_RECORD * ExceptionRecord,
void * EstablisherFrame,
struct _CONTEXT * ContextRecord,
void * DispatcherContext)
{
// Indicate that we made it to our exception handler.
printf("Hello from an exception handler\n"); fflush(stdout);
// Change EAX in the context record so that it points to some
// place where we can successfully write.
ContextRecord->Eax = (DWORD_PTR)&scratch;
// Tell the OS to restart the faulting instruction.
return ExceptionContinueExecution;
}
int main()
{
NT_TIB * teb = (NT_TIB *)NtCurrentTeb();
// Install our handler at the beginning of the list.
EXCEPTION_REGISTRATION_RECORD reg;
reg.prev = teb->ExceptionList;
reg.handler = (PEXCEPTION_ROUTINE)_except_handler;
teb->ExceptionList = ®
__asm__(
"movl $0, %%eax\n" // Zero out EAX
"movl $1, (%%eax)\n" // Write to EAX to deliberately cause a fault
::: "memory", "eax"
);
printf("After writing!\n"); fflush(stdout);
// Remove our handler from the list.
teb->ExceptionList = reg.prev;
printf("scratch = %d\n", scratch);
return 0;
}
We have to use two casts in the code above. First, we cast the output of NtCurrentTeb()
because the struct type it points to by default is not very useful and just has a lot of fields with names like Reserved1
. Second, we cast _except_handler
because there is a disagreement about whether the exception handler should use the __cdecl
calling convention or __stdcall
. Both calling conventions seem to work, but since the calling convention of our _except_handler
function does not match the calling convention of the handler
member of the struct, we need to use a type-cast to satisfy the type-checking the GCC does at compile time. I have noticed cases like this in the past, where I had to change the calling convention of something to get it to compile with GCC, but presumably it would compile fine with the Microsoft C compiler. The function prototype of _except_handler
in Microsoft's headers uses __cdecl
, while the type definition of EXCEPTION_ROUTINE
uses NTAPI
, which means __stdcall
.
What about 64-bit?
Apparently structured exception handling for 64-bit Windows is done in a totally different way, so you can't easily adapt the code above to work in 64-bit mode. More info about x64 Structured Exception Handling is available from OSR Online.
What's the point?
Microsoft's C compiler emits code similar to what I have above when you use the __try, __except, and __finally keywords. GCC does not yet support these keywords, but Clang has partial support for them. I have heard (but not verified) that WebKit uses SEH, so if we want to compile WebKit with GCC, someone needs to add SEH to GCC first. Or maybe we could compile WebKit but we would have to somewhat disable its sandboxing features. I haven't actually tried to compile WebKit with GCC yet.
This comment has been removed by a blog administrator.
ReplyDelete