strace —— 系统调用追踪
一句话定义
strace 拦截并显示一个进程的所有系统调用(open / read / write / connect / mmap 等)—— 把"用户态 ↔ 内核态"的所有交互打出来。是"应用看着卡死但不知道为啥"的最后手段调试工具。
典型场景
- 应用卡死:
strace -p <PID>看它停在哪个 syscall - 启动失败但没日志:
strace ./myapp看它打开了什么文件 / connect 哪里失败 - DNS 解析慢:
strace -e network ./myapp - 文件找不到:
strace -e openat ./myapp看它在哪些路径找 - 性能 profiling:
strace -c ./myapp统计每个 syscall 的累计耗时
⚠️ strace 让被追踪进程慢 10-100 倍。生产慎用,不要长时间挂在关键服务上。
装
apt install -y strace # Debian / Ubuntu
yum install -y strace # CentOS / RHEL
容器里通常没装(要 CAP_SYS_PTRACE)。
基础用法
strace ./myapp # 直接启动并追踪
strace -p <PID> # attach 到运行中的进程
strace -p <PID> -p <PID2> # 多个进程
输出(每行一个 syscall):
openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=12345, ...}) = 0
mmap(NULL, 12345, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f1234567000
close(3) = 0
openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
...
read(0, "hello\n", 4096) = 6
write(1, "hello\n", 6) = 6
exit_group(0) = ?
每行格式:
syscall(args...) = return_value
负数返回值通常是错误(带 errno 名字):
openat(AT_FDCWD, "/nonexistent", O_RDONLY) = -1 ENOENT (No such file or directory)
核心 8 个 flag
strace -p <PID> # attach 现有进程
strace -f cmd # follow fork(追踪子进程)
strace -e <expr> cmd # 过滤
strace -o file.txt cmd # 输出到文件
strace -c cmd # 统计 summary(不显示每条)
strace -tt cmd # 加时间戳(微秒)
strace -T cmd # 显示每个 syscall 的耗时
strace -s 200 cmd # 显示字符串前 200 字符(默认 32)
过滤 -e —— 让输出可读
不加过滤 strace 一秒输出几千行,根本看不过来。永远加 -e。
按 syscall 名
strace -e openat # 只看 openat
strace -e openat,read,write # 多个
strace -e 'openat,close' # 等价
strace -e '!read,!write' # 排除 read / write
按 syscall 类别
strace -e trace=file # 文件操作(open/stat/chmod/...)
strace -e trace=network # 网络(socket/connect/bind/...)
strace -e trace=process # 进程管理(fork/exec/wait/exit/...)
strace -e trace=signal # 信号
strace -e trace=ipc # IPC(pipe/socketpair/shmget/...)
strace -e trace=memory # 内存(mmap/brk/munmap/...)
strace -e trace=all # 全部(默认)
K8s 排错最常用:
strace -e trace=network -p <PID> # 看网络
strace -e trace=file -p <PID> # 看文件
跟踪子进程 -f
很多应用 fork 出子进程 / 多线程。默认 strace 只看主进程:
strace nginx # 只追踪 master,看不到 worker
strace -f nginx # 含所有子进程 / 线程(**必加**)
输出会带 PID 前缀:
[pid 1234] read(0, ...) = 5
[pid 1235] write(1, ...) = 5
attach 现有进程 -p
strace -p 1234 # attach 到 PID 1234
strace -fp 1234 # 含线程
strace -tt -e network -p 1234 # attach + 过滤
Ctrl-C 退出 detach(进程继续跑)。
注意:strace attach 时会让目标进程慢 10-100 倍。生产服务慎用,特别是延迟敏感的(如 etcd / apiserver)。
实用场景
场景 1:应用卡死,看停在哪
$ ./myapp
# 卡住,没输出
# 另一个 shell
$ pgrep myapp
12345
$ strace -p 12345
# 停在某个 syscall
# read(3, ...) ← 正在等读 fd 3
# 或者
# futex(0x..., FUTEX_WAIT_PRIVATE, ...) ← 等锁
# 或者
# connect(4, {sa_family=AF_INET, sin_port=htons(443), ...}, 16) ← TCP 建连
futex 在等:通常是 mutex 死锁、condition variable 等通知。 read 在等:等 stdin / socket / pipe。 connect 在等:网络问题。
场景 2:启动失败、没日志
$ ./myapp
Segmentation fault ← 啥都没说
$ strace ./myapp 2>&1 | tail -30
# 看最后几个 syscall,通常能看出
# openat(AT_FDCWD, "/etc/myapp/config.yaml", O_RDONLY) = -1 ENOENT
# write(2, "config not found", 16)
# exit_group(1)
最后几行通常给你错误根因。
场景 3:找文件在哪些路径找
strace -f -e openat ./myapp 2>&1 | grep ENOENT
# openat(AT_FDCWD, "/etc/ld.so.preload", O_RDONLY|O_CLOEXEC) = -1 ENOENT
# openat(AT_FDCWD, "/etc/myapp/config.yaml", O_RDONLY) = -1 ENOENT
# openat(AT_FDCWD, "./config.yaml", O_RDONLY) = -1 ENOENT
# openat(AT_FDCWD, "/usr/local/etc/myapp/config.yaml", O_RDONLY) = -1 ENOENT
明白应用在哪些路径找 config。把 config 放对地方就好。
场景 4:DNS 解析慢
strace -tt -e trace=network ./myapp 2>&1 | head -30
# 14:30:00.123 socket(AF_INET, SOCK_DGRAM, ...) = 3
# 14:30:00.124 connect(3, {sa_family=AF_INET, sin_port=htons(53), ...}, 16) = 0
# 14:30:00.125 sendto(3, "...", 64, 0, ...) = 64
# 14:30:05.130 recvfrom(3, ...) ← 5 秒后才回,DNS 慢
时间戳显示 DNS 查询用了 5 秒。
场景 5:性能 profile:-c
strace -c ./myapp
跑完输出统计:
% time seconds usecs/call calls errors syscall
------ ----------- ----------- --------- --------- ----------------
45.23 0.123456 100 1234 50 read
30.12 0.082345 80 1029 write
15.06 0.041234 200 206 connect
...
知道哪类 syscall 最耗时。
或者 attach:
strace -c -p <PID> # Ctrl-C 之后输出 summary
训练营场景
1. K8s 容器启动失败
# pod 报 CrashLoopBackOff
kubectl logs my-pod --previous # 没明显日志
# 进节点
ssh m4
PID=$(crictl inspect <last-container-id> | jq '.info.pid')
# 重新启动容器,并在它启动瞬间 strace
# (困难因为容器进程已经退出,需要在容器再次启动时及时抓)
# 或者:用 kubectl debug 起一个相同镜像 + strace
kubectl debug my-pod -it --image=ubuntu --share-processes -- bash
# 在里面 apt install strace 后 strace -p <PID>
2. kubelet 卡住
strace -p $(pgrep kubelet) -ttt -T -e trace=network 2>&1 | head -50
# 看 kubelet 在跟谁 connect、是不是在等 apiserver 回应
3. 应用读不到 ConfigMap
# 进 pod
PID=$(crictl inspect ... | jq '.info.pid')
nsenter -t $PID --mount strace -e openat -p $PID 2>&1 | grep config
# 看它在哪些路径找 config 文件
4. 内存暴涨
strace -e trace=memory -p <PID> 2>&1 | head -100
# 看 mmap / brk 是不是疯狂调用
看字符串完整 -s 200
默认 strace 把字符串截断到 32 字符:
write(1, "Hello, this is a very long messa"..., 50) = 50
加 -s 200(或 -s 0 不限制)看完整:
strace -s 200 -e write -p 1234
排查"应用发出什么内容"必加。
strace vs ltrace vs perf
| 工具 | 看什么 | 性能开销 |
|---|---|---|
strace | 系统调用 | 大(每次 syscall 暂停) |
ltrace | 库函数调用(libc 等) | 大 |
perf trace | 类似 strace 但基于 eBPF | 小 |
bpftrace | 任意 kernel hook | 小 |
新内核(5.0+)有 eBPF 的 perf trace / bpftrace,性能开销小、生产更安全。但 strace 最普及、最易用。
常见踩坑
坑 1:strace 让进程慢 100 倍
strace -p $(pgrep nginx)
# nginx 处理能力骤降
strace 每个 syscall 都暂停进程、复制数据、写输出。生产高 QPS 服务不要长时间挂着。
应急选 eBPF 工具或者抓样本(短时间 detach):
strace -p <PID> -e trace=network &
STRACE_PID=$!
sleep 5
kill $STRACE_PID # 5 秒后 detach
坑 2:容器里跑 strace 报权限
strace -p 1234
# strace: ptrace(PTRACE_ATTACH, ...): Operation not permitted
K8s pod 默认没 CAP_SYS_PTRACE。两个办法:
- pod spec 加 capability:
securityContext: capabilities: add: ["SYS_PTRACE"] - 在节点上跑 strace(节点有 root):
ssh m4 strace -p <container-pid>
坑 3:看不到子进程
strace nginx
# 只看到 master 的几个 syscall,然后没动了
nginx 主进程 fork 出 worker 之后自己不工作了。必加 -f:
strace -f nginx
坑 4:strace 输出太多无法消化
strace -p 1234
# 一秒几千行,看不过来
永远过滤:
strace -e openat -p 1234 # 只看 openat
strace -e trace=network -p 1234 # 只看网络
或者写文件后慢慢看:
strace -p 1234 -o /tmp/strace.log &
sleep 5
kill %1
less /tmp/strace.log
坑 5:误判 syscall 错误
openat(AT_FDCWD, "/etc/x.conf", O_RDONLY) = -1 ENOENT
看到 ENOENT 第一反应"配置缺失"——但很多应用就是这样"探测"路径:先试 /etc/x.conf、不在的话再试 /usr/local/etc/x.conf、再试 ./x.conf。
只有当应用用完所有路径仍然没找到才是真的问题。看上下文。
坑 6:-c 在 attach 模式下要等结束
strace -c ./myapp
# myapp 结束后才打统计
strace -c -p 1234
# 必须 Ctrl-C 退出(attach 模式)才打统计
不到结束看不到统计。
坑 7:strace 在静态链接二进制上输出不一样
静态链接的二进制(Go 程序常见)不通过 libc loader,看到的 syscall 路径完全不同——没有那一堆加载 libc / libpthread 的 openat。
不算坑,但Go 程序的 strace 一开始看着特别干净,习惯了 C 程序看 strace 的人容易困惑。
坑 8:strace 影响 close / read 的语义
少数情况下,strace 拦截 syscall 会让 race condition / timing 发生改变,有 bug 在 strace 下不出现、不在时出现——经典海森堡 bug。
修法:换 eBPF 工具(更低侵入),或者用日志。
strace 的"3 类排错口诀"
| 现象 | strace 滤什么 |
|---|---|
| 卡住不动 | -p <PID> 直接看停在哪 |
| 启动失败 | strace -e openat,connect ... 看最后几行 |
| 网络慢 | strace -tt -T -e trace=network 看耗时 |
| 文件找不到 | strace -e openat 2>&1 | grep ENOENT |
| 内存暴涨 | strace -e trace=memory |
| 突然 segfault | strace -e signal 或看末尾几行 |