当 Linux 程序报出“找不到文件”“权限不足”,或者只留下一句“连接失败”时,它的日志往往已经把一长串判断收束成一个结果。strace 反向展开这一过程。它呈现用户态与内核交界处的对话:进程发起了什么系统调用、传入了哪些参数,以及内核返回了什么值或错误。[3]
Michael Kerrisk 在 NDC TechTown 2018 的演讲经得住时间,缘由在于他没有把这些输出当作终端里四散的碎屑。Kerrisk 当时已经长期维护 Linux man-pages;他从一个微型程序起步,逐层搭出一套阅读方法:先读懂一行 trace,再解释库与子进程带来的活动,随后过滤,最后才讨论计时或故障注入。[1][2] 2018 年以来,相关命令增加了不少选项,这套方法依然与现行手册吻合。[3][6]
观看时可以留意三次转换。第一次从应用程序的词汇转向内核的词汇,第二次从收集全部活动转向提出一个窄而明确的问题,第三次出现在演讲后段,观察故障的工具开始主动制造故障。三次转换让 strace 从一堵诊断信息之墙,逐渐变成一项受控实验。
图片背景:题图拍下 2018 年 1 月 Kerrisk 出席硅谷 Linux 用户组聚会的情形,时间比 NDC 演讲早 7 个月。[7]
把每一行读成内核说出的一句话
开场演示里,一个“hello world”程序引发的活动远多于源代码透露的内容。在看得见的 write 之前,进程先被执行,动态加载器寻找配置与共享库,文件陆续打开并映射,文件描述符随后关闭。strace 用符号化形式呈现这段往来:调用名称、解码后的参数、返回值;遇到失败时,还会显示 errno 名称与解释文字。[1][2][3]
首要功课是读懂这套语法。openat(..., O_RDONLY|O_CLOEXEC) = 3 这样一行表示调用成功,并返回文件描述符 3。返回 -1 ENOENT 则表示这一次查找没有找到对应路径。程序是否存在故障,单凭这一行还无法判定。加载器和应用程序经常依次探查多个候选路径,直到其中一个成功。失败的调用是一条证据;它在整个序列中的位置,才说明它是否就是故障所在。
接下来更让人意外的是,源代码里的名称与内核调用常常无法整齐对上。C 程序可以调用 exit(),trace 里显示的却是 exit_group();程序可以调用名为 fork() 的库封装,抵达内核交界处的则是 clone();wait() 也会显示为 wait4()。[2] trace 在这里没有自相矛盾,它提醒读者:strace 观察的是 libc 和语言运行时之下的 ABI。用它询问“究竟有什么抵达内核”尤其有效;至于触发调用的应用层意图,仅凭这项工具无法复原。
约 16:00,过滤成为真正的功夫
未经筛选的 trace 包罗万象,却很难帮上忙。Kerrisk 讲到过滤时,问题从“把这个进程显示给我看”转为“显示它与文件、网络、进程、信号或内存映射有关的行为”。分类过滤器如今写成 %file、%network、%process、%memory 等集合,省去了逐项维护 syscall 清单的工作。-P 把活动收窄到一条路径,-y 和 -yy 则将原本匿名的描述符编号连回路径、管道、套接字与协议细节。[2][3]
进程范围同样重要。默认追踪时,真正值得查看的工作有时会被命令交给子进程,随即从视野里消失。-f 会跟随经由 fork、vfork 或 clone 创建的进程;配合 -p 附加到进程时,它还能覆盖所选进程中的每个线程。[3] 这种能力需要明确范围。跟踪整棵 worker tree,很容易把一个有用的问题变成数千行交错输出。
一次实用的 trace 因而要从假设开始。排查配置故障时,先运行最短的可复现命令,用 -o 保存输出;工作若转入子进程,再跟随子进程,同时把过滤范围限定在文件调用。遇到连接被拒绝,改看网络调用并解码描述符。排查卡死时,加入时间戳与调用耗时,并将阻塞调用和周围线程的活动放在一起比较;耗时最长的一行本身不足以定案。Brendan Gregg 给出的生产环境案例也指向同一层意思:短 trace 能迅速暴露隐藏依赖,宽泛追踪则会带来真实开销与解读风险。[5]
到了 26:24,观察者开始注入故障
演讲里最令人难忘的转折,是 Kerrisk 用 -e inject=... 改变指定的 syscall。他的示例瞄准第三次 close,阻止真实调用发生,转而返回 EINVAL。加载器随即进入一条用普通输入很难触发的错误路径。[1][2] 现行 strace 可以注入错误返回值、成功值、信号,以及进入或退出调用时的延迟,并用表达式指定受影响的是第几次调用。[3]
这一功能也改变了工具的用途。trace 可以检验清理、重试与错误报告代码,看看它们能否承受内核拒绝请求。它能让罕见的磁盘、描述符或时序故障稳定复现,同时保留应用程序原有代码。使用范围由此十分清楚:故障注入属于测试技术,生产诊断不在其用途之内。它会有意改变程序行为,伪造成功值的风险甚至高于明确报错。使用时应置于受控环境,面对可丢弃的数据,每次只限定一个明确的 syscall 和一条预期执行路径。
到了 34:21,摘要负责指路,profiler 负责性能分析
临近结尾,strace -c 把一次运行归拢为按 syscall 统计的调用次数、错误数和耗时。这些数据适合用来决定下一步查看哪里。大量时间耗在失败路径查找上的进程,与内核时间集中于 poll、futex 或 connect 的进程,提出的是两类问题。现行手册对测量口径写得很精确:摘要报告内核中的耗时,估算追踪开销,并且可以汇总被跟随的进程。[3]
它依然只是实验里的一个测量工具。传统系统调用追踪会在 tracee 进入和退出调用时将其停下,让 tracer 检查参数与结果;这一工作方式同时解释了可见性与扰动从何而来。[4] 对于用 -f 新启动的进程,现代 strace --seccomp-bpf 能把停顿减少到过滤器选中的 syscall;-p 附加不适用这一模式,某些情况下也会回退到普通追踪。[3] 因此,观察到“程序在 strace 下很慢”,还不足以给出性能诊断。
这份成本也划出了安全的操作范围。在开发机或预发布主机上,即使团队只有一人,追踪一个短命令也很合理。面对繁忙的生产服务,团队需要指定 PID、窄过滤器、短捕获时段、空间充足的输出位置,以及可接受的延迟风险。若调查需要持续、覆盖全机群且低开销的观察,profiler 或为此设计的 tracing system 更合适;当问题收窄到一个进程和一个面向内核的谜团时,再让 strace 回到现场。
Kerrisk 的演讲至今仍耐看,因为它教的是一套顺序,参数只是随之就位。把应用程序的抱怨转成一个关于内核交界的问题;捕获足以回答它的最小进程树;先过滤,再阅读;把错误放回上下文,视作序列中的事件;用摘要选择查看方向,归因仍需更多证据;只有环境能够承受后果时,才主动制造故障。这些习惯建立之后,strace 的输出便从噪声中显出秩序。它是一位严格照实记录的证人,力量恰好来自这种字面忠实:它只陈述实际抵达内核的调用,把应用程序的意图留给调查者判断。[1][2][3]
来源
- NDC Conferences,《Strace: Monitoring The Kernel-User-Space Conversation — Michael Kerrisk》,NDC TechTown 2018——本文内嵌的会议演讲。
- Michael Kerrisk,《System Call Tracing with strace》,NDC TechTown 2018——演讲者幻灯片,内容涵盖 trace 语法、子进程、过滤器、描述符解码、故障注入、摘要与附加风险。
- strace 项目,
strace(1)手册——过滤、跟随进程、描述符解码、注入、摘要与 seccomp-BPF 加速功能的现行语法和行为。 - Linux man-pages,
ptrace(2)——传统系统调用追踪所依赖的 syscall 进入与退出停顿。 - Brendan Gregg,《strace Wow Much Syscall》——独立的生产环境调试案例,以及对追踪开销的提醒。
- strace 项目网站——项目范围、版本发布、文档与上游链接。
- Eugene Zelenko,《Michael Kerrisk》,Wikimedia Commons——2018 年 1 月 Linux 用户组照片的来源页面。