summaryrefslogtreecommitdiff
path: root/src/pal/src/exception/machmessage.h
diff options
context:
space:
mode:
Diffstat (limited to 'src/pal/src/exception/machmessage.h')
-rw-r--r--src/pal/src/exception/machmessage.h441
1 files changed, 441 insertions, 0 deletions
diff --git a/src/pal/src/exception/machmessage.h b/src/pal/src/exception/machmessage.h
new file mode 100644
index 0000000000..244396cd35
--- /dev/null
+++ b/src/pal/src/exception/machmessage.h
@@ -0,0 +1,441 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+/*++
+
+Module Name:
+
+ 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_)
+#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 EnvironGetenv("NONPAL_TRACING", /* copyValue */ false)
+
+#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 (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
+
+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.
+class MachMessage
+{
+public:
+ // The message types handled by this class. The values are the actual type codes set in the Mach message
+ // header.
+ 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,
+ EXCEPTION_RAISE_STATE_IDENTITY_MESSAGE_ID = 2403,
+ EXCEPTION_RAISE_64_MESSAGE_ID = 2405,
+ EXCEPTION_RAISE_STATE_64_MESSAGE_ID = 2406,
+ EXCEPTION_RAISE_STATE_IDENTITY_64_MESSAGE_ID = 2407,
+ EXCEPTION_RAISE_REPLY_MESSAGE_ID = 2501,
+ EXCEPTION_RAISE_STATE_REPLY_MESSAGE_ID = 2502,
+ EXCEPTION_RAISE_STATE_IDENTITY_REPLY_MESSAGE_ID = 2503,
+ EXCEPTION_RAISE_REPLY_64_MESSAGE_ID = 2505,
+ EXCEPTION_RAISE_STATE_REPLY_64_MESSAGE_ID = 2506,
+ EXCEPTION_RAISE_STATE_IDENTITY_REPLY_64_MESSAGE_ID = 2507
+ };
+
+ // Construct an empty message. Use Receive() to form a message that can be inspected or SendSetThread(),
+ // ForwardNotification() or ReplyToNotification() to construct a message and sent it.
+ MachMessage();
+
+ // 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
+
+ // Get properties of a received message header.
+ MessageType GetMessageType(); // The message type
+ const char *GetMessageTypeName(); // An ASCII representation of the message type for logging purposes
+ mach_port_t GetLocalPort(); // The destination port the message was sent to
+ mach_port_t GetRemotePort(); // The source port the message came from (if a reply is expected)
+
+ // 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.
+ 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
+ exception_type_t GetException(); // Get the exception type (e.g. EXC_BAD_ACCESS)
+ int GetExceptionCodeCount(); // Get the number of exception sub-codes
+ MACH_EH_TYPE(exception_data_type_t) GetExceptionCode(int iIndex); // Get the exception sub-code at the given index
+
+ // Fetch the thread state flavor from a notification or reply message (return THREAD_STATE_NONE for the
+ // messages that don't contain a thread state).
+ thread_state_flavor_t GetThreadStateFlavor();
+
+ // Get the thread state with the given flavor from the exception or exception reply message. If the
+ // 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).
+ 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, 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.
+ 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& 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
+ // this is non trivial (basically because of the security trailers that Mach appends) but the current
+ // value has proven to be more than enough so far.
+ static const size_t kcbMaxMessageSize = 1500;
+
+ // The following are structures describing the formats of the Mach messages we understand.
+
+ // Request to set the register context on a particular thread.
+ // SET_THREAD_MESSAGE_ID
+ struct set_thread_request_t
+ {
+ thread_act_t thread;
+ 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
+ {
+ mach_msg_body_t msgh_body;
+ mach_msg_port_descriptor_t thread_port;
+ mach_msg_port_descriptor_t task_port;
+ NDR_record_t ndr;
+ exception_type_t exception;
+ mach_msg_type_number_t code_count;
+ exception_data_type_t code[2];
+ };
+
+ // EXCEPTION_RAISE_REPLY_MESSAGE_ID
+ struct exception_raise_reply_t
+ {
+ NDR_record_t ndr;
+ kern_return_t ret;
+ };
+
+ // EXCEPTION_RAISE_64_MESSAGE_ID
+ struct exception_raise_notification_64_t
+ {
+ mach_msg_body_t msgh_body;
+ mach_msg_port_descriptor_t thread_port;
+ mach_msg_port_descriptor_t task_port;
+ NDR_record_t ndr;
+ exception_type_t exception;
+ mach_msg_type_number_t code_count;
+ mach_exception_data_type_t code[2];
+ };
+
+ // EXCEPTION_RAISE_REPLY_64_MESSAGE_ID
+ struct exception_raise_reply_64_t
+ {
+ NDR_record_t ndr;
+ kern_return_t ret;
+ };
+
+ // EXCEPTION_RAISE_STATE_MESSAGE_ID
+ struct exception_raise_state_notification_t
+ {
+ NDR_record_t ndr;
+ exception_type_t exception;
+ mach_msg_type_number_t code_count;
+ 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];
+ };
+
+ // EXCEPTION_RAISE_STATE_REPLY_MESSAGE_ID
+ struct exception_raise_state_reply_t
+ {
+ NDR_record_t ndr;
+ kern_return_t ret;
+ thread_state_flavor_t flavor;
+ mach_msg_type_number_t new_state_count;
+ natural_t new_state[THREAD_STATE_MAX];
+ };
+
+ // EXCEPTION_RAISE_STATE_64_MESSAGE_ID
+ struct exception_raise_state_notification_64_t
+ {
+ NDR_record_t ndr;
+ exception_type_t exception;
+ mach_msg_type_number_t code_count;
+ 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];
+ };
+
+ // EXCEPTION_RAISE_STATE_REPLY_64_MESSAGE_ID
+ struct exception_raise_state_reply_64_t
+ {
+ NDR_record_t ndr;
+ kern_return_t ret;
+ thread_state_flavor_t flavor;
+ mach_msg_type_number_t new_state_count;
+ natural_t new_state[THREAD_STATE_MAX];
+ };
+
+ // EXCEPTION_RAISE_STATE_IDENTITY_MESSAGE_ID
+ struct exception_raise_state_identity_notification_t
+ {
+ mach_msg_body_t msgh_body;
+ mach_msg_port_descriptor_t thread_port;
+ mach_msg_port_descriptor_t task_port;
+ NDR_record_t ndr;
+ exception_type_t exception;
+ mach_msg_type_number_t code_count;
+ 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];
+ };
+
+ // EXCEPTION_RAISE_STATE_IDENTITY_REPLY_MESSAGE_ID
+ struct exception_raise_state_identity_reply_t
+ {
+ NDR_record_t ndr;
+ kern_return_t ret;
+ thread_state_flavor_t flavor;
+ mach_msg_type_number_t new_state_count;
+ natural_t new_state[THREAD_STATE_MAX];
+ };
+
+ // EXCEPTION_RAISE_STATE_IDENTITY_64_MESSAGE_ID
+ struct exception_raise_state_identity_notification_64_t
+ {
+ mach_msg_body_t msgh_body;
+ mach_msg_port_descriptor_t thread_port;
+ mach_msg_port_descriptor_t task_port;
+ NDR_record_t ndr;
+ exception_type_t exception;
+ mach_msg_type_number_t code_count;
+ 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];
+ };
+
+ // EXCEPTION_RAISE_STATE_IDENTITY_REPLY_64_MESSAGE_ID
+ struct exception_raise_state_identity_reply_64_t
+ {
+ NDR_record_t ndr;
+ kern_return_t ret;
+ thread_state_flavor_t flavor;
+ mach_msg_type_number_t new_state_count;
+ 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
+ {
+ mach_msg_header_t header;
+ 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;
+ exception_raise_notification_64_t raise_64;
+ exception_raise_state_notification_64_t raise_state_64;
+ exception_raise_state_identity_notification_64_t raise_state_identity_64;
+ exception_raise_reply_t raise_reply;
+ exception_raise_state_reply_t raise_state_reply;
+ exception_raise_state_identity_reply_t raise_state_identity_reply;
+ exception_raise_reply_64_t raise_reply_64;
+ exception_raise_state_reply_64_t raise_state_reply_64;
+ exception_raise_state_identity_reply_64_t raise_state_identity_reply_64;
+ } data;
+ } __attribute__((packed));;
+
+ // Re-initializes this data structure (to the same state as default construction, containing no message).
+ void ResetMessage();
+
+ // Initialize those fields of a message that are invariant. This method expects that the msgh_id field has
+ // been filled in prior to the call so it can determine which non-header fields to initialize.
+ void InitFixedFields();
+
+ // Initialize the size field of the message header (msgh_size) based on the message type and other fields.
+ // This should be called after all other fields have been initialized.
+ void InitMessageSize();
+
+ // Do the work of getting ports from the message.
+ // * fCalculate -- calculate the thread port if the message did not contain it.
+ // * fValidate -- failfast if the message was not one expected to have a (calculable) thread port.
+ void GetPorts(bool fCalculate, bool fValidThread);
+
+ // Given a thread's register context, locate and return the Mach port representing that thread. Only the
+ // x86_THREAD_STATE and x86_THREAD_STATE32 state flavors are supported.
+ thread_act_t GetThreadFromState(thread_state_flavor_t eFlavor, thread_state_t pState);
+
+ // Transform a exception handler behavior type into the corresponding Mach message ID for the
+ // notification.
+ mach_msg_id_t MapBehaviorToNotificationType(exception_behavior_t eBehavior);
+
+ // 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);
+
+ // 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
+ // replies of different types simpler (we can copy a super-set of all fields between the two, but only
+ // those operations that make sense will do any work).
+
+ // Defined for notifications:
+ 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);
+
+ // Defined for replies:
+ void SetReturnCode(kern_return_t eReturnCode);
+
+ // Defined for both notifications and replies.
+ 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];
+
+ // Initialized by ResetMessage() to point to the buffer above. Gives a typed view of the encapsulated Mach
+ // message.
+ mach_message_t *m_pMessage;
+
+ // Cached value of GetThread() or MACH_PORT_NULL if that has not been computed yet.
+ thread_act_t m_hThread;
+
+ // Cached value of the task port or MACH_PORT_NULL if the message doesn't have one.
+ mach_port_t m_hTask;
+
+ // Considered whether we are responsible for the deallocation of the ports in
+ // this message. It is true for messages we receive, and false for messages we send.
+ bool m_fPortsOwned;
+};
+
+#endif // HAVE_MACH_EXCEPTIONS