A minor mystery recently surfaced while analyzing DOS boot sectors. DOS uses several criteria when deciding whether a boot sector contains a valid BPB, and one of the criteria is (oddly enough) checking whether the first two bytes of the sector contain a jump instruction, which then presumably skips over the BPB. The MSDISK.INC module in the MS-DOS 3.21 OAK is a good example. The opcodes considered valid are EBh (JMP short), E9h (JMP), or 69h. Wait, an IMUL instruction? Well, no, that’s not what the comment in the source code says:
cmp byte ptr cs:[DiskSector],069H ; Is it a direct jump? je Check_Signature ; don't need to find a NOP cmp byte ptr cs:[DiskSector],0E9H ; DOS 2.0 jump? je Check_Signature ; no need for NOP cmp byte ptr cs:[DiskSector],0EBH ; How about a short jump. jne BadDisk
The problem is that 69h is not a documented 8086 instruction. It’s an IMUL opcode on 80186 and later, but that seems highly implausible. Besides, the comment clearly says it’s a jump.
Since the undocumented 8086 opcodes are, well, undocumented, could 69h possibly behave like a jump on an 8088/8086 processor? A very good question, with remarkably few answers. One might think that in the hacker culture surrounding early PCs, it would be inevitable that someone would find out what the undocumented instructions really do. But that doesn’t appear to be the case. A fairly exhaustive search turned up nothing, even in books like Undocumented PC (Frank van Gilluwe) which devote significant space to undocumented instructions. It still seems that someone, somewhere must have published something…
A quick look at several emulators turned up nothing either. Fortunately, Raúl Gutiérrez Sanz had a genuine 8088 and enough determination to find out what the undocumented opcodes really do.
Undocumented Isn’t What It Used to Be
Right at the start, it may be useful to mention that the treatment of undocumented opcodes very significantly changed between the 8086/8088 (1978) and its successor, the 80186 (1982).
The 80186 introduced an invalid opcode exception (interrupt vector 6, now designated #UD), which was triggered by attempting to execute an undefined or invalid opcode. The UD2 instruction is nowadays explicitly reserved to trigger the exception and won’t be reused later, as is the fate of many formerly invalid instruction sequences.
In general, opcodes will either behave as documented or trigger an #UD exception. There are “of course” exceptions to the rule: officially undocumented opcodes D6 and F1. But that’s just Intel’s way of showing they don’t care and is not relevant for the purpose of this discussion.
The simple-minded 8086 had no such mechanism to deal with invalid instructions and every possible opcode would do something, although that something might not be very useful and could potentially lock up the processor.
The Documented Undocumented 8086 Instructions
There are several 8086 instructions not documented by Intel whose functionality has been well established for a very long time. These include:
- POP CS (opcode 0Fh): The existence of this instruction is easy to guess because the encoding follows the other segment register pops. The instruction itself makes sense but is more or less impossible to use effectively. That is why it was never documented and the opcode was reused in the 80286.
- MOV CS (opcode 8Eh): Similar to POP CS, this instruction is fairly obvious but also just as useless.
- SETALC/SALC (D6h): Performs an operation equivalent to SBB AL, AL without modifying any flags. In other words, AL will be set to FFh or 0 depending on whether CF is set or clear. This instruction survives in modern Intel CPUs, but is not documented.
Here Be Dragons
Accounting for the three instructions mentioned above, there are several gaps in the 8086 opcode space: 60h-6Fh (including the mystery 69h instruction), C0h-C1h, C8h-C9h, and F1h. Additionally, there are a few holes in the opcode extensions, especially GRP4.
What Really Happens
Raúl Gutiérrez Sanz analyzed an 8086-2 processor manufactured by Siemens in 1990. This was a chip fabricated under a license from Intel and there is no reason to suspect that it is functionally different from CPUs built by Intel or other licensees (AMD, Harris, etc.).
Analysis showed that the behavior of most of the undocumented opcode space is extremely prosaic: The undocumented instructions are aliases of other, documented instructions. In other words, when decoding certain instructions, the processor ignores specific bits. There are no magic instructions, no CPU hangs, nothing like that.
The entire 60h-6Fh range is simply an alias to the 70h-7Fh range (conditional jumps). The processor ignores bit 4 of the opcode, which creates the aliasing. This is a fairly sane behavior, especially for the 8086. These undocumented instructions do not have any potentially dangerous side effects and no microcode is wasted on special behavior for unused opcodes. At the same time, not documenting the opcodes allowed them to be reused in follow-up processors without nasty surprises for existing code.
For the undocumented instructions in the Cxh range, the CPU ignores bit 1 of the opcode. Thus C0h aliases to C2h, C1h to C3h, C8h to CAh, and C9h to CBh.
The F1h opcode currently remains something a mystery. On newer processors, F1h is an undocumented instruction usually called ICEBP or INT1. That is to say, Intel doesn’t document this instruction but AMD does.
On the 8088, the F1h opcode is not an instruction but rather a prefix. This was determined by single-stepping over a sequence of F1h opcodes followed by a documented instruction—the processor steps over the entire sequence, which fairly conclusively proves that F1h is a prefix.
The current guess is that the F1h opcode is an alias of the F0h opcode, which is the LOCK prefix. Proving this beyond any doubt would probably require custom hardware capable of watching the LOCK# bus signal.
How Can This Boot?
Back to the boot sector question. Would it make sense for a boot sector to start with a 69h opcode? On an 8088, perhaps. If 69h is an alias of the JNS instruction, a (short) jump would be executed if the sign flag is not set. At least on IBM PCs, the state of the flags at the beginning of boot sector execution is probably predictable. So yes, 69h might work.
But who would do such a thing, and why? That’s a very good question. It is currently unknown whether any DOS boot sectors starting with 69h opcode existed, and if so, what they were. Why anyone would use undocumented instructions for this is very unclear; perhaps a misguided implementation of a copy protection scheme. The fact that DOS explicitly looks for the 69h opcode strongly hints that such a thing did exist somewhere, somehow. Any leads are welcome!
Wait, There’s More!
There are additional undocumented opcodes in the opcode extension space (usually known as GRP2, GRP3 etc.). More about that next time.