Skip to content(if available)orjump to list(if available)

An invalid 68030 instruction accidentally allowed the Mac Classic II to boot

adrianmonk

> I’ve discovered an undocumented MC68030 instruction that performs a read-modify-write bus cycle and also changes the value of the A1 register.

Rather than a "real" instruction that CPU designers consciously created and which was meant to do something useful but wasn't documented, it could just be that this is an illegal instruction and the logic in the CPU is doing whatever it happens to do when given don't-care inputs. (Maybe this is what the author meant, and I'm just catching up.)

Normally the CPU would detect illegal instructions and cause an exception. This would mean there are certain situations where it doesn't.

I found a manual at https://www.nxp.com/docs/en/reference-manual/MC68030UM.pdf. On page 8-9 of the manual (which is page 276 in the PDF file), it says:

> An illegal instruction is an instruction that contains any bit pattern in its first word that does not correspond to the bit pattern of the first word of a valid MC68030 instruction or is a MOVEC instruction with an undefined register specification field in the first extension word.

Note "in its first word". According to the write-up, the instruction is 3 words long. The first word is normal, and the weird bits occur in the second word. So quite possibly the 68030 doesn't validate this second word, just plows forward with the logic that implements the CAS instruction, and lets whatever happens happen.

(Great write-up and amazing dedication, by the way!)

sowbug

In college, I took a computer architecture course that briefly went into CPU microcode. The fake CPU we studied divided opcodes into bit fields where a certain bit caused a certain primitive operation, like maybe triggering an adder. From this you could see that a specific opcode was just a particular composition of the right primitives. "Valid" opcodes were simply the combinations that were useful enough to document, and "invalid" ones were superfluous or meaningless combinations. Once you understand this, illegal/invalid/mystery opcodes are a lot less surprising; rather, they're inevitable.

I was also taking a logic course at the time and dreaming in terms of truth tables, so it was quite a formative time in my early understanding of processor architecture.

rep_lodsb

It looks to me (not being an 68k expert) that only the first word is considered the "opcode": the second word just selects what "D" registers are used for the CAS operation. Normally one would expect the zero bits to be completely ignored in that case, since they don't have any role in the instruction.

But maybe on the 68030 in this case, the bits must be zero even if they have no documented use, because there is hardwired logic for another instruction that is activated by those bits being set, somewhat like the 6502 illegal opcodes?

userbinator

This is the closest I could find to what the 68000 opcode map looks like:

http://goldencrystal.free.fr/M68kOpcodes-v2.3.pdf

It's reminiscent of ARM, but the relevant part is that the CAS instruction's second word bits 5:0 look like a "modrm" (to use the x86 terminology) where the officially documented values select only Dn, but the undocumented variant would correspond to (d16,An). At least, that's my theory for why A1 gets modified.

userbinator

Update: from the linked thread at https://68kmla.org/bb/index.php?threads/classic-ii-possible-... there is this quote that supports my theory:

This seems to put something into A1 [...]If you clear bit 3 from the second word of the instruction this stops happening.

kstenerud

CAS has always been a bitch. I think I've received more bug reports about that instruction's emulation than any other instruction.

Incidentally, I remember another old "bug" in King of Fighters that "incorrectly" checked the carry flag of the SBCD instruction, which it used to decrement the round timer and end the current round. Completely undocumented of course, but if you don't emulate the arithmetic status flags when doing binary coded decimal operations, the round timer in KOF will just keep on going forever, cycling from 00 to 99 :P

SNK were really the gods of the 68000 chip.

mrandish

> SNK were really the gods of the 68000 chip.

As a fan of retro arcade machines and the 68k, I'd love to hear more about why SNK were godly in how they maximized the 68K.

azinman2

Yes - were they doing something that Apple, Amiga and the Unix vendors weren’t?

kstenerud

They were game devs, so they weren't so much interested in how the chip was SUPPOSED to behave, but rather in how it ACTUALLY behaved. If you look at their code, you'll see instructions being used in ways that they weren't intended, or for their hidden side effects.

You saw similar kinds of shenanigans with the 6502 chip and its "hidden opcodes" that eventually became semi-official.

The mere idea of using binary coded decimal to implement a time counter that can trivially be converted to decimal numeric sprite indices is brilliant. If SBCD had operated as described in the manual, it would have been completely inappropriate due to the extra time SBCD takes to run. But since the instruction already alters the status register, there's no need for an additional CMP, and so SBCD is faster overall, and then a simple shift-then-index is wayyyy faster than a full binary-to-decimal conversion when displaying the timer on screen (DIV performance was abysmal on the 68000).

This is how they managed to squeeze so much juice out of the chip, as evidenced in the Metal Slug series, and SNK vs Capcom https://en.wikipedia.org/wiki/SNK_vs._Capcom:_SVC_Chaos

An OS vendor wouldn't do these things, because there'd be no guarantee that a future chip (68010, 68020, 68030 etc) mightn't actually behave as the manual states, and then the code would break.

ChuckMcM

That is quite the journey. I find I don't have the patience these days to go quite so far down the rabbit hole as the author does, but I resonate with that feeling of accomplishment in knowing something versus just thinking you know something.

dougg3

Author here. Yeah, I have a tendency to go into pretty big deep dives when I find stuff like this. It's so rewarding at the end, even if it does take a lot of time!

mrandish

Finding a previously unknown undocumented instruction at this late date in a line of processors as prevalent and historically significant as the 68k is surprising. Congrats on your achievement! If someone does dive into fully characterizing the undocumented instruction so it can be properly supported in emulators (as you suggested), please post about it on HN. I suspect, like many undocumented CPU instructions, it was probably to help the original designers test or verify something during development but it would be interesting to know.

While obviously a subjective judgement, a lot of people who hand coded assembler on 68k processors regard the ISA as especially elegant, powerful and fun to develop for. In many ways I think of it as peak CISC, thanks to its orthogonal instruction set and wildly flexible addressing modes. And of course the platforms which used it are legendary, from consumer (Mac/Lisa, Amiga, Atari ST, Sinclair QL) to workstations (SUN, Apollo, Quantel) to gaming (Sega Genesis, Neo Geo, Capcom, Atari, Namco, Sega, Taito, Konami) to embedded (automation, print/network controllers, synthesizers, appliances). I'm certainly biased but to this day the 68k (and its 8-bit little brother the 6809) are the only CPUs I still enjoy writing assembler on.

userbinator

I suspect, like many undocumented CPU instructions, it was probably to help the original designers test or verify something during development but it would be interesting to know.

Or simply be an emergent but unintended behaviour of the implementation, as is the case for most of the undocumented 6502, Z80, and x86 instructions I know of.

bell-cot

> a lot of people who hand coded assembler on 68k processors regard the ISA as especially elegant, powerful and fun to develop for. In many ways I think of it as peak CISC, thanks to its orthogonal instruction set and wildly flexible addressing modes.

I definitely agree...but I'd say Motorola really got carried away with those wildly flexible addressing modes. Which lead them into implementation, power draw, and gate-delay hells by the late 1980's and the 68040. The future was ever-rising transistor counts and clock speeds - and their 68k architecture just couldn't go there.

dougg3

Thank you! Yes, I will definitely make another post if and when someone figures out what the instruction does.

khazhoux

30+ years later, I'm still always amazed at how effective the Mac debugger UI could be with such a tiny screen resolution. It's really quite masterful.

userbinator

Nearly all CPUs have undocumented instructions, and the 68k is no exception; it's just that the vast majority of people with enough interest and low-level knowledge at the time were focused on the x86/PC instead, which was arguably a far more open and stable architecture than Apple's. The 8088 and 8086 microcode was disassembled and studied extensively a few years ago, and I believe there's been some attempts at simulating it at the transistor level already. Even before that, the structure of the x86 opcode space was also explored in detail by many, with documents like these resulting from such effort:

http://ref.x86asm.net/geek.html

https://gist.github.com/seanjensengrey/f971c20d05d4d0efc0781...

We don’t really know the exact details of what this instruction does. With some limited testing, I believe I’ve observed that the resulting value of A1 depends on the original A1 value, the value of A7, and the program counter. But I’m not sure. Maybe someone can make a program that tries out a bunch of different register values and memory contents, and attempt to deduce what exactly the instruction does so that it can be emulated accurately. Until someone decides that it’s worth trying to figure out, MAME is patching this bug out of the ROM in order to allow the Classic II to boot.

IMHO this is definitely worth figuring out for accurate emulation. I'm not familiar with 68k but the bits in the instruction offer a good clue - my theory is that bits 5:3 of the 2nd word seem like another mode field, and instead of selecting one of the Dn registers via mode 000, 101 is selecting (d16, An) again and the Dc field, containing 001, is being interpreted as A1.

fulafel

nitpick: 68k of course wasn't Apple's architecture. The '030 was the nicest GP CPU around in its day, used in the Amiga 3000, Atari Falcon, Sun 3, NeXT Cube etc.

pjmlp

At the time Mac Classic was relevant, PC still wasn't thar great in home computing, no one was bothering with this stuff in x86/PC.

We were still bothering with demoscene stuff in 8 bit home computers, and those of us busy with 16 bit home systems were focused on Atari and Amiga systems.

PC and x86 at home only took off, meany really taking off among demoscene and other home users, was when VGA and sound cards became part of a standard PC.

userbinator

The Mac Classic was released in 1990, the Mac Classic II that is the subject of this article was released in 1991. At that time PCs with 286s and 386s were already common, and the 486 was just starting to gain marketshare at the high end. Most of the undocumented 8086 instructions had already been known for almost a decade; and the majority of those who knew were not demosceners. Many developers used Asm exclusively, and the "classic hacker mindset" was very much alive among them.

pjmlp

Depends on where in the globe one were and in my demoscene circles PCs only took off as interesting after Windows 95, folks using MS-DOS or Windows 3.x were mostly due to their parents family computer.

TazeTSchnitzel

I think non-x86/non-PC-compatible home computers did remain relevant for a bit longer in Europe and Japan than in the US but by 1992 the writing must have been on the wall.

zargon

This is 1992 we're talking about. You might be confusing the Mac Classic II with mid-80s Macs due to the form factor.

pjmlp

Where Amiga and Atari were still calling the shots among European demoscene.

submeta

I had an Amiga 2000 with an 68000 processor when I was a kid. How I got excited when I heared about the 68020, or even 68030! And then even RISC architecture. Those were things that got me excited back in those days. I could let my Amiga say a sentence like „Hello, how are you?“, in a robotic tone, and my friends were baffled as if they‘d been to the moon and back. I couldn’t have imagined that less then four decades later I‘d be talking to my computer in natural language using llms. And with Python, VS Code, and LLM in my toolbelt I can automate almost anything I wish to. Crazy times!

AnimalMuppet

WOW.

Amazing work. Thanks for the exposition.

(I miss the 68000 line. Those were such great chips...)

aruggirello

I miss the assembly. It was so clear and logical, like great engineering should always be. You could browse through it and comprehend at a glance what was happening. By contrast x86 assembly looks like pure rubbish, with stuff that most of the time looks like clever hacks coming from a bad night's sleep, like subtracting a register from itself to get zero... c'mon!

snvzz

Fortunately, sane assembly didn't die with 68k falling out of favor.

MIPS carried the flag, and now RISC-V is even more readable.

The dusk of x86 era is nigh.

jjuran

I hate to disappoint you, but the canonical way to clear a 68K address register is `SUBA.L An,An`. My asm-coded replacement for the Metrowerks code resource runtime uses it:

<https://github.com/jjuran/metamage_1/blob/master/mac/toolcha...>

aruggirello

Oops, you're right. I forgot stuff like `MOVE.L #0,Dn` is restricted to data registers. So this must be the way to go, every time a NULL pointer is needed...

hajile

Was this copy protection to keep it from running on systems or does this happen on all 68030?

kevingadd

The contents of the post make me pretty confident that this wasn't copy protection, the jump table involved here is just missing an entry for the machine because (most likely) everyone involved in working on the ROM forgot to add a new entry to the jump table and it happened to work without a table entry by pure chance.

snvzz

Apple was already dabbling in planned obsolesce.

1over137

This seems like it will be impossible in the future with today’s Macs. Apple’s technical documentation is rubbish these days.

grishka

IMO the reason Apple doesn't provide this level of hardware documentation is because modern Macs don't have comparable expansion capabilities. The kind that expose system buses on connectors that users are supposed to plug cards into, and third-party developers to interact directly with hardware to make those cards work. On a modern Mac, you've got USB and Thunderbolt that you can interact with from a userspace program.

Though I'm not denying that some of the newer macOS APIs are very poorly documented. As in, you know you've stumbled upon the cool shit when you end up on one of those old pages with a blue gradient in the header that says "Apple documentation archive".

kevingadd

I think in modern environments the odds of this sort of bug slipping into released firmware/software are much lower. Address spaces are much bigger and the vast majority of addresses aren't mapped so doing a memory operation on a garbage address is going to fail most of the time, and invalid instructions will probably fail too.

Reading from a jump table with an index that's too big is a realistic sort of bug to have, so I could see that part making it into modern shipped software. But I would expect the process to fall over when it happens, not keep on trucking like it did here.

FWIW, WebAssembly is an environment where bugs of this sort are more possible, since it has a single linear address space where every address is both readable and writable. So if your garbage address is within range you can do an erroneous read, write or CAS and get away with it. But then invalid instructions like in the post will cause the WASM module to fail to load, so it's still not 1:1 comparable with this issue in the mac's ROM.

1over137

Sorry, the “this” I was referring to was the ability to consult docs, reverse engineer to this extent, etc.

saagarjha

I think you’re underestimating people who do this.

snvzz

Fortunately, today we've got pointer masking.

(ARM and RISC-V do, anyway)

basementcat

Do the '040/060 also support this "undocumented instruction"?

dougg3

On the 040, it seems to do something that actually involves D1. Definitely doesn't touch A1 at all. I didn't test further, but it's possible it just handles the instruction as a normal CAS.

It did cause a system error the first time I stepped through the instruction with MacsBug on my LC 475, but then it was fine after that.

mras0

Have an Amiga w/ 060, and that instruction doesn't seem to modify any A registers. (Only did a very quick test of those exact instruction words)

basementcat

I appreciate that I can ask an esoteric question about the behavior of a 30 year old microprocessor and multiple people respond with test results on actual hardware within a few hours. Can y'all also post the mask revision (if known) and whether it is an EC or LC device? (In case it impacts behavior)

mras0

Rev5 "full" 060 (not EC/LC). Quick capture of crappy methodology: https://imgur.com/a/XwQ1Tnp (PCR with revision number is in d0)

dougg3

My test of an 040 (no A1 change, D1 changed) was on a chip with the following markings:

XC68LC040RC25B

02E23G QEDP9348D MALAYSIA

cluckindan

Time to fuzz the rest of the processor.

TapamN

Does Ken Shirriff have time to look at a 68030?

kens

Unfortunately, not any time soon.

phibz

That's great. Merely speak his name and he appears.

Love the work you do Ken. Thank you.