How Does strace Peek Inside Other Processes? A Hands‑On Implementation Walkthrough
This article explains the inner workings of the classic strace command by building a minimal tracer in C, detailing how ptrace attaches to a target process, sets up syscall tracing, waits for signals, reads the ORIG_RAX register, and prints system call names, while also exposing the relevant Linux kernel source.
Overview
The strace utility observes system calls made by a target process by using the Linux ptrace API. It attaches to the target, requests notification on each system call, waits for the kernel‑generated SIGTRAP, reads the syscall number from the ORIG_RAX register, translates it to a name, and repeats.
Minimal strace implementation
A compact C program demonstrates the essential workflow. The full source is available at
https://github.com/yanfeizhang/coder-kung-fu/blob/main/tests/cpu/test11/main.c:
int main(int argc, char *argv[]) {
pid_t pid = /* target pid */;
int status;
// 1. Attach to the target
ptrace(PTRACE_ATTACH, pid, NULL, NULL);
waitpid(pid, &status, 0);
while (1) {
// 2. Request syscall stop
ptrace(PTRACE_SYSCALL, pid, NULL, NULL);
// 3. Wait for SIGTRAP generated at syscall entry
waitpid(pid, &status, 0);
// 4. Read syscall number from ORIG_RAX
long sc = ptrace(PTRACE_PEEKUSER, pid, 8 * ORIG_RAX, NULL);
const char *name;
switch (sc) {
case 5: name = "read"; break;
case 6: name = "write"; break;
case 10: name = "open"; break;
case 11: name = "close"; break;
default: name = "unknown"; break;
}
printf("Syscall: %s (number: %ld)
", name, sc);
}
}Attaching to the target process
The attachment is performed with ptrace(PTRACE_ATTACH, pid, …). Inside the kernel the ptrace system call resolves the task_struct for the given PID and calls ptrace_attach:
// kernel/ptrace.c (simplified)
SYSCALL_DEFINE4(ptrace, long request, long pid, unsigned long addr, ...){
struct task_struct *child = find_get_task_by_vpid(pid);
if (request == PTRACE_ATTACH || request == PTRACE_SEIZE)
ret = ptrace_attach(child, request, addr, data);
...
}
static int ptrace_attach(struct task_struct *task, long request, ...){
if (unlikely(task->flags & PF_KTHREAD))
return -EPERM; // kernel threads cannot be traced
ptrace_link(task, current); // link tracer and tracee
...
} ptrace_linkinserts the child into the tracer’s ptraced list and sets child->parent = current, enabling the tracer to receive signals from the tracee via waitpid.
Enabling syscall tracing
After attachment the tracer issues ptrace(PTRACE_SYSCALL, pid, …). The kernel marks the task with SYSCALL_TRACE:
// kernel/ptrace.c (excerpt)
case PTRACE_SYSCALL:
set_task_syscall_work(child, SYSCALL_TRACE);
break;
// include/linux/thread_info.h
#define set_task_syscall_work(t, fl) \
set_bit(SYSCALL_WORK_BIT_##fl, &task_thread_info(t)->syscall_work)When the tracee executes a system call, the entry point checks the flag and, if set, calls ptrace_report_syscall_entry, which ultimately triggers a SIGTRAP to the tracer.
Waiting for the signal
The tracer blocks with waitpid(pid, &status, 0). The kernel implementation adds the tracer to a wait queue and puts it to TASK_INTERRUPTIBLE until the tracee generates SIGTRAP:
// kernel/exit.c (simplified)
SYSCALL_DEFINE3(waitpid, pid_t pid, int __user *stat_addr, int options){
return kernel_wait4(pid, stat_addr, options, NULL);
}
static long do_wait(struct wait_opts *wo){
init_waitqueue_func_entry(&wo->child_wait, child_wait_callback);
wo->child_wait.private = current;
add_wait_queue(¤t->signal->wait_chldexit, &wo->child_wait);
...
}Reading the syscall number
When SIGTRAP is received, the tracer reads the ORIG_RAX register (the saved syscall number) via PTRACE_PEEKUSER:
// arch/x86/kernel/ptrace.c (excerpt)
case PTRACE_PEEKUSR:
if (addr < sizeof(struct user_regs_struct))
tmp = getreg(child, addr);
ret = put_user(tmp, datap);
break;The value is then mapped to a human‑readable name (e.g., 5 → read) and printed.
Loop and continuation
After printing, the tracer repeats the PTRACE_SYSCALL → waitpid → read cycle, allowing continuous monitoring of the target’s system calls.
Performance considerations
Because the tracee is stopped at every syscall entry (and exit, if PTRACE_SYSCALL is used for both), strace introduces additional context switches and can noticeably increase the execution time of the traced program. It is therefore suited for debugging and short‑term analysis, but not for long‑running production monitoring.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Liangxu Linux
Liangxu, a self‑taught IT professional now working as a Linux development engineer at a Fortune 500 multinational, shares extensive Linux knowledge—fundamentals, applications, tools, plus Git, databases, Raspberry Pi, etc. (Reply “Linux” to receive essential resources.)
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
