- Mac Classic II ROM में out-of-range jump bug की वजह से गलत address पर write करने ही वाला था, लेकिन असली Motorola MC68030 CPU ने एक undocumented instruction execute करते हुए A1 register बदल दिया और crash टल गया
- यह समस्या MAME के Classic II emulation में 32-bit addressing चालू करने पर Sad Mac और Chimes of Death दिखने से सामने आई; 24-bit addressing में यह normal boot जैसा दिख रहा था
- ROM का
InstallSoundIntHandler path Classic II के BoxFlag 17 को 16-entry branch table पर apply कर 0x40A43B94 पर jump कर गया, और यह location असल में MOVEA.L instruction के बीच में थी
- वह byte sequence
0C EC 08 A9 00 04, CAS D1,D2,$0004(A4) जैसा था, लेकिन reserved bits set होने के कारण invalid CAS-family instruction था; असली 68030 पर इसने A1 को FFFF8FBA से 40A4BBB2 में बदल दिया
- असली Classic II में तीन custom ROM डालकर verify करने पर पता चला कि इस instruction को NOP से हटाने पर hardware भी Sad Mac के साथ fail हुआ, और MAME ने exact behavior पता चलने तक ROM patch के जरिए boot को bypass किया
MAME में सामने आई Classic II boot failure
- MAME arcade game emulator के रूप में जाना जाता है, लेकिन यह 68000-based Mac models का emulation भी support करता है
- MAME में command+power key combination से debugger call करने वाली feature को fix करते समय Classic II में अजीब behavior दिखा
- Classic II में physical interrupt button है, और keyboard·mouse वगैरह handle करने वाला “Egret” 68HC05 microcontroller भी है
- MacsBug में command+power key combination activate करने का command भेजने वाला code है
- Classic II 24-bit addressing में normal boot कर रहा था, लेकिन MacsBug load करने के लिए जरूरी 32-bit addressing चालू करने पर Sad Mac और Chimes of Death दिखे
- Apple Tech Info Library document के मुताबिक Sad Mac code
0000000F exception और 00000001 bus error को दर्शाता है
- 68k Mac में bus error आम तौर पर किसी non-existent expansion card को access करने जैसे invalid address access का मतलब होता है
ROM tracing: sound interrupt initialization path
- MAME debugger में bus error handler address
0x40A026F0 पर breakpoint लगाकर, ROM symbol map से call path trace किया गया
- यह address ROM map में
GenExcps के रूप में दिखता है
- common error handling routine
ToDeepShit नाम से दिखता है
- bus error कराने वाला instruction
0x40A43B9C पर मौजूद यह code था
move.b #$90, ($1c00,A1)
- यह code ROM map में
InstallSoundIntHandler के रूप में दिखे routine के अंदर था, और Classic II के sound interrupt initialization से जुड़ा था
- Classic II, original Macintosh Classic की तुलना में Macintosh LC series architecture के ज्यादा करीब है, और LC के V8 chip जैसा EAGLE gate array इस्तेमाल करता है
- ROM code में
V8SndIntPatch1 जैसे नाम दिखने की वजह यही है
- EAGLE में Apple Sound Chip (ASC) के reduced version जैसी functionality शामिल है
गलत branch table और A1 register की समस्या
V8SndIntPatch1 Gestalt trap के gestaltHardwareAttr selector, यानी 'hdwr', का इस्तेमाल करके hardware attributes check करता है
- अगर
gestaltHasASC bit नहीं है, तो return कर देता है
- Classic II में
gestaltHasASC set है
- इसके बाद code RAM के
0xCB3 से BoxFlag value पढ़कर D0 में डालता है
- Classic II का
BoxFlag value 0x11, यानी 17 है
- problem वाला branch code D0 को double करके PC-relative jump offset के रूप में इस्तेमाल करता है
add.w d0,d0
jmp loc_40A43B72(pc,d0.w)
- हर
BRA.S instruction 2 bytes का है, इसलिए BoxFlag value को double करके branch table index के रूप में इस्तेमाल करने वाली structure थी
- असली table में BoxFlag 0~15 के लिए सिर्फ 16 entries थीं, और Classic II की value 17 range से बाहर थी
- calculated jump target
0x40A43B72 + 0x22 = 0x40A43B94 था
- यह location intended instruction start नहीं, बल्कि
MOVEA.L instruction के बीच में थी
- असल
MOVEA.L को A1 में normal address load करना था, लेकिन instruction के बीच में jump होने से A1 ने पिछली relative jump calculation में इस्तेमाल value 0xFFFF8FBA बनाए रखी
- फिर
move.b #$90, ($1c00,A1) execute होने पर write address 0xFFFF8FBA + 0x1C00 = 0xFFFFABBA बनता है, जो Classic II में invalid address है
Undocumented 68030 behavior
- out-of-range jump जिस byte sequence पर पहुंचा, वह यह था
0C EC 08 A9 00 04
- IDA और GNU objdump इस location पर instruction को सही तरीके से disassemble नहीं कर पाए
- Macintosh IIci के MacsBug में वही byte sequence execute करने पर यह
CAS.W D1,D2,$0004(A4) जैसा दिखा, लेकिन execution के बाद A1 value बदल गई
- A1
0xFFFF8FBA से RAM address जैसी दिखने वाली value में बदल गया
- observe किया गया कि नया A1 value original A1, A7 और program counter पर निर्भर करता है
- Motorola M68000 Family Programmer’s Reference Manual के हिसाब से first word CAS instruction जैसा दिखता है, लेकिन second word में 3 ऐसे bits 1 set हैं जिन्हें 0 होना चाहिए
- इसलिए यह instruction documented valid CAS नहीं, बल्कि reserved bits enabled वाला invalid instruction है
- असली 68030 इसे illegal instruction exception की तरह handle नहीं करता, बल्कि A4+4 पर read-modify-write bus cycle perform करता है
- A4 को invalid address पर set करने पर MacsBug, A4+4 पर read-modify-write के दौरान bus error दिखाता है
- normal CAS में नहीं बदलना चाहिए था, फिर भी A1 बदलता है
असली Classic II hardware verification
- hypothesis verify करने के लिए 1991 में बना Classic II खरीदा और repair किया गया, फिर ROM को programmable SST29EE010 EEPROM से replace किया गया
- पुराने Mac के surface-mount capacitors corrosive leakage कर सकते हैं, इसलिए power on करने से पहले उन्हें हटाकर repair किया गया
- CRT और analog board की जगह logic board को अलग से drive किया गया, और Raspberry Pi Pico-based VGA converter से display check किया गया
- A1 value screen पर दिखाने वाला 68030 assembly code ROM की empty space में डाला गया, और तीन custom ROM बनाए गए
- Custom ROM 1: MAME में Sad Mac कराने वाले
0x40A43B9C के MOVE.B को A1 display code से replace किया
- Custom ROM 2: out-of-range jump target
0x40A43B94 के CAS-जैसे instruction को A1 display code से replace किया
- Custom ROM 3:
0x40A43B94 के CAS-जैसे instruction को NOP से replace किया
- Custom ROM 1 ने दिखाया कि वह code असली hardware पर भी execute होता है, और MAME में crash वाले point पर A1
0x40A4BBB2 है
- यह value ROM address है, इसलिए write target के लिए appropriate नहीं है, लेकिन write attempt करने पर भी bus error नहीं होता
- Custom ROM 2 ने दिखाया कि out-of-range jump से ठीक पहले A1 वही
0xFFFF8FBA है जो MAME में देखा गया था
- out-of-range jump सचमुच होता है
- उसके बाद CAS-जैसा instruction A1 बदलता है, यह hypothesis इससे match करता है
- Custom ROM 3 ने दिखाया कि CAS-जैसे instruction को हटाने पर असली Classic II भी Sad Mac के साथ fail होता है
- इस test में hardware ने 24-bit mode में भी वही Sad Mac दिखाया
- MAME ने 24-bit mode में invalid write पर bus error नहीं दिया, यह असली hardware की तुलना में ज्यादा lenient behavior था
MAME और emulation में बाकी समस्या
- मिला हुआ behavior undocumented MC68030 instruction का ऐसा case है, जो read-modify-write bus cycle perform करता है और A1 register तक बदल देता है
- Classic II ROM का bug इसी behavior की वजह से छिप गया, और असली machine normal तरीके से चलती रही
- वही code snippet नए Macintosh IIvx ROM में भी मिला, लेकिन उस ROM में jump table size बढ़ाया गया था और Classic II case सीधे RTS पर jump करता है
- इस behavior की वजह से लगता है कि फिलहाल 100% perfect Motorola MC68030 emulator या clone implementation शायद मौजूद नहीं है
- इस instruction को execute करके A1 result check करने वाला छोटा code physical 68030 और emulator में फर्क कर सकता है
- MAME ने exact instruction behavior पता चलने तक Classic II ROM bug को patch करके boot संभव बनाया
- अलग से, command+power key combination असली Classic II में MacsBug install होने पर काम करता है, लेकिन MAME में अभी काम नहीं करता
1 टिप्पणियां
Hacker News की रायें
खोजा गया MC68030 व्यवहार CPU डिज़ाइनर द्वारा जानबूझकर बनाई गई “असली” instruction से ज़्यादा, illegal instruction पर CPU के internal logic को don’t-care input मिलते हुए संयोग से execute हो जाने का परिणाम हो सकता है
आम तौर पर CPU को illegal instruction detect करके exception देना चाहिए, लेकिन कुछ मामलों में लगता है ऐसा नहीं होता
https://www.nxp.com/docs/en/reference-manual/MC68030UM.pdf के पेज 8-9 पर illegal instruction को “first word” के bit pattern के आधार पर define किया गया है
यह instruction 3-word की है और first word valid है, जबकि अजीब bit दूसरे word में है, इसलिए काफी संभावना है कि 68030 ने second word को verify किए बिना CAS instruction के implementation logic में ही आगे बढ़ा दिया
“valid” instruction code बस ऐसे combinations थे जो document करने लायक उपयोगी थे, और “invalid” combinations बेकार या बेमतलब combinations थे
इस नज़रिए से देखें तो illegal/mystery instruction code कोई चौंकाने वाला exception नहीं, बल्कि लगभग अपरिहार्य चीज़ है
सामान्य तौर पर उम्मीद होगी कि documentation के हिसाब से meaningless 0 bits को पूरी तरह ignore किया जाएगा
लेकिन 68030 में शायद उन bits का 0 होना ज़रूरी है, और 6502 के illegal instruction code की तरह, वे bits set होने पर किसी दूसरी instruction के लिए hardwired logic activate हो सकता है
CAS हमेशा सिरदर्द रहा है, और emulation bug reports सबसे ज़्यादा शायद इसी instruction के लिए मिली होंगी
पुराने King of Fighters में भी SBCD instruction के carry flag को “गलत” तरीके से check करने वाला bug था, और इसे round timer घटाने और round खत्म करने में इस्तेमाल किया गया था
undocumented behavior ही सही, अगर binary-coded decimal operations में arithmetic status flags को emulate न किया जाए, तो KOF का round timer 00 से 99 तक लगातार घूमता रहता है और खत्म नहीं होता
SNK सचमुच 68000 chip के देवता थे
सच में बहुत लंबी यात्रा है
आजकल मेरे पास लेखक की तरह rabbit hole में इतनी गहराई तक उतरने का धैर्य नहीं है, लेकिन किसी चीज़ को जानने का सोचने और सच में जानने के बीच जो संतोष मिलता है, उससे मैं बहुत relate करता हूँ
इसमें बहुत समय लगता है, फिर भी अंत तक पहुँचने पर सच में बहुत संतोष मिलता है
30 साल से भी ज़्यादा समय बीतने के बाद भी, मैं हमेशा हैरान होता हूँ कि इतनी छोटी screen resolution पर Mac debugger UI कितना प्रभावी हो सकता था
सचमुच craftsmanship महसूस होती है
लगभग हर CPU में undocumented instructions होती हैं और 68k भी exception नहीं है
बस उस समय पर्याप्त interest और low-level knowledge रखने वाले ज़्यादातर लोग x86/PC पर focused थे, और x86/PC को Apple side की तुलना में कहीं ज़्यादा open और stable architecture माना जा सकता है
8088 और 8086 microcode कुछ साल पहले disassemble होकर काफी study किया गया था, और जहाँ तक मुझे पता है transistor-level simulation की कोशिशें भी हुई थीं
x86 instruction code space structure को भी नीचे जैसे documents में विस्तार से explore किया गया है
http://ref.x86asm.net/geek.html
https://gist.github.com/seanjensengrey/f971c20d05d4d0efc0781...
यह instruction ठीक-ठीक क्या करती है, अभी मालूम नहीं
सीमित testing से लगा कि A1 का result original A1 value, A7 value और program counter पर निर्भर करता है, लेकिन पक्का नहीं है
अगर कोई अलग-अलग register values और memory contents डालकर program बनाए और exact behavior infer करे, तो emulation भी और accurate हो सकती है
फिलहाल, जब तक कोई इसे गहराई से देखने लायक नहीं मानता, MAME Classic II को boot कराने के लिए ROM के इस bug को patch कर रहा है
व्यक्तिगत रूप से, accurate emulation के लिए इसे पर्याप्त रूप से समझना लायक है
मैं 68k से familiar नहीं हूँ, लेकिन instruction के bits कुछ clues देते हैं। hypothesis यह है कि second word के 5:3 bits किसी और mode field जैसे दिखते हैं, और mode 000 से Dn register चुनने के बजाय 101 फिर से (d16, An) चुनता है, जबकि 001 वाला Dc field A1 के रूप में interpret होता लगता है
68030 उस समय काफी अच्छा general-purpose CPU था और Amiga 3000, Atari Falcon, Sun 3, NeXT Cube वगैरह में भी इस्तेमाल हुआ था
demoscene में लोग अब भी 8-bit home computers से जुड़े हुए थे, और 16-bit home systems इस्तेमाल करने वाले लोग Atari और Amiga पर focused थे
घरों में PC और x86 सच में तब उभरे जब VGA और sound cards standard PC configuration में शामिल हुए
बचपन में मेरे पास 68000 processor वाला Amiga 2000 था
68020 या 68030 की खबरें सुनकर मैं बहुत excited हो जाता था, और बाद में RISC architecture को लेकर भी ऐसा ही था
जब Amiga robot जैसी आवाज़ में “Hello, how are you?” जैसे वाक्य बोलता था, तो दोस्त ऐसे हैरान होते थे जैसे चाँद से होकर आए हों
मैंने कभी नहीं सोचा था कि 40 साल से कम समय में LLM के जरिए computer से natural language में बातचीत करूँगा, और Python, VS Code, LLM को tools की तरह इस्तेमाल करके लगभग जो चाहूँगा automate कर पाऊँगा
सच में पागल कर देने वाला दौर है
68000 family की याद आती है
वे सचमुच शानदार chips थे
अच्छी engineering जैसी होनी चाहिए, वैसी ही clear और logical थी; बस सरसरी तौर पर देखने से समझ आ जाता था कि क्या हो रहा है
इसके उलट x86 assembly ज़्यादातर ऐसी pure junk लगती है जैसे किसी खराब all-nighter के बाद निकली clever trickery हो
0 पाने के लिए register से खुद उसी को घटाना वाकई हद है
मुझे जिज्ञासा है कि यह किसी खास सिस्टम पर न चलने देने के लिए copy protection था, या फिर सभी 68030 पर होने वाली घटना
इसमें शामिल jump table में उस मशीन के लिए entry गायब थी, और शायद ROM पर काम करने वाले लोग नया entry जोड़ना पूरी तरह भूल गए थे, लेकिन संयोग से table entry के बिना भी यह काम कर गया
आज के Mac में ऐसी चीज़ आगे असंभव लगती है
आजकल Apple की technical documentation बहुत खराब है
पहले की तरह ऐसा system bus connector के रूप में expose नहीं किया जाता, जिसमें user card लगा सके और third-party developer hardware से सीधे interact करे
आधुनिक Mac में user-space programs के लिए USB और Thunderbolt हैं
बेशक, इस बात से इनकार नहीं कि latest macOS API में से कुछ की documentation बहुत कमजोर है
जब आप ऐसे पुराने page पर पहुँचते हैं जिसके header में नीला gradient हो और “Apple documentation archive” लिखा हो, तो समझ जाते हैं कि आप किसी शानदार चीज़ तक पहुँच गए हैं
address space कहीं बड़ा है और ज़्यादातर addresses mapped नहीं होते, इसलिए garbage address पर memory operation करने से आम तौर पर failure होगा, और invalid instruction भी शायद fail होगी
out-of-range index से jump table पढ़ने वाला bug अपने-आप में आधुनिक software में भी हो सकता है, लेकिन यहाँ की तरह चलता रहने के बजाय process के मरने की संभावना ज्यादा है
संदर्भ के लिए, WebAssembly में single linear address space होता है और सभी addresses read और write किए जा सकते हैं, इसलिए इस तरह के bug की संभावना ज्यादा होती है
अगर garbage address range के अंदर हो, तो गलत read, write, CAS करके भी आगे बढ़ सकता है
हालांकि, मूल लेख जैसी invalid instruction WASM module loading failure कराएगी, इसलिए यह Mac ROM की इस समस्या से 1:1 समान नहीं है
जानना चाहूँगा कि 040/060 भी इस “undocumented instruction” को support करते हैं या नहीं
A1 को बिल्कुल नहीं छुआ, और मैंने आगे test नहीं किया, लेकिन शायद यह बस सामान्य CAS की तरह process कर रहा हो
LC 475 के MacsBug में जब मैंने इस instruction को पहली बार step-execute किया, तो system error आया, लेकिन उसके बाद सब ठीक था
यह सिर्फ ठीक उसी instruction word का बहुत जल्दी किया गया test था