blob: dc67b5d246bfc5ca43ab52421d7c928a0a124e0c [file]
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license 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_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, ...)
#else // _DEBUG
#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