VPN 启动时的内核 Oops:值得保留的证据
VPN 启动时的崩溃需要匹配的内核构建和第一份跟踪,而不是最后一行截屏。再次重启前先决定要保存什么。
启动 VPN 客户端只是一个方便的时间戳,而不是原因,这一区别决定了接下来会发生什么。当内核 Oops 恰好出现在那一刻附近时,选择在于收集一份可用的故障报告,还是在生产机器上再诱发五次同样的崩溃。重启前保存四样东西:确切的内核构建、第一份故障跟踪、taint 状态,以及同一分钟内其他所有变化的简短说明。其余都是解读,而解读可以等证据放到安全的地方。
抓住第一次故障,而不是最后一行
日志中的第一个 Oops 才是关键,因为之后每一次故障都可能是已经不健康的内核带来的附带损伤。连同调用跟踪一起复制头部,因为头部包含出错指令和标识代码路径的寄存器状态。从最后一行开始的报告,对一周后读到它的人几乎毫无价值。
如果机器已经重启,上一次启动的内核缓冲区通常仍在日志中,前提是当时已启用持久存储。从保存的文件中读取,而不是重建负载,因为第二次复现可能覆盖你所需的内容。在机器无法跨重启保留日志的地方,串口控制台或 netconsole 是故障唯一可能可见的途径。
读任何符号之前先锁定构建
函数名和偏移只有相对于生成它们的二进制文件才有意义,因此把内核发行版字符串和发行版的构建标识一起记下。来自其他构建的符号文件会十分自信地指向错误的行,这比完全没有符号更糟。保留内核软件包的版本,而不只是上游版本号,因为发行版会做大量向后移植。
树外模块值得在记录中单独占一行,因为带有 taint 的内核限制了维护者能得出的结论。直接读取 taint 值,而不是从模块列表推断。
非零 taint 不是指控,也不会让报告变得毫无价值。它告诉读者故障发生时加载了哪些组件,而某些标志会使报告因策略而非判断被降低优先级。记录数值、模块名称,以及同样的硬件和负载是否曾在没有它们的情况下运行过内核。正是这最后一次比较,把新的回归与长期存在的缺陷区分开来。
把触发因素与缺陷分开
启动隧道通常只是走一条早已存在的路径:网络驱动、队列规则、卸载特性或电源状态。触发因素说明故障何时出现,而不是哪段代码有错;把触发因素当成原因,只会带来一连串什么都修不好的配置改动。
先前可用的镜像是证据,而不是证明。比较时每次启动只改一个组件,并把结果保存在机器之外。
检查维护者实际能用什么
发送之前,像陌生人那样读一遍报告。如果配置、确切步骤和构建标识都在,而凭据已经删除,这份报告就是可用的。如果它只是栈跟踪的截屏,那它会被退回,并附上索要同样信息的请求。内核缺陷排查指南描述了有用的跟踪和匹配的符号是什么样子。
uname -r
cat /proc/sys/kernel/tainted
journalctl -k -b -1 --no-pager | sed -n '/BUG:/,/Call Trace/p'
dmesg -T | grep -A 40 'RIP:'
上面每条命令都只读取状态;如果机器还能应答,前两条应在下次重启前执行。在日志无法持久保存的地方,只有这两条还有机会再起作用。
隐去密钥、密码和无关流量,但保留别人读跟踪所需的函数名和偏移。通过适用于该发行版或内核的报告渠道提交材料,而不是把它贴在公开粘贴板上并附带主机名。然后就等对方提问,而不是继续收集崩溃,因为只有针对匹配构建定位到的故障,才能真正推动报告前进。如果没有任何回复,退路是在备用硬件上进行受控复现,绝不在别人依赖的机器上做。