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
9Module Name:
10
11 seh.cpp
12
13Abstract:
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
44using namespace CorUnix;
45
46SET_DEFAULT_DEBUG_CHANNEL(EXCEPT);
47
48/* Constant and type definitions **********************************************/
49
50/* Bit 28 of exception codes is reserved. */
51const UINT RESERVED_SEH_BIT = 0x800000;
52
53/* Internal variables definitions **********************************************/
54
55PHARDWARE_EXCEPTION_HANDLER g_hardwareExceptionHandler = NULL;
56// Function to check if an activation can be safely injected at a specified context
57PHARDWARE_EXCEPTION_SAFETY_CHECK_FUNCTION g_safeExceptionCheckFunction = NULL;
58
59PGET_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.
63void* g_SEHProcessExceptionReturnAddress = NULL;
64
65/* Internal function definitions **********************************************/
66
67/*++
68Function :
69 SEHInitialize
70
71 Initialize all SEH-related stuff (signals, etc)
72
73Parameters :
74 CPalThread * pthrCurrent : reference to the current thread.
75 PAL initialize flags
76
77Return value :
78 TRUE if SEH support initialization succeeded
79 FALSE otherwise
80--*/
81BOOL
82SEHInitialize (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/*++
95Function :
96 SEHCleanup
97
98 Undo work done by SEHInitialize
99
100Parameters :
101 None
102
103 (no return value)
104
105--*/
106VOID
107SEHCleanup()
108{
109 TRACE("Cleaning up SEH\n");
110
111#if HAVE_MACH_EXCEPTIONS
112 SEHCleanupExceptionPort();
113#endif
114 SEHCleanupSignals();
115}
116
117/*++
118Function:
119 PAL_SetHardwareExceptionHandler
120
121 Register a hardware exception handler.
122
123Parameters:
124 handler - exception handler
125
126Return value:
127 None
128--*/
129VOID
130PALAPI
131PAL_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/*++
140Function:
141 PAL_SetGetGcMarkerExceptionCode
142
143 Register a function that determines if the specified IP has code that is a GC marker for GCCover.
144
145Parameters:
146 getGcMarkerExceptionCode - the function to register
147
148Return value:
149 None
150--*/
151VOID
152PALAPI
153PAL_SetGetGcMarkerExceptionCode(
154 IN PGET_GCMARKER_EXCEPTION_CODE getGcMarkerExceptionCode)
155{
156 g_getGcMarkerExceptionCode = getGcMarkerExceptionCode;
157}
158
159EXTERN_C void ThrowExceptionFromContextInternal(CONTEXT* context, PAL_SEHException* ex);
160
161/*++
162Function:
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
170Parameters:
171 CONTEXT* context - context from which the exception will be thrown
172 PAL_SEHException* ex - the exception to throw.
173--*/
174VOID
175PALAPI
176PAL_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/*++
186Function:
187 ThrowExceptionHelper
188
189 Helper function to throw the passed in exception.
190 It is called from the assembler function ThrowExceptionFromContextInternal
191
192Parameters:
193 PAL_SEHException* ex - the exception to throw.
194--*/
195extern "C"
196#ifdef _X86_
197void __fastcall ThrowExceptionHelper(PAL_SEHException* ex)
198#else // _X86_
199void ThrowExceptionHelper(PAL_SEHException* ex)
200#endif // !_X86_
201{
202 throw std::move(*ex);
203}
204
205/*++
206Function:
207 EnsureExceptionRecordsOnHeap
208
209 Helper function to move records from stack to heap.
210
211Parameters:
212 PAL_SEHException* exception
213--*/
214static 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/*++
238Function:
239 SEHProcessException
240
241 Send the PAL exception to any handler registered.
242
243Parameters:
244 PAL_SEHException* exception
245
246Return 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--*/
252BOOL
253SEHProcessException(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/*++
308Function :
309 SEHEnable
310
311 Enable SEH-related stuff on this thread
312
313Parameters:
314 CPalThread * pthrCurrent : reference to the current thread.
315
316Return value :
317 TRUE if enabling succeeded
318 FALSE otherwise
319--*/
320extern "C"
321PAL_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/*++
335Function :
336 SEHDisable
337
338 Disable SEH-related stuff on this thread
339
340Parameters:
341 CPalThread * pthrCurrent : reference to the current thread.
342
343Return value :
344 TRUE if enabling succeeded
345 FALSE otherwise
346--*/
347extern "C"
348PAL_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
367extern "C"
368void
369PALAPI
370PAL_CatchHardwareExceptionHolderEnter()
371{
372 CPalThread *pThread = InternalGetCurrentThread();
373 pThread->IncrementHardwareExceptionHolderCount();
374}
375
376extern "C"
377void
378PALAPI
379PAL_CatchHardwareExceptionHolderExit()
380{
381 CPalThread *pThread = InternalGetCurrentThread();
382 pThread->DecrementHardwareExceptionHolderCount();
383}
384
385bool 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__
402static NativeExceptionHolderBase *t_nativeExceptionHolderHead = nullptr;
403
404extern "C"
405NativeExceptionHolderBase **
406PALAPI
407PAL_GetNativeExceptionHolderHead()
408{
409 return &t_nativeExceptionHolderHead;
410}
411
412NativeExceptionHolderBase *
413NativeExceptionHolderBase::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