Structured Exception Handling (or SEH) is an implementation of exceptions inside the Windows core. It allows code written in different languages to throw exceptions across DLL boundaries, and Windows reports various errors like access violations by throwing them. This section looks at how it works, and how it's implemented in Wine.
SEH is based on embedding
EXCEPTION_REGISTRATION_RECORD
structures in
the stack. Together they form a linked list rooted at offset zero in
the TEB (see the threading section if you don't know what this is). A
registration record points to a handler function, and when an
exception is thrown the handlers are executed in turn. Each handler
returns a code, and they can elect to either continue through the
handler chain or it can handle the exception and then restart the
program. This is referred to as unwinding the stack. After each
handler is called it's popped off the chain.
Before the system begins unwinding the stack, it runs vectored handlers. This is an extension to SEH available in Windows XP, and allows registered functions to get a first chance to watch or deal with any exceptions thrown in the entire program, from any thread.
A thrown exception is represented by an
EXCEPTION_RECORD
structure. It consists of a
code, flags, an address and an arbitrary number of DWORD
parameters. Language runtimes can use these parameters to associate
language-specific information with the exception.
Exceptions can be triggered by many things. They can be thrown explicitly by using the RaiseException API, or they can be triggered by a crash (ie, translated from a signal). They may be used internally by a language runtime to implement language-specific exceptions. They can also be thrown across DCOM connections.
Visual C++ has various extensions to SEH which it uses to implement, eg, object destruction on stack unwind as well as the ability to throw arbitrary types. The code for this is in dlls/msvcrt/except.c
In Windows, compilers are expected to use the system exception interface, and the kernel itself uses the same interface to dynamically insert exceptions into a running program. By contrast on Linux the exception ABI is implemented at the compiler level (inside GCC and the linker) and the kernel tells a thread of exceptional events by sending signals. The language runtime may or may not translate these signals into native exceptions, but whatever happens the kernel does not care.
You may think that if an app crashes, it's game over and it really
shouldn't matter how Wine handles this. It's what you might
intuitively guess, but you'd be wrong. In fact some Windows programs
expect to be able to crash themselves and recover later without the
user noticing, some contain buggy binary-only components from third
parties and use SEH to swallow crashes, and still others execute
priviledged (kernel-level) instructions and expect it to work. In
fact, at least one set of APIs (the IsBad*Ptr()
series) can only be implemented by performing an operation that may
crash and returning TRUE
if it does, and
FALSE
if it doesn't! So, Wine needs to not only
implement the SEH infrastructure but also translate Unix signals into
SEH exceptions.
The code to translate signals into exceptions is a part of NTDLL, and can be found in dlls/ntdll/signal_i386.c. This file sets up handlers for various signals during Wine startup, and for the ones that indicate exceptional conditions translates them into exceptions. Some signals are used by Wine internally and have nothing to do with SEH.
Signal handlers in Wine run on their own stack. Each thread has its own signal stack which resides 4k after the TEB. This is important for a couple of reasons. Firstly, because there's no guarantee that the app thread which triggered the signal has enough stack space for the Wine signal handling code. In Windows, if a thread hits the limits of its stack it triggers a fault on the stack guard page. The language runtime can use this to grow the stack if it wants to. However, because a guard page violation is just a regular segfault to the kernel, that would lead to a nested signal handler and that gets messy really quick so we disallow that in Wine. Secondly, setting up the exception to throw requires modifying the stack of the thread which triggered it, which is quite hard to do when you're still running on it.
Windows exceptions typically contain more information than the Unix
standard APIs provide. For instance, a
STATUS_ACCESS_VIOLATION
exception
(0xC0000005
) structure contains the faulting
address, whereas a standard Unix SIGSEGV
just
tells the app that it crashed. Usually this information is passed as
an extra parameter to the signal handler, however its location and
contents vary between kernels (BSD, Solaris, etc). This data is
provided in a SIGCONTEXT
structure, and on
entry to the signal handler it contains the register state of the CPU
before the signal was sent. Modifying it will cause the kernel to
adjust the context before restarting the thread.