This section documents how unix signals and Mach exceptions interact within XNU.
Unlike XNU, Linux uses unix signals for all purposes, i.e. for both typically hardware-generated (e.g.
SIGSEGV) and software-generated (e.g.
In XNU, hardware (CPU) exceptions are handled by the Mach part of the kernel (
osfmk/kern/exception.c). As a result of such an exception, a Mach exception is generated and delivered to the exception port of given task.
By default - i.e. when the debugger hasn't replaced the task's exception ports - these Mach exceptions make their way into
bsd/uxkern/ux_exception.c, which contains a kernel thread receiving and translating Mach exceptions into BSD signals (via
ux_exception()). This translation takes into account hardware specifics, also by calling
bsd/dev/i386/unix_signal.c. Finally, the BSD signal is sent using
Software signal processing in XNU follows the usual pattern from BSD/Linux.
ptrace(PT_ATTACHEXC) was called on the target process, the signal is also translated into a Mach exception via
do_bsdexception(), the pending signal is removed and the process is set to a stopped state (
The debugger then has to call
ptrace(PT_THUPDATE) to set the unix signal number to be delivered to the target process (which could also be
0, i.e. no signal) and that also resumes the process (state
Debugging support in Darling makes use of what we call "cooperative debugging".
It means the code in the debuggee is aware it's being debugged and actively
assists the process. In Darling, this role is taken on mainly by
libsystem_kernel.dylib, so no application modifications are necessary.
MacOS debuggers use a combination of BSD-like and Mach APIs to control and inspect the debuggee.
To emulate the macOS behavior, Darling makes use of POSIX real-time signals to invoke actions in the cooperative debugging code.
|Attach to debuggee|
Causes the kernel to redirect all signals (aka exceptions) to the Mach "exception port" of the process. Only debuggee termination is notified via
Signals sent to the debuggee and the debuggee termination event are received in the debugger via
|Notify the LKM that we will be tracing the process. Send a RT signal to the debuggee to notify it of the situation. The debuggee sets up handlers to handle all signals and forward them to the exception port.|
|Examine registers||Upon receiving a signal, the debuggee reads its own register state and passes it to the kernel via |
|Pausing the debuggee||Send a RT signal to the debuggee that it should act as if |
|Change signal delivery||Send a RT signal to the debuggee to inform it what it should do with the signal (ignore, pass it to the application etc.)|
|Set memory watchpoints||Implement the effects of |