Tìm lỗi thiết kế CPU trong Xbox 360 (2018)
Tin tức chung·Hacker News·2 lượt xem

Tìm lỗi thiết kế CPU trong Xbox 360 (2018)

Finding a CPU Design Bug in the Xbox 360 (2018)

AI Summary

Lỗi trong **instruction** `xdcbt` của CPU Xbox 360, vốn dùng để tăng tốc **prefetching** dữ liệu lên **L1 cache**, đã bỏ qua các kiểm tra **coherency** của **L2 cache**. Việc sử dụng **instruction** này trong một **memory copy routine** phổ biến đã gây ra **crash**. Nguyên nhân là do các **core** nhìn thấy dữ liệu không nhất quán khi một **core** khác sửa đổi dữ liệu trước khi nó được **flush**. Các **developer** nên lưu ý rằng các tối ưu hóa hiệu năng, đặc biệt là những cái liên quan đến các **specialized instructions** bỏ qua các cơ chế **hardware coherency**, có thể dẫn đến các **bug** khó phát hiện nhưng lại rất nghiêm trọng. Việc kiểm thử kỹ lưỡng và hiểu rõ toàn bộ tác động của các tính năng phần cứng là cực kỳ quan trọng để tránh những vấn đề này.

Tiết lộ gần đây về Meltdown và Spectre khiến tôi nhớ lại thời điểm tôi tìm thấy một lỗi thiết kế liên quan trong CPU Xbox 360 – một lệnh mới được thêm vào mà sự tồn tại của nó rất nguy hiểm. Trở lại năm 2005, tôi là người làm CPU Xbox 360. Tôi đã sống và thở con chip đó. Tôi vẫn có CPU 30 cm…

Tiết lộ gần đây về MeltdownSpectre khiến tôi nhớ lại lần tôi tìm thấy một lỗi thiết kế có liên quan trong CPU Xbox 360 – một lệnh mới được thêm vào mà sự tồn tại của nó rất nguy hiểm.

Trở lại năm 2005, tôi là người làm CPU Xbox 360. Tôi đã sống và thở con chip đó. Tôi vẫn còn treo một tấm wafer CPU dài 30 cm trên tường và một tấm áp phích dài 4 foot về bố cục của CPU. Tôi đã dành rất nhiều thời gian để tìm hiểu cách hoạt động của các đường dẫn của CPU đến mức khi tôi được yêu cầu điều tra một số sự cố không thể xảy ra, tôi có thể trực giác được nguyên nhân của chúng phải là lỗi thiết kế. Nhưng trước tiên, một số thông tin cơ bản…

Annotated Xbox 360 dieCPU Xbox 360 là chip PowerPC ba lõi do IBM sản xuất. Ba lõi nằm trong ba góc phần tư riêng biệt với góc phần tư thứ tư chứa bộ đệm L2 1 MB – bạn có thể thấy các thành phần khác nhau, trong hình bên phải và trên tấm bán dẫn CPU của tôi. Mỗi lõi có bộ đệm lệnh 32 KB và bộ đệm dữ liệu 32 KB.

Thông tin bên lề: Core 0 gần với bộ đệm L2 hơn và có độ trễ L2 thấp hơn đáng kể.

CPU Xbox 360 có độ trễ cao cho mọi thứ, trong đó độ trễ bộ nhớ đặc biệt tệ. Và bộ đệm L2 1 MB (tất cả những gì có thể vừa) là khá nhỏ đối với CPU ba lõi. Vì vậy, việc bảo tồn dung lượng trong bộ nhớ đệm L2 để giảm thiểu lỗi bộ nhớ đệm là rất quan trọng.

Bộ nhớ đệm của CPU cải thiện hiệu suất nhờ vào vị trí không gian và thời gian. Vị trí không gian có nghĩa là nếu bạn đã sử dụng một byte dữ liệu thì có thể bạn sẽ sớm sử dụng các byte dữ liệu lân cận khác. Vị trí tạm thời có nghĩa là nếu bạn đã sử dụng một số bộ nhớ thì có thể bạn sẽ sử dụng lại bộ nhớ đó trong tương lai gần.

Nhưng đôi khi vị trí tạm thời không thực sự xảy ra. Nếu bạn đang xử lý một mảng lớn dữ liệu một lần trên mỗi khung hình thì có thể chứng minh được rằng tất cả dữ liệu đó sẽ biến mất khỏi bộ đệm L2 vào thời điểm bạn cần lại. Bạn vẫn muốn dữ liệu đó trong bộ đệm L1 để có thể hưởng lợi từ vị trí không gian, nhưng việc nó tiêu tốn dung lượng quý giá trong bộ đệm L2 chỉ có nghĩa là nó sẽ loại bỏ các dữ liệu khác, có thể làm chậm hai lõi còn lại.

Thông thường điều này là không thể tránh khỏi. Cơ chế kết hợp bộ nhớ của CPU PowerPC của chúng tôi bắt buộc rằng tất cả dữ liệu trong bộ đệm L1 cũng nằm trong bộ đệm L2. Giao thức MESI được sử dụng để kết hợp bộ nhớ yêu cầu rằng khi một lõi ghi vào dòng bộ nhớ đệm thì mọi lõi khác có bản sao của cùng dòng bộ nhớ đệm đó cần phải loại bỏ nó – và bộ nhớ đệm L2 chịu trách nhiệm theo dõi xem bộ nhớ đệm L1 nào đang lưu vào bộ nhớ đệm địa chỉ nào.

About 40 cores on my wafer, L2 caches visible Tuy nhiên, CPU dành cho máy chơi trò chơi điện tử và hiệu suất vượt trội nên một hướng dẫn mới đã được thêm vào – xdcbt. Lệnh PowerPC dcbt thông thường là lệnh tìm nạp trước điển hình. Lệnh xdcbt là lệnh tìm nạp trước mở rộng được tìm nạp thẳng từ bộ nhớ tới L1 d-cache, bỏ qua L2. Điều này có nghĩa là tính mạch lạc của bộ nhớ không còn được đảm bảo nữa, nhưng này, chúng tôi là những nhà lập trình trò chơi điện tử, chúng tôi biết mình đang làm gì, mọi chuyện sẽ ổn thôi.

Rất tiếc.

Tôi đã viết một quy trình sao chép bộ nhớ Xbox 360 được sử dụng rộng rãi với tùy chọn sử dụng xdcbt. Việc tìm nạp trước dữ liệu nguồn rất quan trọng đối với hiệu suất và thông thường nó sẽ sử dụng dcbt nhưng chuyển vào cờ PREFETCH_EX và nó sẽ tìm nạp trước bằng xdcbt. Điều này đã không được suy nghĩ kỹ lưỡng. Việc tìm nạp trước về cơ bản là:

if (cờ & PREFETCH_EX)
__xdcbt(src+offset);
khác
__dcbt(src+offset);

Một nhà phát triển trò chơi đang sử dụng chức năng này đã báo cáo những sự cố kỳ lạ – lỗi hỏng đống dữ liệu, nhưng cấu trúc đống trong vùng kết xuất bộ nhớ trông vẫn bình thường. Sau khi nhìn chằm chằm vào bãi rác một lúc, tôi nhận ra mình đã phạm sai lầm gì.

Bộ nhớ được tìm nạp trước bằng xdcbt là độc hại. Nếu nó được ghi bởi một lõi khác trước khi bị xóa khỏi L1 thì hai lõi có các chế độ xem bộ nhớ khác nhau và không có gì đảm bảo rằng các chế độ xem của chúng sẽ hội tụ. Các dòng bộ nhớ đệm của Xbox 360 có kích thước 128 byte và quá trình tìm nạp trước của quy trình sao chép của tôi diễn ra ngay cuối bộ nhớ nguồn, nghĩa là xdcbt đã được áp dụng cho một số dòng bộ nhớ đệm có các phần sau là một phần của cấu trúc dữ liệu liền kề. Thông thường, đây là siêu dữ liệu heap – ít nhất đó là nơi chúng tôi thấy sự cố. Lõi không mạch lạc đã nhìn thấy dữ liệu cũ (mặc dù đã sử dụng khóa cẩn thận) và bị lỗi, nhưng kết xuất sự cố đã ghi lại nội dung thực tế của RAM để chúng tôi không thể biết chuyện gì đã xảy ra.

