跳转至

strace

是什么 / 解决什么问题

strace 拦截并记录进程的所有系统调用及其参数和返回值。它是调试"程序在做什么"的第一选择工具——不需要源码、不需要重新编译、不需要修改程序。

使用模式

基本用法

# 跟踪命令的所有 syscall
strace ls /tmp

# 跟踪正在运行的进程
strace -p <pid>

# 跟踪进程及其所有子进程/线程
strace -f -p <pid>

按类别过滤

# 只看网络相关
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 导致看不到子进程

# 错误:多线程程序只看到主线程
strace <multi-thread-app>

# 正确:跟踪所有线程和子进程
strace -f <multi-thread-app>

2. 输出混在 stderr 影响判断

# strace 默认输出到 stderr
# 如果程序自己也输出到 stderr,会混在一起

# 解决:输出到文件
strace -o /tmp/trace.log <cmd>

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 的底层实现:

futex(0x7f..., FUTEX_WAIT_PRIVATE, 0, NULL) = 0
# 含义:线程在等待某个锁或条件变量
# 如果耗时很长(-T 看),可能有锁竞争问题

实战案例

案例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 的低开销替代品