How strace Traces System Calls in Linux
The strace utility is a diagnostic and debugging tool in
Linux used to monitor and capture the interactions between user-space
applications and the Linux kernel. It operates primarily by leveraging
the kernel's ptrace (process trace) system call, which
allows strace to pause an application, inspect its CPU
registers and memory, decode the arguments and return values of executed
system calls, and then resume execution. This article explains the
internal mechanisms strace uses to attach to processes,
intercept kernel transitions, decode syscall data, and display runtime
execution.
Initialization and Attachment via ptrace
When you launch a program using strace or attach it to a
running process (using the -p flag), strace
establishes a parent-child or tracer-tracee relationship using the
ptrace system call.
- Spawning a New Process:
straceforks itself. The child process callsptrace(PTRACE_TRACEME, ...), which notifies the kernel that it wants its parent to trace it. The child then executes the target program viaexecve(). - Attaching to a Running Process:
stracecallsptrace(PTRACE_ATTACH, pid)orptrace(PTRACE_SEIZE, pid), prompting the kernel to stop the target process and assignstraceas its tracer.
The Interception Cycle
Once tracing is established, strace configures the
kernel to stop the tracee every time a system call is made. This is
accomplished using the PTRACE_SYSCALL request within an
event loop managed via the waitpid system call.
Tracing a single system call involves two interception points:
- Syscall-Enter-Stop: When the target process
triggers a software interrupt, a
syscallinstruction (on x86_64), or asysenterinstruction to transition from user mode to kernel mode, the kernel intercepts the action. Instead of immediately running the system call, the kernel pauses the thread and sends aSIGTRAPsignal (specifically withPTRACE_O_TRACESYSGOODset) to notifystrace. - Syscall-Exit-Stop: After
straceinspects the initial call, it allows the kernel to execute the system call. Once the kernel finishes servicing the request and before returning control to user space, it pauses the process again. This second stop enablesstraceto capture the return value or error code.
Reading System Call Arguments
When the tracee is paused at the syscall-enter stage,
strace retrieves the state of the target CPU registers
using ptrace(PTRACE_GETREGSET, ...) or
ptrace(PTRACE_GETREGS, ...).
The registers hold the system call number and its arguments according to the architecture's Application Binary Interface (ABI). On an x86_64 architecture:
- The
raxregister holds the system call number (such as0forreador1forwrite). - The
rdi,rsi,rdx,r10,r8, andr9registers contain the first six arguments passed to the call.
strace maintains an internal lookup table that maps
system call numbers to human-readable names and signatures. If an
argument is a primitive value (like a file descriptor or flag mask),
strace reads it directly from the register.
If an argument is a memory pointer (such as a string pointer for a
file path or a buffer pointer), strace must read the
tracee's virtual memory space. It does this either through the
process_vm_readv system call or via repeated
ptrace(PTRACE_PEEKDATA, ...) operations, decoding
structures and null-terminated strings into readable text.
Capturing Return Values
After reading the arguments, strace issues
ptrace(PTRACE_SYSCALL, ...) to resume execution. The kernel
performs the actual operation and places the result into the designated
return register (rax on x86_64).
At the syscall-exit-stop, strace reads the register
state once more:
- A positive integer or zero indicates a successful return value (such as bytes read or a file descriptor).
- A negative integer within a specific range represents an error,
which
stracetranslates into standard errno names (such as-ENOENTor-EACCES).
Performance Overhead
Because strace requires two context switches between
user space and kernel space—and between the tracee and tracer—for every
individual system call, running a program under strace
introduces substantial execution overhead. This overhead makes
strace well suited for debugging and troubleshooting, but
typically unsuitable for high-throughput production environments.