Document how SEH works in Wine.
This commit is contained in:
parent
65051ec8e2
commit
8ce698e2a4
|
@ -15,7 +15,6 @@
|
|||
<!entity opengl SYSTEM "opengl.sgml">
|
||||
<!entity ddraw SYSTEM "ddraw.sgml">
|
||||
<!entity multimedia SYSTEM "multimedia.sgml">
|
||||
|
||||
]>
|
||||
|
||||
<book id="index">
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue