DTrace and ktrace are the two tracing stacks darwinOS inherits from upstream Darwin. Both let you watch syscalls (and much else) on a running system with minimal overhead. DTrace is more powerful; ktrace is simpler. This guide covers the common syscall-tracing patterns in both.
DTrace
One-liners
Count syscalls per process name, aggregated across the system, until you ^C:
dtrace -n 'syscall:::entry { @[execname, probefunc] = count(); }'Every syscall issued by dd, with arguments:
dtrace -n 'syscall:::entry / execname == "dd" / { printf("%s(%x, %x, %x)", probefunc, arg0, arg1, arg2); }'Wall-clock latency of open:
dtrace -n '
syscall::open*:entry { self->t = timestamp; }
syscall::open*:return / self->t / { @[probefunc] = quantize(timestamp - self->t); self->t = 0; }
'The @ aggregation accumulates in the kernel; DTrace prints histograms on exit.
A script file
Longer scripts go in .d files. A simple “what syscalls is this PID making”:
#!/usr/sbin/dtrace -s
#pragma D option quiet
syscall:::entry
/ pid == $target /
{
printf("%-6d %-16s %s\n", pid, execname, probefunc);
}Invoke with a target PID:
dtrace -s trace_pid.d -p 1234Kernel probes vs. syscall probes
syscall:::entry fires at the BSD syscall boundary — the layer most users think of as “the syscall.” There’s also fbt:::entry (function boundary tracing, every kernel function entry) and provider-specific probes for IOKit, VFS, and networking. Start with syscall::: and drop to fbt::: when you need to see inside a specific kernel path.
List every probe DTrace knows about right now:
dtrace -l | head -100ktrace
ktrace is the simpler, older tool. It records syscalls and signals from specific processes into a file; kdump turns the file into human-readable output.
Trace a command from start to finish
ktrace -f trace.out ls /tmp
kdump -f trace.out | head -40Output is a dense list of CALL, RET, NAMI (name-inspect, for open path arguments), and GIO (generic I/O) events.
Attach to an existing process
ktrace -f trace.out -p 1234
# let it run…
ktrace -C # stop tracing everywhere
kdump -f trace.out-C stops ktrace globally — a safe cleanup after any session.
Filter by event type
ktrace -t cs -f trace.out commandc— syscalls (entry + return)s— signalsn— namei (path translations)i— generic I/Ou— userland events (viautrace(2))
Combine letters for multi-type filters. Default (no -t) is all types.
Choosing between them
- DTrace when you want aggregation, filtering at the source, or visibility into non-syscall layers (IOKit, VFS internals, scheduler).
- ktrace when you just want a chronological log of what a process called, with zero setup cost.
For kernel debugging during development, DTrace usually wins. For reproducing a bug report from a user, ktrace is easier to ask someone else to run.
Overhead
Both tools are designed to be safe on production systems, but overhead is real:
- Syscall tracing typically adds 1–5% overhead to syscall-heavy workloads.
fbt:::adds more — 10% or higher on hot paths.- Aggregations (
@in DTrace) are cheap; per-eventprintfis expensive.
If you’re measuring performance with DTrace attached, you’re measuring with DTrace overhead.
Next steps
- Debug the kernel — when tracing isn’t enough, attach lldb.
- Add a syscall — trace your own new syscall to verify it’s firing correctly.