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
privileged (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.