2 điểm bởi GN⁺ 2025-01-27 | 1 bình luận | Chia sẻ qua WhatsApp
  • Mac Classic II suýt nữa đã ghi vào địa chỉ sai do lỗi nhảy vượt phạm vi trong ROM, nhưng CPU Motorola MC68030 thực tế lại thực thi một lệnh không được tài liệu hóa, làm thay đổi thanh ghi A1 và tránh được crash
  • Vấn đề này lộ ra trong quá trình giả lập Classic II của MAME khi bật định địa chỉ 32-bit, với Sad Mac và Chimes of Death xuất hiện; còn ở chế độ định địa chỉ 24-bit thì máy trông như khởi động bình thường
  • Đường đi InstallSoundIntHandler trong ROM đã áp dụng BoxFlag 17 của Classic II vào một bảng nhánh có 16 phần tử, rồi nhảy tới 0x40A43B94; vị trí này vốn nằm giữa một lệnh MOVEA.L
  • Dãy byte 0C EC 08 A9 00 04 trông giống CAS D1,D2,$0004(A4), nhưng thực ra là một lệnh họ CAS không hợp lệ với các bit dự trữ được bật; trên 68030 thực tế, nó đã đổi A1 từ FFFF8FBA thành 40A4BBB2
  • Sau khi xác minh bằng 3 bản ROM tùy biến trên máy Classic II thật, kết quả cho thấy nếu thay lệnh này bằng NOP thì cả phần cứng cũng thất bại với Sad Mac; MAME tạm thời phải vá ROM để bỏ qua lỗi và cho máy khởi động cho đến khi hiểu chính xác hành vi này

Sự cố khởi động của Classic II được phát hiện trong MAME

  • MAME nổi tiếng là trình giả lập game arcade, nhưng cũng hỗ trợ giả lập các mẫu Mac dùng nền tảng 68000
  • Trong lúc chỉnh sửa tính năng gọi debugger bằng tổ hợp phím command+power trong MAME, một hành vi bất thường đã được phát hiện trên Classic II
    • Classic II có nút ngắt vật lý và cũng có vi điều khiển “Egret” 68HC05 để xử lý bàn phím, chuột và các thiết bị tương tự
    • Trong MacsBug có mã gửi lệnh để kích hoạt tổ hợp phím command+power
  • Classic II khởi động bình thường ở định địa chỉ 24-bit, nhưng khi bật định địa chỉ 32-bit cần cho việc nạp MacsBug thì Sad Mac và Chimes of Death xuất hiện
  • Theo tài liệu Apple Tech Info Library, mã Sad Mac 0000000F nghĩa là ngoại lệ, còn 00000001 nghĩa là lỗi bus
    • Trên Mac 68k, lỗi bus thường có nghĩa là truy cập địa chỉ sai, ví dụ như truy cập vào thẻ mở rộng không tồn tại

Lần theo ROM: đường khởi tạo ngắt âm thanh

  • Trong debugger của MAME, đặt breakpoint tại địa chỉ handler lỗi bus 0x40A026F0 và lần theo đường gọi bằng ROM symbol map
    • Địa chỉ này được ghi là GenExcps trong ROM map
    • Routine xử lý lỗi chung được đặt tên là ToDeepShit
  • Lệnh gây ra lỗi bus là đoạn mã sau tại 0x40A43B9C
move.b #$90, ($1c00,A1)
  • Đoạn mã này nằm trong routine được ghi là InstallSoundIntHandler trong ROM map và có liên quan tới quá trình khởi tạo ngắt âm thanh của Classic II
  • Classic II thực ra gần với kiến trúc dòng Macintosh LC hơn là Macintosh Classic nguyên bản, và dùng gate array EAGLE tương tự chip V8 của LC
    • Đó là lý do trong mã ROM xuất hiện các tên như V8SndIntPatch1
    • EAGLE có tích hợp chức năng tương đương một phiên bản rút gọn của Apple Sound Chip (ASC)

Bảng nhánh sai và vấn đề với thanh ghi A1

  • V8SndIntPatch1 kiểm tra thuộc tính phần cứng bằng selector gestaltHardwareAttr của Gestalt trap, tức 'hdwr'
    • Nếu không có bit gestaltHasASC thì routine sẽ trả về
    • Trên Classic II, gestaltHasASC được bật
  • Sau đó mã đọc giá trị BoxFlag từ 0xCB3 trong RAM và đưa vào D0
    • Trên Classic II, BoxFlag có giá trị 0x11, tức 17
  • Đoạn nhảy có vấn đề nhân đôi D0 rồi dùng nó làm offset nhảy tương đối theo PC
add.w d0,d0
jmp loc_40A43B72(pc,d0.w)
  • Vì mỗi lệnh BRA.S dài 2 byte nên giá trị BoxFlag được nhân đôi để làm chỉ số vào bảng nhánh
  • Nhưng bảng thực tế chỉ có 16 entry tương ứng với BoxFlag 0~15, còn giá trị 17 của Classic II nằm ngoài phạm vi
    • Đích nhảy được tính ra là 0x40A43B72 + 0x22 = 0x40A43B94
    • Vị trí này không phải đầu một lệnh như dự định mà nằm giữa một lệnh MOVEA.L
  • Lẽ ra MOVEA.L phải nạp địa chỉ hợp lệ vào A1, nhưng do nhảy vào giữa lệnh, A1 giữ nguyên giá trị trước đó là 0xFFFF8FBA, vốn được dùng trong phép tính nhảy tương đối trước đó
  • Sau đó nếu thực thi move.b #$90, ($1c00,A1) thì địa chỉ ghi sẽ là 0xFFFF8FBA + 0x1C00 = 0xFFFFABBA, một địa chỉ không hợp lệ trên Classic II

Hành vi 68030 không được tài liệu hóa

  • Dãy byte mà cú nhảy vượt phạm vi đi tới là như sau
0C EC 08 A9 00 04
  • IDA và GNU objdump không thể disassemble vị trí này thành lệnh hợp lệ như bình thường
  • Khi thực thi cùng dãy byte đó trong MacsBug trên Macintosh IIci, nó được hiển thị giống CAS.W D1,D2,$0004(A4), nhưng sau khi chạy thì giá trị A1 lại thay đổi
    • A1 đổi từ 0xFFFF8FBA sang một giá trị trông giống địa chỉ RAM
    • Quan sát cho thấy giá trị A1 mới phụ thuộc vào A1 ban đầu, A7 và program counter
  • Theo Motorola M68000 Family Programmer’s Reference Manual, word đầu tiên trông giống một lệnh CAS, nhưng word thứ hai có 3 bit lẽ ra phải bằng 0 lại được đặt thành 1
  • Vì vậy đây không phải CAS hợp lệ như tài liệu mô tả mà là một lệnh sai với các bit dự trữ đã bật
  • Trên 68030 thật, CPU không xử lý nó như ngoại lệ illegal instruction mà lại thực hiện chu kỳ bus read-modify-write đối với A4+4
    • Nếu cố tình đặt A4 thành địa chỉ sai, MacsBug sẽ hiển thị lỗi bus trong lúc read-modify-write tại A4+4
    • A1, vốn không nên bị thay đổi với CAS bình thường, cũng bị đổi

Xác minh trên phần cứng Classic II thật

  • Để kiểm chứng giả thuyết, tác giả đã mua và sửa một chiếc Classic II sản xuất năm 1991, sau đó thay ROM bằng EEPROM SST29EE010 có thể lập trình
    • Tụ dán trên các máy Mac cũ có thể rò rỉ chất ăn mòn, nên chúng được tháo bỏ và sửa trước khi cấp nguồn
    • Thay vì dùng CRT và bo analog, bo logic được chạy riêng và hình ảnh được kiểm tra bằng bộ chuyển VGA dựa trên Raspberry Pi Pico
  • Mã assembly 68030 để hiển thị giá trị A1 trên màn hình được chèn vào vùng trống của ROM, và tạo ra ba bản ROM tùy biến
    • Custom ROM 1: thay MOVE.B tại 0x40A43B9C — lệnh gây Sad Mac trong MAME — bằng mã hiển thị A1
    • Custom ROM 2: thay lệnh giống CAS tại 0x40A43B94, tức đích của cú nhảy vượt phạm vi, bằng mã hiển thị A1
    • Custom ROM 3: thay lệnh giống CAS tại 0x40A43B94 bằng NOP
  • Custom ROM 1 cho thấy đoạn mã đó thực sự được chạy trên phần cứng thật, và A1 tại điểm MAME bị crash là 0x40A4BBB2
    • Đây là một địa chỉ ROM nên không phù hợp để ghi, nhưng việc thử ghi vào đó cũng không gây lỗi bus
  • Custom ROM 2 cho thấy ngay trước cú nhảy vượt phạm vi, A1 có giá trị 0xFFFF8FBA, đúng như trong MAME
    • Điều này xác nhận cú nhảy vượt phạm vi thực sự xảy ra
    • Và cũng phù hợp với giả thuyết rằng lệnh giống CAS sau đó đã làm thay đổi A1
  • Custom ROM 3 cho thấy nếu loại bỏ lệnh giống CAS thì Classic II thật cũng thất bại với Sad Mac
    • Trong bài test này, phần cứng cũng hiện cùng Sad Mac ngay cả ở chế độ 24-bit
    • Việc MAME không ném lỗi bus với lần ghi sai trong chế độ 24-bit cho thấy nó đang hành xử dễ dãi hơn phần cứng thật

