Chuyển đến nội dung
tinAI
Quay lại

OpenAI mổ core dump để sửa một bug 18 năm tuổi

Bản dịch tiếng Việt của tinAI · Từ Core dump epidemiology: fixing an 18-year-old bug (openai.com) · Ngày gốc: · Dịch ngày:

Bài gốc: Core dump epidemiology: fixing an 18-year-old bug (openai.com)

Tác giả: OpenAI

Ngày đăng: Dịch ngày:

TL;DR

OpenAI debug một nhóm crash C++ trong hạ tầng Rockset bằng cách phân tích core dump ở quy mô fleet. Kết quả: họ tách được hai nguyên nhân bị trộn lẫn, gồm một host Azure lỗi phần cứng và một race condition 18 năm tuổi trong GNU libunwind.

Ước tính đọc: 6 phút

Tóm tắt

OpenAI mô tả cách họ xử lý một nhóm crash C++ tưởng như không thể giải thích trong Rockset, một phần của hạ tầng dữ liệu dùng cho ChatGPT. Ban đầu các core dump trông như cùng một lỗi stack corruption, nhưng phân tích trên toàn bộ tập core dump cho thấy thực ra có hai vấn đề độc lập: một host Azure bị lỗi phần cứng âm thầm và một race condition 18 năm tuổi trong GNU libunwind.

Bài học chính không nằm ở một mẹo assembly hào nhoáng. Điểm quan trọng là biến crash thành dataset đủ sạch để nhìn thấy pattern theo vùng, hardware SKU, thời điểm bắt đầu và code path.

Bối cảnh

Một số service trong hạ tầng dữ liệu của OpenAI được viết bằng C++ để tối ưu hiệu năng và bộ nhớ. Đổi lại, lỗi memory safety có thể làm process crash bằng cách ghi vào địa chỉ sai hoặc không tồn tại.

Trong Rockset, các crash xuất hiện khi một hàm C++ bình thường kết thúc rồi return tới địa chỉ không hợp lệ. Có lúc slot return address trong stack frame là NULL. Có lúc thanh ghi stack pointer %rsp lệch 8 byte, như thể nó bị giảm ngay giữa lúc chương trình đang chạy bình thường.

Những failure mode này rất lạ. Một write lạc chỉ đụng đúng saved return address đã hiếm; %rsp lệch 8 byte mà không dùng inline assembly, setcontext, hoặc longjmp còn khó tin hơn.

Cách debug ban đầu

Team bắt đầu bằng kiểu debug truyền thống: mở vài core dump, soi thật kỹ, đặt giả thuyết rồi loại dần. Phần lớn crash xuất hiện quanh DocumentTree::updateDocument, một method lớn có nhiều inline, nên không gian tìm kiếm rất rộng.

Log ở tầng ứng dụng không đủ tốt để phân loại vì stack trace trong crash kiểu này cũng có thể đã bị corrupt hoặc thiếu. Soi thủ công thêm nhiều core dump giúp có thêm ví dụ, nhưng không tạo được dataset đáng tin.

Sai lầm ban đầu là coi mọi crash là một hiện tượng duy nhất. Vì crash xuất hiện ở nhiều region và nhiều loại hardware, team loại trừ hardware quá sớm và tiếp tục tìm nguyên nhân thuần software.

Bước ngoặt: debug như nhà dịch tễ học

OpenAI chuyển từ “doctor mode” sang “epidemiologist mode”. Thay vì chẩn đoán từng case riêng lẻ, họ phân tích toàn bộ population crash:

Họ dùng ChatGPT để viết script tải một phần mỗi core file, trích xuất register, lọc false positive bằng log, rồi tự động gắn nhãn crash thành return-to-null, misaligned-stack, hoặc nhóm khác. Script chạy song song trên toàn bộ core dump production của Rockset trong năm trước.

Khi dataset sạch xuất hiện, pattern lộ ra ngay: đây không phải một bug, mà là hai crash population khác nhau.

Bug 1: host phần cứng lỗi

Nhóm misaligned-stack chỉ đến từ một region, có ngày bắt đầu rõ ràng, và không xảy ra trên node đã chạy lâu. Pattern này khớp với một physical host có phần cứng lỗi, nơi các VM tình cờ được đặt lên sẽ gặp register corruption.

OpenAI trace được danh sách node và timestamp về một host vật lý, rồi denylist host đó. Họ không reproduce được lỗi trong môi trường kiểm soát, nhưng sau khi loại host khỏi service, nhóm crash này biến mất.

Mitigation sau đó gồm:

Bug 2: race condition trong GNU libunwind

Sau khi tách nhóm hardware, phần return-to-null còn lại bắt đầu có logic hơn: tất cả đều xảy ra trong quá trình exception unwinding.

Khi C++ throw exception, runtime phải đi qua stack, tìm metadata, chạy cleanup handler và chuyển control tới catch block phù hợp. Việc này gần với longjmp hoặc fiber switch hơn là call/return bình thường, vì runtime phải restore nhiều register, bao gồm %rbp, %rsp, và instruction pointer.

Binary của Rockset link cả libgcc và GNU libunwind, nhưng dynamic linker chọn implementation của GNU libunwind. Trong source của libunwind, OpenAI thấy _Ux86_64_setcontext dựng một ucontext_t trên stack rồi dùng assembly để restore register.

Race xảy ra khi _Ux86_64_setcontext cập nhật %rsp trước, làm ucontext_t không còn nằm trong active stack nữa. Nếu signal đến đúng thời điểm đó, kernel dựng signal frame ở vùng dưới %rsp và có thể ghi đè memory mà %rdi vẫn đang trỏ tới. Nếu instruction pointer được đọc sau khi memory bị ghi đè, control transfer có thể đi tới NULL.

Đây là race window chỉ rộng một instruction. Nghe phi lý, nhưng Rockset có ba yếu tố khuếch đại xác suất:

Ở fleet scale, xác suất cực nhỏ vẫn đủ thành hơn chục crash mỗi ngày.

Cách sửa

Mitigation trước mắt là chuyển từ GNU libunwind sang unwinder của libgcc. Đây cũng là trade-off tốt vì implementation của libgcc đã được tối ưu lock contention cho VM lớn.

OpenAI cũng upstream reproducer và fix cho GNU libunwind, đồng thời kiểm tra các unwinder khác để đảm bảo không có lỗi tương tự.

Ý nghĩa với Dev

Bài này là ví dụ rất tốt cho debugging hạ tầng ở scale lớn. Khi một lỗi hiếm có vẻ “không thể xảy ra”, câu trả lời không nhất thiết là soi sâu hơn vào một case; nhiều khi phải xây dataset đủ sạch để thấy các cụm lỗi khác nhau.

Dev vận hành hệ thống C++, Rust, Go native extension, inference service hoặc worker queue nên rút ra vài điểm:


Đường dẫn nguồn

tinAI dịch bài này sang tiếng Việt từ Core dump epidemiology: fixing an 18-year-old bug (openai.com) · Loại nguồn: công ty/blog và giữ bối cảnh từ bản tin tinAI đã giới thiệu bài này .

Bản tin này có 3 bài dịch liên quan từ cùng bản tin.