Each call to mach_thread_self
adds another MACH_PORT_RIGHT_SEND
refcount. For each call to mach_thread_self
, you need to call
mach_port_deallocate
on the result.
(This does not apply to mach_task_self
.)
mach_port_deallocate
is no good for port sets. Use
mach_port_destroy
.
mach_port_deallocate
will return KERN_INVALID_RIGHT
for a receive
right.
For receive rights, use mach_port_destroy
.
(If you’ve got a send/receive port right, due perhaps to having called
mach_port_insert_right
, you need to call both…)
mach_msg_header_t::msgh_size
includes the header size.
See mach_dead_name_notification_t
in /usr/include/mach/notify.h.
Check header’s msgh_id
is MACH_NOTIFY_DEAD_NAME
. If so, cast
message buffer to mach_dead_name_notification_t
.
If you have a message with a port name in it, you now have a reference
to it. It must be unreferenced with mach_port_deallocate
to avoid
port name leaks.
This applies to mach_dead_name_notification_t
!
There’s a global, NDR_record
, holding the native settings for the
current CPU. So compare message’s NDR record against this and swap
stuff if necessary.
The man page is fairly unambiguous:
Takes the name of a mach port, or port set, in ident and waits until a message is received on the port or port set.
But in fact, a kqueue may only watch a port set. You get ENOTSUP
if
trying to watch a port.
There’s even a test for this exact behaviour: http://www.opensource.apple.com/source/xnu/xnu-1456.1.26/tools/tests/xnu_quick_test/kqueue_tests.c
P.S. See also https://stackoverflow.com/questions/33115562 - can’t remember any of the details though.
https://www.mikeash.com/pyblog/friday-qa-2013-01-11-mach-exception-handlers.html
https://llvm.org/bugs/show_bug.cgi?id=22868
Once the dead name notification comes in, the process state - exit
status or killing signal - can still be queried using wait4
. (I’m
pretty sure this is mandatory, to ensure the process gets reaped.)
Declaration of struct thread
. Note:
uint64_t thread_id; /*system wide unique thread-id*/
ptrace
will trap SIGKILL
.
When running a process under ptrace, sending that process SIGKILL
will stop the process rather than kill it. Detect this using
waitpid
- WIFSTOPPED(status)
will be true, and WSTOPSIG(status)
will be SIGKILL
.
Suggestion: pass the signal on to the process using PT_CONTINUE
!
Use PT_KILL to kill threads that are stopped due to an exception. They will die without further ado. (If you are watching for dead names you will get a notification that their names have died.)
PT_KILL won’t kill other threads, so send SIGKILL to the process as well, to kill the other threads. Eventually, the process will end up stopped again in the SIGNALED state, with SIGKILL as the signal. Pass it through and the process will go away.
PT_SIGEXC
is a ptrace code for use by the tracee after it’s called
PT_TRACE_ME
. It directs the OS to send it signals in the form of
Mach exceptions, allowing the tracer to catch them via the exception
port rather than having to bugger about with SIGCHLD.
For such exceptions, the message’s exception
field will be
EXC_SOFTWARE
, code[0]
will be EXC_SOFT_SIGNAL
, and code[1]
will be the signal number.
Start by using task_suspend
to suspend the task.
Don’t do anything further until you’re ready for the process to continue.
Once you’re ready, use PT_THUPDATE
. Pass your Mach thread port as
the addr
argument to ptrace. For data
, as PT_CONTINUE
, pass the
signal number to deliver, or 0
to discard it.
ptrace(PT_THUPDATE,pid,(caddr_t)(uintptr_t)thread_port,exception->code[1])
Then, pass the exception on in the usual fashion, by forming an
appropriate mig_error_reply_t
(see the generated mig code) - this
might best be done when the exception is caught, and saved for later.
The reply’s RetCode
should be KERN_SUCCESS
, indicating that the
exception should be delivered.
TODO: figure out exactly what the results are when deviating from this!
Certain exceptions use the raise message code fields to store addresses. This is no good for 64-bit tracees, because the code fields are 32 bit.
To fix this, when registering an exception port, include the value
MACH_EXCEPTION_CODES
in the behaviour
argument. Exceptions will
then be delivered in an alternative format (64-bit code fields), with
different codes (starting at 2405 rather than 2401).
The MIG stuff for this is in /usr/include/mach/mach_exc.defs. Unlike exc.defs, there’s no pregenerated header for it though.
(It looks as if this works for 32-bit tracees as well.)
Despite what the docs say, the data_count
argument is ignored on
input, and its size is in bytes. There’s no need to set it before the
call. (Of course, this means the buffer must be large enough… Mach
doesn’t check…)
If the read touches an unmapped region, the result is
KERN_INVALID_ADDRESS
. Intersect the area to be read with the list of
VM regions (e.g., using vm_region_64
), and read each intersecting
area.
mach_vm_read
and vm_read
return the actual size of the mapped
region as a mach_msg_type_number_t
- a 32-bit value. These calls are
therefore limited to mapping 4GBytes at a time.
You get KERN_INVALID_ARGUMENT
if this is breached.
See =mach_vm_read=; =vm_read=.
When using vm_read
to map part of a region, that region may be
split. For example:
Old mapping:
0x00000001008f2000: R+W+X- 16384 e772fa9949c60812cd1c6aa8c8e1b2848ff4ef16
Then after calling vm_read
to duplicate 4K starting at
0x00000001008f4000:
0x00000001008f2000: R+W+X- 4096 1d61359d785b37e7a97f5491733f72cf321dd8b2 0x00000001008f3000: R+W+X- 4096 1d61359d785b37e7a97f5491733f72cf321dd8b2 0x00000001008f4000: R+W+X- 8192 f2a97cb03760772953a411e7928fd6ff461152d2
vm_region_64
(and friends) have a mystery out parameter, “no longer
used” according to the documentation:
vm_region_64(vm_map_t map, /* ... */ mach_port_t *object_name) /* OUT */
It’s always set to IPC_NULL
, which is 0.
https://www.spaceflint.com/?p=150