Những vấn đề còn lại cho MAME và giả lập

  • Hành vi được phát hiện ở đây là một trường hợp lệnh MC68030 không được tài liệu hóa vừa thực hiện chu kỳ bus read-modify-write vừa làm thay đổi cả thanh ghi A1
  • Lỗi trong ROM của Classic II đã bị che giấu nhờ chính hành vi này, và vì thế máy thật vẫn hoạt động bình thường
  • Cùng đoạn mã này cũng được tìm thấy trong ROM của Macintosh IIvx đời mới hơn, nhưng ở đó bảng nhảy đã được mở rộng và trường hợp Classic II sẽ nhảy thẳng tới RTS
  • Vì hành vi này, rất có thể hiện chưa tồn tại trình giả lập hay bản sao Motorola MC68030 nào đạt độ chính xác 100%
    • Chỉ cần chạy lệnh này và kiểm tra kết quả A1 là có thể tạo ra một đoạn mã nhỏ để phân biệt 68030 vật lý với trình giả lập
  • Tạm thời, MAME đã vá lỗi ROM của Classic II để máy có thể khởi động cho đến khi hiểu rõ chính xác hành vi của lệnh này
  • Riêng tổ hợp command+power thì trên Classic II thật nó hoạt động khi đã cài MacsBug, nhưng trong MAME hiện vẫn chưa hoạt động

