手把手教你用 strace 和 ltrace 定位生产环境进程卡死问题

生产环境中,进程突然卡死、CPU 飙高或毫无响应,是最让人头疼的问题之一。日志里没有异常,监控指标也看不出端倪,重启虽然能暂时恢复,但根因不明意味着下次还会再犯。这时候,straceltrace 这两个 Linux 下的动态追踪工具,往往能成为破局的关键。本文将从实战角度出发,带你一步步用它们定位进程卡死的真实原因。

为什么 strace 和 ltrace 能定位卡死问题

一个进程卡死,本质上要么是系统调用阻塞(比如读一个永远不来的网络包、等一把永远不释放的锁),要么是库函数调用陷入死循环或阻塞(比如 malloc 卡住、pthread_mutex_lock 等待)。

  • strace 跟踪的是系统调用(syscall),也就是进程和内核之间的交互:文件读写、网络收发、内存映射、信号处理等。
  • ltrace 跟踪的是用户态库函数调用,比如 libc 里的 mallocfreepthread_*str* 等。

两者结合,就能从“内核边界”和“用户态边界”两个层面还原进程到底卡在哪一行。

准备工作:先别急着 attach

在 attach 之前,先做两件事,能省下大量时间:

  1. 确认进程状态:用 ps -o pid,stat,wchan,cmd -p <PID> 查看。STATD 表示不可中断睡眠(通常在等 I/O),S 表示可中断睡眠,R 表示运行中。WCHAN 会告诉你内核里正在等待的函数名,比如 futex_waitpipe_read
  2. 确认权限:attach 需要 ptrace 权限。生产环境常见限制是 kernel.yama.ptrace_scope=1,非 root 只能 attach 自己的子进程。必要时用 root 或调整该参数。

用 strace 定位系统调用层面的卡点

基础用法

最直接的命令是:

strace -p <PID> -T -tt -f

参数含义:

  • -p <PID>:attach 到目标进程。
  • -T:显示每个系统调用的耗时。
  • -tt:显示精确时间戳。
  • -f:跟踪子线程/子进程。多线程程序一定要加,否则只能看到主线程。

如果进程已经完全卡死、没有任何输出,可以加 -e trace=all 并观察最后停留的那个系统调用。卡死时最后一条未返回的调用,往往就是元凶。

实战案例:卡在 futex 等待

假设一个 Java 或 C++ 服务进程 CPU 为 0,但接口无响应。attach 后输出类似:

[pid 12345] 10:22:31.123456 futex(0x7f8a1c002a00, FUTEX_WAIT_PRIVATE, 2, NULL <unfinished ...>

这说明线程在等一把用户态锁(futex)。此时 strace 只能告诉你“在等锁”,但谁持有锁、为什么没释放,需要 ltrace 或 gdb 进一步看。

实战案例:卡在 read/write

如果看到:

[pid 12346] 10:22:33.234567 read(5, <unfinished ...>

说明线程卡在 fd 5 的读操作上。用 ls -l /proc/<PID>/fd/5 看看这个 fd 指向什么——可能是 socket、管道或文件。如果是 socket,再用 ss -tnp 看对端状态,判断是网络对端没发数据,还是本端读逻辑有问题。

常用技巧

  • -c:统计各系统调用耗时,快速找出“哪个调用最慢”。
  • -e trace=network:只看网络相关调用。
  • -e trace=file:只看文件相关调用。
  • -s 256:打印字符串参数时最多显示 256 字节,避免被截断。
  • -o file:输出到文件,避免终端刷屏影响性能。

注意:strace 会显著拖慢进程(尤其是高频 syscall 场景),生产环境建议短时间 attach,或先用 -c 做统计。

用 ltrace 定位库函数层面的卡点

如果 strace 显示进程在用户态空转(没有 syscall),或者卡在 futex 但看不出原因,就该 ltrace 上场了。

基础用法

ltrace -p <PID> -T -tt -f

参数与 strace 类似。ltrace 默认只跟踪动态库调用,如果程序是静态链接或用了 -O2 内联,可能看不到有用信息。

实战案例:卡在 malloc 或锁

假设 strace 显示线程在 futex 等待,ltrace 输出:

[pid 12345] 10:22:31.123456 pthread_mutex_lock(0x7f8a1c002a00 <unfinished ...>

这就明确了:线程在等 pthread_mutex_lock。接下来可以:

  1. gdb -p <PID> attach,执行 thread apply all bt 看所有线程栈,找到持有该 mutex 的线程。
  2. 检查代码中是否有锁顺序不一致导致的死锁,或某个线程持锁后阻塞在 I/O 上。

实战案例:卡在 malloc

如果 ltrace 显示:

[pid 12347] 10:22:35.345678 malloc(1048576 <unfinished ...>

而 strace 同时显示该线程在 mmapbrk 上等待,说明内存分配触发了向内核要内存,可能因为内存碎片或 cgroup 限制导致卡顿。这时要检查 free -m/sys/fs/cgroup/memory/.../memory.limit_in_bytes 以及是否有大量小对象分配。

ltrace 的局限

  • 对 C++ 的 std::mutexstd::condition_variable 等,ltrace 可能只能看到底层 pthread_* 调用,需要结合符号表理解。
  • 对 Go、Rust 等自带运行时的语言,ltrace 效果有限,更适合用 perfgdb 或语言自带的 pprof。
  • ltrace 性能开销比 strace 更大,生产环境慎用,建议只做短时采样。

组合拳:一套完整的排查流程

遇到进程卡死,可以按以下顺序推进:

  1. 看状态ps -o pid,stat,wchan,cmd -p <PID>,判断是 D 还是 S,WCHAN 是什么。
  2. strace 采样strace -p <PID> -T -tt -f -o /tmp/strace.log,跑 5~10 秒后 Ctrl+C,看最后停留的 syscall。
  3. 定位 fd/资源:根据 syscall 的参数,用 /proc/<PID>/fdsslsof 确认资源状态。
  4. ltrace 补充:若卡在 futex 或用户态,用 ltrace -p <PID> -T -tt -f 看库函数。
  5. gdb 收尾gdb -p <PID>thread apply all bt,拿到完整调用栈,结合代码定位根因。
  6. 验证:修复后,用相同场景复现,确认不再卡死。

生产环境注意事项

  • 性能影响:strace/ltrace 会让目标进程变慢数倍甚至数十倍,高并发服务上可能引发雪崩。尽量在低峰期操作,或先用 -c 做轻量统计。
  • 权限与安全:attach 需要 ptrace 权限,容器环境可能被 seccomp 或 apparmor 限制。必要时在容器内安装工具或使用 --cap-add=SYS_PTRACE
  • 日志留存:把输出重定向到文件,方便后续分析,也避免终端卡顿。
  • 别只依赖工具:strace/ltrace 是“现象观察器”,不是“根因分析器”。最终还是要结合代码、配置和业务逻辑判断。

总结

straceltrace 是 Linux 下定位进程卡死的两把利器:一个看内核边界,一个看用户态边界。掌握“先 ps 看状态,再 strace 找 syscall,再 ltrace 找库函数,最后 gdb 拿栈”的流程,大多数卡死问题都能在几分钟内缩小到具体代码行。工具本身不难,难的是在生产环境的压力下保持冷静、按步骤推进。希望这篇实战指南能帮你在下次遇到卡死时,不再只能重启了事。

未经允许不得转载:任鹏个人博客 » 手把手教你用 strace 和 ltrace 定位生产环境进程卡死问题

赞 (0) 打赏

评论 0

取消
  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