---
title: "核心转储流行病学：修复一个 18 年的老 bug"
title_en: "Core dump epidemiology: fixing an 18-year-old bug"
source: "OpenAI"
source_url: "https://openai.com/index/core-dump-epidemiology-data-infrastructure-bug"
published_at: "2026-06-30T00:00:00.000Z"
language: "zh-CN"
topics: ["openai"]
tags: ["OpenAI","Engineering"]
review_status: "unreviewed"
---

# 核心转储流行病学：修复一个 18 年的老 bug

> OpenAI 工程师通过群体分析方法，在 Rockset 数据基础设施中识别并修复了两个看似相关的崩溃 bug：一个由硬件损坏引起，另一个是 GNU libunwind 中隐藏了 18 年的竞态条件。

## 内容摘要

OpenAI 的 ChatGPT 数据基础设施组件 Rockset 出现频繁崩溃，表现为函数返回时跳转到错误地址。工程师最初试图通过仔细检查几个核心转储来调试，但未能找到原因。后来他们转变思路，像流行病学家一样收集并分析所有崩溃数据，发现实际上存在两个独立的 bug：一个是由 Azure 主机硬件损坏导致的栈指针偏移，另一个是 GNU libunwind 中一个存在 18 年的竞态条件，该竞态条件

## 为什么值得关注

这个案例展示了在复杂系统中，看似相同的故障现象可能由完全不同的原因引起。通过群体数据分析而非逐个排查，可以快速分离问题根源。同时，它揭示了底层库中隐藏多年的 bug 如何在高负载、高信号频率的特定条件下被触发，对构建高可靠性基础设施具有重要启示。

## 核心要点

- OpenAI 在 Rockset 服务中观察到两种崩溃：返回地址为空和栈指针偏移。
- 最初将所有崩溃视为同一问题，导致调试陷入僵局。
- 通过自动分析所有核心转储，发现崩溃分为两类：硬件损坏和 libunwind 竞态条件。
- 硬件 bug 由单个 Azure 主机 CPU 损坏引起，隔离后消失。
- libunwind 的竞态条件存在于 18 年，在异常展开时信号覆盖了栈上的上下文结构。
- 修复方法是用 libgcc 的 unwinder 替换 libunwind，并向上游提交了补丁。

