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.
I remember that code sequence in PC DOS 7. Nobody knew what that was about or had ever seen a boot sector starting with 69h. Note I have seen code fragments which claim the way to distinguish between a 8086/8088 and a NEC V20 (or later) is to use PUSHA followed by STC because the NEC CPUs support the 80186 PUSHA/POPA but the 8086/8088 execute 60h as JMP $+2 (apparently a one-byte jump opcode). Look at Appendix G of the file OPCODES.LST which was distributed with Ralf Brown’s Interrupt List. This is the code they list:
mov ax,1
mov cl,33
shl ax,cl
jnz 186_188
pusha ; Executed on 8086/8088 as JMP $+2
stc
jc NECs
jmp 86_88
Was the same filesystem layout ever used on a CPU architecture where 0x69 was a documented jump opcode? It can’t be an 8080 or derivative (where it’s MOV L,C) and on the M68000 you’d expect 0x60 (unconditional branch) rather than 0x69 (branch on overflow, if I’ve read the technical manuals aright).
Or could it just be the normal 0xE9 signature with the top bit reset for some long-lost reason?
Looking at that code, PUSHA/STC would be 60h, F9h so if 60h is aliased to 70h on the 8086/8088 then that would be JO and since the overflow flag is cleared after the SHL it would effectively be the same as JMP $+2 so the OPCODES.LST document is probably wrong in that regard though the detection code would still work.
8080/8085/Z-80: 69h = LD L,C
680x: 69h = ROL indexed
6502: 69h = ADC #$nn
It doesn’t seem likely that 69h would have been a valid initial opcode for booting in any CPU architecture of the early 80s.
Yes, for the 68000 69h is BVS (Branch if oVerflow Set), taking 2 or 4 bytes.
Now I wonder if it is a coincidence that 6xh are jump aliases for the 8086, jump opcodes for the 68000, and 69h is a valid start byte for FAT diskettes… being FAT also the native file system for the Atari ST (68000).
The DOS code to check for 69h opcode might predate the Atari ST. An interesting theory though…
The problem is that the ST (launched in 1985) didn’t use the 69h opcode. According to http://info-coach.fr/atari/software/FD-Soft.php#fd_soft_bs it started with an unconditional branch (jump) instruction (BRA, 1st byte 60h). Note that the bytes preceding the BPB are not exactly the same as those in a DOS disk, and the boot sector ends with a checksum instead of a signature, but it contains a standard BPB using Intel byte order. And the rest of the disk is just like any other FAT disk.
The code sequence in MSDISK.ASM first appears in DOS 3.20 (1986) and is in every standalone version of DOS thereafter though it is optimized in PC DOS 6.1 and above to load the value from DiskSector into AL and then check AL each time. MS-DOS 7.1 (from Windows 98) only checks for E9h and EBh. Assuming 69h is an alias for 79h on the 8086/8088, that is JNS which also makes no sense as the first opcode for boot code since the flags will be in an unknown state upon receiving control from the BIOS INT 19h code.
As far as I can see, that code snippet is in PC DOS 3.2 (and MS-DOS 3.2) but not 3.1. That suggests it was introduced in 1985, though for the reasons above it doesn’t seem likely to be for ST compatibility. Perhaps that value holds special meaning for the processor or BIOS of the IBM 6150 RT?
Is the flag state really random though? I bet that with a given BIOS, the flag state is going to be quite consistent.
DOS 3.2 was developed in mid-to-late 1985 (PC DOS 3.2 files are dated 12-30-85) and released April 1986. Anyway why would Microsoft or IBM care about compatibility with a non-PC system, particularly one that wasn’t even x86 ?
The flag state wouldn’t be consistent across different BIOSes. The only thing you can be sure of after INT 19h is that the boot code was loaded at 0:7c00h and DL=drive code.
The big question is what software actually used 69h in the first byte of its boot sector. Presumably something used it or it wouldn’t be there. Note if I remember correctly, that particular routine only verifies those bytes as part of disk BPB validation so the disk doesn’t necessarily have to be bootable.
And I have been told (but I have never confirmed) that the 69h check is only done for floppies, but not for hard drives.
This led me to another theory I wrote somewhere else: Boot sectors with 69h (if they ever existed) had to be produced before DOS 2.0
The reason: DOS 2.0 was released for the XT and XT/286, so it had to work on 286, where 69h is IMUL. So DOS 2.0 had to avoid making 69h boot sectors.
This would explain why the 69h check is not done for hard drives (no hard drive support before DOS 2.0), but wouldn’t explain why the check is not performed until DOS 3.2
Also, it would be interesting to know when DRDOS started checking for 69h, just because DR worked on platforms different than the PC.
It can be inferred from the code comments that 69h was used prior to DOS 2.0 since E9h is described as a “DOS 2.0 jump”.
Note the XT/286 came out in 1986 when DOS 3.2 was current.
What difference does it make what DR DOS does ? Obviously the 69h check was put in MS-DOS/PC DOS for some reason. Whether it was copied to a partially compatible DOS or not is irrelevant.
“And I have been told (but I have never confirmed) that the 69h check is only done for floppies, but not for hard drives.”
And I think the BIOS does not check for the AA55h signature on floppy boot. I think it uses a different mechanism that depend on the first bytes of the boot sector.
The behaviour of DRDOS might have been relevant if the 0x69 check originated in DRDOS and was copied into MSDOS. But I don’t think it was. As far as I can see, the check only appeared in DRDOS in the Caldera 7.01 release.
About opcode F1h: if this is likely LOCK on a 8086/8088 then what does it do on a 80286 ? Is it the same as the 8086/8088, INT1/ICEBP like the 80386+ or UD ?
“I think it uses a different mechanism that depend on the first bytes of the boot sector.”
Actually, it seems the source code do not support this, so ignore.
The code which checks for the 69h opcode(?) is looking for a valid BPB, which means it cannot predate DOS 2.0.
It seems fairly certain that PC DOS never put 69h at the beginning of a boot sector, but MS-DOS OEMs usually had to supply or at least adapt their FORMAT utility, so who knows what they might have done.
BTW as dosfan noted, the XT/286 has little to do with the original XT and is in fact an updated PC/AT (with e.g. support for 1.44M drives) released in 1986. Confusing naming.
That’s a good point since DOS 1.x didn’t support the BPB but a later DOS seems unlikely as DOS 3.0 came out with the original PC AT where opcode 69h would be IMUL. If the 69h wasn’t an opcode then it wouldn’t matter but the comments in MSDISK.ASM imply that it is by referring to it as a “direct jump”. Perhaps it was for some DOS 2.1 software ? DOS 2.1 was done primarily to support the PCjr so perhaps it was found on the boot sector of some PCjr software.
The XT/286 was basically an AT in an XT case.
dosfan: For me, what DR did, mattered if (as John Elliot says) the 69h check had been introduced by DR and then copied by MS. Now I know that’s not the case, so *now* it doesn’t matter.
And why not care about compatibility with disks produced by another system if it can be achieved just by adding one more opcode to the list? Don’t you think another opcode would have been added if it had been enough to solve Mac disk compatibility?
It depends. The basic rule is that hard disks must have the 0xAA55 signature to be bootable, while floppies don’t. Different BIOSes handle floppies differently.
For example the 06/10/85 PC/AT BIOS requires hard disk boot sectors to have the 0xAA55 signature and that’s it. Floppies OTOH don’t need any signature, but a) the first byte must be 06h or higher, and b) the first 8 words must not be identical. That heuristic has a reasonable chance of catching unusable boot sectors. The heuristic is not consistent across BIOS vendors and releases.
Some BIOSes have additional heuristics for hard disk boot sectors as well. For example if Windows XP installation fails at the “wrong” time, one ends up with a MBR that contains the partition table but no actual boot code. The boot sector then has a valid signature but is not bootable. Some BIOSes catch that, some don’t.
“Okay, wait. If you guys are really us… what number are we thinking of?”
http://cuddlebuggery.com/wp-content/uploads/2012/09/69-Dudes.jpg
Also note the 82h alias to 80h which ended up being supported in later processors for backward compatibility.
Is there a way to subscribe to your channel so I can get email updates when you post new articles?
There’s an RSS feed. http://www.os2museum.com/wp/?feed=rss2 should work.
Just looking back at this thread — the check seems to assume that 0x69 would, like 0xE9, be treated as the first byte of a three-byte jump instruction, because it “[doesn’t] need to find a NOP” after it. That seems to rule out the undocumented 8086 jump (2 bytes) and the 68000 branch (2 or 4 bytes).
There’s absolutely no reason to assume that considering the use of 69h isn’t standard to begin with. There’s also no reason to assume that it was put there for any other CPU architecture, in fact based upon the comment “direct jump” it is much more safe to assume this is an undocumented 8086/8088 jump opcode. Given the scant info the only things you can infer is that whatever software used 69h as the first byte of its boot sector was produced between 1981 and 1985 as DOS 3.2 was where the check first appeared and it was developed in mid-to-late 1985 and that the upper limit on the timeframe is very likely mid-1984 since the comment says “direct jump” and not “IMUL” and thus 8086/8088 software.
The only way to know for certain would be to either find a disk which used this or to ask one of the MS-DOS 3.2 developers (perhaps Larry Osterman would know).
OK, let’s suppose the 69h check is for the JNS undocumented alias. Why the JNS documented opcode is not allowed? And, which value would you expect for the 3rd byte?
I would imagine that there was some software which used 69h that Microsoft considered significant enough to warrant the check in DOS. Why that software used 69h is the bigger question since a conditional jump at the beginning of the boot sector makes no sense. Perhaps the 69h was part of a string (69h = ‘i’) which was incorrectly placed at the start of the boot sector though the comments in DOS suggest it was an opcode. Keep in mind that the code in DOS only validates the BPB, whether the disk can validly boot or not is irrelevant for this purpose. I don’t think the third byte matters since 69h isn’t really a proper first byte to begin with, only E9h and EBh are truly valid first byte opcodes.
One other thing: the comment in MSDISK.ASM could be wrong. Why is E9h termed a “DOS 2.0 jump” and not a “near jump” ? Since this comment really makes no sense then there’s no reason to assume that the comment about 69h being a “direct jump” is correct either as that isn’t proper Intel terminology. It’s too bad the comment doesn’t say something like “check for bogus first byte used by program xyz”.
So, it is a jump opcode (“it is much more safe to assume this is an undocumented 8086/8088 jump opcode”) and it is not a jump opcode (“Perhaps the 69h was part of a string”).
And “There’s absolutely no reason to assume that considering the use of 69h isn’t standard to begin with”, but “69h isn’t really a proper first byte to begin with”.
So, after “There’s also no reason to assume that it was put there for any other CPU architecture” you’ll end up saying the opposite.
Did I say I thought 69h was used for any other CPU architecture ? I said that *perhaps* it was part of a string and the comments were incorrect. 69h = ‘i’ is ASCII, that’s not part of a CPU instruction set architecture. Now if the comments are indeed correct then 69h is a jump opcode though I can’t see any reason why anyone would use an undocumented jump opcode as the first byte of a boot sector, particularly if it is truly an alias for 79h (JNS). Perhaps if it was an alias for E9h (JMP) then at least the comment “direct jump” would make some sense.
Again without seeing the diskette which used this or a statement from the DOS 3.2 developer who actually put the code there in the first place, everything is speculation.
Found this tidbit from the Intel iAPX 86,88,186,188 User’s Manual – Programmer’s Reference on Google Books – http://books.google.com/books?id=lrsmAAAAMAAJ&focus=searchwithinvolume&q=f1h
No surprise in regard to the 186/188 but the part about the 8086/8088 ignoring undefined opcodes certainly isn’t true. F1h is supposedly LOCK on the 8086/8088 and I’ve seen it claimed to be LOCK on the 286 as well. Odd how it generates #UD on the 186 but reverts back to a LOCK alias on the 286.
P.S. Does anyone know of a way to get Google Books to show the entire book ?
No, I don’t know of a way. I know Google is not supposed to show it. Too bad really.
I’m trying to get a 286 board, if I’m successful I’ll find out what F1h opcode really does. Long ago I learned not to trust books and documentation too much, as they often either have no clue or deliberately lie 🙂
So what does 8086 do when there’s an illegal addressing mode like “lea ax, dx” or “call far ax”?
@jva, I, too, wondered what LEA AX,DX would do. I tried it once on an 8086 (in a PS/2 Model 25). It’s entirely possible an 8088 would function slightly differently.
AX would get stored with whatever the last effective address computed was. E.g,:
MOV DX,0abcdh
MOV AX,[1234h]
LEA AX,DX
would result in AX=1234.
Those of you who have bothered to keep real 8088s and 8086s around, please compare notes.
Just for my curiosity: What’s the generated binary code for “LEA AX,DX” ?
AWJ asked about undocumented behaviour of MOV from/to segment register instruction (opcodes 8Ch/8Eh) with bit 5 of the ModR/M byte set to 1. As there are 4 segment registers, there’s one spare bit in the corresponding 3-bit field and it appears as 0 in Intel documentation. So, what happens if we set this bit to 1? Tests on an 8086 show that MOV ignores the value of the spare bit, executing the same operation whether it contains a 0 or a 1.
Needless to say, on the 386 that bit is set to 1 to address new segment registers FS and GS.
“Just for my curiosity: What’s the generated binary code for “LEA AX,DX” ?”
8D C2
Oh, that was it! As [] can sometimes be omitted in alternative syntaxes, I thought they were asking for LEA AX,[DX] which can’t be assembled. But now I understand that they were really asking about LEA AX,DX which assemblers refuse to accept, as it is an illegal (but still codable) instruction/addressing combination.
More generically, what’s undocumented is LEA’s behaviour when the mod field of the ModR/M byte is set to 11b.
As I don’t have the 8086 here I will test it next time I can use it, probably in a week.
Well, someone else already tested the 8086 (see comments above). I wonder if the behavior of NEC V20/V30 is any different.
Even knowing that LEA AX,DX will load AX with the last computed effective address, there are still many undocumented behaviour to check in this instruction.
For example: Does last computed effective address refer just to data addresses (MOV AX,[memory]), or any address (PUSH AX, JMP, CALL, INT, RET, IRET).
And, what about instructions accessing two different memory addresses? (PUSH [memory], CMPS…)
Also I would like to check behaviour of instructions with non-conventional addressing, like XLAT.
The V20 machine has been boxed and moved to make room for a “new” computer, so it will not see the light for a while. But after noted all possible undocumented behaviour of the 8086 I will check it against 8088 and NEC CPUs.
BTW, it would be interesting to check if after retrieving a word operand on 8086 or V20, the last computed effective address is the low or the high order byte of the operand address.
In the last sentence of previous comment I meant 8088, not 8086.
LEA AX,DX: As Joshua Rodd said, it loads AX with the last computed effective address. and I would add: Excluding addresses computed for flow control (addresses to be stored in IP).
LEA computes the memory address of the 2nd parameter and seems to store this address in an internal register before storing it in the 1st parameter. If the 2nd parameter is not in memory, no computation is done, and LEA seems to take the previous contents left in this internal register.
So, the general case is that LEA AX,DX after an instruction whose parameter is [1234h], will load 1234h in AX, regardless of DX.
After XLAT, it will load BX+AL.
After PUSH, PUSHF, POPF and CALL it will load the resulting SP. Presumably the same for INT, RET and IRET, but I had no time to test them.
After POP it will also load the resulting SP, except for POP [1234h] (it will load 1234h).
JMP will not alter the last computed address, except for JMP [1234h].
After LES and LDS, it will load the parameter address + 2. In the 8088 it could be different.
CMPSB and CMPSW seem to calculate the instruction of next elements after performing the comparison (to update SI and DI), and they update SI first and DI next. So, after those instructions, the updated DI will remain in the internal register.
“After PUSH, PUSHF, POPF and CALL it will load the resulting SP. Presumably the same for INT, RET and IRET, but I had no time to test them.”
Confirmed, but one exception: After RET N, LEA AX,DX will load SP – N in AX (because RET N discards parameters, but does not access them).
It’s also worth noting that INT seems to PUSH the return address after reading the interrupt handler address from the vector table.
Solving one mystery, the 0x6x opcodes are used in the boot sector for the DEC Rainbow version of MS-DOS 2.01. We just needed to implement them in MESS.
Arbee,
Please elaborate on this. I assume the hardware is the DEC Rainbow 100 which had two CPUs: 8088 and Z-80. On the Z-80 the 60h-67h is LD H,reg and 68h-6Fh is LD L,reg except that 66h and 6Eh access the byte pointed to by HL instead. 69h is the opcode in question (LD L,C) so does the boot code actually start with this instruction or is it the first byte of a signature ? Also could you provide a list of what software used boot sectors starting with 69h and a dump of such a boot sector.
This is what i encountered during debugging the DOS 2.01 booter.
https://dl.dropboxusercontent.com/u/37819653/0x61_0xc1_Instructions.png
I saw 0x61, 0xC0, 0xC1, 0xC9 in this late session (but i am currently unable to reproduce it).
To me, it looked like obfuscated code (0x61 was already replaced by 0x71 on first screen and 0xc1 by 0xc3 on second screen…).
Implementing the 60h-6Fh aliases + C0h aliases to C2h, C1h to C3h, C8h to CAh, and C9h to CBh mentioned in the first paragraph (here on page) in MESS’ 8086/8088 code (=> i86.c) seemed reasonable and straightforward.
Got my info from this (OS/2 museum) page, so we shouldn’t do too much bootstrapping 😉
P.S.: I grew up with 6502 code, not 8086 or 8088. So feel free to verify my claims…
Here is the RAW image of DOS 2.01
http://rainbow-100.com/archives/46/
Note that the interrupt numbers are different from IBM-PCs, and the hardware is also. There is also a interleave of 2:1 except for track 0 + 1, so add $200 on sector boundaries (512 bytes sector size…)
Okay, but where’s the 69h at the beginning of a boot sector?
Well, bootable diskettes for the Rainbow do not contain any of the structures found on typical DOS boot sectors (at least on tracks 0 and 1). Please have a look at the raw file linked above. We found 2 FATs and a DOS compatible directory, but that’s about it.
There is no media descriptor, no BIOS parameter block, there are no magic bytes (at least i could not locate them…)
I think the illegal 808x codes ($61 at least) were in fact a [i]misguided implementation of a copy protection scheme[/i].
I’ll need to dig through my old hardware reference manuals but I believe that 0x69 was a perfectly valid opcode for the 8089, the IO Coprocessor to the 8088 and the 8086. As a result, an IOP-based system would have a different boot code. Philips had an 8086-based (and later 80186-based) IBM PC compatible machine that used the 8089.