Tracing with kdebug¶
kdebug is the kernel's event tracing facility: the kernel and many system libraries record fixed-size events into it, and a trace session collects them from every process at once. It needs no entitlement, no jailbreak and no change to the traced processes, so it is the main way to watch what platform binaries (daemons, SpringBoard, Apple's apps) do on a stock device.
pymobiledevice3 developer dvt core-profile-session drives the same trace sessions as Instruments.
See CLI recipes for the commands; this guide covers what is in a trace and how to
get at it.
Commands¶
| Command | Prints |
|---|---|
parse-live |
Decoded traces (strace-like): syscalls with their arguments and paths, dyld image loads, process and thread lifecycle. Selects events by group and name (--fs, --xpc, -s, -e, ...; see below). |
live |
Every raw event: time, event name, start/end, thread, process and the four raw arguments. Selects events by class and subclass. |
callstacks-live |
User space call stacks sampled from the running threads. |
save |
The raw trace, to parse later. |
stackshot |
The call stacks of every thread of every process, once. |
Event ids¶
Every event is a 32-bit debugid:
The class names the component (0x04 BSD, 0x29 XPC, ...), the subclass a part of it, and the
function bits mark whether the event starts (1) or ends (2) an interval. The eventid is the
debugid without the function bits, and is what trace codes name (0x29000044 is
XPC_connection_send_message).
live filters on the device by class (-cf 0x29) and by class and subclass together
(-sf 0x0301, the class in the high byte), which also keeps the device from sending what is
filtered out:
# XPC and libdispatch events of every process
pymobiledevice3 developer dvt core-profile-session live -cf 0x29 -cf 0x2e
# file lookups only (VFS_LOOKUP lives in class 0x03, subclass 0x01)
pymobiledevice3 developer dvt core-profile-session live -sf 0x0301
Selecting events in parse-live¶
| Option | Selects |
|---|---|
--fs, --net, --proc, --io, --mem, --signal, --cred, --sync, --mach |
A group of syscalls and Mach traps |
--dyld |
dyld image loads and timing (dlopen, dlsym, ...) |
--xpc, --dispatch, --runloop |
The XPC, libdispatch and CoreFoundation run loop events of every process, printed as their name and raw arguments |
-s NAME |
One syscall, with its _nocancel variant (-s open) |
-e NAME |
One event, by its trace code name (-e DBG_DYLD_TIMING_DLSYM) |
They all add up, and --process narrows the result to one process. The device filters by subclass
at best, so the host drops the rest right after decoding. events takes the same options and lists
exactly the events parse-live prints with them (events --fs, events -s open), and
events dlsym finds a name by text. A class or subclass filter (--bsc, -cf, -sf, --dyld)
prints only the events pykdebugparser decodes: events -cf 0x29 lists nothing, while --xpc
selects the same XPC events by id.
# the XPC traffic of SpringBoard
pymobiledevice3 developer dvt core-profile-session parse-live --xpc --process SpringBoard
# every dlsym, in every process
pymobiledevice3 developer dvt core-profile-session events dlsym
pymobiledevice3 developer dvt core-profile-session parse-live -e DBG_DYLD_TIMING_DLSYM
Events every process emits¶
These are recorded by the kernel or by system libraries in every process, gated only by the trace session's class filter. They were all seen in traces of an iPhone on iOS 27.2.
| Class | Source | Events | What they show |
|---|---|---|---|
0x03 |
kernel | VFS_LOOKUP |
The full path of every file lookup, in every process |
0x04 |
kernel | BSD syscalls | Every syscall with its arguments (parse-live decodes them) |
0x07 |
kernel | thread and process lifecycle | New threads, execs and thread names |
0x1f |
dyld | image map (DYLD_uuid_map_a/b) and timing |
The UUID and load address of every image of every process, and dyld's launch phases |
0x29 |
libxpc | XPC_* |
Messages sent and received, transactions, the process becoming dirty or clean |
0x2e |
libdispatch | DISPATCH_MACH_MSG_hdr_move, DISPATCH_PERF_* |
Mach messages received through dispatch, queue misuse |
0x21, 0x14 |
CoreFoundation | CFRL_* |
Run loop iterations, waits, sources, timers, observers and blocks |
The XPC events carry a per-connection id and the message size (on the end of a send), not the service name. The run loop events show when a main thread is busy and with what kind of callout, which helps find hangs.
callstacks-live samples user space call stacks of the same processes. It prints them as raw
addresses; the dyld image map above tells which image each one falls in.
Events that need a setting¶
Some system libraries emit events only when a setting enables them, on top of the trace's class filter.
| Class | Source | Enabled by |
|---|---|---|
0x33 |
libsystem_malloc: every malloc, calloc, realloc, free (zone, size, pointer) |
The MallocTracing environment variable, read when the process starts |
0x2b (subclasses 0x1d, 0x13, 0x29) |
Network.framework, libquic, CFNetwork and BoringSSL: connections, TLS handshakes, QUIC packet protection | networkd's enable_signposts setting, which only networkd's privileged clients can set |
MallocTracing can be passed to apps, including Apple's own, by launching them through
pymobiledevice3:
pymobiledevice3 developer dvt launch --kill-existing --env MallocTracing=1 com.apple.Preferences
pymobiledevice3 developer dvt core-profile-session live -cf 0x33
Launching only works for apps. Daemons and SpringBoard are started by launchd with launchd's
environment, so there is no way to pass them MallocTracing on a stock device. CoreDevice can
spawn arbitrary executables with an environment, but only on Apple internal builds.
What kdebug does not show¶
- Mach message send and receive in the kernel (
MACH_IPC_msg_send/_recv): the iOS 27.2 release kernel does not emit them. The XPC and libdispatch events cover most of that traffic from user space. - Message contents, XPC service names and TLS plaintext: no event carries them.