Vì vậy, cách an toàn duy nhất để sử dụng xdcbt là phải hết sức cẩn thận để không tìm nạp trước ngay cả một byte đơn lẻ ngoài cuối bộ đệm. Tôi đã sửa quy trình sao chép bộ nhớ của mình để tránh tìm nạp trước quá xa, nhưng trong khi chờ sửa lỗi, nhà phát triển trò chơi đã dừng chuyển cờ PREFETCH_EX và sự cố đã biến mất.

Lỗi thực sự

Cho đến nay vẫn bình thường phải không? Các nhà phát triển trò chơi tự mãn chơi với lửa, bay quá gần mặt trời, kết hôn với mẹ của họ và bảng điều khiển trò chơi gần như bỏ lỡ Giáng sinh.

Tuy nhiên, chúng tôi đã phát hiện kịp thời, xử lý được và tất cả đã sẵn sàng giao trò chơi cũng như bảng điều khiển rồi vui vẻ trở về nhà.

Và rồi trò chơi đó lại bắt đầu gặp sự cố.

Các triệu chứng giống hệt nhau. Ngoại trừ việc trò chơi không còn sử dụng hướng dẫn xdcbt nữa. Tôi có thể bước qua mã và thấy điều đó. Chúng tôi đã gặp phải một vấn đề nghiêm trọng.

Tôi đã sử dụng kỹ thuật sửa lỗi cổ xưa là nhìn chằm chằm vào màn hình của mình với đầu óc trống rỗng, để các đường dẫn CPU lấp đầy tiềm thức của mình và tôi chợt nhận ra vấn đề. Một email nhanh gửi tới IBM đã xác nhận sự nghi ngờ của tôi về một chi tiết tinh vi bên trong CPU mà trước đây tôi chưa bao giờ nghĩ đến. Và đó cũng chính là thủ phạm đằng sau Meltdown và Spectre.

CPU Xbox 360 là CPU theo thứ tự. Nó thực sự khá đơn giản, dựa vào tần số cao (không cao như mong đợi mặc dù có 10 FO4) để đạt hiệu suất. Nhưng nó có một bộ dự đoán nhánh – các đường dẫn rất dài của nó khiến điều đó trở nên cần thiết. Đây là sơ đồ quy trình CPU được chia sẻ công khai mà tôi đã tạo (phiên bản chính xác theo chu kỳ của tôi chỉ là NDA, nhưng tại đây) sẽ hiển thị tất cả các quy trình:

image

Bạn có thể thấy bộ dự đoán nhánh và bạn có thể thấy rằng các quy trình rất dài (rộng trên sơ đồ) – đủ dài để các hướng dẫn bị dự đoán sai có thể tăng tốc, ngay cả khi xử lý theo thứ tự.

Vì vậy, bộ dự đoán nhánh đưa ra dự đoán và các hướng dẫn dự đoán sẽ được tìm nạp, giải mã và thực thi – nhưng không ngừng hoạt động cho đến khi dự đoán được biết là chính xác. Nghe có vẻ quen thuộc? Tôi nhận ra điều đó - điều này còn mới mẻ đối với tôi vào thời điểm đó - là ý nghĩa của việc thực hiện một lần tìm nạp trước theo suy đoán. Độ trễ kéo dài nên điều quan trọng là phải thực hiện giao dịch tìm nạp trước trên xe buýt càng sớm càng tốt và một khi quá trình tìm nạp trước đã được bắt đầu thì không có cách nào để hủy nó. Vì vậy, xdcbt được thực thi theo suy đoán là giống hệt với thực tế xdcbt! (lệnh tải được thực hiện theo suy đoán chỉ là một lệnh tìm nạp trước, FWIW).

