1.0.1.1:
[sbcl.git] / doc / internals-notes / mach-exception-handler-notes
1
2 MACH EXCEPTION HANDLER NOTES
3 Cyrus Harmon, December 2007
4
5 The goal of this work is to use make SBCL use mach exception handlers
6 instead of so-called BSD-style signal handlers on Mac OS X. Cyrus
7 Harmon and Alastair Bridgewater have been working on this.
8
9 Mac OS X has a mach-based kernel that has its own API for things like
10 threads and exception handling. Both mach exception handlers and
11 BSD-style signal handlers are available for use by application
12 programmers, but the signal handlers, which are implemented as a
13 compatibility layer on top of mach exceptions, have some problems. The
14 main problem for SBCL is that when using BSD-style signal handlers to
15 respond to SIGSEGV for access to protected memory areas, we cannot use
16 gdb to debug the process. This is problematic for SBCL which sets up,
17 and reads and writes to, protected memory areas early and
18 often. Additionally, threaded builds are seeing a number of problems
19 with SIGILLs being thrown at odd times. It appears that these are
20 coming from with the OS signal handling libraries directly, so
21 debugging these is rather tricky, especially in the absence of a
22 debugger. Thirdly, the protected memory accesses can, under certain
23 settings, trigger the Mac OS X CrashReporter, either logging
24 voluminous messages to a log file or, worse yet, triggering a
25 user-intervention dialog.
26
27 To address these three problems, we propose replacing the BSD-style
28 signal handling facility with a mach exception handling
29 facility. Preliminary tests with mach exceptions show that GDB is much
30 happier when using mach exceptions to respond to access to protected
31 memory. While mach exceptions probably won't directly fix the
32 threading problems, they remove a potentially problematic section of
33 code, the portion of the Mac OS X system library that deals with
34 BSD-style signal handling emulation and delivery of those signals to
35 multiple threads. Even if using mach exceptions in and of itself
36 doesn't immediately fix the problem, it should me much easier to
37 diagnose using a debugger. Finally, the CrashReporter problem appears
38 to go away as well, as this arises from an unfortunate placement of
39 the CrashReporting facility in the OS between the mach exception
40 handling and the BSD-style signal emulation. By catching the mach
41 exceptions ourselves, we avoid this problem.
42
43 * Mach exception handling details and example
44
45 Mach exceptions work by creating a thread that listens for mach
46 exceptions. The (slightly under-documented) OS function mach_msg_server
47 is passed the exc_server function and exc_server in turn calls our
48 catch_exception_raise function when an appropriate exception is
49 triggered. (Note that catch_exception_raise is called by exc_server
50 directly by that name. I have no idea how to provide multiple such
51 functions or to call this function by another name, but we should be
52 OK with a a single exception handling function.)
53
54 To set this up we perform the following steps:
55
56 1. allocate a mach port for our exceptions.
57
58 2. give the process the right to send and receive exceptions on this
59    port.
60
61 3. create a new thread which calls mach_msg_server, providing
62    exc_server as an argument. exc_server in turn calls our
63    catch_exception_raise when an exception is raised.
64
65 4. finally, each thread for which we would like exceptions to be
66    handled must register itself with the exception port by calling
67    thread_set_exception_ports with the appropriate port and exception
68    mask. Actually, it's a bit more involved than this in order to
69    support multiple threads. Please document this fully.
70
71 * USE_MACH_EXCEPTION_HANDLER
72
73 The conditional compilation directive USE_MACH_EXCEPTION_HANDLER is a
74 flag to use mach exception handling. We should continue to support the
75 BSD-style signal handling until long after we are convinced that the
76 mach exception handling version works better.
77
78 * Establishing the mach exception handler
79
80 ** x86-darwin-os.c
81
82 A new function, darwin_init, is added which creates the mach exception
83 handling thread and establishes the exception port. Currently the
84 "main" thread sets its exception port here, but when we go to a
85 multithreaded SBCL, we will need to do similarly for new threads in
86 arch_os_thread_init. Note that even "non-threaded" SBCL builds will
87 have two threads, one lisp thread and a mach exception handling
88 thread.
89
90 catch_exception_raise listens for EXC_BAD_ACCESS and
91 EXC_BAD_INSTRUCTION (and EXC_BREAKPOINT if we were to use INT3 traps
92 again instead of the SIGILL traps we've set up as a workaround to the
93 broken INT3 traps). Analogous to the signal handling context, mach
94 exceptions allow use to get the thread and exception state of the
95 triggering thread. We build a "fake" signal context, similar to what
96 would be seen if a SIGSEGV/SIGILL were triggered and pass this on to
97 SBCL's memory_fault_handler (for SIGSEGV) or sigill_handler (for
98 SIGILL). when the handlers return, we set the values of the
99 thread_state using the values from the fake context, allowing the
100 "signal handler" to modify the state of the calling thread.
101
102 ** x86-arch.c
103
104 sigill_handler and sigtrap_handler are no longer installed as signal
105 handlers using undoably_install_low_level_interrupt_handler. Instead
106 sigill_handler is called directly by the mach exception handling
107 catch_exception_raise. This means that sigill_handler can no longer be
108 static void and it is changed to just void.
109
110 ** bsd-os.c
111
112 memory_fault_error no longer installed using
113 undoably_install_low_level_interrupt_handler and changed to not be
114 static. darwin_init called.
115
116 * Handling exceptions
117
118 ** interrupt.c
119
120 The code for general purpose error handling, which is generally done
121 by a trap instruction followed by an error opcode (although on Mac OS,
122 we use UD2A instead of INT3 as INT3 trapping is unreliable and the
123 sigill_handler in turn calls the sigtrap_handler). sigtrap_handler in
124 turn calls functions like interrupt_internal_error that are found in
125 interrupt.c.
126
127 Using BSD-style signal handling, interrupt_internal_error calls into
128 lisp via the lisp function INTERNAL-ERROR via funcall2 (the two
129 argument form of funcall). Since we are executing the exception
130 handler on the exception handling thread and we don't really want to
131 be executing lisp code on the exception handlers thread, we want to
132 return to the lisp thread as quickly as possible. With BSD-style
133 signal handling, the signal handlers themselves call into lisp using
134 funcallN. We can't do this as then we would be attempting to execute
135 lisp code on the exception handling thread. This would be a bad thing
136 in a multi-threaded lisp. Therefore, we borrow a trick from the
137 interrupt handling code and hack the stack of the offending thread
138 such that when the mach exception handling code returns, and returns
139 control back to the offending thread, it first calls a lisp function,
140 then (unless otherwise directed) returns control to the lisp
141 thread. This allows us to run our lisp (or other) code on the
142 offending thread's stack, but before the offending thread resumes
143 where it left off. See the arrange_return_to_lisp_function for details
144 on how this is done.
145
146 arrange_return_to_lisp_function was modified to take an additional
147 parameter specifying the number of additional arguments, and
148 additional varargs, which are then placed on the stack in the
149 call_into_lisp_tramp in x86-assem.S.
150
151 The problem with this is that both the signal handling/mach exception
152 handling code, on the one hand, and the lisp code expect access to the
153 "context" for accessing the state of the thread at the time that the
154 signal/exception was raised. This means that the old strategy was:
155
156 offending thread
157   (operating system establishes signal context)
158   signal handler
159     lisp code
160   signal handler
161   (operating system restores state from signal context)
162 offending thread
163
164 and both the signal handler and the lisp code have the chance to
165 examine and modify the signal context. Now the situation looks like
166 this:
167
168 offending thread
169  (operating system establishes thread_state)
170  mach_exception_handler
171    signal handler (which may arrange return to a lisp function)
172  mach_exception_handler
173  (operating system restores from thread_state)
174  (optionally, a lisp function is called here)
175 offending thread
176
177 So we need to figure out how to provide the lisp function with
178 information about the context of the offending thread and allow the
179 lisp code to alter this state and to restore that state prior to
180 resuming control to the offending thread.
181
182 There are, presumably, additional problems with exception masking and
183 when threads are allowed to interrupt other threads or otherwise catch
184 signals/exceptions, but we can defer those for the moment.
185
186 * Providing a "context" for lisp functions called on returning from a
187 mach exception handler
188
189 Given the flow describe above, we need to expand the following step:
190
191  (optionally, a lisp function is called here)
192
193 now it needs to look something like:
194
195 0. offending thread triggers a mach exception
196
197 1. (operating system establishes thread_state)
198
199 2. enter mach_exception_handler
200
201   2a. signal handler (which may arrange return to a lisp function
202
203   2b. frob the offending threads stack, allocating a context on the
204       stack (if appropriate, or always?)
205
206 3. exit mach_exception_handler
207
208 4. (operating system restores from thread_state)
209
210 6. with the context on the stack, transfer control to the lisp
211        function
212
213 7. restore the thread state from the context which either returns
214    control to original location in the offending thread or to wherever
215    the error-handling code has modified the context to point to
216  
217
218 * x86-darwin-os.c (again)
219
220 ** call_c_function_in_context and signal_emulation_wrapper
221
222 We arrange for a function to be a called by the offending the thread
223 when the mach exception handler returns. Essentially we have our own
224 BSD-style signal emulation library that calls memory_fault_handler,
225 sigtrap_handler and sigill_handler, as appropriate. It does this by
226 calling a function call_c_function_in_context which sets up the EIP of
227 the thread context to call the specified C function, which the
228 specified arguments. In this case, signal_emulation_wrapper which
229 takes as arguments a thread_state, which is a copy of the thread state
230 as it existed upon entry into catch_exception_raise, and an emulated
231 signal and siginfo and the signal handling function that
232 signal_emulation_wrapper is to call.
233
234 signal_emulation_wrapper creates a BSD-style signal context and
235 populates it from the values in the passed in thread_state. It calls
236 the specified signal_handler, and then sets the values in the
237 thread_state from the context and loads the address of the thread
238 state to restore into eax and then traps with a special trap that the
239 catch_exception_raise looks for, which then extracts the thread state
240 from the trap exception's thread_state.eax.
241
242 [MORE DETAILS TO FOLLOW]
243
244
245 ===== BUGS =====
246
247 MEH1: on threaded macos builds, init.test.sh fails with a
248       memory-fault-error (NOTE: this is a threaded macos issue, not a
249       mach exception handler bug).
250
251 MEH2: timer.impure lisp fails on mach-exception-handler builds
252
253 MEH3: threads.impure lisp fails on mach-exception-handler builds
254