strace¶
是什么 / 解决什么问题¶
strace 拦截并记录进程的所有系统调用及其参数和返回值。它是调试"程序在做什么"的第一选择工具——不需要源码、不需要重新编译、不需要修改程序。
使用模式¶
基本用法¶
按类别过滤¶
# 只看网络相关
strace -e trace=network <cmd>
# 只看文件相关
strace -e trace=file <cmd> # open, stat, unlink, ...
strace -e trace=%file <cmd> # 同上(新语法)
# 只看特定 syscall
strace -e trace=read,write,epoll_wait <cmd>
# 排除某些 syscall(减少噪音)
strace -e trace=!mmap,mprotect,brk <cmd>
常用过滤类别:
| 类别 | 包含 |
|---|---|
file |
open, stat, chmod, unlink, rename, ... |
process |
fork, clone, execve, wait, exit, ... |
network |
socket, bind, connect, accept, send, recv, ... |
signal |
kill, sigaction, sigprocmask, ... |
ipc |
shmget, semop, msgrcv, ... |
memory |
mmap, mprotect, brk, ... |
desc |
read, write, close, poll, epoll, select, ... |
输出时间信息¶
# 每个 syscall 的耗时(微秒)
strace -T <cmd>
# read(3, "hello", 1024) = 5 <0.000012>
# 绝对时间戳
strace -t <cmd> # HH:MM:SS
strace -tt <cmd> # HH:MM:SS.usec
strace -ttt <cmd> # epoch.usec
# 相对时间(距上一个 syscall 的间隔)
strace -r <cmd>
# 0.000021 read(3, "hello", 1024) = 5
# 0.012345 write(1, "hello\n", 6) = 6 ← 这里有 12ms 延迟
输出到文件¶
# 输出到文件(不混淆 stderr)
strace -o trace.log <cmd>
# 多进程时每个 PID 单独一个文件
strace -ff -o trace <cmd>
# 生成 trace.1234, trace.1235, ...
统计模式¶
# 只看统计摘要,不输出每个调用
strace -c <cmd>
# 输出示例:
# % time seconds usecs/call calls errors syscall
# ------ ----------- ----------- --------- --------- ----------------
# 45.21 0.005432 12 452 read
# 30.12 0.003618 8 452 write
# 15.33 0.001842 921 2 1 futex
# 5.22 0.000627 11 57 epoll_wait
# ...
# 统计 + 详细输出一起(调试性能问题时最有用)
strace -C <cmd>
字符串和数据显示¶
# 增加字符串显示长度(默认 32 字节太短)
strace -s 1024 <cmd>
# read(3, "HTTP/1.1 200 OK\r\nContent-Type: application/json\r\n...", 4096) = 1234
# 显示 read/write 的完整数据(hex dump)
strace -e read=3 -e write=3 <cmd>
# 不截断路径
strace -s 256 -e trace=file <cmd>
实用组合¶
# 看程序打开了哪些文件
strace -e trace=openat -s 256 <cmd> 2>&1 | grep -v ENOENT
# 看网络连接建立
strace -e trace=connect -s 128 <cmd>
# 看程序为什么卡住(找到阻塞的 syscall)
strace -p <pid>
# 通常会看到卡在 read, poll, epoll_wait, futex 等
# 找出慢 syscall
strace -T -e trace=read,write -p <pid> 2>&1 | awk -F'[<>]' '$NF > 0.01'
# 看 DNS 解析(connect 到 53 端口)
strace -e trace=network -f <cmd> 2>&1 | grep ":53"
行为与语义¶
strace 的工作原理¶
strace (tracer)
│
│ ptrace(PTRACE_SYSCALL, ...)
▼
目标进程 (tracee)
│
│ ──→ syscall 入口 ──→ strace 拦截,记录参数
│ ◄── syscall 返回 ◄── strace 拦截,记录返回值
│
▼
继续执行
使用 ptrace 系统调用实现。每个 syscall 会触发两次停止(进入和返回),所以有开销。
输出格式¶
syscall名(参数1, 参数2, ...) = 返回值 <耗时>
# 示例
openat(AT_FDCWD, "/etc/hosts", O_RDONLY) = 3
read(3, "127.0.0.1 localhost\n...", 4096) = 234
close(3) = 0
connect(4, {sa_family=AF_INET, sin_port=htons(80), sin_addr=inet_addr("1.2.3.4")}, 16) = -1 EINPROGRESS (Operation now in progress)
返回值解读¶
= 0 # 成功
= 3 # 成功,返回 fd
= 1234 # 成功,返回字节数
= -1 ENOENT (No such file or directory) # 失败
= ? ERESTARTSYS (To be restarted if SA_RESTART is set) # 被信号中断
性能考量¶
strace 的开销¶
strace 会显著降低目标进程性能(通常 10x~100x 慢):
- 每个 syscall 两次 ptrace 停止 + 两次上下文切换
- 高频 syscall 的程序(如网络服务器)影响尤其大
# 减少开销的方法:
# 1. 只跟踪特定 syscall
strace -e trace=connect,accept4 -p <pid>
# 2. 只统计不输出详情
strace -c -p <pid>
# 3. 用 perf/bpftrace 替代(生产环境)
perf trace -p <pid> # 基于 perf_event,开销小得多
bpftrace -e 'tracepoint:syscalls:sys_enter_read /pid == 1234/ { ... }'
与其他工具的对比¶
| 工具 | 性能开销 | 功能 | 适用场景 |
|---|---|---|---|
| strace | 高(10-100x) | 完整 syscall 参数和返回值 | 开发/调试环境 |
| perf trace | 低(2-5%) | syscall 跟踪 | 生产环境初步诊断 |
| bpftrace | 极低(< 1%) | 可编程过滤/聚合 | 生产环境深度分析 |
| ltrace | 高 | 库函数调用 | 调试动态链接问题 |
常见陷阱与 FAQ¶
1. 忘记 -f 导致看不到子进程¶
2. 输出混在 stderr 影响判断¶
3. 大量 ENOENT 是正常的¶
程序查找动态库、配置文件时会尝试多个路径:
openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY) = 3 # 找到
openat(AT_FDCWD, "/lib/libc.so.6", O_RDONLY) = -1 ENOENT # 没找到
openat(AT_FDCWD, "/usr/lib/libc.so.6", O_RDONLY) = 4 # 换个路径找到
这不是错误,是正常的库搜索过程。
4. Permission denied¶
# 非 root 用户不能 strace 其他用户的进程
# 且 Yama LSM 默认限制 ptrace 范围
# 检查
cat /proc/sys/kernel/yama/ptrace_scope
# 0: 无限制
# 1: 只能 trace 自己的子进程(默认)
# 2: 只有 CAP_SYS_PTRACE 能 trace
# 临时允许(需要 root)
echo 0 > /proc/sys/kernel/yama/ptrace_scope
5. 解读 futex 调用¶
看到大量 futex 通常不是问题——这是 pthread mutex/condvar 的底层实现:
实战案例¶
案例1:程序启动慢¶
strace -T -e trace=file,network -f ./myapp 2>&1 | sort -t'<' -k2 -rn | head -20
# 找到最耗时的 syscall,通常是:
# - DNS 解析慢(connect 到 DNS 服务器超时)
# - 读取远程文件系统(NFS)
# - 加载大量 .so 文件
案例2:程序卡住¶
strace -p <pid>
# 立即看到卡在哪个 syscall:
# read(5, ← 等待某个 fd 的数据
# futex(... ← 等待锁
# epoll_wait(← 正常等待事件(可能是空闲状态)
# 进一步看 fd 5 是什么
ls -la /proc/<pid>/fd/5
# lrwx------ 1 user user 64 ... /proc/1234/fd/5 -> socket:[56789]
ss -tnp | grep 56789
案例3:"No such file or directory" 但不知道是哪个文件¶
strace -e trace=openat -s 256 ./myapp 2>&1 | grep ENOENT
# openat(AT_FDCWD, "/etc/myapp/config.yaml", O_RDONLY) = -1 ENOENT
# 立即知道程序在找哪个配置文件
延伸阅读¶
man 1 strace— 完整参数说明man 2 ptrace— strace 的底层机制- Brendan Gregg 的 strace Wow Much Syscall — 为什么生产环境应该用 perf/bpf 替代
perf trace— strace 的低开销替代品