1 | // Licensed to the .NET Foundation under one or more agreements. |
2 | // The .NET Foundation licenses this file to you under the MIT license. |
3 | // See the LICENSE file in the project root for more information. |
4 | |
5 | /*++ |
6 | |
7 | |
8 | |
9 | Module Name: |
10 | |
11 | seh.cpp |
12 | |
13 | Abstract: |
14 | |
15 | Implementation of exception API functions. |
16 | |
17 | |
18 | |
19 | --*/ |
20 | |
21 | #include "pal/thread.hpp" |
22 | #include "pal/handleapi.hpp" |
23 | #include "pal/seh.hpp" |
24 | #include "pal/dbgmsg.h" |
25 | #include "pal/critsect.h" |
26 | #include "pal/debug.h" |
27 | #include "pal/init.h" |
28 | #include "pal/process.h" |
29 | #include "pal/malloc.hpp" |
30 | #include "pal/signal.hpp" |
31 | |
32 | #if HAVE_MACH_EXCEPTIONS |
33 | #include "machexception.h" |
34 | #else |
35 | #include <signal.h> |
36 | #endif |
37 | |
38 | #include <string.h> |
39 | #include <unistd.h> |
40 | #include <pthread.h> |
41 | #include <stdlib.h> |
42 | #include <utility> |
43 | |
44 | using namespace CorUnix; |
45 | |
46 | SET_DEFAULT_DEBUG_CHANNEL(EXCEPT); |
47 | |
48 | /* Constant and type definitions **********************************************/ |
49 | |
50 | /* Bit 28 of exception codes is reserved. */ |
51 | const UINT RESERVED_SEH_BIT = 0x800000; |
52 | |
53 | /* Internal variables definitions **********************************************/ |
54 | |
55 | PHARDWARE_EXCEPTION_HANDLER g_hardwareExceptionHandler = NULL; |
56 | // Function to check if an activation can be safely injected at a specified context |
57 | PHARDWARE_EXCEPTION_SAFETY_CHECK_FUNCTION g_safeExceptionCheckFunction = NULL; |
58 | |
59 | PGET_GCMARKER_EXCEPTION_CODE g_getGcMarkerExceptionCode = NULL; |
60 | |
61 | // Return address of the SEHProcessException, which is used to enable walking over |
62 | // the signal handler trampoline on some Unixes where the libunwind cannot do that. |
63 | void* g_SEHProcessExceptionReturnAddress = NULL; |
64 | |
65 | /* Internal function definitions **********************************************/ |
66 | |
67 | /*++ |
68 | Function : |
69 | SEHInitialize |
70 | |
71 | Initialize all SEH-related stuff (signals, etc) |
72 | |
73 | Parameters : |
74 | CPalThread * pthrCurrent : reference to the current thread. |
75 | PAL initialize flags |
76 | |
77 | Return value : |
78 | TRUE if SEH support initialization succeeded |
79 | FALSE otherwise |
80 | --*/ |
81 | BOOL |
82 | SEHInitialize (CPalThread *pthrCurrent, DWORD flags) |
83 | { |
84 | if (!SEHInitializeSignals(pthrCurrent, flags)) |
85 | { |
86 | ERROR("SEHInitializeSignals failed!\n" ); |
87 | SEHCleanup(); |
88 | return FALSE; |
89 | } |
90 | |
91 | return TRUE; |
92 | } |
93 | |
94 | /*++ |
95 | Function : |
96 | SEHCleanup |
97 | |
98 | Undo work done by SEHInitialize |
99 | |
100 | Parameters : |
101 | None |
102 | |
103 | (no return value) |
104 | |
105 | --*/ |
106 | VOID |
107 | SEHCleanup() |
108 | { |
109 | TRACE("Cleaning up SEH\n" ); |
110 | |
111 | #if HAVE_MACH_EXCEPTIONS |
112 | SEHCleanupExceptionPort(); |
113 | #endif |
114 | SEHCleanupSignals(); |
115 | } |
116 | |
117 | /*++ |
118 | Function: |
119 | PAL_SetHardwareExceptionHandler |
120 | |
121 | Register a hardware exception handler. |
122 | |
123 | Parameters: |
124 | handler - exception handler |
125 | |
126 | Return value: |
127 | None |
128 | --*/ |
129 | VOID |
130 | PALAPI |
131 | PAL_SetHardwareExceptionHandler( |
132 | IN PHARDWARE_EXCEPTION_HANDLER exceptionHandler, |
133 | IN PHARDWARE_EXCEPTION_SAFETY_CHECK_FUNCTION exceptionCheckFunction) |
134 | { |
135 | g_hardwareExceptionHandler = exceptionHandler; |
136 | g_safeExceptionCheckFunction = exceptionCheckFunction; |
137 | } |
138 | |
139 | /*++ |
140 | Function: |
141 | PAL_SetGetGcMarkerExceptionCode |
142 | |
143 | Register a function that determines if the specified IP has code that is a GC marker for GCCover. |
144 | |
145 | Parameters: |
146 | getGcMarkerExceptionCode - the function to register |
147 | |
148 | Return value: |
149 | None |
150 | --*/ |
151 | VOID |
152 | PALAPI |
153 | PAL_SetGetGcMarkerExceptionCode( |
154 | IN PGET_GCMARKER_EXCEPTION_CODE getGcMarkerExceptionCode) |
155 | { |
156 | g_getGcMarkerExceptionCode = getGcMarkerExceptionCode; |
157 | } |
158 | |
159 | EXTERN_C void ThrowExceptionFromContextInternal(CONTEXT* context, PAL_SEHException* ex); |
160 | |
161 | /*++ |
162 | Function: |
163 | PAL_ThrowExceptionFromContext |
164 | |
165 | This function creates a stack frame right below the target frame, restores all callee |
166 | saved registers from the passed in context, sets the RSP to that frame and sets the |
167 | return address to the target frame's RIP. |
168 | Then it uses the ThrowExceptionHelper to throw the passed in exception from that context. |
169 | |
170 | Parameters: |
171 | CONTEXT* context - context from which the exception will be thrown |
172 | PAL_SEHException* ex - the exception to throw. |
173 | --*/ |
174 | VOID |
175 | PALAPI |
176 | PAL_ThrowExceptionFromContext(CONTEXT* context, PAL_SEHException* ex) |
177 | { |
178 | // We need to make a copy of the exception off stack, since the "ex" is located in one of the stack |
179 | // frames that will become obsolete by the ThrowExceptionFromContextInternal and the ThrowExceptionHelper |
180 | // could overwrite the "ex" object by stack e.g. when allocating the low level exception object for "throw". |
181 | static __thread BYTE threadLocalExceptionStorage[sizeof(PAL_SEHException)]; |
182 | ThrowExceptionFromContextInternal(context, new (threadLocalExceptionStorage) PAL_SEHException(std::move(*ex))); |
183 | } |
184 | |
185 | /*++ |
186 | Function: |
187 | ThrowExceptionHelper |
188 | |
189 | Helper function to throw the passed in exception. |
190 | It is called from the assembler function ThrowExceptionFromContextInternal |
191 | |
192 | Parameters: |
193 | PAL_SEHException* ex - the exception to throw. |
194 | --*/ |
195 | extern "C" |
196 | #ifdef _X86_ |
197 | void __fastcall ThrowExceptionHelper(PAL_SEHException* ex) |
198 | #else // _X86_ |
199 | void ThrowExceptionHelper(PAL_SEHException* ex) |
200 | #endif // !_X86_ |
201 | { |
202 | throw std::move(*ex); |
203 | } |
204 | |
205 | /*++ |
206 | Function: |
207 | EnsureExceptionRecordsOnHeap |
208 | |
209 | Helper function to move records from stack to heap. |
210 | |
211 | Parameters: |
212 | PAL_SEHException* exception |
213 | --*/ |
214 | static void EnsureExceptionRecordsOnHeap(PAL_SEHException* exception) |
215 | { |
216 | if( !exception->RecordsOnStack || |
217 | exception->ExceptionPointers.ExceptionRecord == NULL ) |
218 | { |
219 | return; |
220 | } |
221 | |
222 | CONTEXT* contextRecord = exception->ExceptionPointers.ContextRecord; |
223 | EXCEPTION_RECORD* exceptionRecord = exception->ExceptionPointers.ExceptionRecord; |
224 | |
225 | CONTEXT* contextRecordCopy; |
226 | EXCEPTION_RECORD* exceptionRecordCopy; |
227 | AllocateExceptionRecords(&exceptionRecordCopy, &contextRecordCopy); |
228 | |
229 | *exceptionRecordCopy = *exceptionRecord; |
230 | *contextRecordCopy = *contextRecord; |
231 | |
232 | exception->ExceptionPointers.ExceptionRecord = exceptionRecordCopy; |
233 | exception->ExceptionPointers.ContextRecord = contextRecordCopy; |
234 | exception->RecordsOnStack = false; |
235 | } |
236 | |
237 | /*++ |
238 | Function: |
239 | SEHProcessException |
240 | |
241 | Send the PAL exception to any handler registered. |
242 | |
243 | Parameters: |
244 | PAL_SEHException* exception |
245 | |
246 | Return value: |
247 | Returns TRUE if the exception happened in managed code and the execution should |
248 | continue (with possibly modified context). |
249 | Returns FALSE if the exception happened in managed code and it was not handled. |
250 | In case the exception was handled by calling a catch handler, it doesn't return at all. |
251 | --*/ |
252 | BOOL |
253 | SEHProcessException(PAL_SEHException* exception) |
254 | { |
255 | g_SEHProcessExceptionReturnAddress = __builtin_return_address(0); |
256 | |
257 | CONTEXT* contextRecord = exception->GetContextRecord(); |
258 | EXCEPTION_RECORD* exceptionRecord = exception->GetExceptionRecord(); |
259 | |
260 | if (!IsInDebugBreak(exceptionRecord->ExceptionAddress)) |
261 | { |
262 | if (g_hardwareExceptionHandler != NULL) |
263 | { |
264 | _ASSERTE(g_safeExceptionCheckFunction != NULL); |
265 | // Check if it is safe to handle the hardware exception (the exception happened in managed code |
266 | // or in a jitter helper or it is a debugger breakpoint) |
267 | if (g_safeExceptionCheckFunction(contextRecord, exceptionRecord)) |
268 | { |
269 | if (exceptionRecord->ExceptionCode == EXCEPTION_ACCESS_VIOLATION) |
270 | { |
271 | // Check if the failed access has hit a stack guard page. In such case, it |
272 | // was a stack probe that detected that there is not enough stack left. |
273 | void* stackLimit = CPalThread::GetStackLimit(); |
274 | void* stackGuard = (void*)((size_t)stackLimit - getpagesize()); |
275 | void* violationAddr = (void*)exceptionRecord->ExceptionInformation[1]; |
276 | if ((violationAddr >= stackGuard) && (violationAddr < stackLimit)) |
277 | { |
278 | // The exception happened in the page right below the stack limit, |
279 | // so it is a stack overflow |
280 | (void)write(STDERR_FILENO, StackOverflowMessage, sizeof(StackOverflowMessage) - 1); |
281 | PROCAbort(); |
282 | } |
283 | } |
284 | |
285 | EnsureExceptionRecordsOnHeap(exception); |
286 | if (g_hardwareExceptionHandler(exception)) |
287 | { |
288 | // The exception happened in managed code and the execution should continue. |
289 | return TRUE; |
290 | } |
291 | |
292 | // The exception was a single step or a breakpoint and it was not handled by the debugger. |
293 | } |
294 | } |
295 | |
296 | if (CatchHardwareExceptionHolder::IsEnabled()) |
297 | { |
298 | EnsureExceptionRecordsOnHeap(exception); |
299 | PAL_ThrowExceptionFromContext(exception->GetContextRecord(), exception); |
300 | } |
301 | } |
302 | |
303 | // Unhandled hardware exception pointers->ExceptionRecord->ExceptionCode at pointers->ExceptionRecord->ExceptionAddress |
304 | return FALSE; |
305 | } |
306 | |
307 | /*++ |
308 | Function : |
309 | SEHEnable |
310 | |
311 | Enable SEH-related stuff on this thread |
312 | |
313 | Parameters: |
314 | CPalThread * pthrCurrent : reference to the current thread. |
315 | |
316 | Return value : |
317 | TRUE if enabling succeeded |
318 | FALSE otherwise |
319 | --*/ |
320 | extern "C" |
321 | PAL_ERROR SEHEnable(CPalThread *pthrCurrent) |
322 | { |
323 | #if HAVE_MACH_EXCEPTIONS |
324 | return pthrCurrent->EnableMachExceptions(); |
325 | #elif defined(__linux__) || defined(__FreeBSD__) || defined(__NetBSD__) |
326 | // TODO: This needs to be implemented. Cannot put an ASSERT here |
327 | // because it will make other parts of PAL fail. |
328 | return NO_ERROR; |
329 | #else// HAVE_MACH_EXCEPTIONS |
330 | #error not yet implemented |
331 | #endif // HAVE_MACH_EXCEPTIONS |
332 | } |
333 | |
334 | /*++ |
335 | Function : |
336 | SEHDisable |
337 | |
338 | Disable SEH-related stuff on this thread |
339 | |
340 | Parameters: |
341 | CPalThread * pthrCurrent : reference to the current thread. |
342 | |
343 | Return value : |
344 | TRUE if enabling succeeded |
345 | FALSE otherwise |
346 | --*/ |
347 | extern "C" |
348 | PAL_ERROR SEHDisable(CPalThread *pthrCurrent) |
349 | { |
350 | #if HAVE_MACH_EXCEPTIONS |
351 | return pthrCurrent->DisableMachExceptions(); |
352 | // TODO: This needs to be implemented. Cannot put an ASSERT here |
353 | // because it will make other parts of PAL fail. |
354 | #elif defined(__linux__) || defined(__FreeBSD__) || defined(__NetBSD__) |
355 | return NO_ERROR; |
356 | #else // HAVE_MACH_EXCEPTIONS |
357 | #error not yet implemented |
358 | #endif // HAVE_MACH_EXCEPTIONS |
359 | } |
360 | |
361 | /*++ |
362 | |
363 | CatchHardwareExceptionHolder implementation |
364 | |
365 | --*/ |
366 | |
367 | extern "C" |
368 | void |
369 | PALAPI |
370 | PAL_CatchHardwareExceptionHolderEnter() |
371 | { |
372 | CPalThread *pThread = InternalGetCurrentThread(); |
373 | pThread->IncrementHardwareExceptionHolderCount(); |
374 | } |
375 | |
376 | extern "C" |
377 | void |
378 | PALAPI |
379 | PAL_CatchHardwareExceptionHolderExit() |
380 | { |
381 | CPalThread *pThread = InternalGetCurrentThread(); |
382 | pThread->DecrementHardwareExceptionHolderCount(); |
383 | } |
384 | |
385 | bool CatchHardwareExceptionHolder::IsEnabled() |
386 | { |
387 | CPalThread *pThread = GetCurrentPalThread(); |
388 | return pThread ? pThread->IsHardwareExceptionsEnabled() : false; |
389 | } |
390 | |
391 | /*++ |
392 | |
393 | NativeExceptionHolderBase implementation |
394 | |
395 | --*/ |
396 | |
397 | #ifdef __llvm__ |
398 | __thread |
399 | #else // __llvm__ |
400 | __declspec(thread) |
401 | #endif // !__llvm__ |
402 | static NativeExceptionHolderBase *t_nativeExceptionHolderHead = nullptr; |
403 | |
404 | extern "C" |
405 | NativeExceptionHolderBase ** |
406 | PALAPI |
407 | PAL_GetNativeExceptionHolderHead() |
408 | { |
409 | return &t_nativeExceptionHolderHead; |
410 | } |
411 | |
412 | NativeExceptionHolderBase * |
413 | NativeExceptionHolderBase::FindNextHolder(NativeExceptionHolderBase *currentHolder, void *stackLowAddress, void *stackHighAddress) |
414 | { |
415 | NativeExceptionHolderBase *holder = (currentHolder == nullptr) ? t_nativeExceptionHolderHead : currentHolder->m_next; |
416 | |
417 | while (holder != nullptr) |
418 | { |
419 | if (((void *)holder >= stackLowAddress) && ((void *)holder < stackHighAddress)) |
420 | { |
421 | return holder; |
422 | } |
423 | // Get next holder |
424 | holder = holder->m_next; |
425 | } |
426 | |
427 | return nullptr; |
428 | } |
429 | |
430 | #include "seh-unwind.cpp" |
431 | |