.intel_syntax noprefix
#include "unixasmmacros.inc"
-#if defined(__x86_64__)
-#define PAL_DISPATCHEXCEPTION __Z21PAL_DispatchExceptionmmmmmmP8_CONTEXTP17_EXCEPTION_RECORDP11MachMessage
-#else //!defined(_AMD64_)
-#define PAL_DISPATCHEXCEPTION __Z21PAL_DispatchExceptionP8_CONTEXTP17_EXCEPTION_RECORDP11MachMessage
-#endif // defined(_AMD64_)
-
-// Offset of the return address from the PAL_DISPATCHEXCEPTION in the PAL_DispatchExceptionWrapper
+// Offset of the return address from the PAL_DispatchException in the PAL_DispatchExceptionWrapper
.globl C_FUNC(PAL_DispatchExceptionReturnOffset)
C_FUNC(PAL_DispatchExceptionReturnOffset):
- .int LOCAL_LABEL(PAL_DispatchExceptionReturn)-C_FUNC(PAL_DispatchExceptionWrapper)
+ .int LOCAL_LABEL(PAL_DispatchExceptionReturn) - C_FUNC(PAL_DispatchExceptionWrapper)
//
// PAL_DispatchExceptionWrapper will never be called; it only serves
// unwinding behavior is equivalent to any standard function having
// an ebp frame. It is analogous to the following source file.
//
-// void PAL_DispatchException(CONTEXT *pContext, EXCEPTION_RECORD *pExceptionRecord, MachMessage *pMessage);
+// extern "C" void PAL_DispatchException(CONTEXT *pContext, EXCEPTION_RECORD *pExceptionRecord, MachExceptionInfo *pMachExceptionInfo);
//
// extern "C" void PAL_DispatchExceptionWrapper()
// {
// CONTEXT Context;
// EXCEPTION_RECORD ExceptionRecord;
-// MachMessage Message;
-// PAL_DispatchException(&Context, &ExceptionRecord, &Message);
+// MachExceptionInfo MachExceptionInfo;
+// PAL_DispatchException(&Context, &ExceptionRecord, &MachExceptionInfo);
// }
//
mov rbp, rsp
set_cfa_register rbp, (2*8)
int3
- call PAL_DISPATCHEXCEPTION
+ call C_FUNC(PAL_DispatchException)
LOCAL_LABEL(PAL_DispatchExceptionReturn):
int3
pop_nonvol_reg rbp
ret
NESTED_END PAL_DispatchExceptionWrapper, _TEXT
-
/*++
-
-
Module Name:
machexception.cpp
Implementation of MACH exception API functions.
-
-
--*/
#include "pal/thread.hpp"
SET_DEFAULT_DEBUG_CHANNEL(EXCEPT);
-void ForwardMachException(CPalThread *pThread, MachMessage *pMessage);
-
// The port we use to handle exceptions and to set the thread context
mach_port_t s_ExceptionPort;
static const char * PAL_MACH_EXCEPTION_MODE = "PAL_MachExceptionMode";
+// This struct is used to track the threads that need to have an exception forwarded
+// to the next thread level port in the chain (if exists). An entry is added by the
+// faulting sending a special message to the exception thread which saves it on an
+// list that is searched when the restarted exception notification is received again.
+struct ForwardedException
+{
+ ForwardedException *m_next;
+ thread_act_t Thread;
+ exception_type_t ExceptionType;
+ CPalThread *PalThread;
+};
+
+// The singly linked list and enumerator for the ForwardException struct
+struct ForwardedExceptionList
+{
+private:
+ ForwardedException *m_head;
+ ForwardedException *m_previous;
+
+public:
+ ForwardedException *Current;
+
+ ForwardedExceptionList()
+ {
+ m_head = NULL;
+ MoveFirst();
+ }
+
+ void MoveFirst()
+ {
+ Current = m_head;
+ m_previous = NULL;
+ }
+
+ bool IsEOL()
+ {
+ return Current == NULL;
+ }
+
+ void MoveNext()
+ {
+ m_previous = Current;
+ Current = Current->m_next;
+ }
+
+ void Add(ForwardedException *item)
+ {
+ item->m_next = m_head;
+ m_head = item;
+ }
+
+ void Delete()
+ {
+ if (m_previous == NULL)
+ {
+ m_head = Current->m_next;
+ }
+ else
+ {
+ m_previous->m_next = Current->m_next;
+ }
+ free(Current);
+
+ Current = m_head;
+ m_previous = NULL;
+ }
+};
+
enum MachExceptionMode
{
// special value to indicate we've not initialized yet
MachException_Default = 0,
};
-static exception_mask_t GetExceptionMask()
+/*++
+Function :
+ GetExceptionMask()
+
+ Returns the mach exception mask for the exceptions to hook for a thread.
+
+Return value :
+ mach exception mask
+--*/
+static
+exception_mask_t
+GetExceptionMask()
{
static MachExceptionMode exMode = MachException_Uninitialized;
return machExceptionMask;
}
-#define NONPAL_RETAIL_ASSERT(...) \
- { \
- { \
- PAL_EnterHolder enterHolder; \
- PAL_printf(__VA_ARGS__); \
- PAL_DisplayDialogFormatted("NON-PAL ASSERT", __VA_ARGS__); \
- } \
- DBG_DebugBreak(); \
- abort(); \
- }
-
-#define CHECK_MACH(function, machret) \
- if (machret != KERN_SUCCESS) \
- { \
- NONPAL_RETAIL_ASSERT(function " failed: %08X: %s\n", machret, mach_error_string(machret)); \
- }
-
#ifdef FEATURE_PAL_SXS
/*++
countBits = ((countBits & 0xF0F0F0F0) >> 4) + (countBits & 0x0F0F0F0F);
countBits = ((countBits & 0xFF00FF00) >> 8) + (countBits & 0x00FF00FF);
countBits = ((countBits & 0xFFFF0000) >> 16) + (countBits & 0x0000FFFF);
- if (countBits != static_cast<exception_mask_t>(
- CThreadMachExceptionHandlerNode::s_nPortsMax))
+ if (countBits != static_cast<exception_mask_t>(CThreadMachExceptionHandlers::s_nPortsMax))
{
ASSERT("s_nPortsMax is %u, but needs to be %u\n",
- CThreadMachExceptionHandlerNode::s_nPortsMax, countBits);
+ CThreadMachExceptionHandlers::s_nPortsMax, countBits);
}
#endif // _DEBUG
- // We store a set of previous handlers and register an exception port that is unique to both (to help
- // us get the correct chain-back semantics in as many scenarios as possible). The following call tells
- // us which we should do.
- CThreadMachExceptionHandlerNode *pSavedHandlers = m_sMachExceptionHandlers.GetNodeForInitialization();
- NONPAL_TRACE("Enabling handlers for thread %08X exception port %08X\n", GetMachPortSelf(), s_ExceptionPort);
+ NONPAL_TRACE("Enabling handlers for thread %08x exception mask %08x exception port %08x\n",
+ GetMachPortSelf(), machExceptionMask, s_ExceptionPort);
+
+ CThreadMachExceptionHandlers *pSavedHandlers = GetSavedMachHandlers();
// Swap current handlers into temporary storage first. That's because it's possible (even likely) that
// some or all of the handlers might still be ours. In those cases we don't want to overwrite the
// chain-back entries with these useless self-references.
- kern_return_t MachRet;
- mach_msg_type_number_t oldCount = CThreadMachExceptionHandlerNode::s_nPortsMax;
- exception_mask_t rgMasks[CThreadMachExceptionHandlerNode::s_nPortsMax];
- exception_handler_t rgHandlers[CThreadMachExceptionHandlerNode::s_nPortsMax];
- exception_behavior_t rgBehaviors[CThreadMachExceptionHandlerNode::s_nPortsMax];
- thread_state_flavor_t rgFlavors[CThreadMachExceptionHandlerNode::s_nPortsMax];
+ kern_return_t machret;
+ kern_return_t machretDeallocate;
thread_port_t thread = mach_thread_self();
- exception_behavior_t excepBehavior = EXCEPTION_STATE_IDENTITY;
-
- MachRet = thread_swap_exception_ports(thread,
- machExceptionMask,
- s_ExceptionPort,
- excepBehavior,
- MACHINE_THREAD_STATE,
- rgMasks,
- &oldCount,
- rgHandlers,
- rgBehaviors,
- rgFlavors);
-
- kern_return_t MachRetDeallocate = mach_port_deallocate(mach_task_self(), thread);
- CHECK_MACH("mach_port_deallocate", MachRetDeallocate);
- if (MachRet != KERN_SUCCESS)
+ machret = thread_swap_exception_ports(
+ thread,
+ machExceptionMask,
+ s_ExceptionPort,
+ EXCEPTION_DEFAULT | MACH_EXCEPTION_CODES,
+ THREAD_STATE_NONE,
+ pSavedHandlers->m_masks,
+ &pSavedHandlers->m_nPorts,
+ pSavedHandlers->m_handlers,
+ pSavedHandlers->m_behaviors,
+ pSavedHandlers->m_flavors);
+
+ machretDeallocate = mach_port_deallocate(mach_task_self(), thread);
+ CHECK_MACH("mach_port_deallocate", machretDeallocate);
+
+ if (machret != KERN_SUCCESS)
{
- ASSERT("thread_swap_exception_ports failed: %d\n", MachRet);
- return UTIL_MachErrorToPalError(MachRet);
+ ASSERT("thread_swap_exception_ports failed: %d %s\n", machret, mach_error_string(machret));
+ return UTIL_MachErrorToPalError(machret);
}
- // Scan through the returned handlers looking for those that are ours.
- for (mach_msg_type_number_t i = 0; i < oldCount; i++)
+#ifdef _DEBUG
+ NONPAL_TRACE("EnableMachExceptions: THREAD PORT count %d\n", pSavedHandlers->m_nPorts);
+ for (mach_msg_type_number_t i = 0; i < pSavedHandlers->m_nPorts; i++)
{
- if (rgHandlers[i] == s_ExceptionPort)
- {
- // We were already registered for the exceptions indicated by rgMasks[i]. Look through each
- // exception (set bit in the mask) separately, checking whether we previously had a (non-CLR)
- // registration for that handle.
- for (size_t j = 0; j < (sizeof(exception_mask_t) * 8); j++)
- {
- // Skip unset bits (exceptions not covered by this entry).
- exception_mask_t bmException = rgMasks[i] & (1 << j);
- if (bmException == 0)
- continue;
-
- // Find record in the previous data that covers this exception.
- bool fFoundPreviousHandler = false;
- for (int k = 0; k < pSavedHandlers->m_nPorts; k++)
- {
- // Skip records for different exceptions.
- if (!(pSavedHandlers->m_masks[k] & bmException))
- continue;
-
- // Found one. By definition it shouldn't be one of our handlers.
- if (pSavedHandlers->m_handlers[k] == s_ExceptionPort)
- ASSERT("Stored our own handlers in Mach exception chain-back info.\n");
-
- // We need to replicate the handling details back into our temporary data in place of
- // the CLR record. There are several things that can happen:
- // 1) One of the other entries has the same handler, behavior and flavor (for a
- // different set of exceptions). We could merge the data for this exception into
- // that record (set another bit in the masks array entry).
- // 2) This was the only exception in the current entry (only one bit was set in the
- // mask) and we can simply re-use this entry (overwrite the handler, behavior and
- // flavor entries).
- // 3) Multiple exceptions were covered by this entry. In this case we should add a new
- // entry covering just the current exception. We're guaranteed to have space to do
- // this since we allocated enough entries to cover one exception per-entry and we
- // have at least one entry with two or more exceptions (this one).
- // It turns out we can ignore case 1 (which involves complicating our logic still
- // further) since we have no requirement to tightly pack all the entries for the same
- // handler/behavior/flavor (like thread_swap_exception_ports does). We're perfectly
- // happy having six entries for six exceptions handled by identical handlers rather
- // than a single entry with six bits set in the exception mask.
- if (rgMasks[i] == bmException)
- {
- // Entry was only for this exception. Simply overwrite handler/behavior and flavor
- // with the stored values.
- rgHandlers[i] = pSavedHandlers->m_handlers[k];
- rgBehaviors[i] = pSavedHandlers->m_behaviors[k];
- rgFlavors[i] = pSavedHandlers->m_flavors[k];
- }
- else
- {
- // More than one exception handled by this record. Store the old data in a new
- // cell of the temporary data and remove the exception from the old cell.
- if ((int)oldCount == CThreadMachExceptionHandlerNode::s_nPortsMax)
- ASSERT("Ran out of space to expand exception handlers. This shouldn't happen.\n");
-
- rgMasks[oldCount] = bmException;
- rgHandlers[oldCount] = pSavedHandlers->m_handlers[k];
- rgBehaviors[oldCount] = pSavedHandlers->m_behaviors[k];
- rgFlavors[oldCount] = pSavedHandlers->m_flavors[k];
-
- // The old cell no longer describes this exception.
- rgMasks[i] &= ~bmException;
-
- oldCount++;
- }
-
- // We found a match.
- fFoundPreviousHandler = true;
- break;
- }
-
- // If we didn't find a match then we still don't want to record our own handler. Just
- // reset the bit in the masks value (implicitly recording that we have no-chain back entry
- // for this exception).
- if (!fFoundPreviousHandler)
- rgMasks[i] &= ~bmException;
- }
- }
+ _ASSERTE(pSavedHandlers->m_handlers[i] != s_ExceptionPort);
+ NONPAL_TRACE("EnableMachExceptions: THREAD PORT mask %08x handler: %08x behavior %08x flavor %u\n",
+ pSavedHandlers->m_masks[i],
+ pSavedHandlers->m_handlers[i],
+ pSavedHandlers->m_behaviors[i],
+ pSavedHandlers->m_flavors[i]);
}
-
- // We've cleaned any mention of our own handlers from the data. It's safe to persist it.
- pSavedHandlers->m_nPorts = oldCount;
- memcpy(pSavedHandlers->m_masks, rgMasks, sizeof(rgMasks));
- memcpy(pSavedHandlers->m_handlers, rgHandlers, sizeof(rgHandlers));
- memcpy(pSavedHandlers->m_behaviors, rgBehaviors, sizeof(rgBehaviors));
- memcpy(pSavedHandlers->m_flavors, rgFlavors, sizeof(rgFlavors));
+#endif // _DEBUG
}
return ERROR_SUCCESS;
}
if (0 == GetExceptionMask())
return palError;
- // Get the handlers to restore. It isn't really as simple as this. We keep two sets of handlers (which
- // improves our ability to chain correctly in more scenarios) but this means we can encounter dilemmas
- // where we've recorded two different handlers for the same port and can only re-register one of them
- // (with a very high chance that it does not chain to the other). I don't believe it matters much today:
- // in the absence of CoreCLR shutdown we don't throw away our thread context until a thread dies (in fact
- // usually a bit later than this). Hopefully by the time this changes we'll have a better design for
- // hardware exception handling overall.
- CThreadMachExceptionHandlerNode *savedPorts = m_sMachExceptionHandlers.GetNodeForCleanup();
+ // Get the handlers to restore.
+ CThreadMachExceptionHandlers *savedPorts = GetSavedMachHandlers();
kern_return_t MachRet = KERN_SUCCESS;
for (int i = 0; i < savedPorts->m_nPorts; i++)
// MACH_PORT_NULL for the handler and zero values for behavior
// and flavor. Unfortunately, the latter are invalid even for
// MACH_PORT_NULL when you use thread_set_exception_ports.
- exception_behavior_t behavior =
- savedPorts->m_behaviors[i] ? savedPorts->m_behaviors[i] : EXCEPTION_DEFAULT;
- thread_state_flavor_t flavor =
- savedPorts->m_flavors[i] ? savedPorts->m_flavors[i] : MACHINE_THREAD_STATE;
+ exception_behavior_t behavior = savedPorts->m_behaviors[i] ? savedPorts->m_behaviors[i] : EXCEPTION_DEFAULT;
+ thread_state_flavor_t flavor = savedPorts->m_flavors[i] ? savedPorts->m_flavors[i] : MACHINE_THREAD_STATE;
thread_port_t thread = mach_thread_self();
MachRet = thread_set_exception_ports(thread,
savedPorts->m_masks[i],
savedPorts->m_handlers[i],
behavior,
flavor);
+
kern_return_t MachRetDeallocate = mach_port_deallocate(mach_task_self(), thread);
CHECK_MACH("mach_port_deallocate", MachRetDeallocate);
#endif // FEATURE_PAL_SXS
#if !defined(_AMD64_)
-void PAL_DispatchException(PCONTEXT pContext, PEXCEPTION_RECORD pExRecord, MachMessage *pMessage)
+extern "C"
+void PAL_DispatchException(PCONTEXT pContext, PEXCEPTION_RECORD pExRecord, MachExceptionInfo *pMachExceptionInfo)
#else // defined(_AMD64_)
-// Since HijackFaultingThread pushed the context, exception record and mach exception message on the stack,
-// we need to adjust the signature of PAL_DispatchException such that the corresponding arguments are considered
-// to be on the stack per GCC64 calling convention rules. Hence, the first 6 dummy arguments (corresponding to RDI,
-// RSI, RDX,RCX, R8, R9).
-void PAL_DispatchException(DWORD64 dwRDI, DWORD64 dwRSI, DWORD64 dwRDX, DWORD64 dwRCX, DWORD64 dwR8, DWORD64 dwR9, PCONTEXT pContext, PEXCEPTION_RECORD pExRecord, MachMessage *pMessage)
+// Since HijackFaultingThread pushed the context, exception record and info on the stack, we need to adjust the
+// signature of PAL_DispatchException such that the corresponding arguments are considered to be on the stack
+// per GCC64 calling convention rules. Hence, the first 6 dummy arguments (corresponding to RDI, RSI, RDX,RCX, R8, R9).
+extern "C"
+void PAL_DispatchException(DWORD64 dwRDI, DWORD64 dwRSI, DWORD64 dwRDX, DWORD64 dwRCX, DWORD64 dwR8, DWORD64 dwR9, PCONTEXT pContext, PEXCEPTION_RECORD pExRecord, MachExceptionInfo *pMachExceptionInfo)
#endif // !defined(_AMD64_)
{
CPalThread *pThread = InternalGetCurrentThread();
TRACE("PAL_DispatchException(EC %08x EA %p)\n", pExRecord->ExceptionCode, pExRecord->ExceptionAddress);
SEHProcessException(&pointers);
- // Chain the exception to the next PAL
- ForwardMachException(pThread, pMessage);
+ // Send the forward request to the exception thread to process
+ MachMessage sSendMessage;
+ sSendMessage.SendForwardException(s_ExceptionPort, pMachExceptionInfo, pThread);
+
+ // Spin wait until this thread is hijacked by the exception thread
+ while (TRUE)
+ {
+ sched_yield();
+ }
}
#if defined(_X86_) || defined(_AMD64_)
/*++
Function :
- ExceptionRecordFromMessage
+ BuildExceptionRecord
- Setups up an ExceptionRecord from an exception message
+ Sets up up an ExceptionRecord from an exception message
Parameters :
- message - exception message to build the exception record
+ exceptionInfo - exception info to build the exception record
pExceptionRecord - exception record to setup
*/
-static void
-ExceptionRecordFromMessage(
- MachMessage &message, // [in] exception message
- EXCEPTION_RECORD *pExceptionRecord) // [out] Used to return exception parameters
+static
+void
+BuildExceptionRecord(
+ MachExceptionInfo& exceptionInfo, // [in] exception info
+ EXCEPTION_RECORD *pExceptionRecord) // [out] Used to return exception parameters
{
- exception_type_t exception = message.GetException();
- MACH_EH_TYPE(exception_data_type_t) subcodes[2];
- mach_msg_type_number_t subcode_count;
-
- subcode_count = message.GetExceptionCodeCount();
- if (subcode_count < 0 || subcode_count > 2)
- NONPAL_RETAIL_ASSERT("Bad exception subcode count: %d", subcode_count);
-
- for (int i = 0; i < subcode_count; i++)
- subcodes[i] = message.GetExceptionCode(i);
-
memset(pExceptionRecord, 0, sizeof(EXCEPTION_RECORD));
DWORD exceptionCode = EXCEPTION_ILLEGAL_INSTRUCTION;
- switch(exception)
+ switch(exceptionInfo.ExceptionType)
{
// Could not access memory. subcode contains the bad memory address.
case EXC_BAD_ACCESS:
- if (subcode_count != 2)
+ if (exceptionInfo.SubcodeCount != 2)
{
NONPAL_RETAIL_ASSERT("Got an unexpected subcode");
exceptionCode = EXCEPTION_ILLEGAL_INSTRUCTION;
pExceptionRecord->NumberParameters = 2;
pExceptionRecord->ExceptionInformation[0] = 0;
- pExceptionRecord->ExceptionInformation[1] = subcodes[1];
- NONPAL_TRACE("subcodes[1] = %llx\n", subcodes[1]);
+ pExceptionRecord->ExceptionInformation[1] = exceptionInfo.Subcodes[1];
+ NONPAL_TRACE("subcodes[1] = %llx\n", exceptionInfo.Subcodes[1]);
}
break;
// Arithmetic exception; exact nature of exception is in subcode field.
case EXC_ARITHMETIC:
- if (subcode_count != 2)
+ if (exceptionInfo.SubcodeCount != 2)
{
NONPAL_RETAIL_ASSERT("Got an unexpected subcode");
exceptionCode = EXCEPTION_ILLEGAL_INSTRUCTION;
}
else
{
- switch (subcodes[0])
+ switch (exceptionInfo.Subcodes[0])
{
#if defined(_X86_) || defined(_AMD64_)
case EXC_I386_DIV:
// Trace, breakpoint, etc. Details in subcode field.
case EXC_BREAKPOINT:
#if defined(_X86_) || defined(_AMD64_)
- if (subcodes[0] == EXC_I386_SGL)
+ if (exceptionInfo.Subcodes[0] == EXC_I386_SGL)
{
exceptionCode = EXCEPTION_SINGLE_STEP;
}
- else if (subcodes[0] == EXC_I386_BPT)
+ else if (exceptionInfo.Subcodes[0] == EXC_I386_BPT)
{
exceptionCode = EXCEPTION_BREAKPOINT;
}
#endif
else
{
- WARN("unexpected subcode %d for EXC_BREAKPOINT", subcodes[0]);
+ WARN("unexpected subcode %d for EXC_BREAKPOINT", exceptionInfo.Subcodes[0]);
exceptionCode = EXCEPTION_BREAKPOINT;
}
break;
break;
default:
- ASSERT("Got unknown trap code %d\n", exception);
+ NONPAL_ASSERT("Got unknown trap code %d\n", exceptionInfo.ExceptionType);
break;
}
return "EXC_MACH_SYSCALL";
default:
- ASSERT("Got unknown trap code %d\n", exception);
+ NONPAL_ASSERT("Got unknown trap code %d\n", exception);
break;
}
return "INVALID CODE";
HijackFaultingThread
Sets the faulting thread up to return to PAL_DispatchException with an
- ExceptionRecord, thread CONTEXT and the exception MachMessage.
+ ExceptionRecord and thread CONTEXT.
Parameters:
thread - thread the exception happened
Return value :
None
--*/
-
static
void
HijackFaultingThread(
mach_port_t thread, // [in] thread the exception happened on
mach_port_t task, // [in] task the exception happened on
- MachMessage &message) // [in] exception message
+ MachMessage& message) // [in] exception message
{
- thread_state_flavor_t threadStateFlavor;
- x86_thread_state_t threadState;
+ MachExceptionInfo exceptionInfo(thread, message);
EXCEPTION_RECORD exceptionRecord;
CONTEXT threadContext;
kern_return_t machret;
- unsigned int count;
- // Fill in the exception record from the exception message
- ExceptionRecordFromMessage(message, &exceptionRecord);
+ // Fill in the exception record from the exception info
+ BuildExceptionRecord(exceptionInfo, &exceptionRecord);
- // Get the thread state from the exception message and convert the count of bytes into
- // the count of ints
- threadStateFlavor = message.GetThreadStateFlavor();
- count = message.GetThreadState(threadStateFlavor, (thread_state_t)&threadState, thread) / sizeof(natural_t);
-
#ifdef _X86_
- threadContext.ContextFlags = CONTEXT_FLOATING_POINT | CONTEXT_EXTENDED_REGISTERS
+ threadContext.ContextFlags = CONTEXT_FLOATING_POINT | CONTEXT_EXTENDED_REGISTERS;
#else
threadContext.ContextFlags = CONTEXT_FLOATING_POINT;
#endif
- // Get just the floating point registers directly from the thread because the message context is only
- // the general registers.
- machret = CONTEXT_GetThreadContextFromPort(thread, &threadContext);
- CHECK_MACH("CONTEXT_GetThreadContextFromPort", machret);
+ CONTEXT_GetThreadContextFromThreadState(x86_FLOAT_STATE, (thread_state_t)&exceptionInfo.FloatState, &threadContext);
- // Now get the rest of the registers from the exception message. Don't save/restore the debug registers
- // because loading them on OSx causes a privileged instruction fault. The "DE" in CR4 is set.
threadContext.ContextFlags |= CONTEXT_CONTROL | CONTEXT_INTEGER | CONTEXT_SEGMENTS;
- CONTEXT_GetThreadContextFromThreadState(threadStateFlavor, (thread_state_t)&threadState, &threadContext);
+ CONTEXT_GetThreadContextFromThreadState(x86_THREAD_STATE, (thread_state_t)&exceptionInfo.ThreadState, &threadContext);
#if defined(CORECLR) && (defined(_X86_) || defined(_AMD64_))
// For CoreCLR we look more deeply at access violations to determine whether they're the result of a stack
#else
machret = vm_region(
#endif
- mach_task_self(),
- &vm_address,
- &vm_size,
- vm_flavor,
- (vm_region_info_t)&info,
- &infoCnt,
- &object_name);
+ mach_task_self(),
+ &vm_address,
+ &vm_size,
+ vm_flavor,
+ (vm_region_info_t)&info,
+ &infoCnt,
+ &object_name);
#ifdef _X86_
CHECK_MACH("vm_region", machret);
#elif defined(_AMD64_)
{
// Check if we can read pointer sizeD bytes below the target thread's stack pointer.
// If we are unable to, then it implies we have run into SO.
- void **targetSP = (void **)threadState.uts.ts64.__rsp;
+ void **targetSP = (void **)threadContext.Rsp;
vm_address_t targetAddr = (mach_vm_address_t)(targetSP);
targetAddr -= sizeof(void *);
vm_size_t vm_size = sizeof(void *);
#endif // CORECLR && _X86_
#if defined(_X86_)
- _ASSERTE((threadStateFlavor == x86_THREAD_STATE32) || ((threadStateFlavor == x86_THREAD_STATE) && (threadState.tsh.flavor == x86_THREAD_STATE32)));
+ NONPAL_ASSERTE(exceptionInfo.ThreadState.tsh.flavor == x86_THREAD_STATE32);
+
+ // Make a copy of the thread state because the one in exceptionInfo needs to be preserved to restore
+ // the state if the exception is forwarded.
+ x86_thread_state32_t ts32 = exceptionInfo.ThreadState.uts.ts32;
// If we're in single step mode, disable it since we're going to call PAL_DispatchException
if (exceptionRecord.ExceptionCode == EXCEPTION_SINGLE_STEP)
{
- threadState.uts.ts32.eflags &= ~EFL_TF;
+ ts32.eflags &= ~EFL_TF;
}
exceptionRecord.ExceptionFlags = EXCEPTION_IS_SIGNAL;
exceptionRecord.ExceptionRecord = NULL;
- exceptionRecord.ExceptionAddress = (void *)threadContext.Eip;
+ exceptionRecord.ExceptionAddress = (void *)ts32.eip;
- void **FramePointer = (void **)threadState.uts.ts32.esp;
+ void **FramePointer = (void **)ts32.esp;
- *--FramePointer = (void *)((ULONG_PTR)threadState.uts.ts32.eip);
+ *--FramePointer = (void *)ts32.eip;
// Construct a stack frame for a pretend activation of the function
// PAL_DispatchExceptionWrapper that serves only to make the stack
// PAL_DispatchExceptionWrapper has an ebp frame, its local variables
// are the context and exception record, and it has just "called"
// PAL_DispatchException.
- *--FramePointer = (void *)threadState.uts.ts32.ebp;
- threadState.uts.ts32.ebp = (unsigned)FramePointer;
+ *--FramePointer = (void *)ts32.ebp;
+ ts32.ebp = (unsigned)FramePointer;
// Put the context on the stack
FramePointer = (void **)((ULONG_PTR)FramePointer - sizeof(CONTEXT));
EXCEPTION_RECORD *pExceptionRecord = (EXCEPTION_RECORD *)FramePointer;
*pExceptionRecord = exceptionRecord;
- // Put the exception message on the stack
- FramePointer = (void **)((ULONG_PTR)FramePointer - sizeof(MachMessage));
- MachMessage *pMessage = (MachMessage *)FramePointer;
- pMessage->InitializeFrom(message);
+ FramePointer = (void **)((ULONG_PTR)FramePointer - sizeof(MachExceptionInfo));
+ MachExceptionInfo *pMachExceptionInfo = (MachExceptionInfo *)FramePointer;
+ *pMachExceptionInfo = exceptionInfo;
// Push arguments to PAL_DispatchException
FramePointer = (void **)((ULONG_PTR)FramePointer - 3 * sizeof(void *));
FramePointer = (void **)((ULONG_PTR)FramePointer - ((ULONG_PTR)FramePointer % 16));
FramePointer[0] = pContext;
FramePointer[1] = pExceptionRecord;
- FramePointer[2] = pMessage;
+ FramePointer[2] = pMachExceptionInfo;
// Place the return address to right after the fake call in PAL_DispatchExceptionWrapper
FramePointer[-1] = (void *)((ULONG_PTR)PAL_DispatchExceptionWrapper + PAL_DispatchExceptionReturnOffset);
// Make the instruction register point to DispatchException
- threadState.uts.ts32.eip = (unsigned)PAL_DispatchException;
- threadState.uts.ts32.esp = (unsigned)&FramePointer[-1]; // skip return address
+ ts32.eip = (unsigned)PAL_DispatchException;
+ ts32.esp = (unsigned)&FramePointer[-1]; // skip return address
+
+ // Now set the thread state for the faulting thread so that PAL_DispatchException executes next
+ machret = thread_set_state(thread, x86_THREAD_STATE32, (thread_state_t)&ts32, x86_THREAD_STATE32_COUNT);
+ CHECK_MACH("thread_set_state(thread)", machret);
#elif defined(_AMD64_)
- _ASSERTE((threadStateFlavor == x86_THREAD_STATE64) || ((threadStateFlavor == x86_THREAD_STATE) && (threadState.tsh.flavor == x86_THREAD_STATE64)));
+ NONPAL_ASSERTE(exceptionInfo.ThreadState.tsh.flavor == x86_THREAD_STATE64);
+
+ // Make a copy of the thread state because the one in exceptionInfo needs to be preserved to restore
+ // the state if the exception is forwarded.
+ x86_thread_state64_t ts64 = exceptionInfo.ThreadState.uts.ts64;
// If we're in single step mode, disable it since we're going to call PAL_DispatchException
if (exceptionRecord.ExceptionCode == EXCEPTION_SINGLE_STEP)
{
- threadState.uts.ts64.__rflags &= ~EFL_TF;
+ ts64.__rflags &= ~EFL_TF;
}
exceptionRecord.ExceptionFlags = EXCEPTION_IS_SIGNAL;
exceptionRecord.ExceptionRecord = NULL;
- exceptionRecord.ExceptionAddress = (void *)threadContext.Rip;
+ exceptionRecord.ExceptionAddress = (void *)ts64.__rip;
- void **FramePointer = (void **)threadState.uts.ts64.__rsp;
+ void **FramePointer = (void **)ts64.__rsp;
- *--FramePointer = (void *)((ULONG_PTR)threadState.uts.ts64.__rip);
+ *--FramePointer = (void *)ts64.__rip;
// Construct a stack frame for a pretend activation of the function
// PAL_DispatchExceptionWrapper that serves only to make the stack
// PAL_DispatchExceptionWrapper has an ebp frame, its local variables
// are the context and exception record, and it has just "called"
// PAL_DispatchException.
- *--FramePointer = (void *)threadState.uts.ts64.__rbp;
- threadState.uts.ts64.__rbp = (SIZE_T)FramePointer;
+ *--FramePointer = (void *)ts64.__rbp;
+ ts64.__rbp = (SIZE_T)FramePointer;
// Put the context on the stack
FramePointer = (void **)((ULONG_PTR)FramePointer - sizeof(CONTEXT));
EXCEPTION_RECORD *pExceptionRecord = (EXCEPTION_RECORD *)FramePointer;
*pExceptionRecord = exceptionRecord;
- // Put the exception message on the stack
- FramePointer = (void **)((ULONG_PTR)FramePointer - sizeof(MachMessage));
- MachMessage *pMessage = (MachMessage *)FramePointer;
- pMessage->InitializeFrom(message);
+ FramePointer = (void **)((ULONG_PTR)FramePointer - sizeof(MachExceptionInfo));
+ MachExceptionInfo *pMachExceptionInfo = (MachExceptionInfo *)FramePointer;
+ *pMachExceptionInfo = exceptionInfo;
// Push arguments to PAL_DispatchException
FramePointer = (void **)((ULONG_PTR)FramePointer - 3 * sizeof(void *));
FramePointer = (void **)((ULONG_PTR)FramePointer - ((ULONG_PTR)FramePointer % 16));
FramePointer[0] = pContext;
FramePointer[1] = pExceptionRecord;
- FramePointer[2] = pMessage;
+ FramePointer[2] = pMachExceptionInfo;
// Place the return address to right after the fake call in PAL_DispatchExceptionWrapper
FramePointer[-1] = (void *)((ULONG_PTR)PAL_DispatchExceptionWrapper + PAL_DispatchExceptionReturnOffset);
// Make the instruction register point to DispatchException
- threadState.uts.ts64.__rip = (SIZE_T)PAL_DispatchException;
- threadState.uts.ts64.__rsp = (SIZE_T)&FramePointer[-1]; // skip return address
+ ts64.__rip = (SIZE_T)PAL_DispatchException;
+ ts64.__rsp = (SIZE_T)&FramePointer[-1]; // skip return address
+
+ // Now set the thread state for the faulting thread so that PAL_DispatchException executes next
+ machret = thread_set_state(thread, x86_THREAD_STATE64, (thread_state_t)&ts64, x86_THREAD_STATE64_COUNT);
+ CHECK_MACH("thread_set_state(thread)", machret);
#else
#error HijackFaultingThread not defined for this architecture
#endif
+}
- // Now set the thread state for the faulting thread so that PAL_DispatchException executes next
- machret = thread_set_state(thread, threadStateFlavor, (thread_state_t)&threadState, count);
- CHECK_MACH("thread_set_state", machret);
+/*++
+Function :
+ SuspendMachThread
+
+ Suspend the specified thread.
+
+Parameters:
+ thread - mach thread port
+
+Return value :
+ None
+--*/
+static
+void
+SuspendMachThread(thread_act_t thread)
+{
+ kern_return_t machret;
+
+ while (true)
+ {
+ machret = thread_suspend(thread);
+ CHECK_MACH("thread_suspend", machret);
+
+ // Ensure that if the thread was running in the kernel, the kernel operation
+ // is safely aborted so that it can be restarted later.
+ machret = thread_abort_safely(thread);
+ if (machret == KERN_SUCCESS)
+ {
+ break;
+ }
+
+ // The thread was running in the kernel executing a non-atomic operation
+ // that cannot be restarted, so we need to resume the thread and retry
+ machret = thread_resume(thread);
+ CHECK_MACH("thread_resume", machret);
+ }
}
/*++
Return value :
Never returns
--*/
-void *SEHExceptionThread(void *args)
+void *
+SEHExceptionThread(void *args)
{
- MachMessage sMessage;
+ ForwardedExceptionList feList;
MachMessage sReplyOrForward;
-
+ MachMessage sMessage;
kern_return_t machret;
- thread_act_t hThread;
+ thread_act_t thread;
// Loop processing incoming messages forever.
while (true)
// Receive the next message.
sMessage.Receive(s_ExceptionPort);
- NONPAL_TRACE("Received message %s from %08x to %08x\n",
+ NONPAL_TRACE("Received message %s (%08x) from (remote) %08x to (local) %08x\n",
sMessage.GetMessageTypeName(),
+ sMessage.GetMessageType(),
sMessage.GetRemotePort(),
sMessage.GetLocalPort());
{
// Handle a request to set the thread context for the specified target thread.
CONTEXT sContext;
- hThread = sMessage.GetThreadContext(&sContext);
+ thread = sMessage.GetThreadContext(&sContext);
- while (true)
- {
- machret = thread_suspend(hThread);
- CHECK_MACH("thread_suspend", machret);
-
- // Ensure that if the thread was running in the kernel, the kernel operation
- // is safely aborted so that it can be restarted later.
- machret = thread_abort_safely(hThread);
- if (machret == KERN_SUCCESS)
- {
- break;
- }
-
- // The thread was running in the kernel executing a non-atomic operation
- // that cannot be restarted, so we need to resume the thread and retry
- machret = thread_resume(hThread);
- CHECK_MACH("thread_resume", machret);
- }
+ // Suspend the target thread
+ SuspendMachThread(thread);
- machret = CONTEXT_SetThreadContextOnPort(hThread, &sContext);
+ machret = CONTEXT_SetThreadContextOnPort(thread, &sContext);
CHECK_MACH("CONTEXT_SetThreadContextOnPort", machret);
- machret = thread_resume(hThread);
+ machret = thread_resume(thread);
CHECK_MACH("thread_resume", machret);
}
else if (sMessage.IsExceptionNotification())
{
// This is a notification of an exception occurring on another thread.
- hThread = sMessage.GetThread();
+ exception_type_t exceptionType = sMessage.GetException();
+ thread = sMessage.GetThread();
- NONPAL_TRACE("Notification is for exception %u (%s) on thread port %08x flavor %d\n",
- sMessage.GetException(),
- GetExceptionString(sMessage.GetException()),
- hThread,
- sMessage.GetThreadStateFlavor());
+#ifdef _DEBUG
+ if (NONPAL_TRACE_ENABLED)
+ {
+ NONPAL_TRACE("ExceptionNotification %s (%u) thread %08x flavor %u\n",
+ GetExceptionString(exceptionType),
+ exceptionType,
+ thread,
+ sMessage.GetThreadStateFlavor());
+
+ int subcode_count = sMessage.GetExceptionCodeCount();
+ for (int i = 0; i < subcode_count; i++)
+ NONPAL_TRACE("ExceptionNotification subcode[%d] = %llx\n", i, sMessage.GetExceptionCode(i));
+
+ x86_thread_state64_t threadStateActual;
+ unsigned int count = sizeof(threadStateActual) / sizeof(unsigned);
+ machret = thread_get_state(thread, x86_THREAD_STATE64, (thread_state_t)&threadStateActual, &count);
+ CHECK_MACH("thread_get_state", machret);
+
+ NONPAL_TRACE("ExceptionNotification actual rip %016llx rsp %016llx rbp %016llx rax %016llx r15 %016llx eflags %08llx\n",
+ threadStateActual.__rip,
+ threadStateActual.__rsp,
+ threadStateActual.__rbp,
+ threadStateActual.__rax,
+ threadStateActual.__r15,
+ threadStateActual.__rflags);
+
+ x86_exception_state64_t threadExceptionState;
+ unsigned int ehStateCount = sizeof(threadExceptionState) / sizeof(unsigned);
+ machret = thread_get_state(thread, x86_EXCEPTION_STATE64, (thread_state_t)&threadExceptionState, &ehStateCount);
+ CHECK_MACH("thread_get_state", machret);
+
+ NONPAL_TRACE("ExceptionNotification trapno %04x cpu %04x err %08x faultAddr %016llx\n",
+ threadExceptionState.__trapno,
+ threadExceptionState.__cpu,
+ threadExceptionState.__err,
+ threadExceptionState.__faultvaddr);
+ }
+#endif // _DEBUG
- HijackFaultingThread(hThread, mach_task_self(), sMessage);
+ bool feFound = false;
+ feList.MoveFirst();
- // Send the result of handling the exception back in a reply.
- sReplyOrForward.ReplyToNotification(&sMessage, KERN_SUCCESS);
+ while (!feList.IsEOL())
+ {
+ mach_port_type_t ePortType;
+ if (mach_port_type(mach_task_self(), feList.Current->Thread, &ePortType) != KERN_SUCCESS || (ePortType & MACH_PORT_TYPE_DEAD_NAME))
+ {
+ NONPAL_TRACE("Forwarded exception: invalid thread port %08x\n", feList.Current->Thread);
+
+ // Unlink and delete the forwarded exception instance
+ feList.Delete();
+ }
+ else
+ {
+ if (feList.Current->Thread == thread)
+ {
+ bool isSameException = feList.Current->ExceptionType == exceptionType;
+ feFound = true;
+
+ // Locate the record of previously installed handlers that the target thread keeps.
+ CThreadMachExceptionHandlers *pHandlers = feList.Current->PalThread->GetSavedMachHandlers();
+
+ // Unlink and delete the forwarded exception instance
+ feList.Delete();
+
+ // Check if the current exception type matches the forwarded one and whether
+ // there's a handler for the particular exception we've been handed.
+ MachExceptionHandler sHandler;
+ if (isSameException && pHandlers->GetHandler(exceptionType, &sHandler))
+ {
+ NONPAL_TRACE("ForwardNotification thread %08x to handler %08x\n", thread, sHandler.m_handler);
+ sReplyOrForward.ForwardNotification(&sHandler, sMessage);
+ }
+ else
+ {
+ NONPAL_TRACE("ReplyToNotification KERN_FAILURE thread %08x port %08x sameException %d\n",
+ thread, sMessage.GetRemotePort(), isSameException);
+ sReplyOrForward.ReplyToNotification(sMessage, KERN_FAILURE);
+ }
+ break;
+ }
+
+ feList.MoveNext();
+ }
+ }
+
+ if (!feFound)
+ {
+ NONPAL_TRACE("HijackFaultingThread thread %08x\n", thread);
+ HijackFaultingThread(thread, mach_task_self(), sMessage);
+
+ // Send the result of handling the exception back in a reply.
+ NONPAL_TRACE("ReplyToNotification KERN_SUCCESS thread %08x port %08x\n", thread, sMessage.GetRemotePort());
+ sReplyOrForward.ReplyToNotification(sMessage, KERN_SUCCESS);
+ }
}
- else if (sMessage.IsExceptionReply())
+ else if (sMessage.IsForwardExceptionRequest())
{
- NONPAL_TRACE("Exception reply - ignored\n");
+ thread = sMessage.GetThread();
+
+ NONPAL_TRACE("ForwardExceptionRequest for thread %08x\n", thread);
+
+ // Suspend the faulting thread.
+ SuspendMachThread(thread);
+
+ // Set the context back to the original faulting state.
+ MachExceptionInfo *pExceptionInfo = sMessage.GetExceptionInfo();
+ pExceptionInfo->RestoreState(thread);
+
+ // Allocate an forwarded exception entry
+ ForwardedException *pfe = (ForwardedException *)malloc(sizeof(ForwardedException));
+ if (pfe == NULL)
+ {
+ NONPAL_RETAIL_ASSERT("Exception thread ran out of memory to track forwarded exception notifications");
+ }
+
+ // Save the forwarded exception entry away for the restarted exception message
+ pfe->Thread = thread;
+ pfe->ExceptionType = pExceptionInfo->ExceptionType;
+ pfe->PalThread = sMessage.GetPalThread();
+ feList.Add(pfe);
+
+ // Now let the thread run at the original exception context to restart the exception
+ NONPAL_TRACE("ForwardExceptionRequest resuming thread %08x exception type %08x\n", thread, pfe->ExceptionType);
+ machret = thread_resume(thread);
+ CHECK_MACH("thread_resume", machret);
}
else
{
}
}
}
+
+/*++
+Function :
+ MachExceptionInfo constructor
+
+ Saves the exception info from the exception notification message and
+ the current thread state.
+
+Parameters:
+ thread - thread port to restore
+ message - exception message
-void ForwardMachException(CPalThread *pThread, MachMessage *pMessage)
+Return value :
+ none
+--*/
+MachExceptionInfo::MachExceptionInfo(mach_port_t thread, MachMessage& message)
{
- thread_act_t hThread = pThread->GetMachPortSelf();
- MachMessage sReplyOrForward;
+ kern_return_t machret;
- // Locate the record of previously installed handlers that the target thread keeps.
- CorUnix::CThreadMachExceptionHandlers *pHandlers = pThread->GetSavedMachHandlers();
+ ExceptionType = message.GetException();
+ SubcodeCount = message.GetExceptionCodeCount();
+ NONPAL_RETAIL_ASSERTE(SubcodeCount >= 0 && SubcodeCount <= 2);
- // Check whether there's even a handler for the particular exception we've been handed.
- CorUnix::MachExceptionHandler sHandler;
- if (pHandlers->GetHandler(pMessage->GetException(), &sHandler))
- {
- NONPAL_TRACE("Forward request to port %08x\n", sHandler.m_handler);
+ for (int i = 0; i < SubcodeCount; i++)
+ Subcodes[i] = message.GetExceptionCode(i);
- // Forward the notification
- sReplyOrForward.ForwardNotification(&sHandler, pMessage);
+ mach_msg_type_number_t count = x86_THREAD_STATE_COUNT;
+ machret = thread_get_state(thread, x86_THREAD_STATE, (thread_state_t)&ThreadState, &count);
+ CHECK_MACH("thread_get_state", machret);
- // Spin wait until this thread is hijacked or the process is aborted.
- while (TRUE)
+ count = x86_FLOAT_STATE_COUNT;
+ machret = thread_get_state(thread, x86_FLOAT_STATE, (thread_state_t)&FloatState, &count);
+ CHECK_MACH("thread_get_state(float)", machret);
+
+ count = x86_DEBUG_STATE_COUNT;
+ machret = thread_get_state(thread, x86_DEBUG_STATE, (thread_state_t)&DebugState, &count);
+ CHECK_MACH("thread_get_state(debug)", machret);
+}
+
+/*++
+Function :
+ MachExceptionInfo::RestoreState
+
+ Restore the thread to the saved exception info state.
+
+Parameters:
+ thread - thread port to restore
+
+Return value :
+ none
+--*/
+void MachExceptionInfo::RestoreState(mach_port_t thread)
+{
+ // If we are restarting a breakpoint, we need to bump the IP back one to
+ // point at the actual int 3 instructions.
+ if (ExceptionType == EXC_BREAKPOINT)
+ {
+ if (Subcodes[0] == EXC_I386_BPT)
{
- sched_yield();
+#ifdef _X86_
+ ThreadState.uts.ts32.eip--;
+#elif defined(_AMD64_)
+ ThreadState.uts.ts64.__rip--;
+#else
+#error Platform not supported
+#endif
}
}
- else
- {
- // There's no previous handler to forward this notification to.
- NONPAL_TRACE("Unhandled exception and no chain-back - aborting process\n");
- PROCAbort();
- }
+ kern_return_t machret = thread_set_state(thread, x86_THREAD_STATE, (thread_state_t)&ThreadState, x86_THREAD_STATE_COUNT);
+ CHECK_MACH("thread_set_state(thread)", machret);
+
+ machret = thread_set_state(thread, x86_FLOAT_STATE, (thread_state_t)&FloatState, x86_FLOAT_STATE_COUNT);
+ CHECK_MACH("thread_set_state(float)", machret);
+
+ machret = thread_set_state(thread, x86_DEBUG_STATE, (thread_state_t)&DebugState, x86_DEBUG_STATE_COUNT);
+ CHECK_MACH("thread_set_state(debug)", machret);
}
+/*++
+Function :
+ MachSetThreadContext
+
+ Sets the context of the current thread by sending a notification
+ to the exception thread.
+
+Parameters:
+ lpContext - the CONTEXT to set the current thread
+
+Return value :
+ Doesn't return
+--*/
PAL_NORETURN
-void MachSetThreadContext(CONTEXT *lpContext)
+void
+MachSetThreadContext(CONTEXT *lpContext)
{
- // We need to send a message to the worker thread so that it can set our thread context
- // It is responsible for deallocating the thread port.
+ // We need to send a message to the worker thread so that it can set our thread context.
MachMessage sRequest;
- sRequest.SendSetThread(s_ExceptionPort, mach_thread_self(), lpContext);
+ sRequest.SendSetThread(s_ExceptionPort, lpContext);
// Make sure we don't do anything
while (TRUE)
TRUE if SEH support initialization succeeded
FALSE otherwise
--*/
-BOOL SEHInitializeMachExceptions (void)
+BOOL
+SEHInitializeMachExceptions(void)
{
- kern_return_t MachRet;
- int CreateRet;
pthread_t exception_thread;
+ kern_return_t machret;
// Allocate a mach port that will listen in on exceptions
- MachRet = mach_port_allocate(mach_task_self(),
- MACH_PORT_RIGHT_RECEIVE,
- &s_ExceptionPort);
-
- if (MachRet != KERN_SUCCESS)
+ machret = mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &s_ExceptionPort);
+ if (machret != KERN_SUCCESS)
{
- ASSERT("mach_port_allocate failed: %d\n", MachRet);
- UTIL_SetLastErrorFromMach(MachRet);
+ ASSERT("mach_port_allocate failed: %d\n", machret);
+ UTIL_SetLastErrorFromMach(machret);
return FALSE;
}
// Insert the send right into the task
- MachRet = mach_port_insert_right(mach_task_self(),
- s_ExceptionPort,
- s_ExceptionPort,
- MACH_MSG_TYPE_MAKE_SEND);
-
- if (MachRet != KERN_SUCCESS)
+ machret = mach_port_insert_right(mach_task_self(), s_ExceptionPort, s_ExceptionPort, MACH_MSG_TYPE_MAKE_SEND);
+ if (machret != KERN_SUCCESS)
{
- ASSERT("mach_port_insert_right failed: %d\n", MachRet);
- UTIL_SetLastErrorFromMach(MachRet);
+ ASSERT("mach_port_insert_right failed: %d\n", machret);
+ UTIL_SetLastErrorFromMach(machret);
return FALSE;
}
// Create the thread that will listen to the exception for all threads
- CreateRet = pthread_create(&exception_thread, NULL, SEHExceptionThread, NULL);
-
- if ( CreateRet != 0 )
+ int createret = pthread_create(&exception_thread, NULL, SEHExceptionThread, NULL);
+ if (createret != 0)
{
- ERROR("pthread_create failed, error is %d (%s)\n", CreateRet, strerror(CreateRet));
+ ERROR("pthread_create failed, error is %d (%s)\n", createret, strerror(createret));
SetLastError(ERROR_NOT_ENOUGH_MEMORY);
return FALSE;
}
+#ifdef _DEBUG
+ if (NONPAL_TRACE_ENABLED)
+ {
+ CThreadMachExceptionHandlers taskHandlers;
+ machret = task_get_exception_ports(mach_task_self(),
+ PAL_EXC_ALL_MASK,
+ taskHandlers.m_masks,
+ &taskHandlers.m_nPorts,
+ taskHandlers.m_handlers,
+ taskHandlers.m_behaviors,
+ taskHandlers.m_flavors);
+
+ if (machret == KERN_SUCCESS)
+ {
+ NONPAL_TRACE("SEHInitializeMachExceptions: TASK PORT count %d\n", taskHandlers.m_nPorts);
+ for (mach_msg_type_number_t i = 0; i < taskHandlers.m_nPorts; i++)
+ {
+ NONPAL_TRACE("SEHInitializeMachExceptions: TASK PORT mask %08x handler: %08x behavior %08x flavor %u\n",
+ taskHandlers.m_masks[i],
+ taskHandlers.m_handlers[i],
+ taskHandlers.m_behaviors[i],
+ taskHandlers.m_flavors[i]);
+ }
+ }
+ else
+ {
+ NONPAL_TRACE("SEHInitializeMachExceptions: task_get_exception_ports FAILED %d %s\n", machret, mach_error_string(machret));
+ }
+ }
+#endif // _DEBUG
+
#ifndef FEATURE_PAL_SXS
if (!SEHEnableMachExceptions())
{
(handle manager is off, etc). Past that point, we can't avoid crashing on
an exception.
--*/
-void SEHCleanupExceptionPort(void)
+void
+SEHCleanupExceptionPort(void)
{
TRACE("Restoring default exception ports\n");
#ifndef FEATURE_PAL_SXS
s_DebugInitialized = FALSE;
}
-extern "C" void ActivationHandler(CONTEXT* context)
+extern "C"
+void
+ActivationHandler(CONTEXT* context)
{
if (g_activationFunction != NULL)
{
extern "C" void ActivationHandlerWrapper();
extern "C" int ActivationHandlerReturnOffset;
-PAL_ERROR InjectActivationInternal(CPalThread* pThread)
+/*++
+Function :
+ InjectActivationInternal
+
+ Sets up the specified thread to call the ActivationHandler.
+
+Parameters:
+ pThread - PAL thread instance
+
+Return value :
+ PAL_ERROR
+--*/
+PAL_ERROR
+InjectActivationInternal(CPalThread* pThread)
{
PAL_ERROR palError;
/*++
-
-
Module Name:
- exception/machexception.h
+ machexception.h
Abstract:
Private mach exception handling utilities for SEH
-
-
--*/
#ifndef _MACHEXCEPTION_H_
#define HIJACK_ON_SIGNAL 1
-// Process and thread Initialization/Cleanup routines
-BOOL SEHInitializeMachExceptions(void);
-void SEHCleanupExceptionPort (void);
-void MachExceptionInitializeDebug(void);
-
// List of exception types we will be watching for
// NOTE: if you change any of these, you need to adapt s_nMachExceptionPortsMax in thread.hpp
-#define PAL_EXC_ILLEGAL_MASK (EXC_MASK_BAD_INSTRUCTION|EXC_MASK_EMULATION)
-#define PAL_EXC_DEBUGGING_MASK (EXC_MASK_BREAKPOINT|EXC_MASK_SOFTWARE)
-#define PAL_EXC_MANAGED_MASK (EXC_MASK_BAD_ACCESS|EXC_MASK_ARITHMETIC)
-#define PAL_EXC_ALL_MASK (PAL_EXC_ILLEGAL_MASK|PAL_EXC_DEBUGGING_MASK|PAL_EXC_MANAGED_MASK)
+#define PAL_EXC_ILLEGAL_MASK (EXC_MASK_BAD_INSTRUCTION | EXC_MASK_EMULATION)
+#define PAL_EXC_DEBUGGING_MASK (EXC_MASK_BREAKPOINT | EXC_MASK_SOFTWARE)
+#define PAL_EXC_MANAGED_MASK (EXC_MASK_BAD_ACCESS | EXC_MASK_ARITHMETIC)
+#define PAL_EXC_ALL_MASK (PAL_EXC_ILLEGAL_MASK | PAL_EXC_DEBUGGING_MASK | PAL_EXC_MANAGED_MASK)
+// Process and thread initialization/cleanup/context routines
+BOOL SEHInitializeMachExceptions(void);
+void SEHCleanupExceptionPort (void);
+void MachExceptionInitializeDebug(void);
PAL_NORETURN void MachSetThreadContext(CONTEXT *lpContext);
#ifdef __cplusplus
/*++
-
-
Module Name:
- machexception.cpp
+ machmessage.cpp
Abstract:
Abstraction over Mach messages used during exception handling.
-
-
--*/
#include "config.h"
#if HAVE_MACH_EXCEPTIONS
-// The vast majority of Mach calls we make in this module are critical: we cannot recover from failures of
-// these methods (principally because we're handling hardware exceptions in the context of a single dedicated
-// handler thread). The following macro encapsulates checking the return code from Mach methods (we always
-// name this 'machret' for consistency) and emitting some useful data and aborting the process on failure.
-#define MACH_CHECK(_msg) do { \
- if (machret != KERN_SUCCESS) \
- { \
- char _szError[1024]; \
- sprintf(_szError, "%s: %u: %s", __FUNCTION__, __LINE__, _msg); \
- mach_error(_szError, machret); \
- abort(); \
- } \
- } while (false)
-
-// This macro terminates the process with some useful debug info as above, but for the general failure points
-// that have nothing to do with Mach.
-#define FATAL_ERROR(_msg, ...) do { \
- printf("%s: %u: " _msg "\n", __FUNCTION__, __LINE__, ## __VA_ARGS__); \
- abort(); \
- } while (false)
-
-#ifdef _DEBUG
-// Assert macro that doesn't rely on the PAL.
-#define MACHMESSAGE_ASSERT(_expr) do { \
- if (!(_expr)) \
- FATAL_ERROR("ASSERTION FAILURE: %s\n", #_expr); \
- } while (false)
-#else // _DEBUG
-#define MACHMESSAGE_ASSERT(_expr)
-#endif // _DEBUG
-
-
// Construct an empty message. Use Receive() to form a message that can be inspected or SendSetThread(),
// ForwardNotification(), ReplyToNotification() or ForwardReply() to construct a message and sent it.
MachMessage::MachMessage()
ResetMessage();
}
-void MachMessage::InitializeFrom(const MachMessage& source)
-{
- m_fPortsOwned = false;
- ResetMessage();
-
- memcpy(&m_rgMessageBuffer, &source.m_rgMessageBuffer, sizeof(m_rgMessageBuffer));
-}
-
// Listen for the next message on the given port and initialize this class with the contents. The message type
// must match one of the MessageTypes indicated above (or the process will be aborted).
void MachMessage::Receive(mach_port_t hPort)
hPort,
MACH_MSG_TIMEOUT_NONE,
MACH_PORT_NULL);
- MACH_CHECK("mach_msg()");
+ CHECK_MACH("mach_msg()", machret);
// Check it's one of the messages we're expecting.
switch (m_pMessage->header.msgh_id)
{
case SET_THREAD_MESSAGE_ID:
+ case FORWARD_EXCEPTION_MESSAGE_ID:
case EXCEPTION_RAISE_MESSAGE_ID:
case EXCEPTION_RAISE_STATE_MESSAGE_ID:
case EXCEPTION_RAISE_STATE_IDENTITY_MESSAGE_ID:
case EXCEPTION_RAISE_REPLY_64_MESSAGE_ID:
case EXCEPTION_RAISE_STATE_REPLY_64_MESSAGE_ID:
case EXCEPTION_RAISE_STATE_IDENTITY_REPLY_64_MESSAGE_ID:
+ case NOTIFY_SEND_ONCE_MESSAGE_ID:
break;
default:
- FATAL_ERROR("Unsupported message type: %u", m_pMessage->header.msgh_id);
+ NONPAL_RETAIL_ASSERT("Unsupported message type: %u", m_pMessage->header.msgh_id);
}
m_fPortsOwned = true;
return m_pMessage->header.msgh_id == SET_THREAD_MESSAGE_ID;
}
+// Indicates whether the message is a request to forward the exception
+bool MachMessage::IsForwardExceptionRequest()
+{
+ return m_pMessage->header.msgh_id == FORWARD_EXCEPTION_MESSAGE_ID;
+}
+
+// Indicates whether the message is a notification that a send-once message was destroyed by the receiver.
+bool MachMessage::IsSendOnceDestroyedNotify()
+{
+ return m_pMessage->header.msgh_id == NOTIFY_SEND_ONCE_MESSAGE_ID;
+}
+
// Indicates whether the message is a notification of an exception.
bool MachMessage::IsExceptionNotification()
{
{
case SET_THREAD_MESSAGE_ID:
return "SET_THREAD";
+ case FORWARD_EXCEPTION_MESSAGE_ID:
+ return "FORWARD_EXCEPTION";
case EXCEPTION_RAISE_MESSAGE_ID:
return "EXCEPTION_RAISE";
case EXCEPTION_RAISE_REPLY_MESSAGE_ID:
case SET_THREAD_MESSAGE_ID:
m_hThread = m_pMessage->data.set_thread.thread;
break;
+
+ case FORWARD_EXCEPTION_MESSAGE_ID:
+ m_hThread = m_pMessage->data.forward_exception.thread;
+ break;
case EXCEPTION_RAISE_MESSAGE_ID:
m_hThread = m_pMessage->data.raise.thread_port.name;
default:
if (fValidThread)
{
- FATAL_ERROR("Can only get thread from notification message.");
+ NONPAL_RETAIL_ASSERT("Can only get thread from notification message.");
}
break;
}
}
-// Get the properties of a set thread request. Fills in the provided context structure with the context from
-// the message and returns the target thread to which the context should be applied.
+// Get the properties of a set thread or forward exception request. Fills in the provided
+// context structure with the context from the message and returns the target thread to
+// which the context should be applied.
thread_act_t MachMessage::GetThreadContext(CONTEXT *pContext)
{
- if (m_pMessage->header.msgh_id != SET_THREAD_MESSAGE_ID)
- FATAL_ERROR("Unhandled message type for GetThreadContext(): %u", m_pMessage->header.msgh_id);
+ NONPAL_ASSERTE(IsSetThreadRequest());
memcpy(pContext, &m_pMessage->data.set_thread.new_context, sizeof(CONTEXT));
m_hThread = m_pMessage->data.set_thread.thread;
return m_pMessage->data.raise_state_identity_64.exception;
default:
- FATAL_ERROR("Can only get exception from notification message.");
+ NONPAL_RETAIL_ASSERT("Can only get exception from notification message.");
}
}
return m_pMessage->data.raise_state_identity_64.code_count;
default:
- FATAL_ERROR("Can only get exception code count from notification message.");
+ NONPAL_RETAIL_ASSERT("Can only get exception code count from notification message.");
}
}
{
if (iIndex < 0 || iIndex >= GetExceptionCodeCount())
{
- FATAL_ERROR("GetExceptionCode() index out of range.");
+ NONPAL_RETAIL_ASSERT("GetExceptionCode() index out of range.");
}
switch (m_pMessage->header.msgh_id)
{
case EXCEPTION_RAISE_MESSAGE_ID:
- return (int)m_pMessage->data.raise.code[iIndex];
+ return (MACH_EH_TYPE(exception_data_type_t))m_pMessage->data.raise.code[iIndex];
case EXCEPTION_RAISE_64_MESSAGE_ID:
return m_pMessage->data.raise_64.code[iIndex];
case EXCEPTION_RAISE_STATE_MESSAGE_ID:
- return (int)m_pMessage->data.raise_state.code[iIndex];
+ return (MACH_EH_TYPE(exception_data_type_t))m_pMessage->data.raise_state.code[iIndex];
case EXCEPTION_RAISE_STATE_64_MESSAGE_ID:
return m_pMessage->data.raise_state_64.code[iIndex];
case EXCEPTION_RAISE_STATE_IDENTITY_MESSAGE_ID:
- return (int)m_pMessage->data.raise_state_identity.code[iIndex];
+ return (MACH_EH_TYPE(exception_data_type_t))m_pMessage->data.raise_state_identity.code[iIndex];
case EXCEPTION_RAISE_STATE_IDENTITY_64_MESSAGE_ID:
return m_pMessage->data.raise_state_identity_64.code[iIndex];
default:
- FATAL_ERROR("Can only get exception code from notification message.");
+ NONPAL_RETAIL_ASSERT("Can only get exception code from notification message.");
}
}
return m_pMessage->data.raise_state_identity_reply_64.flavor;
default:
- FATAL_ERROR("Unsupported message type: %u", m_pMessage->header.msgh_id);
+ NONPAL_RETAIL_ASSERT("Unsupported message type: %u", m_pMessage->header.msgh_id);
}
}
// doesn't contain a thread state or the flavor of the state in the message doesn't match, the state will be
// fetched directly from the target thread instead (which can be computed implicitly for exception messages or
// passed explicitly for reply messages).
-size_t MachMessage::GetThreadState(thread_state_flavor_t eFlavor, thread_state_t pState, thread_act_t hThread)
+mach_msg_type_number_t MachMessage::GetThreadState(thread_state_flavor_t eFlavor, thread_state_t pState, thread_act_t thread)
{
+ mach_msg_type_number_t count;
kern_return_t machret;
- size_t cbState;
switch (m_pMessage->header.msgh_id)
{
// after (if not we'll fall through and get the correct flavor below).
if (m_pMessage->data.raise_state.flavor == eFlavor)
{
- cbState = m_pMessage->data.raise_state.old_state_count * sizeof(natural_t);
- memcpy(pState,
- m_pMessage->data.raise_state.old_state,
- cbState);
- return cbState;
+ count = m_pMessage->data.raise_state.old_state_count;
+ memcpy(pState, m_pMessage->data.raise_state.old_state, count * sizeof(natural_t));
+ return count;
}
break;
}
// after (if not we'll fall through and get the correct flavor below).
if (m_pMessage->data.raise_state_64.flavor == eFlavor)
{
- cbState = m_pMessage->data.raise_state_64.old_state_count * sizeof(natural_t);
- memcpy(pState,
- m_pMessage->data.raise_state_64.old_state,
- cbState);
- return cbState;
+ count = m_pMessage->data.raise_state_64.old_state_count;
+ memcpy(pState, m_pMessage->data.raise_state_64.old_state, count * sizeof(natural_t));
+ return count;
}
break;
}
// after (if not we'll fall through and get the correct flavor below).
if (m_pMessage->data.raise_state_identity.flavor == eFlavor)
{
- cbState = m_pMessage->data.raise_state_identity.old_state_count * sizeof(natural_t);
- memcpy(pState,
- m_pMessage->data.raise_state_identity.old_state,
- cbState);
- return cbState;
+ count = m_pMessage->data.raise_state_identity.old_state_count;
+ memcpy(pState, m_pMessage->data.raise_state_identity.old_state, count * sizeof(natural_t));
+ return count;
}
break;
}
// after (if not we'll fall through and get the correct flavor below).
if (m_pMessage->data.raise_state_identity_64.flavor == eFlavor)
{
- cbState = m_pMessage->data.raise_state_identity_64.old_state_count * sizeof(natural_t);
- memcpy(pState,
- m_pMessage->data.raise_state_identity_64.old_state,
- cbState);
- return cbState;
+ count = m_pMessage->data.raise_state_identity_64.old_state_count;
+ memcpy(pState, m_pMessage->data.raise_state_identity_64.old_state, count * sizeof(natural_t));
+ return count;
}
break;
}
// after (if not we'll fall through and get the correct flavor below).
if (m_pMessage->data.raise_state_reply.flavor == eFlavor)
{
- cbState = m_pMessage->data.raise_state_reply.new_state_count * sizeof(natural_t);
- memcpy(pState,
- m_pMessage->data.raise_state_reply.new_state,
- cbState);
- return cbState;
+ count = m_pMessage->data.raise_state_reply.new_state_count;
+ memcpy(pState, m_pMessage->data.raise_state_reply.new_state, count * sizeof(natural_t));
+ return count;
}
break;
}
// after (if not we'll fall through and get the correct flavor below).
if (m_pMessage->data.raise_state_reply_64.flavor == eFlavor)
{
- cbState = m_pMessage->data.raise_state_reply_64.new_state_count * sizeof(natural_t);
- memcpy(pState,
- m_pMessage->data.raise_state_reply_64.new_state,
- cbState);
- return cbState;
+ count = m_pMessage->data.raise_state_reply_64.new_state_count;
+ memcpy(pState, m_pMessage->data.raise_state_reply_64.new_state, count * sizeof(natural_t));
+ return count;
}
break;
}
// after (if not we'll fall through and get the correct flavor below).
if (m_pMessage->data.raise_state_identity_reply.flavor == eFlavor)
{
- cbState = m_pMessage->data.raise_state_identity_reply.new_state_count * sizeof(natural_t);
- memcpy(pState,
- m_pMessage->data.raise_state_identity_reply.new_state,
- cbState);
- return cbState;
+ count = m_pMessage->data.raise_state_identity_reply.new_state_count;
+ memcpy(pState, m_pMessage->data.raise_state_identity_reply.new_state, count * sizeof(natural_t));
+ return count;
}
break;
}
// after (if not we'll fall through and get the correct flavor below).
if (m_pMessage->data.raise_state_identity_reply_64.flavor == eFlavor)
{
- cbState = m_pMessage->data.raise_state_identity_reply_64.new_state_count * sizeof(natural_t);
- memcpy(pState,
- m_pMessage->data.raise_state_identity_reply_64.new_state,
- cbState);
- return cbState;
+ count = m_pMessage->data.raise_state_identity_reply_64.new_state_count;
+ memcpy(pState, m_pMessage->data.raise_state_identity_reply_64.new_state, count * sizeof(natural_t));
+ return count;
}
break;
}
default:
- FATAL_ERROR("Unsupported message type for requesting thread state.");
+ NONPAL_RETAIL_ASSERT("Unsupported message type for requesting thread state.");
}
// No state in the message or the flavor didn't match. Get the requested flavor of state directly from the
// thread instead.
- mach_msg_type_number_t iStateCount = THREAD_STATE_MAX;
- machret = thread_get_state(hThread ? hThread : GetThread(), eFlavor, (thread_state_t)pState, &iStateCount);
- MACH_CHECK("thread_get_state()");
+ count = THREAD_STATE_MAX;
+ machret = thread_get_state(thread ? thread : GetThread(), eFlavor, (thread_state_t)pState, &count);
+ CHECK_MACH("thread_get_state()", machret);
+
+ return count;
+}
+
+// Fetch the return code from a reply type message.
+kern_return_t MachMessage::GetReturnCode()
+{
+ switch (m_pMessage->header.msgh_id)
+ {
+ case EXCEPTION_RAISE_REPLY_MESSAGE_ID:
+ return m_pMessage->data.raise_reply.ret;
+
+ case EXCEPTION_RAISE_REPLY_64_MESSAGE_ID:
+ return m_pMessage->data.raise_reply_64.ret;
+
+ case EXCEPTION_RAISE_STATE_REPLY_MESSAGE_ID:
+ return m_pMessage->data.raise_state_reply.ret;
+
+ case EXCEPTION_RAISE_STATE_REPLY_64_MESSAGE_ID:
+ return m_pMessage->data.raise_state_reply_64.ret;
- return iStateCount * sizeof(natural_t);
+ case EXCEPTION_RAISE_STATE_IDENTITY_REPLY_MESSAGE_ID:
+ return m_pMessage->data.raise_state_identity_reply.ret;
+
+ case EXCEPTION_RAISE_STATE_IDENTITY_REPLY_64_MESSAGE_ID:
+ return m_pMessage->data.raise_state_identity_reply_64.ret;
+
+ default:
+ NONPAL_RETAIL_ASSERT("Unsupported message type: %u", m_pMessage->header.msgh_id);
+ }
}
// Initialize and send a request to set the register context of a particular thread.
-void MachMessage::SendSetThread(mach_port_t hServerPort, thread_act_t hThread, CONTEXT *pContext)
+void MachMessage::SendSetThread(mach_port_t hServerPort, CONTEXT *pContext)
{
kern_return_t machret;
// set above).
InitFixedFields();
- // Initialize type-specific fields.
- m_pMessage->data.set_thread.thread = hThread;
+ // Initialize type-specific fields. The receiving end is responsible for deallocating the thread port.
+ m_pMessage->data.set_thread.thread = mach_thread_self();
memcpy(&m_pMessage->data.set_thread.new_context, pContext, sizeof(CONTEXT));
// Initialize header fields.
MACH_PORT_NULL,
MACH_MSG_TIMEOUT_NONE,
MACH_PORT_NULL);
- MACH_CHECK("mach_msg()");
+ CHECK_MACH("mach_msg()", machret);
// Erase any stale data. (This may not finish executing; nothing is needed to be freed here.)
ResetMessage();
}
-// Initialize the message to represent a forwarded version of the given
-// exception notification message and send that message to the chain-back handler previously registered for
-// the exception type being notified. The new message takes account of the fact that the target handler may
-// not have requested the same notification behavior or flavor as our handler. A new Mach port is created to
-// receive the reply, and this port is returned to the caller. Clean up the message afterwards.
-void MachMessage::ForwardNotification(CorUnix::MachExceptionHandler *pHandler, MachMessage *pNotification)
+void MachMessage::SendForwardException(mach_port_t hServerPort, MachExceptionInfo *pExceptionInfo, CPalThread *ppalThread)
+{
+ kern_return_t machret;
+
+ // Set the message type.
+ m_pMessage->header.msgh_id = FORWARD_EXCEPTION_MESSAGE_ID;
+
+ // Initialize the fields that don't need any further input (this depends on the message type having been
+ // set above).
+ InitFixedFields();
+
+ // Initialize type-specific fields. The receiving end is responsible for deallocating the thread port.
+ m_pMessage->data.forward_exception.thread = mach_thread_self();
+ m_pMessage->data.forward_exception.ppalThread = ppalThread;
+ memcpy(&m_pMessage->data.forward_exception.exception_info, pExceptionInfo, sizeof(MachExceptionInfo));
+
+ // Initialize header fields.
+ m_pMessage->header.msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND, 0);
+ m_pMessage->header.msgh_remote_port = hServerPort; // Destination port
+ m_pMessage->header.msgh_local_port = MACH_PORT_NULL; // We expect no reply
+
+ // Set the message header size field based on the contents of the message (call this function after all
+ // other fields have been initialized).
+ InitMessageSize();
+
+ // Send the formatted message.
+ machret = mach_msg((mach_msg_header_t*)m_pMessage,
+ MACH_SEND_MSG | MACH_MSG_OPTION_NONE,
+ m_pMessage->header.msgh_size,
+ 0,
+ MACH_PORT_NULL,
+ MACH_MSG_TIMEOUT_NONE,
+ MACH_PORT_NULL);
+ CHECK_MACH("mach_msg()", machret);
+
+ // Erase any stale data.
+ ResetMessage();
+}
+
+// Returns the pal thread instance for the forward exception message
+CPalThread *MachMessage::GetPalThread()
+{
+ NONPAL_ASSERTE(IsForwardExceptionRequest());
+ return m_pMessage->data.forward_exception.ppalThread;
+}
+
+MachExceptionInfo *MachMessage::GetExceptionInfo()
+{
+ NONPAL_ASSERTE(IsForwardExceptionRequest());
+ return &m_pMessage->data.forward_exception.exception_info;
+}
+
+// Initialize the message to represent a forwarded version of the given exception notification message and
+// send that message to the chain-back handler previously registered for the exception type being notified.
+// The new message takes account of the fact that the target handler may not have requested the same notification
+// behavior or flavor as our handler. A new Mach port is created to receive the reply, and this port is returned
+// to the caller. Clean up the message afterwards.
+void MachMessage::ForwardNotification(MachExceptionHandler *pHandler, MachMessage& message)
{
kern_return_t machret;
// the two messages may be in different formats (e.g. RAISE vs RAISE_STATE). We silently drop data that is
// not needed in the outgoing message and synthesize any required data that is not present in the incoming
// message.
- SetThread(pNotification->GetThread());
- SetException(pNotification->GetException());
+ SetThread(message.GetThread());
+ SetException(message.GetException());
- int cCodes = pNotification->GetExceptionCodeCount();
+ int cCodes = message.GetExceptionCodeCount();
SetExceptionCodeCount(cCodes);
for (int i = 0; i < cCodes; i++)
- SetExceptionCode(i, pNotification->GetExceptionCode(i));
-
- NONPAL_TRACE("ForwardNotification: handler thread flavor %04x\n", pHandler->m_flavor);
+ SetExceptionCode(i, message.GetExceptionCode(i));
// Don't bother fetching thread state unless the destination actually requires it.
if (pHandler->m_flavor != THREAD_STATE_NONE)
{
- thread_state_data_t sThreadState;
- size_t cbState = pNotification->GetThreadState(pHandler->m_flavor, (thread_state_t)&sThreadState);
- SetThreadState(pHandler->m_flavor, (thread_state_t)&sThreadState, cbState);
+ thread_state_data_t threadState;
+ mach_msg_type_number_t count = message.GetThreadState(pHandler->m_flavor, (thread_state_t)&threadState);
+ SetThreadState(pHandler->m_flavor, (thread_state_t)&threadState, count);
}
// Initialize header fields.
- m_pMessage->header.msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND, MACH_MSG_TYPE_MAKE_SEND_ONCE);
- m_pMessage->header.msgh_remote_port = pHandler->m_handler; // Forward to here
- m_pMessage->header.msgh_local_port = pNotification->GetLocalPort(); // The reply will come here
+ m_pMessage->header.msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND, MACH_MSG_TYPE_MOVE_SEND_ONCE);
+ m_pMessage->header.msgh_remote_port = pHandler->m_handler; // Forward to here
+ m_pMessage->header.msgh_local_port = message.GetRemotePort(); // The reply will come here
// Set the message header size field based on the contents of the message (call this function after all
// other fields have been initialized).
MACH_PORT_NULL,
MACH_MSG_TIMEOUT_NONE,
MACH_PORT_NULL);
- MACH_CHECK("mach_msg()");
+ CHECK_MACH("mach_msg()", machret);
// Erase any stale data.
ResetMessage();
}
-// Initialize the message to represent a reply to the given exception
-// notification and send that reply back to the original sender of the notification. This is used when our
-// handler handles the exception rather than forwarding it to a chain-back handler.
+// Initialize the message to represent a reply to the given exception notification message
+// and send that reply back to the original sender of the notification. This is used when
+// our handler handles the exception rather than forwarding it to a chain-back handler.
// Clean up the message afterwards.
-void MachMessage::ReplyToNotification(MachMessage *pNotification, kern_return_t eResult)
+void MachMessage::ReplyToNotification(MachMessage& message, kern_return_t eResult)
{
kern_return_t machret;
// Set the message type.
- m_pMessage->header.msgh_id = MapNotificationToReplyType(pNotification->m_pMessage->header.msgh_id);
+ m_pMessage->header.msgh_id = MapNotificationToReplyType(message.m_pMessage->header.msgh_id);
// Initialize the fields that don't need any further input (this depends on the message type having been
// set above).
SetReturnCode(eResult);
- thread_state_flavor_t eNotificationFlavor = pNotification->GetThreadStateFlavor();
+ thread_state_flavor_t eNotificationFlavor = message.GetThreadStateFlavor();
if (eNotificationFlavor != THREAD_STATE_NONE)
{
// If the reply requires a thread state be sure to get it from the thread directly rather than the
// notification message (handling the exception is likely to have changed the thread state).
- thread_state_data_t sThreadState;
- mach_msg_type_number_t iStateCount = THREAD_STATE_MAX;
- machret = thread_get_state(pNotification->GetThread(),
- eNotificationFlavor,
- (thread_state_t)&sThreadState,
- &iStateCount);
- MACH_CHECK("thread_get_state()");
-
- SetThreadState(eNotificationFlavor, (thread_state_t)&sThreadState, iStateCount * sizeof(natural_t));
+ thread_state_data_t threadState;
+ mach_msg_type_number_t count = THREAD_STATE_MAX;
+ machret = thread_get_state(message.GetThread(), eNotificationFlavor, (thread_state_t)&threadState, &count);
+
+ SetThreadState(eNotificationFlavor, (thread_state_t)&threadState, count);
}
// Initialize header fields.
m_pMessage->header.msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_MOVE_SEND_ONCE, 0);
- m_pMessage->header.msgh_remote_port = pNotification->GetRemotePort(); // Reply goes back to sender
- m_pMessage->header.msgh_local_port = 0; // No reply to this expected
+ m_pMessage->header.msgh_remote_port = message.GetRemotePort(); // Reply goes back to sender
+ m_pMessage->header.msgh_local_port = 0; // No reply to this expected
// Set the message header size field based on the contents of the message (call this function after all
// other fields have been initialized).
MACH_PORT_NULL,
MACH_MSG_TIMEOUT_NONE,
MACH_PORT_NULL);
- MACH_CHECK("mach_msg()");
+ CHECK_MACH("mach_msg()", machret);
// Erase any stale data.
ResetMessage();
if (m_hThread != MACH_PORT_NULL)
{
machret = mach_port_deallocate(mach_task_self(), m_hThread);
- MACH_CHECK("mach_port_deallocate(m_hThread)");
+ CHECK_MACH("mach_port_deallocate(m_hThread)", machret);
}
if (m_hTask != MACH_PORT_NULL)
{
machret = mach_port_deallocate(mach_task_self(), m_hTask);
- MACH_CHECK("mach_port_deallocate(m_hTask)");
+ CHECK_MACH("mach_port_deallocate(m_hTask)", machret);
}
}
case SET_THREAD_MESSAGE_ID:
break;
+ case FORWARD_EXCEPTION_MESSAGE_ID:
+ break;
+
case EXCEPTION_RAISE_MESSAGE_ID:
m_pMessage->data.raise.msgh_body.msgh_descriptor_count = 0;
m_pMessage->data.raise.ndr = NDR_record;
break;
default:
- FATAL_ERROR("Unhandled message type: %u", m_pMessage->header.msgh_id);
+ NONPAL_RETAIL_ASSERT("Unhandled message type: %u", m_pMessage->header.msgh_id);
}
m_pMessage->header.msgh_reserved = 0;
m_pMessage->header.msgh_size = sizeof(mach_msg_header_t) + sizeof(set_thread_request_t);
break;
+ case FORWARD_EXCEPTION_MESSAGE_ID:
+ m_pMessage->header.msgh_size = sizeof(mach_msg_header_t) + sizeof(forward_exception_request_t);
+ break;
+
case EXCEPTION_RAISE_MESSAGE_ID:
m_pMessage->header.msgh_size = sizeof(mach_msg_header_t) + sizeof(exception_raise_notification_t);
break;
break;
default:
- FATAL_ERROR("Unhandled message type: %u", m_pMessage->header.msgh_id);
+ NONPAL_RETAIL_ASSERT("Unhandled message type: %u", m_pMessage->header.msgh_id);
}
}
// thread).
switch (eFlavor)
{
- case x86_THREAD_STATE32:
#ifdef _X86_
- targetSP = ((x86_thread_state32_t*)pState)->esp;
-#elif defined(_AMD64_)
- targetSP = ((x86_thread_state32_t*)pState)->__esp;
-#else
-#error Unexpected architecture.
-#endif
- break;
-
case x86_THREAD_STATE:
-#ifdef _X86_
targetSP = ((x86_thread_state_t*)pState)->uts.ts32.esp;
+ break;
+
+ case x86_THREAD_STATE32:
+ targetSP = ((x86_thread_state32_t*)pState)->esp;
+ break;
#elif defined(_AMD64_)
+ case x86_THREAD_STATE:
targetSP = ((x86_thread_state_t*)pState)->uts.ts64.__rsp;
-#else
-#error Unexpected architecture.
-#endif
break;
-#ifdef _AMD64_
case x86_THREAD_STATE64:
targetSP = ((x86_thread_state64_t*)pState)->__rsp;
break;
-#endif // _AMD64_
-
+#else
+#error Unexpected architecture.
+#endif
default:
- FATAL_ERROR("Unhandled thread state flavor: %u", eFlavor);
+ NONPAL_RETAIL_ASSERT("Unhandled thread state flavor: %u", eFlavor);
}
// Capture the list of threads in the current task. Obviously this changes asynchronously to us, but that
// doesn't matter since we know the thread we're after is suspended in the kernel and can't go anywhere.
mach_msg_type_number_t cThreads;
thread_act_t *pThreads;
- kern_return_t machret = task_threads(mach_task_self(),
- &pThreads,
- &cThreads);
- MACH_CHECK("task_threads()");
+ kern_return_t machret = task_threads(mach_task_self(), &pThreads, &cThreads);
+ CHECK_MACH("task_threads()", machret);
// Iterate through each of the threads in the list.
for (mach_msg_type_number_t i = 0; i < cThreads; i++)
{
// Get the general register state of each thread.
-#ifdef _X86_
- x86_thread_state32_t sThreadState;
- const thread_state_flavor_t sThreadStateFlavor = x86_THREAD_STATE32;
-#elif defined(_AMD64_)
- x86_thread_state64_t sThreadState;
- const thread_state_flavor_t sThreadStateFlavor = x86_THREAD_STATE64;
-#else
-#error Unexpected architecture.
-#endif
- mach_msg_type_number_t cThreadState = sizeof(sThreadState) / sizeof(natural_t);
- if (thread_get_state(pThreads[i],
- sThreadStateFlavor,
- (thread_state_t)&sThreadState,
- &cThreadState) == KERN_SUCCESS)
+ x86_thread_state_t threadState;
+ mach_msg_type_number_t count = x86_THREAD_STATE_COUNT;
+ machret = thread_get_state(pThreads[i], x86_THREAD_STATE, (thread_state_t)&threadState, &count);
+ if (machret == KERN_SUCCESS)
{
// If a thread has the same SP as our target it should be the same thread (otherwise we have two
// threads sharing the same stack which is very bad). Conversely the thread we're looking for is
// suspended in the kernel so its SP should not change. We should always be able to find an exact
// match as a result.
#ifdef _X86_
- if (sThreadState.esp == targetSP)
+ if (threadState.uts.ts32.esp == targetSP)
#elif defined(_AMD64_)
- if (sThreadState.__rsp == targetSP)
+ if (threadState.uts.ts64.__rsp == targetSP)
#else
#error Unexpected architecture.
#endif
{
- thread_act_t hThread = pThreads[i];
+ thread_act_t thread = pThreads[i];
// Increment the refcount; the thread is a "send" right.
- machret = mach_port_mod_refs(mach_task_self(), hThread, MACH_PORT_RIGHT_SEND, 1);
- MACH_CHECK("mach_port_mod_refs()");
+ machret = mach_port_mod_refs(mach_task_self(), thread, MACH_PORT_RIGHT_SEND, 1);
+ CHECK_MACH("mach_port_mod_refs()", machret);
// Deallocate the thread list now we're done with it.
- machret = vm_deallocate(mach_task_self(),
- (vm_address_t)pThreads,
- cThreads * sizeof(thread_act_t));
- MACH_CHECK("vm_deallocate()");
+ machret = vm_deallocate(mach_task_self(), (vm_address_t)pThreads, cThreads * sizeof(thread_act_t));
+ CHECK_MACH("vm_deallocate()", machret);
// Return the thread we found.
- return hThread;
+ return thread;
}
}
}
// If we got here no thread matched. That shouldn't be possible.
- FATAL_ERROR("Failed to locate thread from state.");
+ NONPAL_RETAIL_ASSERT("Failed to locate thread from state.");
}
// Transform a exception handler behavior type into the corresponding Mach message ID for the notification.
case MACH_EXCEPTION_CODES|EXCEPTION_STATE_IDENTITY:
return EXCEPTION_RAISE_STATE_IDENTITY_64_MESSAGE_ID;
default:
- FATAL_ERROR("Unsupported exception behavior type: %u", eBehavior);
+ NONPAL_RETAIL_ASSERT("Unsupported exception behavior type: %u", eBehavior);
}
}
case EXCEPTION_RAISE_STATE_IDENTITY_64_MESSAGE_ID:
return EXCEPTION_RAISE_STATE_IDENTITY_REPLY_64_MESSAGE_ID;
default:
- FATAL_ERROR("Unsupported message type: %u", eNotificationType);
- }
-}
-
-// Fetch the return code from a reply type message.
-kern_return_t MachMessage::GetReturnCode()
-{
- switch (m_pMessage->header.msgh_id)
- {
- case EXCEPTION_RAISE_REPLY_MESSAGE_ID:
- return m_pMessage->data.raise_reply.ret;
-
- case EXCEPTION_RAISE_REPLY_64_MESSAGE_ID:
- return m_pMessage->data.raise_reply_64.ret;
-
- case EXCEPTION_RAISE_STATE_REPLY_MESSAGE_ID:
- return m_pMessage->data.raise_state_reply.ret;
-
- case EXCEPTION_RAISE_STATE_REPLY_64_MESSAGE_ID:
- return m_pMessage->data.raise_state_reply_64.ret;
-
- case EXCEPTION_RAISE_STATE_IDENTITY_REPLY_MESSAGE_ID:
- return m_pMessage->data.raise_state_identity_reply.ret;
-
- case EXCEPTION_RAISE_STATE_IDENTITY_REPLY_64_MESSAGE_ID:
- return m_pMessage->data.raise_state_identity_reply_64.ret;
-
- default:
- FATAL_ERROR("Unsupported message type: %u", m_pMessage->header.msgh_id);
+ NONPAL_RETAIL_ASSERT("Unsupported message type: %u", eNotificationType);
}
}
// Set faulting thread in an exception notification message.
-void MachMessage::SetThread(thread_act_t hThread)
+void MachMessage::SetThread(thread_act_t thread)
{
- bool fSet;
+ bool fSet = false;
switch (m_pMessage->header.msgh_id)
{
case EXCEPTION_RAISE_MESSAGE_ID:
- m_pMessage->data.raise.thread_port.name = hThread;
+ m_pMessage->data.raise.thread_port.name = thread;
m_pMessage->data.raise.thread_port.pad1 = 0;
m_pMessage->data.raise.thread_port.pad2 = 0;
m_pMessage->data.raise.thread_port.disposition = MACH_MSG_TYPE_COPY_SEND;
break;
case EXCEPTION_RAISE_64_MESSAGE_ID:
- m_pMessage->data.raise_64.thread_port.name = hThread;
+ m_pMessage->data.raise_64.thread_port.name = thread;
m_pMessage->data.raise_64.thread_port.pad1 = 0;
m_pMessage->data.raise_64.thread_port.pad2 = 0;
m_pMessage->data.raise_64.thread_port.disposition = MACH_MSG_TYPE_COPY_SEND;
case EXCEPTION_RAISE_STATE_MESSAGE_ID:
case EXCEPTION_RAISE_STATE_64_MESSAGE_ID:
// No thread field in RAISE_STATE messages.
- fSet = false;
break;
case EXCEPTION_RAISE_STATE_IDENTITY_MESSAGE_ID:
- m_pMessage->data.raise_state_identity.thread_port.name = hThread;
+ m_pMessage->data.raise_state_identity.thread_port.name = thread;
m_pMessage->data.raise_state_identity.thread_port.pad1 = 0;
m_pMessage->data.raise_state_identity.thread_port.pad2 = 0;
m_pMessage->data.raise_state_identity.thread_port.disposition = MACH_MSG_TYPE_COPY_SEND;
break;
case EXCEPTION_RAISE_STATE_IDENTITY_64_MESSAGE_ID:
- m_pMessage->data.raise_state_identity_64.thread_port.name = hThread;
+ m_pMessage->data.raise_state_identity_64.thread_port.name = thread;
m_pMessage->data.raise_state_identity_64.thread_port.pad1 = 0;
m_pMessage->data.raise_state_identity_64.thread_port.pad2 = 0;
m_pMessage->data.raise_state_identity_64.thread_port.disposition = MACH_MSG_TYPE_COPY_SEND;
break;
default:
- FATAL_ERROR("Unsupported message type: %u", m_pMessage->header.msgh_id);
- fSet = false;
+ NONPAL_RETAIL_ASSERT("Unsupported message type: %u", m_pMessage->header.msgh_id);
}
if (fSet)
{
// Addref the thread port.
kern_return_t machret;
- machret = mach_port_mod_refs(mach_task_self(), hThread, MACH_PORT_RIGHT_SEND, 1);
+ machret = mach_port_mod_refs(mach_task_self(), thread, MACH_PORT_RIGHT_SEND, 1);
}
}
break;
default:
- FATAL_ERROR("Unsupported message type: %u", m_pMessage->header.msgh_id);
+ NONPAL_RETAIL_ASSERT("Unsupported message type: %u", m_pMessage->header.msgh_id);
}
}
break;
default:
- FATAL_ERROR("Unsupported message type: %u", m_pMessage->header.msgh_id);
+ NONPAL_RETAIL_ASSERT("Unsupported message type: %u", m_pMessage->header.msgh_id);
}
}
void MachMessage::SetExceptionCode(int iIndex, MACH_EH_TYPE(exception_data_type_t) iCode)
{
if (iIndex < 0 || iIndex > 1)
- FATAL_ERROR("Exception code index out of range");
+ NONPAL_RETAIL_ASSERT("Exception code index out of range");
// Note that although the 64-bit message variants support 64-bit exception sub-codes the CoreCLR only
// supports 32-bit processes. We should never see the upper 32-bits containing a non-zero value therefore.
break;
default:
- FATAL_ERROR("Unsupported message type: %u", m_pMessage->header.msgh_id);
+ NONPAL_RETAIL_ASSERT("Unsupported message type: %u", m_pMessage->header.msgh_id);
}
}
break;
default:
- FATAL_ERROR("Unsupported message type: %u", m_pMessage->header.msgh_id);
+ NONPAL_RETAIL_ASSERT("Unsupported message type: %u", m_pMessage->header.msgh_id);
}
}
// Set faulting thread register state in an exception notification or reply message.
-void MachMessage::SetThreadState(thread_state_flavor_t eFlavor, thread_state_t pState, size_t cbState)
+void MachMessage::SetThreadState(thread_state_flavor_t eFlavor, thread_state_t pState, mach_msg_type_number_t count)
{
switch (m_pMessage->header.msgh_id)
{
case EXCEPTION_RAISE_STATE_MESSAGE_ID:
m_pMessage->data.raise_state.flavor = eFlavor;
- m_pMessage->data.raise_state.old_state_count = cbState / sizeof(natural_t);
- memcpy(m_pMessage->data.raise_state.old_state, pState, cbState);
+ m_pMessage->data.raise_state.old_state_count = count;
+ memcpy(m_pMessage->data.raise_state.old_state, pState, count * sizeof(natural_t));
break;
case EXCEPTION_RAISE_STATE_64_MESSAGE_ID:
m_pMessage->data.raise_state_64.flavor = eFlavor;
- m_pMessage->data.raise_state_64.old_state_count = cbState / sizeof(natural_t);
- memcpy(m_pMessage->data.raise_state_64.old_state, pState, cbState);
+ m_pMessage->data.raise_state_64.old_state_count = count;
+ memcpy(m_pMessage->data.raise_state_64.old_state, pState, count * sizeof(natural_t));
break;
case EXCEPTION_RAISE_STATE_IDENTITY_MESSAGE_ID:
m_pMessage->data.raise_state_identity.flavor = eFlavor;
- m_pMessage->data.raise_state_identity.old_state_count = cbState / sizeof(natural_t);
- memcpy(m_pMessage->data.raise_state_identity.old_state, pState, cbState);
+ m_pMessage->data.raise_state_identity.old_state_count = count;
+ memcpy(m_pMessage->data.raise_state_identity.old_state, pState, count * sizeof(natural_t));
break;
case EXCEPTION_RAISE_STATE_IDENTITY_64_MESSAGE_ID:
m_pMessage->data.raise_state_identity_64.flavor = eFlavor;
- m_pMessage->data.raise_state_identity_64.old_state_count = cbState / sizeof(natural_t);
- memcpy(m_pMessage->data.raise_state_identity_64.old_state, pState, cbState);
+ m_pMessage->data.raise_state_identity_64.old_state_count = count;
+ memcpy(m_pMessage->data.raise_state_identity_64.old_state, pState, count * sizeof(natural_t));
break;
case EXCEPTION_RAISE_STATE_REPLY_MESSAGE_ID:
m_pMessage->data.raise_state_reply.flavor = eFlavor;
- m_pMessage->data.raise_state_reply.new_state_count = cbState / sizeof(natural_t);
- memcpy(m_pMessage->data.raise_state_reply.new_state, pState, cbState);
+ m_pMessage->data.raise_state_reply.new_state_count = count;
+ memcpy(m_pMessage->data.raise_state_reply.new_state, pState, count * sizeof(natural_t));
break;
case EXCEPTION_RAISE_STATE_REPLY_64_MESSAGE_ID:
m_pMessage->data.raise_state_reply_64.flavor = eFlavor;
- m_pMessage->data.raise_state_reply_64.new_state_count = cbState / sizeof(natural_t);
- memcpy(m_pMessage->data.raise_state_reply_64.new_state, pState, cbState);
+ m_pMessage->data.raise_state_reply_64.new_state_count = count;
+ memcpy(m_pMessage->data.raise_state_reply_64.new_state, pState, count * sizeof(natural_t));
break;
case EXCEPTION_RAISE_STATE_IDENTITY_REPLY_MESSAGE_ID:
m_pMessage->data.raise_state_identity_reply.flavor = eFlavor;
- m_pMessage->data.raise_state_identity_reply.new_state_count = cbState / sizeof(natural_t);
- memcpy(m_pMessage->data.raise_state_identity_reply.new_state, pState, cbState);
+ m_pMessage->data.raise_state_identity_reply.new_state_count = count;
+ memcpy(m_pMessage->data.raise_state_identity_reply.new_state, pState, count * sizeof(natural_t));
break;
case EXCEPTION_RAISE_STATE_IDENTITY_REPLY_64_MESSAGE_ID:
m_pMessage->data.raise_state_identity_reply_64.flavor = eFlavor;
- m_pMessage->data.raise_state_identity_reply_64.new_state_count = cbState / sizeof(natural_t);
- memcpy(m_pMessage->data.raise_state_identity_reply_64.new_state, pState, cbState);
+ m_pMessage->data.raise_state_identity_reply_64.new_state_count = count;
+ memcpy(m_pMessage->data.raise_state_identity_reply_64.new_state, pState, count * sizeof(natural_t));
break;
default:
- FATAL_ERROR("Unsupported message type: %u", m_pMessage->header.msgh_id);
+ NONPAL_RETAIL_ASSERT("Unsupported message type: %u", m_pMessage->header.msgh_id);
}
}
/*++
-
-
Module Name:
- machexception.cpp
+ machmessage.h
Abstract:
Abstraction over Mach messages used during exception handling.
-
-
--*/
#include <mach/mach.h>
#include <mach/mach_error.h>
#include <mach/thread_status.h>
+using namespace CorUnix;
+
#if HAVE_MACH_EXCEPTIONS
#if defined(_AMD64_)
-// Constant (exception behavior modifier) defined only in the 10.5 SDK. This is OR'd with one of the standard
-// behavior codes (EXCEPTION_STATE_IDENTITY etc.) to modify the format of the message sent to use 64-bit
-// exception sub-codes instead of the default 32-bit ones.
-#ifndef MACH_EXCEPTION_CODES
-#define MACH_EXCEPTION_CODES 0x80000000
-#endif
-
-typedef int64_t mach_exception_data_type_t;
-typedef mach_exception_data_type_t *mach_exception_data_t;
-typedef mach_exception_data_type_t mach_exception_code_t;
-typedef mach_exception_data_type_t mach_exception_subcode_t;
-
#define MACH_EH_TYPE(x) mach_##x
#else
#define MACH_EH_TYPE(x) x
#endif // defined(_AMD64_)
+// The vast majority of Mach calls we make in this module are critical: we cannot recover from failures of
+// these methods (principally because we're handling hardware exceptions in the context of a single dedicated
+// handler thread). The following macro encapsulates checking the return code from Mach methods and emitting
+// some useful data and aborting the process on failure.
+#define CHECK_MACH(_msg, machret) do { \
+ if (machret != KERN_SUCCESS) \
+ { \
+ char _szError[1024]; \
+ sprintf(_szError, "%s: %u: %s", __FUNCTION__, __LINE__, _msg); \
+ mach_error(_szError, machret); \
+ abort(); \
+ } \
+ } while (false)
+
+// This macro terminates the process with some useful debug info as above, but for the general failure points
+// that have nothing to do with Mach.
+#define NONPAL_RETAIL_ASSERT(_msg, ...) do { \
+ printf("%s: %u: " _msg "\n", __FUNCTION__, __LINE__, ## __VA_ARGS__); \
+ abort(); \
+ } while (false)
+
+#define NONPAL_RETAIL_ASSERTE(_expr) do { \
+ if (!(_expr)) \
+ NONPAL_RETAIL_ASSERT("ASSERT: %s\n", #_expr); \
+ } while (false)
+
#ifdef _DEBUG
+#define NONPAL_TRACE_ENABLED getenv("NONPAL_TRACING")
+
+#define NONPAL_ASSERT(_msg, ...) NONPAL_RETAIL_ASSERT(_msg, __VA_ARGS__)
+
+// Assert macro that doesn't rely on the PAL.
+#define NONPAL_ASSERTE(_expr) do { \
+ if (!(_expr)) \
+ NONPAL_RETAIL_ASSERT("ASSERT: %s\n", #_expr); \
+ } while (false)
+
// Debug-only output with printf-style formatting.
#define NONPAL_TRACE(_format, ...) do { \
- if (getenv("NONPAL_TRACING")) printf("NONPAL_TRACE: " _format, ## __VA_ARGS__); \
+ if (NONPAL_TRACE_ENABLED) printf("NONPAL_TRACE: " _format, ## __VA_ARGS__); \
} while (false)
+
#else // _DEBUG
+
+#define NONPAL_TRACE_ENABLED false
+#define NONPAL_ASSERT(_msg, ...)
+#define NONPAL_ASSERTE(_expr)
#define NONPAL_TRACE(_format, ...)
+
#endif // _DEBUG
-struct MachExceptionHandler;
+class MachMessage;
+
+// Contains all the exception and thread state information needed to forward the exception.
+struct MachExceptionInfo
+{
+ exception_type_t ExceptionType;
+ mach_msg_type_number_t SubcodeCount;
+ MACH_EH_TYPE(exception_data_type_t) Subcodes[2];
+ x86_thread_state_t ThreadState;
+ x86_float_state_t FloatState;
+ x86_debug_state_t DebugState;
+
+ MachExceptionInfo(mach_port_t thread, MachMessage& message);
+ void RestoreState(mach_port_t thread);
+};
// Abstraction of a subset of Mach message types. Provides accessors that hide the subtle differences in the
// message layout of similar message types.
enum MessageType
{
SET_THREAD_MESSAGE_ID = 1,
+ FORWARD_EXCEPTION_MESSAGE_ID = 2,
NOTIFY_SEND_ONCE_MESSAGE_ID = 71,
EXCEPTION_RAISE_MESSAGE_ID = 2401,
EXCEPTION_RAISE_STATE_MESSAGE_ID = 2402,
// ForwardNotification() or ReplyToNotification() to construct a message and sent it.
MachMessage();
- // Initializes this message from another
- void InitializeFrom(const MachMessage& source);
-
// Listen for the next message on the given port and initialize this class with the contents. The message
// type must match one of the MessageTypes indicated above (or the process will be aborted).
void Receive(mach_port_t hPort);
// Indicate whether a received message belongs to a particular semantic class.
bool IsSetThreadRequest(); // Message is a request to set the context of a particular thread
+ bool IsForwardExceptionRequest(); // Message is a request to forward the exception
+ bool IsSendOnceDestroyedNotify(); // Message is a notification that a send-once message was destroyed by the receiver
bool IsExceptionNotification(); // Message is a notification of an exception
bool IsExceptionReply(); // Message is a reply to the notification of an exception
- bool IsSendOnceDestroyedNotify(); // Message is a notification that a send-once message was destroyed by
- // the receiver
// Get properties of a received message header.
MessageType GetMessageType(); // The message type
// from the message and returns the target thread to which the context should be applied.
thread_act_t GetThreadContext(CONTEXT *pContext);
+ // Returns the pal thread instance for the forward exception message
+ CPalThread *GetPalThread();
+
+ // Returns the exception info from the forward exception message
+ MachExceptionInfo *GetExceptionInfo();
+
// Get properties of the type-specific portion of the message. The following properties are supported by
// exception notification messages only.
thread_act_t GetThread(); // Get the faulting thread
// message doesn't contain a thread state or the flavor of the state in the message doesn't match, the
// state will be fetched directly from the target thread instead (which can be computed implicitly for
// exception messages or passed explicitly for reply messages).
- size_t GetThreadState(thread_state_flavor_t eFlavor, thread_state_t pState, thread_act_t hThread = NULL);
+ mach_msg_type_number_t GetThreadState(thread_state_flavor_t eFlavor, thread_state_t pState, thread_act_t thread = NULL);
+
+ // Fetch the return code from a reply type message.
+ kern_return_t GetReturnCode();
// Initialize and send a request to set the register context of a particular thread.
- void SendSetThread(mach_port_t hServerPort, thread_act_t hThread, CONTEXT *pContext);
+ void SendSetThread(mach_port_t hServerPort, CONTEXT *pContext);
+
+ // Initialize and send a request to forward the exception message to the notification thread
+ void SendForwardException(mach_port_t hServerPort, MachExceptionInfo *pExceptionInfo, CPalThread *ppalThread);
// Initialize the message (overwriting any previous content) to represent a forwarded version of the given
// exception notification message and send that message to the chain-back handler previously registered
// for the exception type being notified. The new message takes account of the fact that the target
- // handler may not have requested the same notification behavior or flavor as our handler. Blocks until
- // the reply is received.
- void ForwardNotification(CorUnix::MachExceptionHandler *pHandler, MachMessage *pNotification);
+ // handler may not have requested the same notification behavior or flavor as our handler.
+ void ForwardNotification(MachExceptionHandler *pHandler, MachMessage& message);
// Initialize the message (overwriting any previous content) to represent a reply to the given exception
// notification and send that reply back to the original sender of the notification. This is used when our
// handler handles the exception rather than forwarding it to a chain-back handler.
- void ReplyToNotification(MachMessage *pNotification, kern_return_t eResult);
+ void ReplyToNotification(MachMessage& message, kern_return_t eResult);
private:
// The maximum size in bytes of any Mach message we can send or receive. Calculating an exact size for
CONTEXT new_context;
};
+ // Request to forward the exception notification
+ // FORWARD_EXCEPTION_MESSAGE_ID
+ struct forward_exception_request_t
+ {
+ thread_act_t thread;
+ CPalThread *ppalThread;
+ MachExceptionInfo exception_info;
+ };
+
+#pragma pack(4)
+
// EXCEPTION_RAISE_MESSAGE_ID
struct exception_raise_notification_t
{
NDR_record_t ndr;
exception_type_t exception;
mach_msg_type_number_t code_count;
- integer_t code[2];
+ exception_data_type_t code[2];
};
// EXCEPTION_RAISE_REPLY_MESSAGE_ID
NDR_record_t ndr;
exception_type_t exception;
mach_msg_type_number_t code_count;
- int64_t code[2];
+ mach_exception_data_type_t code[2];
};
// EXCEPTION_RAISE_REPLY_64_MESSAGE_ID
NDR_record_t ndr;
exception_type_t exception;
mach_msg_type_number_t code_count;
- integer_t code[2];
+ exception_data_type_t code[2];
thread_state_flavor_t flavor;
mach_msg_type_number_t old_state_count;
natural_t old_state[THREAD_STATE_MAX];
NDR_record_t ndr;
exception_type_t exception;
mach_msg_type_number_t code_count;
- int64_t code[2];
+ mach_exception_data_type_t code[2];
thread_state_flavor_t flavor;
mach_msg_type_number_t old_state_count;
natural_t old_state[THREAD_STATE_MAX];
NDR_record_t ndr;
exception_type_t exception;
mach_msg_type_number_t code_count;
- integer_t code[2];
+ exception_data_type_t code[2];
thread_state_flavor_t flavor;
mach_msg_type_number_t old_state_count;
natural_t old_state[THREAD_STATE_MAX];
NDR_record_t ndr;
exception_type_t exception;
mach_msg_type_number_t code_count;
- int64_t code[2];
+ mach_exception_data_type_t code[2];
thread_state_flavor_t flavor;
mach_msg_type_number_t old_state_count;
natural_t old_state[THREAD_STATE_MAX];
natural_t new_state[THREAD_STATE_MAX];
};
+#pragma pack()
+
// All the above messages are sent with a standard Mach header prepended. This structure unifies the
// message formats.
struct mach_message_t
union
{
set_thread_request_t set_thread;
+ forward_exception_request_t forward_exception;
exception_raise_notification_t raise;
exception_raise_state_notification_t raise_state;
exception_raise_state_identity_notification_t raise_state_identity;
// Transform a Mach message ID for an exception notification into the corresponding ID for the reply.
mach_msg_id_t MapNotificationToReplyType(mach_msg_id_t eNotificationType);
- // Fetch the return code from a reply type message.
- kern_return_t GetReturnCode();
-
// The following methods initialize fields on the message prior to transmission. Each is valid for either
// notification, replies or both. If a particular setter is defined for replies, say, then it will be a
// no-op for any replies which don't contain that field. This makes transforming between notifications and
// those operations that make sense will do any work).
// Defined for notifications:
- void SetThread(thread_act_t hThread);
+ void SetThread(thread_act_t thread);
void SetException(exception_type_t eException);
void SetExceptionCodeCount(int cCodes);
void SetExceptionCode(int iIndex, MACH_EH_TYPE(exception_data_type_t) iCode);
void SetReturnCode(kern_return_t eReturnCode);
// Defined for both notifications and replies.
- void SetThreadState(thread_state_flavor_t eFlavor, thread_state_t pState, size_t cbState);
+ void SetThreadState(thread_state_flavor_t eFlavor, thread_state_t pState, mach_msg_type_number_t count);
// Maximally sized buffer for the message to be received into or transmitted out of this class.
unsigned char m_rgMessageBuffer[kcbMaxMessageSize];
#ifdef FEATURE_PAL_SXS
#if HAVE_MACH_EXCEPTIONS
- // Structure used to record all Mach exception handlers registered on a given thread at a specific point
- // in time.
- struct CThreadMachExceptionHandlerNode
- {
- // Maximum number of exception ports we hook. Must be the count
- // of all bits set in the exception masks defined in machexception.h.
- static const int s_nPortsMax = 6;
-
- // Saved exception ports, exactly as returned by
- // thread_swap_exception_ports.
- int m_nPorts;
- exception_mask_t m_masks[s_nPortsMax];
- exception_handler_t m_handlers[s_nPortsMax];
- exception_behavior_t m_behaviors[s_nPortsMax];
- thread_state_flavor_t m_flavors[s_nPortsMax];
-
- CThreadMachExceptionHandlerNode() : m_nPorts(-1) {}
- };
// Structure used to return data about a single handler to a caller.
struct MachExceptionHandler
thread_state_flavor_t m_flavor;
};
- // Class abstracting previousy registered Mach exception handlers for a thread.
- class CThreadMachExceptionHandlers
+ // Class abstracting previously registered Mach exception handlers for a thread.
+ struct CThreadMachExceptionHandlers
{
public:
- // Returns a pointer to the handler node that should be initialized next. The first time this is
- // called for a thread the bottom node will be returned. Thereafter the top node will be returned.
- // Also returns the Mach exception port that should be registered.
- CThreadMachExceptionHandlerNode *GetNodeForInitialization();
-
- // Returns a pointer to the handler node for cleanup. This will always be the bottom node. This isn't
- // really the right algorithm (because there isn't one). There are lots of reasonable scenarios where
- // we will break chaining by removing our handler from a thread (by registering two handler ports we
- // can support a lot more chaining scenarios but we can't pull the same sort of trick when
- // unregistering, in particular we have two sets of chain back handlers and no way to reach into other
- // components and alter what their chain-back information is).
- CThreadMachExceptionHandlerNode *GetNodeForCleanup() { return &m_node; }
+ // Maximum number of exception ports we hook. Must be the count
+ // of all bits set in the exception masks defined in machexception.h.
+ static const int s_nPortsMax = 6;
+
+ // Saved exception ports, exactly as returned by
+ // thread_swap_exception_ports.
+ mach_msg_type_number_t m_nPorts;
+ exception_mask_t m_masks[s_nPortsMax];
+ exception_handler_t m_handlers[s_nPortsMax];
+ exception_behavior_t m_behaviors[s_nPortsMax];
+ thread_state_flavor_t m_flavors[s_nPortsMax];
+
+ CThreadMachExceptionHandlers() :
+ m_nPorts(-1)
+ {
+ }
// Get handler details for a given type of exception. If successful the structure pointed at by
// pHandler is filled in and true is returned. Otherwise false is returned.
private:
// Look for a handler for the given exception within the given handler node. Return its index if
// successful or -1 otherwise.
- int GetIndexOfHandler(exception_mask_t bmExceptionMask, CThreadMachExceptionHandlerNode *pNode);
-
- CThreadMachExceptionHandlerNode m_node;
+ int GetIndexOfHandler(exception_mask_t bmExceptionMask);
};
#endif // HAVE_MACH_EXCEPTIONS
#endif // FEATURE_PAL_SXS
extern mach_port_t s_ExceptionPort;
-// Returns a pointer to the handler node that should be initialized next. The first time this is called for a
-// thread the bottom node will be returned. Thereafter the top node will be returned. Also returns the Mach
-// exception port that should be registered.
-CorUnix::CThreadMachExceptionHandlerNode *CorUnix::CThreadMachExceptionHandlers::GetNodeForInitialization()
-{
- return &m_node;
-}
-
// Get handler details for a given type of exception. If successful the structure pointed at by pHandler is
// filled in and true is returned. Otherwise false is returned.
bool CorUnix::CThreadMachExceptionHandlers::GetHandler(exception_type_t eException, CorUnix::MachExceptionHandler *pHandler)
{
exception_mask_t bmExceptionMask = (1 << eException);
- CThreadMachExceptionHandlerNode *pNode = &m_node;
- int idxHandler = GetIndexOfHandler(bmExceptionMask, pNode);
+ int idxHandler = GetIndexOfHandler(bmExceptionMask);
// Did we find a handler?
if (idxHandler == -1)
return false;
// Found one, so initialize the output structure with the details.
- pHandler->m_mask = pNode->m_masks[idxHandler];
- pHandler->m_handler = pNode->m_handlers[idxHandler];
- pHandler->m_behavior = pNode->m_behaviors[idxHandler];
- pHandler->m_flavor = pNode->m_flavors[idxHandler];
+ pHandler->m_mask = m_masks[idxHandler];
+ pHandler->m_handler = m_handlers[idxHandler];
+ pHandler->m_behavior = m_behaviors[idxHandler];
+ pHandler->m_flavor = m_flavors[idxHandler];
return true;
}
// Look for a handler for the given exception within the given handler node. Return its index if successful or
// -1 otherwise.
-int CorUnix::CThreadMachExceptionHandlers::GetIndexOfHandler(exception_mask_t bmExceptionMask,
- CorUnix::CThreadMachExceptionHandlerNode *pNode)
+int CorUnix::CThreadMachExceptionHandlers::GetIndexOfHandler(exception_mask_t bmExceptionMask)
{
// Check all handler entries for one handling the exception mask.
- for (int i = 0; i < pNode->m_nPorts; i++)
+ for (mach_msg_type_number_t i = 0; i < m_nPorts; i++)
{
- if (pNode->m_masks[i] & bmExceptionMask && // Entry covers this exception type
- pNode->m_handlers[i] != MACH_PORT_NULL && // And the handler isn't null
- pNode->m_handlers[i] != s_ExceptionPort) // And the handler isn't ourselves
- {
+ // Entry covers this exception type and the handler isn't null
+ if (m_masks[i] & bmExceptionMask && m_handlers[i] != MACH_PORT_NULL)
+ {
+ _ASSERTE(m_handlers[i] != s_ExceptionPort);
+
// One more check; has the target handler port become dead?
mach_port_type_t ePortType;
- if (mach_port_type(mach_task_self(), pNode->m_handlers[i], &ePortType) == KERN_SUCCESS &&
- !(ePortType & MACH_PORT_TYPE_DEAD_NAME))
+ if (mach_port_type(mach_task_self(), m_handlers[i], &ePortType) == KERN_SUCCESS && !(ePortType & MACH_PORT_TYPE_DEAD_NAME))
{
// Got a matching entry.
return i;
PAL_TRY(VOID*, unused, NULL)
{
- volatile int* p = (volatile int *)0x11; /* invalid pointer */
+ volatile int* p = (volatile int *)0x11; // Invalid pointer
- bTry = TRUE; /* indicate we hit the PAL_TRY block */
- *p = 13; /* causes an access violation exception */
+ bTry = TRUE; // Indicate we hit the PAL_TRY block
+ *p = 1; // Causes an access violation exception
Fail("ERROR: code was executed after the access violation.\n");
}
Fail("ERROR: PAL_EXCEPT was hit without PAL_TRY being hit.\n");
}
- bExcept = TRUE; /* indicate we hit the PAL_EXCEPT block */
+ // Validate that the faulting address is correct; the contents of "p" (0x11).
+ if (ex.ExceptionRecord.ExceptionInformation[1] != 0x11)
+ {
+ Fail("ERROR: PAL_EXCEPT ExceptionInformation[1] != 0x11\n");
+ }
+
+ bExcept = TRUE; // Indicate we hit the PAL_EXCEPT block
}
PAL_ENDTRY;
Trace("ERROR: the code in the PAL_EXCEPT block was not executed.\n");
}
- /* did we hit all the code blocks? */
+ // Did we hit all the code blocks?
if(!bTry || !bExcept)
{
Fail("DllTest1 FAILED\n");
}
Trace("DLLTest1 PASSED\n");
-
return PASS;
}
PAL_TRY(VOID*, unused, NULL)
{
- volatile int* p = (volatile int *)0x22; /* invalid pointer */
+ volatile int* p = (volatile int *)0x22; // Invalid pointer
- bTry = TRUE; /* indicate we hit the PAL_TRY block */
- *p = 13; /* causes an access violation exception */
+ bTry = TRUE; // Indicate we hit the PAL_TRY block
+ *p = 2; // Causes an access violation exception
Fail("ERROR: code was executed after the access violation.\n");
}
Fail("ERROR: PAL_EXCEPT was hit without PAL_TRY being hit.\n");
}
- bExcept = TRUE; /* indicate we hit the PAL_EXCEPT block */
+ // Validate that the faulting address is correct; the contents of "p" (0x22).
+ if (ex.ExceptionRecord.ExceptionInformation[1] != 0x22)
+ {
+ Fail("ERROR: PAL_EXCEPT ExceptionInformation[1] != 0x22\n");
+ }
+
+ bExcept = TRUE; // Indicate we hit the PAL_EXCEPT block
}
PAL_ENDTRY;
Trace("ERROR: the code in the PAL_EXCEPT block was not executed.\n");
}
- /* did we hit all the code blocks? */
+ // Did we hit all the code blocks?
if(!bTry || !bExcept)
{
Fail("DllTest2 FAILED\n");
}
Trace("DLLTest2 PASSED\n");
-
return PASS;
}
**
**===================================================================*/
+#include <stdio.h>
+#include <signal.h>
+#include <errno.h>
+#include <sys/ucontext.h>
+#include <sys/utsname.h>
+#include <unistd.h>
+
+enum
+{
+ PASS = 0,
+ FAIL = 1
+};
+
extern "C" int InitializeDllTest1();
extern "C" int InitializeDllTest2();
extern "C" int DllTest1();
extern "C" int DllTest2();
-int main(int argc, char *argv[])
+#if !defined(__FreeBSD__) && !defined(__NetBSD__)
+
+bool bSignal = false;
+bool bCatch = false;
+bool bHandler = false;
+
+void sigsegv_handler(int code, siginfo_t *siginfo, void *context)
+{
+ printf("pal_sxs test1: signal handler called\n");
+ bHandler = true; // Mark that the signal handler was executed
+
+ if (!bSignal)
+ {
+ printf("ERROR: executed signal handler NOT from try/catch\n");
+ _exit(FAIL);
+ }
+
+ // Validate that the faulting address is correct; the contents of "p" (0x22).
+ if (siginfo->si_addr != (void *)0x33)
+ {
+ printf("ERROR: signal handler faulting address != 0x33\n");
+ _exit(FAIL);
+ }
+
+ // Unmask signal so we can receive it again
+ sigset_t signal_set;
+ sigemptyset(&signal_set);
+ sigaddset(&signal_set, SIGSEGV);
+ if (-1 == sigprocmask(SIG_UNBLOCK, &signal_set, NULL))
+ {
+ printf("ERROR: sigprocmask failed; error is %d\n", errno);
+ _exit(FAIL);
+ }
+
+ printf("Signal chaining PASSED\n");
+ _exit(PASS);
+}
+
+#endif
+
+int __cdecl main(int argc, char *argv[])
{
#if !defined(__FreeBSD__) && !defined(__NetBSD__)
+ struct sigaction newAction;
+ struct sigaction oldAction;
+ newAction.sa_flags = SA_SIGINFO | SA_RESTART;
+ newAction.sa_handler = NULL;
+ newAction.sa_sigaction = sigsegv_handler;
+ sigemptyset(&newAction.sa_mask);
+
+ if (-1 == sigaction(SIGSEGV, &newAction, &oldAction))
+ {
+ printf("ERROR: sigaction failed; error is %d\n", errno);
+ return FAIL;
+ }
+
+ printf("PAL_SXS test1 SIGSEGV handler %p\n", oldAction.sa_sigaction);
+
if (0 != InitializeDllTest1())
{
- return 1;
+ return FAIL;
}
if (0 != InitializeDllTest2())
{
- return 1;
+ return FAIL;
}
+ // Test catching exceptions in other PAL instances
DllTest2();
DllTest1();
DllTest2();
+
+ if (bHandler)
+ {
+ printf("ERROR: signal handler called by PAL sxs tests\n");
+ return FAIL;
+ }
+
+ printf("Starting PAL_SXS test1 signal chaining\n");
+
+ bSignal = true;
+
+ volatile int* p = (volatile int *)0x33; // Invalid pointer
+ *p = 3; // Causes an access violation exception
+
+ printf("ERROR: code was executed after the access violation.\n");
+ return FAIL;
#endif
- return 0;
+ return PASS;
}