- 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
Ý 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
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
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
Đú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ó
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
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.
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
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
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
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ệ
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ị
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
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
Đây chỉ là kết quả thử rất nhanh đúng word lệnh đó