1 bình luận

 
GN⁺ 2025-01-27
Ý kiến trên Hacker News
  • Hành vi MC68030 được phát hiện có thể không phải là một lệnh “thật” do nhà thiết kế CPU cố ý tạo ra, mà là kết quả tình cờ khi logic bên trong CPU thực thi với đầu vào don’t-care đối với một lệnh bất hợp lệ
    Thông thường CPU phải phát hiện lệnh bất hợp lệ và phát sinh ngoại lệ, nhưng có vẻ trong một số trường hợp thì không như vậy
    Trang 8-9 của https://www.nxp.com/docs/en/reference-manual/MC68030UM.pdf nói rằng lệnh bất hợp lệ được định nghĩa dựa trên mẫu bit của “word đầu tiên”
    Lệnh lần này dài 3 word, word đầu tiên hợp lệ và các bit lạ nằm ở word thứ hai, nên rất có khả năng 68030 không kiểm tra word thứ hai mà cứ đi tiếp theo logic triển khai lệnh CAS

    • CPU giả mà tôi học trong lớp kiến trúc máy tính ở đại học chia mã lệnh thành nhiều trường bit, trong đó một số bit bật các thao tác nguyên thủy như bộ cộng
      Mã lệnh “hợp lệ” chỉ là những tổ hợp đủ hữu ích để được ghi tài liệu, còn tổ hợp “không hợp lệ” là những tổ hợp vô dụng hoặc vô nghĩa
      Nhìn theo cách này, mã lệnh bất hợp pháp/bí ẩn không phải là ngoại lệ đáng ngạc nhiên mà gần như là điều tất yếu
    • Tôi không phải chuyên gia 68k, nhưng có vẻ chỉ word đầu tiên được xem là mã lệnh, còn word thứ hai dùng để chọn thanh ghi D sẽ dùng cho CAS
      Thông thường ta sẽ kỳ vọng các bit 0 không có ý nghĩa theo tài liệu sẽ bị bỏ qua hoàn toàn
      Nhưng trên 68030, có thể các bit đó bắt buộc phải là 0, và giống như mã lệnh bất hợp pháp của 6502, nếu các bit đó được bật thì logic hardwired dành cho lệnh khác sẽ được kích hoạt
  • CAS luôn là thứ gây đau đầu, và có lẽ đây cũng là lệnh mà tôi nhận được nhiều báo cáo lỗi giả lập nhất
    Trong King of Fighters ngày xưa cũng có một lỗi “kiểm tra sai” cờ carry của lệnh SBCD, và họ dùng việc này để giảm bộ đếm thời gian vòng đấu và kết thúc vòng đấu
    Dù là hành vi không được tài liệu hóa, nếu không giả lập các cờ trạng thái số học trong phép toán thập phân mã hóa nhị phân, bộ đếm thời gian vòng đấu của KOF sẽ cứ quay vòng từ 00 về 99 mãi và không kết thúc
    SNK đúng là những vị thần của chip 68000

    • Là một người hâm mộ arcade cổ và 68k, tôi rất muốn nghe thêm về cách SNK khai thác 68K đến cực hạn
  • Đúng là một hành trình dài
    Dạo này tôi không còn đủ kiên nhẫn để chui sâu xuống hang thỏ như tác giả, nhưng tôi rất đồng cảm với cảm giác thành tựu đến từ khoảng cách giữa việc tưởng rằng mình biết một thứ và thật sự biết nó

    • Với tư cách tác giả, khi phát hiện những thứ như vậy tôi có xu hướng rơi vào một cuộc phân tích chuyên sâu khá lớn
      Dù tốn nhiều thời gian, đi đến tận cùng thật sự rất đáng công
  • Dù đã hơn 30 năm trôi qua, tôi vẫn luôn ngạc nhiên rằng UI trình gỡ lỗi Mac có thể hiệu quả đến vậy trên độ phân giải màn hình nhỏ như thế
    Thật sự cảm nhận được tinh thần thủ công bậc thầy

  • Hầu như CPU nào cũng có lệnh không được tài liệu hóa, và 68k cũng không ngoại lệ
    Chỉ là thời đó phần lớn những người có đủ quan tâm và kiến thức cấp thấp lại tập trung vào x86/PC, còn x86/PC có thể xem là một kiến trúc cởi mở và ổn định hơn rất nhiều so với phía Apple
    Microcode của 8088 và 8086 đã được tháo dịch ngược vài năm trước và được nghiên cứu khá nhiều, và theo tôi biết cũng từng có các nỗ lực mô phỏng ở cấp transistor
    Cấu trúc không gian mã lệnh x86 cũng đã được khảo sát kỹ trong các tài liệu như sau
    http://ref.x86asm.net/geek.html
    https://gist.github.com/seanjensengrey/f971c20d05d4d0efc0781...
    Vẫn chưa biết chính xác lệnh này làm gì
    Qua các thử nghiệm hạn chế, kết quả A1 dường như thay đổi theo giá trị A1 ban đầu, giá trị A7 và program counter, nhưng chưa chắc chắn
    Nếu ai đó viết một chương trình thử nhiều giá trị thanh ghi và nội dung bộ nhớ khác nhau để suy luận chính xác hành vi, việc giả lập cũng có thể chính xác hơn
    Hiện tại, cho đến khi có người xem việc đào sâu là đáng làm, MAME đang vá lỗi này trong ROM để Classic II khởi động
    Cá nhân tôi nghĩ nó đủ đáng để làm rõ nhằm có giả lập chính xác
    Tôi không quen 68k, nhưng các bit của lệnh cho thấy một manh mối. Giả thuyết là các bit 5:3 của word thứ hai trông giống một trường mode khác; thay vì chọn thanh ghi Dn với mode 000, mode 101 lại chọn (d16, An), và trường Dc chứa 001 dường như được diễn giải thành A1

    • Đính chính nhỏ, nhưng 68k dĩ nhiên không phải là kiến trúc của Apple
      68030 là một CPU đa dụng khá xuất sắc vào thời đó và cũng được dùng trong Amiga 3000, Atari Falcon, Sun 3, NeXT Cube, v.v.
    • Vào thời Mac Classic còn có ý nghĩa, PC vẫn chưa quá ghê gớm trong điện toán gia đình, và cũng không có nhiều người đào sâu những thứ như vậy ở phía x86/PC
      Demoscene vẫn còn bám lấy các máy tính gia đình 8-bit, còn những người dùng hệ thống gia đình 16-bit thì tập trung vào Atari và Amiga
      PC và x86 chỉ thật sự nổi lên trong gia đình sau khi VGA và card âm thanh trở thành một phần của cấu hình PC tiêu chuẩn
  • Hồi nhỏ tôi có một chiếc Amiga 2000 dùng bộ xử lý 68000
    Khi nghe tin về 68020 hay 68030 tôi đã cực kỳ phấn khích, và sau đó với kiến trúc RISC cũng vậy
    Khi làm cho Amiga nói những câu như “Hello, how are you?” bằng giọng giống robot, bạn bè tôi kinh ngạc như thể vừa lên Mặt Trăng về
    Tôi không thể tưởng tượng rằng chưa đầy 40 năm sau mình sẽ trò chuyện với máy tính bằng ngôn ngữ tự nhiên nhờ LLM, và dùng Python, VS Code, LLM làm công cụ để tự động hóa gần như mọi thứ mình muốn
    Đúng là một thời đại điên rồ

  • Tôi nhớ dòng 68000
    Chúng thật sự là những con chip tuyệt vời

    • Tôi nhớ assembly 68000
      Nó rõ ràng và logic, đúng như kỹ thuật xuất sắc luôn nên như vậy, đến mức chỉ cần lướt qua là có thể hiểu ngay chuyện gì đang xảy ra
      Ngược lại, assembly x86 phần lớn giống như một đống tạp nham thuần túy, như những mẹo thông minh được nghĩ ra sau một đêm thức trắng tồi tệ
      Kiểu trừ chính nó khỏi một thanh ghi để lấy 0 thật sự là quá đáng
  • Tôi thắc mắc liệu đây có phải là cơ chế chống sao chép nhằm ngăn chạy trên một số hệ thống nhất định, hay là hiện tượng xảy ra trên mọi 68030

    • Theo nội dung bài viết thì tôi không nghĩ đây là chống sao chép
      Bảng nhảy liên quan ở đây thiếu mục dành cho máy đó, và có lẽ những người làm ROM đã quên thêm mục mới, nhưng hoàn toàn tình cờ là nó vẫn hoạt động dù không có mục trong bảng
    • Apple khi đó đã bắt đầu thử nghiệm từng chút một việc làm lỗi thời có chủ đích
  • Trên Mac ngày nay, có vẻ những chuyện như vậy sẽ không còn khả thi nữa
    Tài liệu kỹ thuật của Apple thời nay rất tệ

    • Tôi nghĩ lý do Apple không cung cấp tài liệu phần cứng ở mức này là vì Mac hiện đại không có khả năng mở rộng tương đương
      Họ không còn để lộ bus hệ thống qua đầu nối như trước, nơi người dùng có thể cắm card và nhà phát triển bên thứ ba phải tương tác trực tiếp với phần cứng
      Trên Mac hiện đại có USB và Thunderbolt, những thứ chương trình ở không gian người dùng có thể xử lý
      Tất nhiên, tôi không phủ nhận rằng tài liệu của một số API macOS mới nhất cực kỳ sơ sài
      Khi bạn tới được một trang cũ có dải chuyển sắc màu xanh ở phần đầu và dòng “Apple documentation archive”, bạn biết mình đã chạm tới thứ gì đó thú vị
    • Tôi nghĩ trong môi trường hiện đại, khả năng một lỗi như thế này lọt vào firmware hay phần mềm phát hành là thấp hơn nhiều
      Không gian địa chỉ lớn hơn rất nhiều và phần lớn địa chỉ không được ánh xạ, nên nếu thực hiện thao tác bộ nhớ với địa chỉ rác thì thường sẽ thất bại, và lệnh không hợp lệ có lẽ cũng sẽ thất bại
      Bản thân lỗi đọc bảng nhảy bằng chỉ mục vượt phạm vi vẫn có thể xảy ra trong phần mềm hiện đại, nhưng nhiều khả năng tiến trình sẽ chết thay vì tiếp tục chạy như ở đây
      Nhân tiện, WebAssembly có một không gian địa chỉ tuyến tính duy nhất và mọi địa chỉ đều có thể đọc và ghi, nên kiểu lỗi này có khả năng xảy ra hơn
      Nếu địa chỉ rác nằm trong phạm vi, nó có thể đọc, ghi, CAS sai rồi vẫn tiếp tục
      Tuy nhiên, một lệnh không hợp lệ như trong bài sẽ khiến việc tải mô-đun WASM thất bại, nên không hoàn toàn tương ứng 1:1 với vấn đề này trong ROM của Mac
  • Tôi thắc mắc liệu 040/060 có hỗ trợ “lệnh không được tài liệu hóa” này không

    • Trên 040, nó có vẻ thực sự làm gì đó có liên quan đến D1
      A1 hoàn toàn không bị đụng tới, và tôi chưa thử thêm, nhưng cũng có thể nó chỉ được xử lý như một CAS bình thường
      Khi tôi bước qua lệnh này lần đầu trong MacsBug trên LC 475 thì xảy ra lỗi hệ thống, nhưng sau đó thì ổn
    • Tôi thử kiểm tra trên một Amiga có 060, và lệnh này dường như không sửa đổi bất kỳ thanh ghi A nào
      Đây chỉ là kết quả thử rất nhanh đúng word lệnh đó