[跳到主要内容](#main)

* [研究](https://openai.com/research/index/)
* 产品
* [企业](https://openai.com/business/)
* [开发者](https://openai.com/api/)
* [公司](https://openai.com/about/)
* [基金会（在新窗口打开）](https://openaifoundation.org/)

登录[试用 ChatGPT（在新窗口打开）](https://chatgpt.com/?openaicom-did=6b4e5ba4-f156-469a-86e7-c924dccbee69&openaicom%5Freferred=true)

* 研究
* 产品
* 企业
* 开发者
* 公司
* [基金会（在新窗口打开）](https://openaifoundation.org/)

核心转储流行病学：修复一个 18 年的老 bug | OpenAI

2026 年 6 月 30 日

[工程](https://openai.com/news/engineering/)

# 核心转储流行病学：修复一个 18 年的老 bug

用群体分析来调试数据基础设施中棘手的崩溃问题。

听文章21:48

分享

第一次调试尝试：仔细检查几个核心转储

* [第一次调试尝试：仔细检查几个核心转储](#first-debugging-attempt-carefully-examining-a-few-core-dumps)
* [堆栈中的线索](#clues-from-the-stack)
* [医生还是流行病学家？](#doctor-or-epidemiologist)
* [清理数据](#cleaning-the-data)
* [Bug #1：坏主机](#bug-1-the-bad-host)
* [异常处理是一种动态控制转移](#exception-handling-is-a-dynamic-control-transfer)
* [推翻最后一个假设](#undoing-one-last-assumption)
* [Bug #2：libunwind 的 bug](#bug-2-the-libunwind-bug)
* [为什么核心转储伪装成普通的错误返回](#why-the-cores-masked-as-ordinary-bad-returns)
* [一个单指令的竞态窗口](#a-single-instruction-race-window)
* [为什么 libunwind 的 bug 现在才出现？](#why-did-the-libunwind-bug-appear-now)
* [群体诊断的力量](#the-power-of-a-population-level-diagnosis)

* [第一次调试尝试：仔细检查几个核心转储](#first-debugging-attempt-carefully-examining-a-few-core-dumps)
* [堆栈中的线索](#clues-from-the-stack)
* [医生还是流行病学家？](#doctor-or-epidemiologist)
* [清理数据](#cleaning-the-data)
* [Bug #1：坏主机](#bug-1-the-bad-host)
* [异常处理是一种动态控制转移](#exception-handling-is-a-dynamic-control-transfer)
* [推翻最后一个假设](#undoing-one-last-assumption)
* [Bug #2：libunwind 的 bug](#bug-2-the-libunwind-bug)
* [为什么核心转储伪装成普通的错误返回](#why-the-cores-masked-as-ordinary-bad-returns)
* [一个单指令的竞态窗口](#a-single-instruction-race-window)
* [为什么 libunwind 的 bug 现在才出现？](#why-did-the-libunwind-bug-appear-now)
* [群体诊断的力量](#the-power-of-a-population-level-diagnosis)

_作者：Nathan Bronson，技术团队成员_

OpenAI 的模型和智能体越来越依赖可扩展的数据基础设施，以便在推理时（也就是模型思考你的问题的时候）搜索相关数据。其中一些服务是用 C++ 编写的，这种语言对系统有底层控制能力，让我们能最大化性能并最小化内存使用。随着规模扩大，这些效率优势很重要，但 C++ 缺乏内存安全性，意味着 bug 可能会通过写入错误或不存在的内存地址导致崩溃。

几个月前，我们在 Rockset 服务中观察到一些崩溃。Rockset 是我们 ChatGPT 数据基础设施中一个定制化的部分，对许多数据插件和搜索对话历史至关重要。在每次崩溃中，一个正常的 C++ 函数似乎执行完毕，然后返回到了一个错误的地址，导致内核停止程序，因为指令指针不再指向代码。有时，栈帧中的返回地址槽是 NULL。有时，栈指针 CPU 寄存器本身似乎偏移了 8 个字节，就好像 `%rsp` 在正常执行过程中被莫名其妙地减少了。在这两种情况下，崩溃都发生在返回时。

这些不是应用程序代码的正常故障模式。一个只命中保存的返回地址的随机写入是可能的，但极不可能。一个不涉及内联汇编、`setcontext` 或 `longjmp`（我们都没用）的 bug 导致 `%rsp` 偏移 8 个字节，这就更奇怪了，因为编译后的代码只在函数序言和尾声里直接调整这个寄存器。我们（或 ChatGPT）能想到的每一个假设都有强有力的证据反对它，所以这个 bug 看起来不可能。

我们最初以为是一个问题，结果发现是两个不相关的 bug，碰巧同时被发现。首先，一个 Azure 主机上的静默硬件损坏，CPU 就是不能正确做数学运算。其次，GNU libunwind 中一个 18 年的竞态条件，这是一个广泛使用的开源库中未被注意到的 bug。

这篇文章讲述了我们如何通过像流行病学家一样思考，并构建一个关于所有崩溃的高质量数据集，来识别和修复看似无法解释的崩溃。

## 第一次调试尝试：仔细检查几个核心转储

首先，让我们深入了解 Rockset。它是一个用于搜索和实时分析的云原生数据系统，我们在 OpenAI 内部用于许多场景，比如同步连接器（Rockset 在 2024 年被 OpenAI 收购）。流式更新用于维护工作区知识库的最新索引，这样 ChatGPT 在回答问题或执行操作时可以搜索相关信息。

Rockset 的执行层是用 C++ 编写的。C++ 语言提供对 CPU 的底层访问，这对性能和效率有好处，但这意味着应用程序 bug 可能导致无效内存访问和段错误。为了帮助追踪这些问题，我们使用 folly 的致命信号处理程序在崩溃发生时记录堆栈跟踪，并将相应的核心转储（程序崩溃时状态的快照）上传到 Azure blob 存储以供后续分析。Rockset 的所有查询处理副本都是复制的，这最小化了崩溃对客户端的影响。然而，每个段错误都对应一个需要修复的 bug，以达到我们的可靠性和质量目标。

我们最初的方法是把这些核心转储当作传统的调试问题来处理：非常仔细地检查几个核心转储，形成假设，然后逐一排除。

大多数崩溃发生在一个叫 `DocumentTree::updateDocument` 的方法中。在这些崩溃中，看起来 `updateDocument` 调用了一个未知函数 X，堆栈在 X 活动时被损坏，然后 X 返回到了一个不是可执行代码的地址。在某些情况下，X 刚被弹出的帧看起来是有效的，只是它保存的返回地址是 NULL。在其他情况下，栈指针本身看起来不对，但下一个有效的帧似乎仍然是 `updateDocument`。

我们不知道堆栈是什么时候被损坏的，这留下了巨大的搜索空间。`updateDocument` 是一个很大的方法，经历了大量的内联，所以 X 的候选者数量多得吓人。

这是我们的 C++ 代码中的 bug 吗？编译器或链接问题？运行时库的问题？关于信号传递或上下文切换的 Linux 内核 bug？还是更罕见的问题？如果这是一个随机写入，为什么我们的 ASAN 测试环境没有捕获到？

我们试图用应用程序级别的日志来识别所有出现的问题，但堆栈损坏的 bug 很难仅从日志中分类，因为记录的堆栈跟踪本身就被损坏或丢失了。我们无法构建一个既没有误报也没有漏报的日志查询。我们手动检查了更多的核心转储，找到了一些额外的例子，但这个过程太费力了，无法给我们一个可信的数据集。

在调查的这个阶段，我们（错误地）排除了硬件 bug，因为我们在多个区域和多种硬件类型上都看到了崩溃，所以我们仍然在寻找纯软件原因。有几天，我们深入研究了单个 `%rsp` 偏移的崩溃，使用堆栈和寄存器内容重建崩溃前的历史。这产生了一些可能的线索，但由于我们没有放弃最初的所有 bug 都有相同原因的结论，这并没有让我们摆脱困境。

## 堆栈中的线索

在进入我们调查的转折点之前，有必要解释一下我们从核心文件中提取了什么样的信息。

Rockset 是用 `-fno-omit-frame-pointer` 编译的，所以活动的栈帧总是可以通过 `%rbp` 访问，调用者形成一个帧指针的链表。

在 Linux `x86_64` 上，AMD64 System V ABI 还在 `%rsp` 下方保留了 128 个字节作为红区。这个区域可供用户空间代码使用，重要的是，作为 ABI 契约的一部分，内核承诺在传递信号时不会覆盖它。

红区对我们调试返回后崩溃至关重要，因为它保存了返回前的一些信息。当触发 `SIGSEGV` 时，folly 的致命信号处理程序在崩溃线程的堆栈上运行。不再活动的栈帧（因为它们的函数已经返回）会被信号处理程序覆盖，除了最后 128 个字节。这就是为什么我们可以说“X 刚被弹出的栈帧看起来是有效的，只是返回地址是 NULL”。红区保存了一些不活动的帧，或者有时只是一个不活动帧的尾部。

![堆栈图显示损坏的栈帧可以覆盖返回地址并导致崩溃。](https://images.ctfassets.net/kftzwdyauwt9/3eBjjDfMl3Ja4JE1V8skqx/99caec095d4669342d2a013f0b6befda/Corrupted-stack_crashes_light_desktop.svg?w=3840&q=90)

我们发现了一个堆栈偏移的崩溃，其中涉及的所有函数都非常小。这让我们看到，`%rsp` 在一个相对简单的函数执行过程中变得偏移了，并且之后更多的调用成功了。程序只有在活动函数最终试图返回时才崩溃。这些代码路径都没有使用异常、内联汇编、`setcontext` 或 `longjmp`，所以如果栈指针真的像核心转储显示的那样改变了，用户空间代码中没有合理的 bug 能解释这个问题。

这把我们推向了内核。

Rockset 比大多数程序更积极地使用信号。查询执行被分解成许多交换数据的轻量级任务。这对于高效处理高 QPS 工作负载很重要，但它使得每个查询的 CPU 记账变得棘手，因为许多查询的工作被多路复用到同一个线程池。

我们的解决方案是我们称之为 `coarse_thread_cputime_clock` 的东西，它足够廉价地近似 `clock_gettime(CLOCK_THREAD_CPUTIME_ID, ...)`，以便在每个任务边界进行采样。`timer_create` API 可用于基于几种时间流逝的概念（包括 CPU 时间的累积）来调度周期性信号传递。我们调度一个信号（SIGUSR2）每几毫秒 CPU 时间传递一次，此时信号处理程序更新一个线程局部值。即使许多任务在执行时没有看到粗略时钟前进，但将所有增量相加会产生查询实际 CPU 时间的无偏估计。

因为我们如此频繁地传递信号，一个关于上下文切换或信号传递的罕见内核 bug 似乎很合理。我们花时间阅读 bug 报告、内核源代码和 Azure 特定的内核补丁。我们尝试了压力测试。我们没能找到任何看起来相关的东西。

那时我们决定退一步，尝试不同的方法。

## 医生还是流行病学家？

调试这样的问题有两种广泛的方法。

一种是像医生一样：专注于一个病人，做很多测试，并尝试从详细的证据中诊断单个病例。

另一种是更像流行病学家：观察整个群体，并询问是否存在单个病例无法揭示的模式。这个 bug 是在特定版本开始的吗？它是否与一个硬件 SKU（特定的 CPU 和服务器型号）、一个区域或一个内核版本相关？在看起来像一个综合征的内部是否隐藏着多个不同的集群？

我们之前主要是医生模式。关键的转变是决定我们需要收集高质量的群体数据。

## 清理数据

我们之前自动找到所有问题实例的尝试失败了，因为我们试图使用日志的文本搜索。核心转储本身有更多信息，但手动查看它们无法扩展。我们决定投入精力构建一个可以自动分析核心转储的管道。

我们让 ChatGPT 写了一个脚本，下载每个核心文件的前缀，提取寄存器，使用日志过滤已知的误报，并自动将崩溃标记为返回空、堆栈偏移或其他。然后我们在过去一年中所有的生产 Rockset 核心转储上并行运行这个脚本。

这是转折点。

一旦我们有了干净的数据集，相关性立即显现。我们之前当作一个奇怪 bug 处理的东西，实际上是_两个_独立的崩溃群体。

返回空的核心转储分布在许多集群和地理区域。它们的频率最近增加了，但没有明确的开始日期和清晰的基础设施边界。

堆栈偏移的崩溃看起来完全不同。它们都来自一个区域，有明确的开始日期，并且从未在运行了很长时间的节点上发生。尽管它们涉及多个 Azure VM（云中托管的虚拟机），但模式看起来像是一台物理机器有坏硬件，导致落在它上面的任何 VM 出现问题。

![按集群随时间变化的崩溃率点图，显示大多数崩溃集中在集群 2、3 和 6，在周期结束时集群 1 有一个峰值。](https://images.ctfassets.net/kftzwdyauwt9/2Cu7fZUNQdMpqwby9xRFkE/1d171387051612f2f8fb5e352764a2a2/Crash_rate_by_cluster_light_desktop.svg?w=3840&q=90)

那一刻我们意识到，我们一直在心理上混淆了两个 bug。因为我们一直在混合来自两个 bug 的反例，我们无法找到一个单一的连贯解释。

## Bug #1：坏主机

有了干净的 Kubernetes 节点和时间戳列表，我们能够将堆栈偏移的崩溃追溯到单个物理主机，这很容易加入黑名单。

我们无法在该主机上的受控环境中重现寄存器损坏，即使在几周的压力测试之后也是如此。然而，一旦有问题的主机被停止服务，堆栈偏移的崩溃就消失了。

移除坏主机不是一个永久性的解决方案，因为它不能防止同样问题的新发生。然而，我们可以更改软件，以便如果类似问题再次发生，可以轻松检测和处理。我们改进了致命信号处理程序，包含寄存器状态，这样我们只能从日志中检测复发（不需要核心转储）。我们更改了控制平面，以便 VM 通常被重用而不是回收，这使得在我们的基础设施堆栈层面更容易检测坏节点。我们还更新了我们的运行手册（和我们团队的思维模型），以包括这种可能性。

随着坏主机崩溃被分离出来，剩下的返回空核心转储变得更容易推理。之前我们排除了异常展开，因为我们以为有反例：在肯定不使用异常的代码路径中的崩溃。但那些反例都来自硬件损坏集群。

一旦我们带着这个想法重新审视剩下的核心转储，我们发现这个结论完全反了：崩溃都发生在异常展开期间。

## 异常处理是一种动态控制转移

当 C++ 抛出异常时，运行时必须发现哪个 catch 块应该接收它，以及哪些析构函数或清理处理程序应该沿途运行。编译器发出这些元数据，但实际的匹配在运行时动态发生。

异常展开实际上不是由调用 `throw` 的函数执行的，而是由生成的编译代码调用的辅助函数执行的。这些运行时例程检查堆栈，获取堆栈上函数的元数据，动态查找清理处理程序和 catch 块，然后将控制转移到其中一个位置。转移控制包括展开所有中间的栈帧（包括辅助函数的栈帧）。

操作上，这更接近于 `longjmp` 或协程切换，而不是普通的调用和返回。被调用者保存的寄存器必须恢复，以及栈帧寄存器 `%rbp` 和 `%rsp`。

我们的二进制文件链接了两个包含执行 C++ 异常展开的函数的库：libgcc 和 GNU libunwind。动态链接器选择了 GNU libunwind 的定义。这让我们很惊讶；我们原本期望 libgcc 的实现会因为符号版本控制规则而胜出；然而，检查正在运行的二进制文件显示情况并非如此。

## 推翻最后一个假设

此时，我们的工作假设发生了变化，因为我们放松了另一个假设，这个假设是在我们认为只有一个 bug 时做出的。

也许我们看到的不是普通函数返回 NULL。也许我们看到的是一个展开转移——实际上是一个 `setcontext` 风格的寄存器恢复——其中目标指令指针在控制转移之前变成了 NULL。换句话说，是展开库提供了错误的数据，而不是堆栈上错误的返回地址槽。

这极大地缩小了问题范围。要么是 GNU libunwind 计算了错误的目标状态，要么是它计算了正确的状态，但在应用之前被某些东西损坏了。

我们阅读了 GNU libunwind 的源代码，发现它在堆栈上合成一个 `ucontext_t`，填充清理处理程序帧所需的寄存器状态，然后将指向该结构的指针交给一个内部汇编例程：`_Ux86_64_setcontext`。

此时我们有了所有的碎片。

合成的 `ucontext_t` 位于 `_Ux86_64_setcontext` 执行期间被展开的栈帧之一中。`_Ux86_64_setcontext` 在改变 `%rsp` 之后是否从该结构读取数据？此时该结构不再属于活动堆栈？那会使它容易受到信号传递的覆盖，比如我们频繁的 `SIGUSR2`。

## Bug #2：libunwind 的 bug

答案是肯定的。

以下是我们使用的 GNU libunwind 版本中 `_Ux86_64_setcontext` 的最后六条指令，主要由从内存加载到目标寄存器的 `mov` 指令组成：

#### 纯文本

`1

74:        mov    UC_MCONTEXT_GREGS_RSP(%rdi),%rsp

2

75:

3

76:        /* 将返回地址压入堆栈 */

4

77:        mov    UC_MCONTEXT_GREGS_RIP(%rdi),%rcx

5

78:        push   %rcx

6

79:

7

80:        mov    UC_MCONTEXT_GREGS_RCX(%rdi),%rcx

8

81:        mov    UC_MCONTEXT_GREGS_RDI(%rdi),%rdi

9

82:        retq

`

（`%rdi` 指向堆栈分配的 `ucontext_t`，`UC_MCONTEXT_*` 宏只是展开为存储特定寄存器的固定偏移量。）

第一条指令是竞态窗口的开始。它更新 `%rsp` 以指向活动堆栈的新底部。一旦发生这种情况，由 `%rdi` 指向的结构就不再是活动堆栈（或红区）的一部分，并且不再对内核不可访问。

通常这不会引起问题，但如果信号恰好在正确（或错误？）的时刻到达，内核将在 `%rsp-128` 处构建信号帧。这可能会覆盖 `%rdi` 指向的内存。

如果这发生在下一条指令读取 `UC_MCONTEXT_GREGS_RIP(%rdi)` 之前，那么恢复的指令指针可能会被损坏。在我们的崩溃中，它变成了 NULL。

这就是 bug。

## 为什么核心转储伪装成普通的错误返回

这个汇编也解释了一个让我们困惑的观察：为什么函数 X 在前一个栈帧的返回地址槽中有一个 NULL。

`setcontext` 被编写为恢复所有寄存器，包括 `%rdi`，所以它不能在控制转移的最后时刻使用该寄存器来读取 `UC_MCONTEXT_GREGS_RIP(%rdi)`。相反，它更早地读取该值，将其保存到堆栈，恢复更多寄存器，然后使用 `retq` 读取保存的值并转移控制。

在核心转储中看起来像“一个函数返回到了 NULL”的东西，实际上是“展开器在堆栈上合成了一个目标返回地址，但该目标在转移完成之前被损坏了。”我们假设返回地址槽的损坏一定发生在原地，因为我们不知道有任何地方（可损坏的）数据被故意写入返回地址槽。

## 一个单指令的竞态窗口

使这个 bug 看起来荒谬的是这个竞态窗口有多窄。在这种竞态条件中，外部事件（信号）需要在另一个线程采取的两个步骤之间发生。这些步骤彼此越接近，竞态条件发生的可能性就越小。

在这种情况下，脆弱窗口实际上只有一个指令宽！信号必须在 `%rsp` 被改变之后，但在下一条指令加载 `%rip` 之前传递。在现代超标量乱序 CPU 上，几个这样简单的指令可以在一个周期内运行，所以竞态窗口大约是一百皮秒。

当我们发现这个竞态时，我们的第一反应是它一定太罕见了，无法解释观察到的崩溃率。我们在整个集群中每天看到超过十几个返回空崩溃。一个在异常清理期间的单指令竞态真的能解释这个吗？

我们转向费米估算。如果脆弱窗口大约是 10^{-10} 秒，而 SIGUSR2 每 10^{-2} 秒 CPU 时间到达一次，那么每个异常清理处理程序或 catch 块有大约 10^{-8} 的概率输掉竞态。

Rockset 使用异常作为其内部摄取背压机制的一部分。单个过载的主机每秒可以抛出大约 10^4 个异常。这意味着使用背压的主机的平均故障间隔时间是 10^4 秒，或者每几小时一次崩溃。在集群规模上，这足以解释观察到的崩溃频率。

## 为什么 libunwind 的 bug 现在才出现？

GNU libunwind 的 bug 很老——超过 18 年，存在于第一个支持 C++ 异常展开的 `x86_64` 版本中。

那么为什么它现在才出现？

崩溃率大致与抛出多少异常和传递多少信号成正比。它也取决于信号处理程序消耗多少堆栈。

Rockset 在所有三个轴上都不同寻常。我们作为正常过载控制的一部分以高速率抛出异常；由于 `coarse_thread_cputime_clock`，我们异常频繁地传递 `SIGUSR2`；今年早些时候，我们通过添加对 `timer_getoverrun` 的调用使 `SIGUSR2` 处理程序使用了更多堆栈，以便我们可以处理合并的信号。

最后一个变化似乎很重要。如果处理程序使用足够少的堆栈，它可能不会到达并覆盖过时的 `ucontext_t` 内存。在那个变化之前，我们根本没有观察到这些崩溃。变化之后，速率保持较低，直到我们为一些强调背压机制的用例增加了负载。

换句话说，libunwind 的 bug 一直存在，但我们的异常率、信号率和处理程序堆栈使用量的乘积直到最近才越过阈值，变得在操作上可见。

这个机制也解释了为什么硬件 bug 和 libunwind bug 都主要导致 `DocumentTree::updateDocument` 崩溃。libunwind 导致的崩溃特别集中在这个方法上，因为当我们抛出异常来施加数据摄入反压（ingest backpressure，即控制数据流入速度的机制）时，这个方法总是处于活跃状态。它也是 `%rsp` 未对齐崩溃的高发区，因为出问题的硬件节点属于我们用于批量数据摄入的 SKU（库存单位，这里指硬件型号），而这类节点的大部分 CPU 时间都花在这个方法里。

我们立即采取的缓解措施是把 GNU libunwind 换成 libgcc 的 unwinder（解卷器，用于在程序崩溃时回溯函数调用栈的工具）。这个替换本身就很值得：libgcc 的实现经过大量优化，减少了锁竞争（lock contention，多个线程争抢同一把锁导致的性能问题），这在扩展到大型虚拟机时非常重要。

我们还向上游的 GNU libunwind 提交了一个可独立运行的复现程序和一个[修复⁠(在新窗口中打开)](https://github.com/libunwind/libunwind/commit/a9b9293b286c14b9ed19db501aa347b46edd8a28)，并验证了其他解卷器没有类似问题。

## 群体诊断的力量

这次调试之旅让我们学到了很多关于动态链接、DWARF 解卷元数据（DWARF unwind metadata，一种用于描述程序运行时栈帧信息的标准格式）、Linux 信号传递、System V ABI（应用程序二进制接口，定义了程序如何与操作系统交互）以及 C++ 异常机制的细节。但最重要的教训比这些都简单。

最关键的一步不是聪明的汇编代码阅读或对细节的深入了解，而是构建一个高质量的数据集。没有这个数据集，我们就把两种不同的现象混为一谈，试图在混乱中理出头绪。一旦我们有了准确、完整的群体数据，问题的结构就变得一目了然：一类崩溃属于有问题的硬件主机，另一类属于 libunwind 中的竞态条件（race condition，多个线程或进程因执行顺序不确定而导致的错误）。数据质量提高了，调试就变得容易了。

对于像 Rockset 这样的基础设施系统来说，这一点非常重要。这次调查进一步坚定了我们对深度监控、自动化调查和持续改进运维工具的承诺。可靠性不仅仅是事后修复 bug，更是要构建数据、工作流程和技能，把看似不可能解决的问题变成可诊断、可解决的问题。

* [2026](https://openai.com/news/?tags=2026)

## 作者

Nathan Bronson，技术团队成员

## 继续阅读

[查看全部](https://openai.com/news/)

![Tax Agent > Art Card](https://images.ctfassets.net/kftzwdyauwt9/6ojJ6B55QUlmNdaLifgMkQ/22e429ec7b3fdd119a2499358af899b7/Art_Card.png?w=3840&q=90&fm=webp)

[用 Codex 构建能自我改进的税务代理工程2026年5月27日](https://openai.com/index/building-self-improving-tax-agents-with-codex/)

![codex windows > art card](https://images.ctfassets.net/kftzwdyauwt9/6ZvTl8ZL23BOhoI6jz0EmR/49d6038b9f92773d4f866f4bfacabdbf/Art_Card__5_.png?w=3840&q=90&fm=webp)

[构建安全有效的沙箱，让 Codex 在 Windows 上运行工程2026年5月13日](https://openai.com/index/building-codex-windows-sandbox/)

![MRC 1_1](https://images.ctfassets.net/kftzwdyauwt9/IRqiqOUeNlFne8NPTbELM/9ab024f4581e7065eaf42aa18d14b724/Art_Card.png?w=3840&q=90&fm=webp)

[用超级计算机网络加速大规模 AI 训练工程2026年5月5日](https://openai.com/index/mrc-supercomputer-networking/)

研究
* [研究索引](https://openai.com/research/index/)
* [研究概览](https://openai.com/research/)
* [经济研究](https://openai.com/signals/)

最新进展
* [GPT-5.6](https://openai.com/index/gpt-5-6/)
* [GPT-5.5](https://openai.com/index/introducing-gpt-5-5/)
* [GPT-5.4](https://openai.com/index/introducing-gpt-5-4/)

安全
* [安全方法](https://openai.com/safety/)
* [部署安全(在新窗口中打开)](https://deploymentsafety.openai.com/)
* [安全与隐私](https://openai.com/security-and-privacy/)
* [信任与透明度](https://openai.com/trust-and-transparency/)

产品
* [ChatGPT(在新窗口中打开)](https://chatgpt.com/?openaicom-did=6b4e5ba4-f156-469a-86e7-c924dccbee69&openaicom%5Freferred=true)
* [ChatGPT Business(在新窗口中打开)](https://chatgpt.com/business/?openaicom-did=6b4e5ba4-f156-469a-86e7-c924dccbee69&openaicom%5Freferred=true)
* [ChatGPT Enterprise(在新窗口中打开)](https://chatgpt.com/business/enterprise/?openaicom-did=6b4e5ba4-f156-469a-86e7-c924dccbee69&openaicom%5Freferred=true)
* [ChatGPT for Education(在新窗口中打开)](https://chatgpt.com/business/education/?openaicom-did=6b4e5ba4-f156-469a-86e7-c924dccbee69&openaicom%5Freferred=true)
* [Codex](https://openai.com/codex/)
* [发布说明](https://openai.com/products/release-notes/)

API 平台
* [概览](https://openai.com/api/)
* [API 登录(在新窗口中打开)](https://platform.openai.com/login)
* [文档(在新窗口中打开)](https://developers.openai.com/api/docs)

业务
* [概览](https://openai.com/business/)
* [解决方案](https://openai.com/solutions/)
* [资源](https://openai.com/business/learn/)
* [客户案例](https://openai.com/business/customer-stories/)
* [合作伙伴网络](https://openai.com/business/partners/)
* [联系销售](https://openai.com/contact-sales/)

开发者
* [Apps SDK(在新窗口中打开)](https://developers.openai.com/apps-sdk)
* [开放模型](https://openai.com/open-models/)
* [文档(在新窗口中打开)](https://developers.openai.com/)
* [资源(在新窗口中打开)](https://developers.openai.com/learn)
* [开发者论坛(在新窗口中打开)](https://community.openai.com/)

公司
* [关于我们](https://openai.com/about/)
* [我们的章程](https://openai.com/charter/)
* [招聘](https://openai.com/careers/)
* [新闻](https://openai.com/news/)

支持
* [帮助中心(在新窗口中打开)](https://help.openai.com/)

更多
* [故事](https://openai.com/stories/)
* [学院](https://openai.com/academy/)
* [直播](https://openai.com/live/)
* [播客](https://openai.com/podcast/)
* [RSS](https://openai.com/news/rss.xml)

条款与政策
* [使用条款](https://openai.com/policies/terms-of-use/)
* [隐私政策](https://openai.com/policies/privacy-policy/)
* [其他政策](https://openai.com/policies/)

[(在新窗口中打开)](https://x.com/OpenAI)[(在新窗口中打开)](https://www.youtube.com/OpenAI)[(在新窗口中打开)](https://www.linkedin.com/company/openai)[(在新窗口中打开)](https://github.com/openai)[(在新窗口中打开)](https://www.instagram.com/openai/)[(在新窗口中打开)](https://www.tiktok.com/@openai)[(在新窗口中打开)](https://discord.gg/openai)

OpenAI © 2015–2026您的隐私选择

英语美国

![](https://bat.bing.com/action/0?ti=187204252&Ver=2&mid=db5007d3-b174-43c1-9c68-c48fa66052f7&bo=1&sid=b51b47107dc411f19bc9db3a9eb246ed&vid=b51b88f07dc411f1a7c27342f6b1e836&vids=1&msclkid=N&pi=918639831&lg=en-US&sw=800&sh=600&sc=24&nwd=1&tl=Core%20dump%20epidemiology%3A%20fixing%20an%2018-year-old%20bug%20%7C%20OpenAI&p=https%3A%2F%2Fopenai.com%2Findex%2Fcore-dump-epidemiology-data-infrastructure-bug%2F&r=&lt=2334&evt=pageLoad&sv=2&cdb=AQAQ&rn=119997)

---

英文原文：[Core dump epidemiology: fixing an 18-year-old bug](https://openai.com/index/core-dump-epidemiology-data-infrastructure-bug)
