Kernel

Trace a syscall

Use DTrace and ktrace to observe syscall behaviour on a running darwinOS system without rebooting or rebuilding.

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 1234

Kernel 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 -100

ktrace

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 -40

Output 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 command
  • c — syscalls (entry + return)
  • s — signals
  • n — namei (path translations)
  • i — generic I/O
  • u — userland events (via utrace(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-event printf is expensive.

If you’re measuring performance with DTrace attached, you’re measuring with DTrace overhead.

Next steps