
解读 OpenAI 工程博客 Core dump epidemiology:Rockset 的诡异崩溃如何从单点 core dump 调试转向群体数据分析,并最终拆出一个坏宿主机和 GNU libunwind 的 18 年竞态。
_Ux86_64_setcontext,也就是 C++ 异常展开链路里的用户态 unwinder。Linux ABI、red zone 和高频信号只是让这个旧问题暴露出来的环境。1NULL,有时 CPU 的栈指针 %rsp 像是被平白错开了 8 个字节。文章明确说,相关代码路径没有 inline assembly、setcontext 或 longjmp,所以它不像普通业务代码能轻易制造出的故障。1| 崩溃群体 | 分布特征 | 后来的根因 |
|---|---|---|
| return-to-null | 分散在多个集群和地理区域,近期频率上升,但没有清晰的基础设施边界。1 | GNU libunwind 在异常展开时的竞态。1 |
| misaligned-stack | 都来自一个区域,有清晰起始时间,而且不会发生在已长期运行的节点上。1 | 单台物理宿主机疑似静默硬件损坏;下线后该类崩溃消失。1 |
longjmp 或 fiber 切换。1ucontext_t,里面放目标寄存器状态,再把这个结构体指针传给 _Ux86_64_setcontext。问题就藏在最后几条汇编里。1ucontext_t 里读出新的 %rsp,把当前栈指针切到新栈;随后又继续通过 %rdi 指向的旧 ucontext_t 读取目标 %rip,也就是下一步要跳去哪里。这个顺序非常危险:一旦 %rsp 已经切走,旧 ucontext_t 就不再属于当前活动栈,也不在 red zone 保护范围内。如果这时信号正好送达,内核会在新 %rsp - 128 附近构造 signal frame,可能覆盖旧结构体里的内容。110^4 次异常。它还用 coarse_thread_cputime_clock 高频发送 SIGUSR2,用来低成本估算每个 query 的 CPU 时间。OpenAI 估算,如果脆弱窗口约 10^-10 秒,信号约每 10^-2 秒 CPU 时间送达一次,每个 exception cleanup handler 或 catch block 输掉竞态的概率约 10^-8。在 fleet 规模下,这足以解释每天十几次 return-to-null 崩溃。1SIGUSR2 handler 调用了 timer_getoverrun,用于统计被合并的信号,这让 handler 消耗更多栈空间。handler 栈用得更深,才更可能踩到已经失去保护的 ucontext_t。负载上来、异常变多之后,18 年老问题终于变成生产事故。1%rsp 之前,先把恢复后的 RIP 和 RDI 暂存到恢复后的栈位置;等真正切到新栈后,从新栈里取回这些值,再 retq。这样 _Ux86_64_setcontext 就不会在切换 %rsp 之后继续从旧栈读取关键控制流数据。12SIGUSR2 用于低成本 CPU 计量。但系统事故经常发生在这些「单项都合理」的边界交汇处。This story was produced automatically by a channel. One sentence is all it takes for Neodrop to keep producing for you.