22/10/2026 · VietTechies · Biên tập

Đến tháng 3 năm 2014, các lập trình viên Mozilla, trong đó có Robert O’Callahan, đã phát hành phiên bản 1.0 của rr, một công cụ mã nguồn mở được xây dựng quanh một giới hạn có chủ đích: mỗi lúc chỉ chạy một luồng của ứng dụng. Công cụ này đánh đổi tốc độ xử lý đa lõi để ghi lại một lần thực thi chương trình Linux, rồi tái hiện chính xác cùng một lỗi bao nhiêu lần tùy ý.
Cách gỡ lỗi truyền thống không còn hiệu quả khi lỗi phụ thuộc vào những chênh lệch rất nhỏ về thời điểm thực thi. Một lỗi tranh chấp thời điểm (race condition) có thể khiến chương trình gặp sự cố chỉ một lần trong vài nghìn lượt kiểm thử. Nhưng gắn trình gỡ lỗi truyền thống vào chương trình hoặc chèn lệnh in thông tin lại làm thay đổi tốc độ thực thi, khiến bug biến mất. Suốt nhiều thập niên, các nhà khoa học máy tính đã tìm cách xây dựng những hệ thống ghi và phát lại có thể dùng trong thực tế để lưu giữ các lỗi thoắt ẩn thoắt hiện như vậy. Tuy nhiên, những hệ thống trước đó rất khó triển khai: chúng đòi hỏi bản vá riêng cho nhân hệ điều hành (kernel), phải ghi lại toàn bộ máy ảo, hoặc phải chèn mã theo dõi khắp chương trình, làm tăng chi phí thực thi lẫn công sức triển khai.
Kiến trúc để phát lại chính xác một lần thực thi
Nhóm Mozilla thiết kế rr trong những giới hạn thực tế nghiêm ngặt. Công trình của họ được trình bày trong báo cáo kỹ thuật tháng 5 năm 2017, do O’Callahan, Chris Jones, Nathan Froyd, Kyle Huey, Albert Noll và Nimrod Partush đồng tác giả. Công cụ chạy hoàn toàn trong không gian người dùng trên kernel Linux nguyên bản và bộ xử lý Intel x86 tương thích, không cần sửa kernel hay dùng phần mềm giám sát máy ảo. Tuy vậy, trên nhiều bản phân phối, trước tiên vẫn phải đổi một thiết lập của kernel để cho phép truy cập các sự kiện hiệu năng cần thiết mà không cần đặc quyền.
Để làm được điều đó, rr xác định rõ ranh giới ghi lại dữ liệu giữa các tiến trình trong không gian người dùng và kernel Linux. Khi ghi, rr lưu các đầu vào mà kernel cung cấp cho tiến trình, chẳng hạn mã trả về của lời gọi hệ thống (system call), dữ liệu đầu ra trong các vùng đệm bộ nhớ và tín hiệu của hệ điều hành. Công cụ cũng lưu những tác động không tất định của các lệnh của bộ xử lý, chẳng hạn lệnh đọc dấu thời gian. Khi phát lại, rr tái tạo chính xác các đầu vào và trạng thái thanh ghi đã ghi, tạo ra cùng một chuỗi thực thi ở cấp lệnh.
Lựa chọn thiết kế cốt lõi nằm ở cách rr xử lý ứng dụng đa luồng. Trên bộ xử lý đa lõi, các thao tác đọc và ghi không được đồng bộ vào bộ nhớ dùng chung gây ra những tranh chấp dữ liệu không tất định, vốn cực kỳ khó ghi lại một cách hiệu quả. Thay vì chèn mã theo dõi nặng nề để giám sát từng lần truy cập bộ nhớ, rr áp đặt một giới hạn đơn giản: tại mọi thời điểm, chỉ một luồng ứng dụng được chạy. Bằng cách lập lịch có quyền ngắt luồng trên một lõi duy nhất, rr tránh cho các luồng chạy đồng thời trên nhiều lõi nhưng vẫn giữ các lượt thực thi xen kẽ giữa chúng. Tuy nhiên, cách này không thể làm lộ những bug do mô hình bộ nhớ yếu gây ra. Những bug do thực thi xen kẽ vẫn có thể được ghi lại nếu việc chuyển ngữ cảnh (context switch) tình cờ xảy ra đúng thời điểm then chốt trong quá trình thực thi.
Bộ đếm nhánh và cơ chế chặn lời gọi ngay trong tiến trình
Việc phát lại chính xác cũng đòi hỏi xác định đúng thời điểm của các sự kiện bất đồng bộ, chẳng hạn tín hiệu từ bộ định thời và các lần chuyển ngữ cảnh. Khi phát lại, chỉ cần chuyển một tín hiệu sớm hoặc muộn hơn vị trí đã ghi đúng một lệnh cũng có thể khiến toàn bộ quá trình thực thi đi chệch. Thế nhưng, bộ đếm lệnh đã hoàn tất trên các bộ xử lý x86 được khảo sát lại không có tính tất định: chẳng hạn, một lệnh được khởi động lại sau lỗi trang có thể bị đếm hai lần. Tuy nhiên, các bộ xử lý Intel hiện đại có một bộ đếm hiệu năng tất định: bộ đếm nhánh có điều kiện đã hoàn tất (retired conditional branches, RCB). rr kết hợp số đếm RCB với các thanh ghi đa dụng và một phần dữ liệu ngăn xếp để xác định các vị trí thực thi. Các tác giả cho biết cách này hoạt động ổn định trong thực tế, nhưng không khẳng định rằng mỗi tổ hợp như vậy luôn xác định duy nhất một vị trí.
Ban đầu, việc giám sát từng lời gọi hệ thống bằng các thao tác ptrace tiêu chuẩn của Linux làm giảm hiệu năng đáng kể, vì mỗi lời gọi cần bốn lần chuyển ngữ cảnh. Để có thể ghi lại quá trình chạy của những chương trình có lượng mã lớn như trình duyệt Firefox, rr bổ sung cơ chế chặn lời gọi hệ thống ngay trong tiến trình. Bằng cách nạp một thư viện gọn nhẹ vào ứng dụng và dùng bộ lọc lời gọi hệ thống seccomp-bpf của Linux, rr cho phép những lời gọi thông dụng không gây chờ ghi kết quả thẳng vào các vùng đệm dữ liệu ghi vết dùng chung, không cần chuyển quyền điều khiển sang tiến trình giám sát. Thông báo về sự kiện hiệu năng giúp rr phát hiện khi một lời gọi được cơ chế này tiếp nhận phải chờ, rồi cho luồng khác chạy để tránh nguy cơ bế tắc. Phần triển khai cũng xử lý cả những thông báo phát sinh khi thực tế không có sự kiện tương ứng.
Gỡ lỗi ngược dòng thời gian
Vì cơ chế phát lại tất định bảo đảm địa chỉ bộ nhớ và giá trị thanh ghi giống hệt nhau giữa các lần chạy, rr đã thay đổi trải nghiệm gỡ lỗi trong GNU Debugger (GDB), trình gỡ lỗi của GNU. Thay vì khởi động lại chương trình rồi hy vọng dừng đúng điểm dừng (breakpoint) cần tìm, lập trình viên có thể thực sự chạy ngược quá trình thực thi.
Khi chương trình gặp sự cố vì một con trỏ bị hỏng hoặc một giá trị sai, triệu chứng xuất hiện rất lâu sau thao tác ghi gây lỗi. Với rr, lập trình viên đặt một điểm theo dõi dữ liệu bằng phần cứng (watchpoint) lên biến bị hỏng, rồi chạy lệnh reverse-continue. Trình gỡ lỗi đi ngược qua lịch sử thực thi và dừng đúng tại lệnh đã sửa vị trí bộ nhớ đó. Cách này có thể giảm việc dò từng bước theo chiều tiến dựa trên phỏng đoán, dù chính lần ghi cuối cùng ấy cũng có thể đã nhận một giá trị sai từ trước.
Chế độ hỗn loạn: làm xuất hiện lỗi tranh chấp khó tái hiện
Cơ chế phát lại tất định có một giới hạn cơ bản: trình gỡ lỗi chỉ có thể phát lại một bug đã xảy ra. Nếu lỗi chập chờn không xuất hiện trong lúc ghi, phát lại lượt kiểm thử thành công ấy cũng không thể làm lộ lỗi chưa xảy ra. Tháng 2 năm 2016, O’Callahan minh họa giới hạn này bằng một bug thực tế liên quan đến xử lý đồng thời trong luồng ImageBridge của Firefox. Bug thỉnh thoảng xuất hiện trên thiết bị Android nhưng không tái hiện qua hàng nghìn lần chạy dưới rr; chèn một khoảng tạm dừng lại có thể khiến lỗi xuất hiện đều đặn trên máy tính để bàn chạy Linux.
Để làm xuất hiện bug, cần giữ cho luồng ImageBridge không được chạy trong khi luồng chính chờ hết thời hạn 500 mili giây. Vì ImageBridge là luồng duy nhất có thể chạy trong khoảng đó, bộ lập lịch chỉ biết chọn giữa các luồng sẵn sàng chạy không thể tạo ra độ trễ cần thiết. Để giải quyết vấn đề, O’Callahan đưa vào “chế độ hỗn loạn”, được bật bằng lệnh rr record với cờ cấu hình (flag) -h. Chế độ này chọn ngẫu nhiên mức ưu tiên và độ dài lát thời gian của các luồng, đồng thời định kỳ không cho các luồng ưu tiên thấp chạy trong những khoảng kéo dài tới vài giây, ngay cả khi không có luồng nào khác sẵn sàng chạy. Bằng cách mô phỏng áp lực lập lịch cực lớn, chế độ hỗn loạn khiến những lỗi tranh chấp hiếm gặp liên quan đến thời hạn chờ có thể xuất hiện trong lúc ghi. Nhờ đó, dữ liệu ghi vết có thể được kiểm tra ngược về sau.
Những đánh đổi và bài học kỹ thuật
Thiết kế của rr là một chuỗi đánh đổi kỹ thuật có chủ đích. Buộc mọi luồng chạy trên một lõi khiến những tác vụ có mức độ xử lý song song cao chậm đi đáng kể. Trong báo cáo năm 2017, việc ghi lại một lượt biên dịch song song DynamoRIO mất thời gian gấp 7,85 lần so với chạy bình thường. Bốn tác vụ còn lại trong báo cáo mất thời gian ghi bằng 1,49 đến 1,79 lần mức bình thường; tác vụ kiểm thử biểu mẫu web trên Firefox mất thời gian bằng 1,49 lần.
Công cụ vẫn chưa hỗ trợ các ứng dụng chia sẻ bộ nhớ với những tiến trình không được ghi lại nằm ngoài cây tiến trình, và các lần cập nhật hệ điều hành đôi khi đòi hỏi phải cập nhật mô hình lời gọi hệ thống bên trong rr. Dù vậy, công cụ đã trở thành nền tảng cho những hệ thống gỡ lỗi tiên tiến như Pernosco và được lập trình viên của các dự án phần mềm hệ thống lớn sử dụng. rr cho thấy một đánh đổi thực tế: hy sinh khả năng thực thi đa lõi có thể biến một lỗi đã ghi lại thành lỗi tái hiện được, giúp lập trình viên lần ngược đến thao tác sửa đổi cần kiểm tra.
Bài liên quan
Đọc bằng ngôn ngữ khác: English
Nguồn
- Dự án rr, “rr: lightweight recording & deterministic debugging”, tài liệu dự án.
- Robert O’Callahan, Chris Jones, Nathan Froyd, Kyle Huey, Albert Noll và Nimrod Partush, “Engineering Record And Replay For Deployability: Extended Technical Report”, arXiv:1705.05937, tháng 5 năm 2017.
- rr Debugger, “Record and Replay Framework (rr)”, kho mã GitHub và yêu cầu hệ thống.
- Robert O’Callahan, “Introducing rr Chaos Mode”, blog kỹ thuật cá nhân, tháng 2 năm 2016.
- Hiệp hội USENIX, “Engineering Record and Replay for Deployability”, Hội nghị kỹ thuật thường niên USENIX (ATC), 2017.
- Ảnh đại diện: Colin; Wikimedia Commons; CC BY-SA 4.0 (đã chỉnh sửa từ ảnh gốc)