Và đó chính là vấn đề – bộ dự đoán nhánh đôi khi khiến các lệnh xdcbt được thực thi theo suy đoán và điều đó cũng tệ như việc thực thi chúng thực sự. Một trong những đồng nghiệp của tôi (cảm ơn Tracy!) đã đề xuất một thử nghiệm thông minh để xác minh điều này – thay thế mọi xdcbt trong trò chơi bằng một điểm dừng. Điều này đã đạt được hai điều:

  1. Các điểm dừng không đạt được, do đó chứng tỏ rằng trò chơi không thực thi các hướng dẫn xdcbt.
  2. Các sự cố đã biến mất.

Tôi biết đó sẽ là kết quả nhưng nó vẫn thật tuyệt vời. Tất cả những năm sau đó và thậm chí sau khi đọc về Meltdown, thật tuyệt vời khi thấy bằng chứng chắc chắn rằng các hướng dẫn không được thực thi đã gây ra sự cố.

Việc thực hiện bộ dự đoán nhánh đã làm rõ rằng lệnh này quá nguy hiểm để có ở bất kỳ đâu trong đoạn mã của bất kỳ trò chơi nào – việc kiểm soát thời điểm một lệnh có thể được thực thi theo suy đoán là quá khó. Về mặt lý thuyết, bộ dự đoán nhánh cho các nhánh gián tiếp có thể dự đoán bất kỳ địa chỉ nào, do đó không có “nơi an toàn” để đặt lệnh xdcbt. Và, nếu được thực thi theo suy đoán, nó sẽ vui vẻ thực hiện tìm nạp trước mở rộng bất kỳ bộ nhớ nào mà các thanh ghi được chỉ định tình cờ chứa ngẫu nhiên. Có thể giảm thiểu rủi ro nhưng không loại bỏ được nó và điều đó thật không đáng. Trong khi các cuộc thảo luận về kiến trúc Xbox 360 tiếp tục đề cập đến hướng dẫn, tôi nghi ngờ rằng không có trò chơi nào từng được phát hành kèm theo hướng dẫn này.

Tôi đã đề cập đến điều này một lần trong một cuộc phỏng vấn xin việc – “hãy mô tả lỗi khó khăn nhất mà bạn phải điều tra” – và phản ứng của người phỏng vấn là “vâng, chúng tôi đã gặp phải lỗi tương tự trên bộ xử lý Alpha”. Càng có nhiều thứ thay đổi…

Cảm ơn Michael về việc chỉnh sửa.

Phần tái bút

Làm sao có thể dự đoán được một nhánh chưa bao giờ được chọn sẽ được chọn? Dễ. Công cụ dự đoán nhánh không duy trì lịch sử hoàn hảo cho mọi nhánh trong tệp thực thi – điều đó sẽ không thực tế. Thay vào đó, các bộ dự đoán nhánh đơn giản thường tập hợp một loạt các bit địa chỉ lại với nhau, có thể cả một số bit lịch sử nhánh và lập chỉ mục thành một mảng các mục nhập hai bit. Do đó, kết quả dự đoán nhánh bị ảnh hưởng bởi các nhánh khác, không liên quan, dẫn đến đôi khi dự đoán sai. Nhưng không sao cả, vì đó “chỉ là dự đoán” và không cần phải đúng.

Bạn có thể tìm thấy các cuộc thảo luận về bài đăng này trên tin tức về hacker (tin tức về hacker năm 2021), r/programming, r/emulationtwitter.

Một lỗi có phần liên quan (Xbox, bộ nhớ đệm) đã được thảo luận cách đây vài năm tại đây.

Tác giả: mariuz

#discussion