Mac maintenance
Is There an strace for Mac? (Tracing Syscalls on macOS With dtruss/dtrace)
macOS has no strace, but dtruss and dtrace can trace system calls — here's how to use them, what's restricted by SIP, and when Instruments is better.
You’re trying to figure out why an app is hanging. Or what files it’s opening. Or which library it can’t find. On Linux, the answer is strace ./app. On macOS, you’ll quickly discover that strace doesn’t exist — and the alternatives are a bit fiddly because of SIP.
Here’s the practical guide to syscall tracing on a Mac in 2026.
The short answer
There is no strace on macOS. The equivalents:
dtruss— a DTrace-based wrapper that mimics strace’s interface. Built-in.dtraceitself — the underlying tracing framework. More powerful, less ergonomic.ktrace— Apple’s modern tracing API (the one Instruments uses). CLI is limited.- Instruments — Xcode’s GUI profiler. The Apple-recommended path.
The first two need either a partial SIP disable or developer mode, depending on the macOS version. Instruments works under full SIP and is what Apple wants you to use.
dtruss — the quick approach
sudo dtruss ./yourbinary
Output looks like strace’s:
SYSCALL(args) = return
open("/etc/passwd\0", 0x0, 0x0) = 3 0
read(0x3, "...\0", 0x1000) = 1234 0
close(0x3) = 0 0
Same flags strace users will recognize:
sudo dtruss -f ./binary # follow forks
sudo dtruss -p 12345 # attach to running PID
sudo dtruss -t open ./binary # filter to specific syscall
sudo dtruss -n binary # by name
sudo dtruss -e ./binary # show timestamps
sudo dtruss -d ./binary # show relative timestamps
Output goes to stderr; redirect to capture:
sudo dtruss ./binary 2> trace.log
The SIP problem
System Integrity Protection (on by default since El Capitan) restricts what DTrace can see. Specifically:
- You can’t trace processes signed with the “no DTrace” entitlement (most Apple binaries, like
Finder,Safari). - You can’t trace processes signed with hardened runtime unless the target binary explicitly allows it.
- Even with an unsigned binary you wrote yourself, some syscalls require additional permissions.
The error you’ll see most:
dtrace: failed to control pid 12345: process is restricted from being traced
Workarounds, from least invasive to most:
- Use only on your own unsigned binaries. A C program you compiled yourself, no codesign, runs fine under
dtruss. - Disable just the DTrace restriction: boot into recovery mode, run
csrutil enable --without dtrace. SIP stays on for everything else; DTrace becomes unrestricted. - Disable SIP entirely. Recovery mode →
csrutil disable. Don’t do this on a regular work machine.
Most diagnostic use cases — “trace this Python script I wrote” — fall under option 1 and just work. For tracing third-party app behavior, you’ll usually need option 2.
csrutil status in Terminal. It tells you which subsystems are restricted.Common dtruss patterns
What files is this app opening?
sudo dtruss -t open -f ./binary
You’ll see every open() call, with the path. Useful for “why isn’t it finding my config file?”
What’s it sending over the network?
sudo dtruss -t connect -t sendto -f ./binary
Captures connect() (TCP) and sendto() (UDP) calls. The actual payloads aren’t visible — for that, you want tcpdump or Charles Proxy.
Why is it slow?
sudo dtruss -e ./binary 2>&1 | awk '{print $2, $1}' | sort -n | tail -20
Shows the 20 slowest syscalls by elapsed time. If one specific call is taking 5 seconds, that’s where the wait is.
Trace a specific running process
sudo dtruss -p 12345
Attaches to PID 12345. The process keeps running; you see its syscalls live. Ctrl+C to detach.
dtrace — the underlying tool
dtrace is the actual tracing engine. dtruss is a wrapper that runs a particular DTrace script. To see what dtruss does under the hood:
sudo dtrace -ln 'syscall:::entry'
-l lists matching probes. syscall:::entry matches every syscall entry on the system. There are thousands.
For a custom trace, you write a D script:
#!/usr/sbin/dtrace -s
syscall::open*:entry
/execname == "yourbinary"/
{
printf("%s opens %s\n", execname, copyinstr(arg0));
}
Save as trace-opens.d, make executable, run with sudo.
For one-liners:
sudo dtrace -n 'syscall::open*:entry /execname == "Finder"/ { printf("%s\n", copyinstr(arg0)); }'
Watches every file Finder opens. (Won’t actually work on Finder due to SIP, but it’s the syntax.)
The D language is tiny but expressive: filters in /.../, actions in {...}, aggregations like @hist[col] = quantize(value) for histograms.
A useful one-liner: histogram of syscall latency
sudo dtrace -n 'syscall:::entry { self->ts = timestamp; } syscall:::return /self->ts/ { @[probefunc] = quantize(timestamp - self->ts); self->ts = 0; }'
Output (when you Ctrl+C):
read
value ------------- Distribution ------------- count
1024 | 0
2048 |@ 12
4096 |@@@@@@@@ 156
8192 |@@@@@@@@@@@@@@@@@@@@@@@@@@ 412
16384 |@@@@ 67
32768 |@ 15
Time buckets (microseconds), distribution histogram, count. Lets you see which syscalls are slow and how frequently.
Why dtrace is sometimes the wrong tool
A few realities to set expectations:
- DTrace’s overhead is real on heavy systems. Tracing
syscall:::entrysystem-wide can slow things down enough to perturb the thing you’re trying to measure. - The user-space part of a slow operation often dominates the kernel part. If your app is slow because of bad algorithms, syscall tracing won’t help.
- DTrace can’t see what’s happening inside a syscall — only the entry, exit, and arguments.
For application-level profiling (function-level timing), Instruments is much better suited.
Instruments — the Apple-supported path
Open Xcode → Open Developer Tool → Instruments. Pick the “Time Profiler” template (CPU sampling), “Allocations” (memory), “Leaks,” “File Activity,” or one of two dozen others.
Why use Instruments:
- Works under full SIP.
- GUI timeline view of the trace.
- Function-level resolution, not just syscalls.
- Symbolicated stack traces.
- Can attach to running processes.
Why CLI dtrace might still be better:
- Lighter weight (no GUI framework needed).
- Scriptable / automatable.
- Can run on a remote/headless Mac.
- Doesn’t need Xcode (which is a multi-GB install).
For day-to-day “this app is slow, why?” debugging, Instruments is the right answer. For “I want to log every file my script touches and grep the result,” dtruss is faster.
fs_usage — when you only care about file I/O
Already covered in another guide, but worth mentioning here: if all you want is “what files is this opening,” fs_usage is friendlier than dtruss:
sudo fs_usage -w yourbinary
It shows filesystem syscalls only, with full paths and durations. No D language to learn. Doesn’t need SIP changes (mostly). Sufficient for the most common debugging case.
dtruss shines when you need to see network calls, signals, or other non-filesystem syscalls.
sample — quick CPU sampling without DTrace
For “what’s this process doing right now”:
sample 12345 5 -f /tmp/sample.txt
Samples PID 12345 for 5 seconds, writing the result to /tmp/sample.txt. The output is a stack-by-stack profile — basically, “where in its code is this process spending time.”
sample doesn’t need SIP changes and works on most processes. It’s the “spindump for one process” command. For a process that seems hung:
sample 12345 3
You’ll see the stack traces. The bottom of the stack tells you what kernel call it’s blocked on.
spindump — when an app hangs
sudo spindump -timelimit 5 ./binary
Or for a running process:
sudo spindump 12345 5
Generates a “spindump” — a multi-second sample showing what every thread of the process was doing. Output goes to a .txt file in /tmp/ by default.
This is the same data macOS captures when an app hangs and you click “Force Quit.” Useful for diagnosing your own apps that go unresponsive.
A workflow for tracing a misbehaving binary
Suppose you have a CLI tool that mysteriously fails. The pattern:
# 1. Run it under fs_usage to see what files it touches
sudo fs_usage -w yourbinary &
./yourbinary
# Ctrl+C the fs_usage
# 2. If that doesn't reveal it, drop down to dtruss
sudo dtruss ./yourbinary 2> trace.log
# 3. Find the failing call
grep ENOENT trace.log # missing file
grep EACCES trace.log # permission
grep ECONNREFUSED trace.log
The error code at the end of each syscall is the most useful single piece of info. macOS uses standard errno values, same as Linux.
Tracing GUI apps
Most GUI apps are signed with hardened runtime, which restricts dtruss. You have two options:
- Open them via the binary inside the bundle:
sudo dtruss /Applications/Slack.app/Contents/MacOS/Slack 2> /tmp/slack.log
That sometimes works for apps not signed with the hard restrictions; sometimes it doesn’t.
- Use Instruments, which has Apple’s blessing to attach to anything.
For the average curious user, Instruments is the answer. For developers debugging their own (unsigned, debug-built) binaries, dtruss is fine.
Apple Silicon specifics
DTrace on Apple Silicon works the same way. The probes are the same. The performance is generally excellent — M-series Macs trace cleanly without significant slowdown for moderate workloads.
ARM64 syscall numbers differ from x86_64 in some cases. dtruss handles the translation transparently. If you’re writing your own DTrace scripts that reference numeric syscall IDs, watch for arch-specific values.
Network tracing — out of scope but related
For network behavior, dtruss -t connect shows the syscall, but the content of network traffic needs different tools:
tcpdump -i any 'host example.com'— packet capture.httptoolkitormitmproxy— HTTP-level interception.- Charles Proxy / Proxyman — GUI HTTP proxies.
For “is this app calling home?”, a network proxy is more useful than dtruss.
A cheat sheet
| Goal | Tool |
|---|---|
| What files is this opening? | sudo fs_usage -w binary or sudo dtruss -t open binary |
| What’s the slow syscall? | sudo dtruss -e binary |
| Stack trace of a hung process | sample PID 5 |
| Full trace of a hang | sudo spindump PID 5 |
| Application-level profiling | Instruments |
| System-wide histogram of any syscall | sudo dtrace -n '...' |
Bottom line
The Linux strace ./app muscle memory doesn’t translate directly to macOS, mostly because Apple invested in DTrace and Instruments instead of building a strace-equivalent. The good news: once you know the tools, you have more capability than strace — DTrace can do system-wide aggregation and histogramming that strace simply can’t.
The bad news: SIP makes it fiddly. For tracing your own unsigned binaries, dtruss works out of the box. For everything else, Instruments is the path that doesn’t require touching SIP.
For most “why is my Mac slow?” questions, you don’t need any of this. Reach for top, fs_usage, and Activity Monitor first. Drop to dtruss when those answer “the disk is busy” without telling you why.