| /* Structured Exception Handling (SEH) runtime interface routines. |
| Copyright (C) 2010-2022 Free Software Foundation, Inc. |
| |
| This file is part of GCC. |
| |
| GCC is free software; you can redistribute it and/or modify it |
| under the terms of the GNU General Public License as published by |
| the Free Software Foundation; either version 3, or (at your option) |
| any later version. |
| |
| GCC is distributed in the hope that it will be useful, but WITHOUT |
| ANY WARRANTY; without even the implied warranty of MERCHANTABILITY |
| or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public |
| License for more details. |
| |
| Under Section 7 of GPL version 3, you are granted additional |
| permissions described in the GCC Runtime Library Exception, version |
| 3.1, as published by the Free Software Foundation. |
| |
| You should have received a copy of the GNU General Public License and |
| a copy of the GCC Runtime Library Exception along with this program; |
| see the files COPYING3 and COPYING.RUNTIME respectively. If not, see |
| <http://www.gnu.org/licenses/>. */ |
| |
| #include "tconfig.h" |
| #include "tsystem.h" |
| #include "coretypes.h" |
| #include "tm.h" |
| #include "unwind.h" |
| |
| #if defined (__SEH__) && !defined (__USING_SJLJ_EXCEPTIONS__) |
| |
| /* At the moment everything is written for x64, but in theory this could |
| also be used for i386, arm, mips and other extant embedded Windows. */ |
| #ifndef __x86_64__ |
| #error "Unsupported architecture." |
| #endif |
| |
| /* Define GCC's exception codes. See |
| http://msdn.microsoft.com/en-us/library/het71c37(v=VS.80).aspx |
| In particular, MS defines bits: |
| [31:30] = 3 (error), 2 (warning), 1 (info), 0 (success) |
| [29] = 1 (user-defined) |
| [28] = 0 (reserved) |
| We define bits: |
| [24:27] = type |
| [0:23] = magic |
| We set "magic" to "GCC", which is similar to MVC++ which uses "msc" |
| as the low 3 bytes of its user-defined codes for C++ exceptions. |
| |
| We define the ExceptionInformation entries as follows: |
| [0] = _Unwind_Exception pointer |
| [1] = target frame |
| [2] = target ip |
| [3] = target rdx |
| */ |
| |
| #define STATUS_USER_DEFINED (1U << 29) |
| |
| #define GCC_MAGIC (('G' << 16) | ('C' << 8) | 'C') |
| #define GCC_EXCEPTION(TYPE) \ |
| (STATUS_USER_DEFINED | ((TYPE) << 24) | GCC_MAGIC) |
| |
| #define STATUS_GCC_THROW GCC_EXCEPTION (0) |
| #define STATUS_GCC_UNWIND GCC_EXCEPTION (1) |
| #define STATUS_GCC_FORCED GCC_EXCEPTION (2) |
| |
| |
| struct _Unwind_Context |
| { |
| _Unwind_Word cfa; |
| _Unwind_Word ra; |
| _Unwind_Word reg[2]; |
| PDISPATCHER_CONTEXT disp; |
| }; |
| |
| /* Get the value of register INDEX as saved in CONTEXT. */ |
| |
| _Unwind_Word |
| _Unwind_GetGR (struct _Unwind_Context *c, int index) |
| { |
| if (index < 0 || index >= 2) |
| abort (); |
| return c->reg[index]; |
| } |
| |
| /* Overwrite the saved value for register INDEX in CONTEXT with VAL. */ |
| |
| void |
| _Unwind_SetGR (struct _Unwind_Context *c, int index, _Unwind_Word val) |
| { |
| if (index < 0 || index >= 2) |
| abort (); |
| c->reg[index] = val; |
| } |
| |
| /* Get the value of the CFA as saved in CONTEXT. */ |
| |
| _Unwind_Word |
| _Unwind_GetCFA (struct _Unwind_Context *c) |
| { |
| return c->cfa; |
| } |
| |
| /* Retrieve the return address for CONTEXT. */ |
| |
| _Unwind_Ptr |
| _Unwind_GetIP (struct _Unwind_Context *c) |
| { |
| return c->ra; |
| } |
| |
| /* Retrieve the return address and flag whether that IP is before |
| or after first not yet fully executed instruction. */ |
| |
| _Unwind_Ptr |
| _Unwind_GetIPInfo (struct _Unwind_Context *c, int *ip_before_insn) |
| { |
| /* ??? Is there a concept of a signal context properly? There's |
| obviously an UNWP_PUSH_MACHFRAME opcode, but the runtime might |
| have arranged for that not to matter, really. */ |
| *ip_before_insn = 0; |
| return c->ra; |
| } |
| |
| /* Overwrite the return address for CONTEXT with VAL. */ |
| |
| void |
| _Unwind_SetIP (struct _Unwind_Context *c, _Unwind_Ptr val) |
| { |
| c->ra = val; |
| } |
| |
| void * |
| _Unwind_GetLanguageSpecificData (struct _Unwind_Context *c) |
| { |
| return c->disp->HandlerData; |
| } |
| |
| _Unwind_Ptr |
| _Unwind_GetRegionStart (struct _Unwind_Context *c) |
| { |
| return c->disp->FunctionEntry->BeginAddress + c->disp->ImageBase; |
| } |
| |
| void * |
| _Unwind_FindEnclosingFunction (void *pc) |
| { |
| PRUNTIME_FUNCTION entry; |
| ULONG64 ImageBase; |
| |
| entry = RtlLookupFunctionEntry ((ULONG64)pc, &ImageBase, NULL); |
| |
| return (entry ? (void *)(entry->BeginAddress + ImageBase) : NULL); |
| } |
| |
| _Unwind_Ptr |
| _Unwind_GetDataRelBase (struct _Unwind_Context *c ATTRIBUTE_UNUSED) |
| { |
| return 0; |
| } |
| |
| _Unwind_Ptr |
| _Unwind_GetTextRelBase (struct _Unwind_Context *c) |
| { |
| return c->disp->ImageBase; |
| } |
| |
| |
| /* The two-phase unwind process that GCC uses is ordered differently |
| from the two-phase unwind process that SEH uses. The mechansism |
| that GCC uses is to have the filter return _URC_HANDER_FOUND; the |
| mechanism that SEH uses is for the filter function call back into |
| the unwinder. |
| |
| An Ideal port to SEH would have GCC emit handler functions that |
| can be called, given a pointer to the "EstablisherFrame" (i.e. |
| the frame pointer base of the user-level function) can manipulate |
| the user-level variables within the user-level function's stack |
| frame. Once done manipulating the variables, it would return |
| a ExceptionContinueSearch, and the unwind process would continue. |
| |
| GCC has always done things a bit differently. We continue to |
| transfer control back into the user-level function which, once |
| done manipulating the user-level variables, re-throws the exception. */ |
| |
| /* The "real" language-specific personality handler forwards to here |
| where we handle the MS SEH state and transforms it into the GCC |
| unwind state as per GCC's <unwind.h>, at which point we defer to |
| the regular language-specfic exception handler, which is passed in. */ |
| |
| EXCEPTION_DISPOSITION |
| _GCC_specific_handler (PEXCEPTION_RECORD ms_exc, void *this_frame, |
| PCONTEXT ms_orig_context, PDISPATCHER_CONTEXT ms_disp, |
| _Unwind_Personality_Fn gcc_per) |
| { |
| DWORD ms_flags = ms_exc->ExceptionFlags; |
| DWORD ms_code = ms_exc->ExceptionCode; |
| |
| struct _Unwind_Exception *gcc_exc |
| = (struct _Unwind_Exception *) ms_exc->ExceptionInformation[0]; |
| struct _Unwind_Context gcc_context; |
| _Unwind_Action gcc_action; |
| _Unwind_Reason_Code gcc_reason; |
| |
| if (ms_flags & EXCEPTION_TARGET_UNWIND) |
| { |
| /* This frame is known to be the target frame. We've already |
| "installed" the target_ip and RAX value via the arguments |
| to RtlUnwindEx. All that's left is to set the RDX value |
| and "continue" to have the context installed. */ |
| ms_disp->ContextRecord->Rdx = ms_exc->ExceptionInformation[3]; |
| return ExceptionContinueSearch; |
| } |
| |
| if (ms_code == STATUS_GCC_UNWIND) |
| { |
| /* This is a colliding exception that we threw so that we could |
| cancel the already in-flight exception and stop in a frame |
| that wanted to perform some unwind action. The only relevant |
| test is that we're the target frame. */ |
| if (ms_exc->ExceptionInformation[1] == (_Unwind_Ptr) this_frame) |
| { |
| RtlUnwindEx (this_frame, (PVOID) ms_exc->ExceptionInformation[2], |
| ms_exc, gcc_exc, ms_orig_context, |
| ms_disp->HistoryTable); |
| abort (); |
| } |
| return ExceptionContinueSearch; |
| } |
| |
| gcc_context.cfa = ms_disp->ContextRecord->Rsp; |
| gcc_context.ra = ms_disp->ControlPc; |
| gcc_context.reg[0] = 0xdeadbeef; /* These are write-only. */ |
| gcc_context.reg[1] = 0xdeadbeef; |
| gcc_context.disp = ms_disp; |
| |
| if (ms_code == STATUS_GCC_FORCED) |
| { |
| _Unwind_Stop_Fn stop = (_Unwind_Stop_Fn) gcc_exc->private_[0]; |
| void *stop_argument = (void *) gcc_exc->private_[4]; |
| |
| gcc_action = _UA_FORCE_UNWIND | _UA_CLEANUP_PHASE; |
| |
| stop (1, gcc_action, gcc_exc->exception_class, gcc_exc, |
| &gcc_context, stop_argument); |
| |
| goto phase2; |
| } |
| |
| /* ??? TODO: handling non-gcc user-defined exceptions as foreign. */ |
| if (ms_code != STATUS_GCC_THROW) |
| return ExceptionContinueSearch; |
| |
| if (ms_flags & (EXCEPTION_UNWINDING | EXCEPTION_EXIT_UNWIND)) |
| { |
| /* This is Phase 2. */ |
| /* We know this isn't the target frame because we've already tested |
| EXCEPTION_TARGET_UNWIND. The remaining possibility is that the |
| gcc personality has unwind code to run. */ |
| |
| gcc_action = _UA_CLEANUP_PHASE; |
| phase2: |
| gcc_reason = gcc_per (1, gcc_action, gcc_exc->exception_class, |
| gcc_exc, &gcc_context); |
| |
| if (gcc_reason == _URC_CONTINUE_UNWIND) |
| return ExceptionContinueSearch; |
| |
| if (gcc_reason == _URC_INSTALL_CONTEXT) |
| { |
| /* Scratch space for the bits for the unwind catch. */ |
| ms_exc->ExceptionInformation[1] = (_Unwind_Ptr) this_frame; |
| ms_exc->ExceptionInformation[2] = gcc_context.ra; |
| ms_exc->ExceptionInformation[3] = gcc_context.reg[1]; |
| |
| /* Cancel the current exception by raising another. */ |
| RaiseException (STATUS_GCC_UNWIND, EXCEPTION_NONCONTINUABLE, |
| 4, ms_exc->ExceptionInformation); |
| |
| /* Is RaiseException declared noreturn? */ |
| } |
| |
| /* In _Unwind_RaiseException_Phase2 we return _URC_FATAL_PHASE2_ERROR. */ |
| } |
| else |
| { |
| /* This is Phase 1. */ |
| gcc_reason = gcc_per (1, _UA_SEARCH_PHASE, gcc_exc->exception_class, |
| gcc_exc, &gcc_context); |
| |
| if (gcc_reason == _URC_CONTINUE_UNWIND) |
| return ExceptionContinueSearch; |
| |
| if (gcc_reason == _URC_HANDLER_FOUND) |
| { |
| /* We really need some of the information that GCC's personality |
| routines compute during phase 2 right now, like the target IP. |
| Go ahead and ask for it now, and cache it. */ |
| gcc_reason = gcc_per (1, _UA_CLEANUP_PHASE | _UA_HANDLER_FRAME, |
| gcc_exc->exception_class, gcc_exc, |
| &gcc_context); |
| if (gcc_reason != _URC_INSTALL_CONTEXT) |
| abort (); |
| |
| gcc_exc->private_[1] = (_Unwind_Ptr) this_frame; |
| gcc_exc->private_[2] = gcc_context.ra; |
| gcc_exc->private_[3] = gcc_context.reg[1]; |
| |
| ms_exc->NumberParameters = 4; |
| ms_exc->ExceptionInformation[1] = (_Unwind_Ptr) this_frame; |
| ms_exc->ExceptionInformation[2] = gcc_context.ra; |
| ms_exc->ExceptionInformation[3] = gcc_context.reg[1]; |
| |
| /* Begin phase 2. Perform the unwinding. */ |
| RtlUnwindEx (this_frame, (PVOID)gcc_context.ra, ms_exc, |
| (PVOID)gcc_context.reg[0], ms_orig_context, |
| ms_disp->HistoryTable); |
| } |
| |
| /* In _Unwind_RaiseException we return _URC_FATAL_PHASE1_ERROR. */ |
| } |
| abort (); |
| } |
| |
| /* Raise an exception, passing along the given exception object. */ |
| |
| _Unwind_Reason_Code |
| _Unwind_RaiseException (struct _Unwind_Exception *exc) |
| { |
| memset (exc->private_, 0, sizeof (exc->private_)); |
| |
| /* The ExceptionInformation array will have only 1 element, EXC. */ |
| RaiseException (STATUS_GCC_THROW, 0, 1, (ULONG_PTR *)&exc); |
| |
| /* The exception handler installed in crt0 will continue any GCC |
| exception that reaches there (and isn't marked non-continuable). |
| Returning allows the C++ runtime to call std::terminate. */ |
| return _URC_END_OF_STACK; |
| } |
| |
| /* Resume propagation of an existing exception. This is used after |
| e.g. executing cleanup code, and not to implement rethrowing. */ |
| |
| void |
| _Unwind_Resume (struct _Unwind_Exception *gcc_exc) |
| { |
| UNWIND_HISTORY_TABLE ms_history; |
| EXCEPTION_RECORD ms_exc; |
| CONTEXT ms_context; |
| |
| memset (&ms_exc, 0, sizeof(ms_exc)); |
| memset (&ms_history, 0, sizeof(ms_history)); |
| |
| /* ??? Not 100% perfect, since we aren't passing on the *original* |
| exception context, but should be good enough. */ |
| ms_exc.ExceptionCode = STATUS_GCC_THROW; |
| ms_exc.ExceptionFlags = EXCEPTION_NONCONTINUABLE; |
| ms_exc.NumberParameters = 4; |
| ms_exc.ExceptionInformation[0] = (ULONG_PTR) gcc_exc; |
| ms_exc.ExceptionInformation[1] = gcc_exc->private_[1]; |
| ms_exc.ExceptionInformation[2] = gcc_exc->private_[2]; |
| ms_exc.ExceptionInformation[3] = gcc_exc->private_[3]; |
| |
| ms_context.ContextFlags = CONTEXT_ALL; |
| RtlCaptureContext (&ms_context); |
| |
| RtlUnwindEx ((void *) gcc_exc->private_[1], (PVOID)gcc_exc->private_[2], |
| &ms_exc, gcc_exc, &ms_context, &ms_history); |
| |
| /* Is RtlUnwindEx declared noreturn? */ |
| abort (); |
| } |
| |
| static _Unwind_Reason_Code |
| _Unwind_ForcedUnwind_Phase2 (struct _Unwind_Exception *exc) |
| { |
| _Unwind_Stop_Fn stop; |
| void * stop_argument; |
| |
| RaiseException (STATUS_GCC_FORCED, 0, 1, (ULONG_PTR *)&exc); |
| |
| /* If we get here, we got to top-of-stack. */ |
| /* ??? We no longer have a context pointer to pass in. */ |
| |
| stop = (_Unwind_Stop_Fn) exc->private_[0]; |
| stop_argument = (void *) exc->private_[4]; |
| stop (1, _UA_FORCE_UNWIND | _UA_CLEANUP_PHASE | _UA_END_OF_STACK, |
| exc->exception_class, exc, NULL, stop_argument); |
| |
| return _UA_END_OF_STACK; |
| } |
| |
| _Unwind_Reason_Code |
| _Unwind_Resume_or_Rethrow (struct _Unwind_Exception *exc) |
| { |
| if (exc->private_[0] == 0) |
| _Unwind_RaiseException (exc); |
| else |
| _Unwind_ForcedUnwind_Phase2 (exc); |
| abort (); |
| } |
| |
| /* Raise an exception for forced unwinding. */ |
| |
| _Unwind_Reason_Code |
| _Unwind_ForcedUnwind (struct _Unwind_Exception *exc, |
| _Unwind_Stop_Fn stop, void * stop_argument) |
| { |
| /* ??? This is a hack that only works with _GCC_specific_handler. |
| There's no way to invoke STOP within frames that use a different |
| exception handler. This is essentially just good enough to run |
| the code within the gcc testsuite. */ |
| |
| memset (exc->private_, 0, sizeof (exc->private_)); |
| exc->private_[0] = (_Unwind_Ptr) stop; |
| exc->private_[4] = (_Unwind_Ptr) stop_argument; |
| |
| return _Unwind_ForcedUnwind_Phase2 (exc); |
| } |
| |
| /* A convenience function that calls the exception_cleanup field. */ |
| |
| void |
| _Unwind_DeleteException (struct _Unwind_Exception *exc) |
| { |
| if (exc->exception_cleanup) |
| (*exc->exception_cleanup) (_URC_FOREIGN_EXCEPTION_CAUGHT, exc); |
| } |
| |
| /* Perform stack backtrace through unwind data. */ |
| |
| _Unwind_Reason_Code |
| _Unwind_Backtrace(_Unwind_Trace_Fn trace, |
| void *trace_argument) |
| { |
| UNWIND_HISTORY_TABLE ms_history; |
| CONTEXT ms_context; |
| struct _Unwind_Context gcc_context; |
| DISPATCHER_CONTEXT disp_context; |
| |
| memset (&ms_history, 0, sizeof(ms_history)); |
| memset (&gcc_context, 0, sizeof(gcc_context)); |
| memset (&disp_context, 0, sizeof(disp_context)); |
| |
| ms_context.ContextFlags = CONTEXT_ALL; |
| RtlCaptureContext (&ms_context); |
| |
| gcc_context.disp = &disp_context; |
| gcc_context.disp->ContextRecord = &ms_context; |
| gcc_context.disp->HistoryTable = &ms_history; |
| |
| while (1) |
| { |
| gcc_context.disp->ControlPc = ms_context.Rip; |
| gcc_context.disp->FunctionEntry |
| = RtlLookupFunctionEntry (ms_context.Rip, &gcc_context.disp->ImageBase, |
| &ms_history); |
| |
| if (!gcc_context.disp->FunctionEntry) |
| return _URC_END_OF_STACK; |
| |
| gcc_context.disp->LanguageHandler |
| = RtlVirtualUnwind (0, gcc_context.disp->ImageBase, ms_context.Rip, |
| gcc_context.disp->FunctionEntry, &ms_context, |
| &gcc_context.disp->HandlerData, |
| &gcc_context.disp->EstablisherFrame, NULL); |
| |
| /* Set values that the callback can inspect via _Unwind_GetIP |
| * and _Unwind_GetCFA. */ |
| gcc_context.ra = ms_context.Rip; |
| gcc_context.cfa = ms_context.Rsp; |
| |
| /* Call trace function. */ |
| if (trace (&gcc_context, trace_argument) != _URC_NO_REASON) |
| return _URC_FATAL_PHASE1_ERROR; |
| |
| /* ??? Check for invalid stack pointer. */ |
| if (ms_context.Rip == 0) |
| return _URC_END_OF_STACK; |
| } |
| } |
| #endif /* __SEH__ && !defined (__USING_SJLJ_EXCEPTIONS__) */ |