Document how SEH works in Wine.

This commit is contained in:
Mike Hearn 2004-11-04 21:06:14 +00:00 committed by Alexandre Julliard
parent 65051ec8e2
commit 8ce698e2a4
2 changed files with 141 additions and 1 deletions

View File

@ -15,7 +15,6 @@
<!entity opengl SYSTEM "opengl.sgml">
<!entity ddraw SYSTEM "ddraw.sgml">
<!entity multimedia SYSTEM "multimedia.sgml">
]>
<book id="index">

View File

@ -813,6 +813,147 @@ ExitProcess( entry( peb ) );
</para>
</sect2>
</sect1>
<sect1 id="seh">
<title>Structured Exception Handling</title>
<para>
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.
</para>
<sect2>
<title> How SEH works </title>
<para>
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.
</para>
<para>
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.
</para>
<para>
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.
</para>
<para>
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.
</para>
<para>
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
</para>
</sect2>
<sect2>
<title> Translating signals to exceptions </title>
<para>
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 <emphasis>signals</emphasis>. The
language runtime may or may not translate these signals into
native exceptions, but whatever happens the kernel does not care.
</para>
<para>
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.
</para>
<para>
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.
</para>
<para>
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.
<!-- fixme: is it really the language runtime that does this? i
can't find any code in Wine to reallocate the stack on
STATUS_GUARD_PAGE_VIOLATION -->
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.
</para>
<para>
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.
</para>
</sect2>
</sect1>
</chapter>
<!-- Keep this comment at the end of the file