IPC — Mach Message

Ali Pourhadi
3 min readSep 27, 2019

--

If you like me want to have a deep understanding of things follow me in this tutorial coz I am gonna talk about low-level IPC in OSX/iOS.

I have published a tutorial for XPC-IPC. We succeeded to make a simple service and send a message from a client and get the expected response. Now we want to see what’s happening under the hood. What is happening when you send a request.

Before starting I have to say that XPC is not the way of IPC communication. We also have MIG/CFPort/DO ( it’s a bit old ). The mechanism of IPC call for all is almost the same. I just made CFPort sample for myself and it wasn’t that much complicated. If you do research you will find that the base of all of them is the same. They all are using Mach Messages in order to communicate.

Why XNU?
Perhaps you know that OSX and iOS are based on XNU. So technically Mach Messages will be created on XNU layer. Mach IPC is derived from the Mach microkernel.

There is some concept that knowing them will help you to continue understanding how Mach Message works: Tasks, Threads, Port. I recommend you to have a look into apple documents for Mach Overview.

How is a Mach Message looks like?

To see Mach Message’s structure lets take a look into a document:

There are three important parts that we have to take a look into them.

typedef	struct 
{
mach_msg_bits_t msgh_bits;
mach_msg_size_t msgh_size;
mach_port_t msgh_remote_port;
mach_port_t msgh_local_port;
mach_msg_size_t msgh_reserved;
mach_msg_id_t msgh_id;
} mach_msg_header_t;
typedef struct
{
mach_msg_size_t msgh_descriptor_count;
} mach_msg_body_t;
typedef struct
{
mach_msg_header_t header;
mach_msg_body_t body;
} mach_msg_base_t;

Let’s begin header:

  • msgh_bits: It’s a bitmap who specifies some properties of the message like the message is simple or complex ( will talk about it later )
  • msgh_size: Size of the message which includes header and body
  • msgh_remote_port: Port right of where we are going to send the message.
  • msgh_local_port: Specifies an auxiliary port right, which is conventionally used as a reply port by the recipient of the message
  • msgh_reserved -> msgh_voucher_port : The msgh_reserved field has become msgh_voucher_port with the introduction of vouchers. Vouchers are used to pass arbitrary data in messages over key-value pairs
  • msgh_id: 32-bit field message-id.

In the message body, we see something about “descriptor”

typedef union
{
mach_msg_port_descriptor_t port;
mach_msg_ool_descriptor_t out_of_line;
mach_msg_ool_ports_descriptor_t ool_ports;
mach_msg_type_descriptor_t type;
} mach_msg_descriptor_t;
  • mach_msg_port_descriptor_t : Embedding a port into the message
  • mach_msg_ool_descriptor_t: Attaching ool data to the message
  • mach_msg_ool_ports_descriptor_t: Adding OOL ports array in a message
  • mach_msg_type_descriptor_t: is gonna be of these types.
#define MACH_MSG_PORT_DESCRIPTOR 		0
#define MACH_MSG_OOL_DESCRIPTOR 1
#define MACH_MSG_OOL_PORTS_DESCRIPTOR 2
#define MACH_MSG_OOL_VOLATILE_DESCRIPTOR 3

Based on names and former descriptions we the names almost clear about what they are doing except MACH_MSG_OOL_VOLATILE_DESCRIPTOR which is used for Sending volatile data in a message. Could be very handy.

If the message is complex, it contains count of type descriptors and the type descriptors themselves (mach_msg_descriptor_t). Then the size of the message must be specified in bytes, and includes the message header, descriptor count, descriptors,and inline data.

now let’s make our hands dirty. I going to create a very simple application to test sending a very simple Mach Message. basically, it’s just a header without content.

In our test app, at the beginning we create a port to establish communication, Then simply change message id for each message that we sending and listening to messages.

That’s it. it seems complicated at the start what when you jump into it’s not that hard. I tried to explain things as an iOS Dev point of view.

--

--