use crate::CrashContext;
use mach2::{
bootstrap, kern_return::KERN_SUCCESS, mach_port, message as msg, port, task,
traps::mach_task_self,
};
pub use mach2::{kern_return::kern_return_t, message::mach_msg_return_t};
use std::{ffi::CStr, time::Duration};
unsafe extern "C" {
pub fn pid_for_task(task: port::mach_port_name_t, pid: *mut i32) -> kern_return_t;
}
#[repr(C, packed(4))]
struct MachMsgPortDescriptor {
name: u32,
__pad1: u32,
__pad2: u16,
disposition: u8,
__type: u8,
}
impl MachMsgPortDescriptor {
fn new(name: u32, disposition: u32) -> Self {
Self {
name,
disposition: disposition as u8,
__pad1: 0,
__pad2: 0,
__type: msg::MACH_MSG_PORT_DESCRIPTOR as u8,
}
}
}
#[repr(C, packed(4))]
struct MachMsgBody {
pub descriptor_count: u32,
}
#[repr(C, packed(4))]
pub struct MachMsgTrailer {
pub kind: u32,
pub size: u32,
}
#[repr(C, packed(4))]
struct MachMsgHeader {
pub bits: u32,
pub size: u32,
pub remote_port: u32,
pub local_port: u32,
pub voucher_port: u32,
pub id: u32,
}
#[repr(C, packed(4))]
struct CrashContextMessage {
head: MachMsgHeader,
body: MachMsgBody,
task: MachMsgPortDescriptor,
crash_thread: MachMsgPortDescriptor,
handler_thread: MachMsgPortDescriptor,
ack_port: MachMsgPortDescriptor,
flags: u32,
exception_kind: u32,
exception_code: u64,
exception_subcode: u64,
trailer: MachMsgTrailer,
}
const FLAG_HAS_EXCEPTION: u32 = 0x1;
const FLAG_HAS_SUBCODE: u32 = 0x2;
#[repr(C, packed(4))]
struct AcknowledgementMessage {
head: MachMsgHeader,
result: u32,
}
#[derive(Copy, Clone, Debug)]
pub enum Error {
Kernel(kern_return_t),
Message(mach_msg_return_t),
}
impl std::error::Error for Error {}
use std::fmt;
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:?}", self)
}
}
macro_rules! kern {
($call:expr) => {{
let res = $call;
if res != KERN_SUCCESS {
return Err(Error::Kernel(res));
}
}};
}
macro_rules! msg {
($call:expr) => {{
let res = $call;
if res != msg::MACH_MSG_SUCCESS {
return Err(Error::Message(res));
}
}};
}
pub struct Client {
port: port::mach_port_t,
}
impl Client {
pub fn create(name: &CStr) -> Result<Self, Error> {
unsafe {
let mut task_bootstrap_port = 0;
kern!(task::task_get_special_port(
mach_task_self(),
task::TASK_BOOTSTRAP_PORT,
&mut task_bootstrap_port
));
let mut port = 0;
kern!(bootstrap::bootstrap_look_up(
task_bootstrap_port,
name.as_ptr(),
&mut port
));
Ok(Self { port })
}
}
pub fn send_crash_context(
&self,
ctx: &CrashContext,
send_timeout: Option<Duration>,
receive_timeout: Option<Duration>,
) -> Result<Option<u32>, Error> {
unsafe {
let mut ack_port = AckReceiver::new()?;
let (flags, exception_kind, exception_code, exception_subcode) =
if let Some(exc) = ctx.exception {
(
FLAG_HAS_EXCEPTION
| if exc.subcode.is_some() {
FLAG_HAS_SUBCODE
} else {
0
},
exc.kind,
exc.code,
exc.subcode.unwrap_or_default(),
)
} else {
(0, 0, 0, 0)
};
let mut msg = CrashContextMessage {
head: MachMsgHeader {
bits: msg::MACH_MSG_TYPE_COPY_SEND | msg::MACH_MSGH_BITS_COMPLEX,
size: std::mem::size_of::<CrashContextMessage>() as u32 - 8,
remote_port: self.port,
local_port: port::MACH_PORT_NULL,
voucher_port: port::MACH_PORT_NULL,
id: 0,
},
body: MachMsgBody {
descriptor_count: 4,
},
task: MachMsgPortDescriptor::new(ctx.task, msg::MACH_MSG_TYPE_COPY_SEND),
crash_thread: MachMsgPortDescriptor::new(ctx.thread, msg::MACH_MSG_TYPE_COPY_SEND),
handler_thread: MachMsgPortDescriptor::new(
ctx.handler_thread,
msg::MACH_MSG_TYPE_COPY_SEND,
),
ack_port: MachMsgPortDescriptor::new(ack_port.port, msg::MACH_MSG_TYPE_COPY_SEND),
flags,
exception_kind,
exception_code,
exception_subcode,
trailer: MachMsgTrailer { kind: 0, size: 8 },
};
msg!(msg::mach_msg(
((&mut msg.head) as *mut MachMsgHeader).cast(),
msg::MACH_SEND_MSG | msg::MACH_SEND_TIMEOUT,
msg.head.size,
0,
port::MACH_PORT_NULL,
send_timeout
.map(|st| st.as_millis() as u32)
.unwrap_or_default(),
port::MACH_PORT_NULL
));
match ack_port.recv_ack(receive_timeout) {
Ok(result) => Ok(Some(result)),
Err(Error::Message(msg::MACH_RCV_TIMED_OUT)) => Ok(None),
Err(e) => Err(e),
}
}
}
}
pub struct ReceivedCrashContext {
pub crash_context: CrashContext,
pub acker: Acknowledger,
pub pid: u32,
}
pub struct Server {
port: port::mach_port_t,
}
impl Server {
pub fn create(name: &CStr) -> Result<Self, Error> {
unsafe {
let mut task_bootstrap_port = 0;
kern!(task::task_get_special_port(
mach_task_self(),
task::TASK_BOOTSTRAP_PORT,
&mut task_bootstrap_port
));
let mut port = 0;
kern!(bootstrap::bootstrap_check_in(
task_bootstrap_port,
name.as_ptr(),
&mut port,
));
Ok(Self { port })
}
}
pub fn try_recv_crash_context(
&mut self,
timeout: Option<Duration>,
) -> Result<Option<ReceivedCrashContext>, Error> {
unsafe {
let mut crash_ctx_msg: CrashContextMessage = std::mem::zeroed();
crash_ctx_msg.head.local_port = self.port;
let ret = msg::mach_msg(
((&mut crash_ctx_msg.head) as *mut MachMsgHeader).cast(),
msg::MACH_RCV_MSG | msg::MACH_RCV_TIMEOUT,
0,
std::mem::size_of::<CrashContextMessage>() as u32,
self.port,
timeout.map(|t| t.as_millis() as u32).unwrap_or_default(),
port::MACH_PORT_NULL,
);
if ret == msg::MACH_RCV_TIMED_OUT {
return Ok(None);
} else if ret != msg::MACH_MSG_SUCCESS {
return Err(Error::Message(ret));
}
let exception = if crash_ctx_msg.flags & FLAG_HAS_EXCEPTION != 0 {
Some(crate::ExceptionInfo {
kind: crash_ctx_msg.exception_kind,
code: crash_ctx_msg.exception_code,
subcode: (crash_ctx_msg.flags & FLAG_HAS_SUBCODE != 0)
.then_some(crash_ctx_msg.exception_subcode),
})
} else {
None
};
let crash_context = CrashContext {
task: crash_ctx_msg.task.name,
thread: crash_ctx_msg.crash_thread.name,
handler_thread: crash_ctx_msg.handler_thread.name,
exception,
};
let mut pid = 0;
kern!(pid_for_task(crash_ctx_msg.task.name, &mut pid));
let ack_port = crash_ctx_msg.ack_port.name;
let acker = Acknowledger {
port: (ack_port != port::MACH_PORT_DEAD && ack_port != port::MACH_PORT_NULL)
.then_some(ack_port),
};
Ok(Some(ReceivedCrashContext {
crash_context,
acker,
pid: pid as u32,
}))
}
}
}
impl Drop for Server {
fn drop(&mut self) {
unsafe {
mach_port::mach_port_deallocate(mach_task_self(), self.port);
}
}
}
pub struct Acknowledger {
port: Option<port::mach_port_t>,
}
impl Acknowledger {
pub fn send_ack(&mut self, ack: u32, timeout: Option<Duration>) -> Result<(), Error> {
if let Some(port) = self.port {
unsafe {
let mut msg = AcknowledgementMessage {
head: MachMsgHeader {
bits: msg::MACH_MSG_TYPE_COPY_SEND,
size: std::mem::size_of::<AcknowledgementMessage>() as u32,
remote_port: port,
local_port: port::MACH_PORT_NULL,
voucher_port: port::MACH_PORT_NULL,
id: 0,
},
result: ack,
};
msg!(msg::mach_msg(
((&mut msg.head) as *mut MachMsgHeader).cast(),
msg::MACH_SEND_MSG | msg::MACH_SEND_TIMEOUT,
msg.head.size,
0,
port::MACH_PORT_NULL,
timeout.map(|t| t.as_millis() as u32).unwrap_or_default(),
port::MACH_PORT_NULL
));
Ok(())
}
} else {
Ok(())
}
}
}
struct AckReceiver {
port: port::mach_port_t,
}
impl AckReceiver {
unsafe fn new() -> Result<Self, Error> {
unsafe {
let mut port = 0;
kern!(mach_port::mach_port_allocate(
mach_task_self(),
port::MACH_PORT_RIGHT_RECEIVE,
&mut port
));
kern!(mach_port::mach_port_insert_right(
mach_task_self(),
port,
port,
msg::MACH_MSG_TYPE_MAKE_SEND
));
Ok(Self { port })
}
}
unsafe fn recv_ack(&mut self, timeout: Option<Duration>) -> Result<u32, Error> {
unsafe {
let mut ack = AcknowledgementMessage {
head: MachMsgHeader {
bits: 0,
size: std::mem::size_of::<AcknowledgementMessage>() as u32,
remote_port: port::MACH_PORT_NULL,
local_port: self.port,
voucher_port: port::MACH_PORT_NULL,
id: 0,
},
result: 0,
};
msg!(msg::mach_msg(
((&mut ack.head) as *mut MachMsgHeader).cast(),
msg::MACH_RCV_MSG | msg::MACH_RCV_TIMEOUT,
0,
ack.head.size,
self.port,
timeout.map(|t| t.as_millis() as u32).unwrap_or_default(),
port::MACH_PORT_NULL
));
Ok(ack.result)
}
}
}
impl Drop for AckReceiver {
fn drop(&mut self) {
unsafe {
mach_port::mach_port_deallocate(mach_task_self(), self.port);
}
}
}