27

I figure this is fitting for Retrocomputing.SE because CPUs like the 6502 and Z80 and the PDP-8, and probably others, have undocumented instructions and will happily execute them, in contrast with serious and modern CPUs which will trigger some sort of "this can't continue" state.

On the nesdev wiki it talks about some games which use alternatives to the usual NOP. It seems a though those games could have used the actual NOP on the 6502 ($EA) or left that instruction entirely out of course. And some programs mentioned use the LAX instruction, but those could have used LDA and TAX together for to get the same effect (even though it's a bit slower). Similarly, SAX can probably be replaced by a (longer and slower) sequence involving TAX, AND and STA.

My question is if there is a known use of an undocumented instruction, where a documented/official instruction would not have done?

8
  • 8
    Don't underestimate the utility of saving single bytes or single cycles. For instance, on the Apple II, cycle-accurate code is used in disk routines and to split the screen between different graphics modes.
    – zellyn
    Oct 21, 2016 at 3:18
  • 5
    for the purposes of anti-disassembly, yes - that's a known use of an undocumented instruction, where a documented instruction would not have done. Feb 12, 2017 at 4:10
  • 1
    "in contrast with serious and modern CPUs" in what way were the 6502, Z80 and PDP-8 not serious?
    – JeremyP
    Jun 11, 2019 at 8:42
  • 3
    @JeremyP I think OP uses "serious" in the sense that in many/most later CPUs (probably especially those with "protected" modes), encountering an undefined instruction would trigger an ILLEGAL INSTRUCTION TRAP of some kind, rather than let it "just do what the silicon does": see this answer to a partially related question.
    – TripeHound
    Jun 11, 2019 at 9:46
  • 1
    @JeremyP they didn't have the features of mainframes (large addressing space, virtual memory, memory protection, etc.). Modern processors usually do.
    – RETRAC
    Jun 11, 2019 at 22:48

4 Answers 4

33

In some cases undocumented instructions may be useful as shortcuts to accomplish tasks that are "difficult" to achieve with the official instructions.

Going with the Z80, it is common practice amongst developers to make use of the undocumented instructions that allow to access index registers IX and IY as pairs of 8-bit registers (so IXh, IXl, IYh, IYl). For example, how do you load A with the upper half of IX? With the official instructions you are forced to do something like this:

push ix
pop af

;But hey, I don't want to lose my flags!
push ix
pop hl
ld a,h

However there's an undocumented instruction that does exactly that, so why not using it?

ld a,ixh

By the way, a good resource for undocumented Z80 behavior information is the The Undocumented Z80 Documented document by Sean Young.

5
  • 8
    You should always ask 'why was the opcode undocumented?' It may not work under certain odd conditions, or it may have been reserved for replacement by another function in later versions.
    – amI
    Oct 20, 2016 at 20:41
  • 9
    @aml In the case of Z80 all the undocumented instructions have proven to be reliable across all the manufactured processors (the mirrored instructions are a different story), but that's indeed a very good point.
    – Konamiman
    Oct 21, 2016 at 7:50
  • 3
    @Konamiman what are mirrored instructions?
    – Lorraine
    Dec 20, 2018 at 9:00
  • 1
    "there's an undocumented instruction that does exactly that, so why not using it?" I will tell you why not: it's because the instruction could stop working any time the manufacturer tweaked the design of its chips.
    – JeremyP
    Jun 11, 2019 at 8:45
  • 5
    Widely used undocumented op-codes tend to become semi-official, in that manufacturers are careful not to break them because then a lot of popular software wouldn't work.
    – user
    Jun 11, 2019 at 9:10
27

In many cases, undocumented opcodes were not deliberately created, but are merely the result of designers including the minimal circuitry necessary to create a specified set of opcodes. On the 6502, for example, consider how one might go about creating the instructions LDA, LDY, and LDX. Rather than handling the three instructions separately, it would make sense to have six bits of the opcode mean "load something with address mode __", and then use the remaining bits to control the enables of the registers.

While it would be possible to have each register-enable respond to one particular pattern of those last two bits, one can save a few transistors by saying that one register responds when bit 0 is high, one responds when bit 1 is high, and one responds when neither is high. As it happens, that's exactly what the designers of the 6502 did. The bit pattern 101mmmrr is the bit pattern for "load something to the register specified by using memory mode m" opcode. If rr is 00, it loads the Y register; if the lower bit of rr is 1 it loads A, and if higher bit of rr is 1 it loads X. Now for the undocumented part: If both bits of rr are 1, it loads both A and X.

Unfortunately, because the designers of the chip never realized that loading two registers with one instruction might be useful, the decoding circuitry for some other instructions will also respond to bit pattern 10101011 which would be "load immediate, enabling both A and X". As a consequence, while the most useful memory-addressing mode for loading both A and X together would be immediate, there is no usable "load A and X immediate" instruction.

I would expect that the biggest reason many such opcodes were never documented is that the chip designers probably never gave any particular thought to the possibility that bit patterns to which they had never attached any particular meaning might actually serve some useful purpose. What makes this particularly bizarre in the case of the 6502 is that there are a number of cases where instructions don't support an addressing mode, but an undocumented variation does. Two of the most notable examples:

  • LDX does not support the indirect, Y addressing mode, but LAX does. As a consequence, it's sometimes useful to use LAX even in cases where the value in the accumulator afterward is irrelevant, since it's two cycles faster than doing an LDA followed by a TAX.

  • DEC only supports absolute, abs+x, zero page, and zp+x addressing modes, but the bitwise OR of the DEC and CMP opcodes will act as a DEC that supports any addressing mode that CMP supports other than immediate, and which will set flags on the result in the same way that CMP would set flags based upon its operand. Thus, code which needs to perform a DEC using some other addressing mode can use "DCP", whether or not it needs the flags (though in many cases setting Z based upon whether the value was decremented to match the accumulator--rather than zero--can be a nice bonus).

DCP brings up an interesting point: the 6502 documentation specifies that the maximum time required to execute any opcode is seven cycles, and some system designs might rely upon the worst-case interrupt response time that would imply. The indirect-X and indirect-Y variations of DCP take eight cycles. The 6502 executes them without difficulty, but some systems might have trouble if the interrupt line is sampled just before the interrupt line becomes active, and the next instruction is an eight-cycle DCP. While it might be considered bizarre to design a system that has zero margin on interrupt response time, there were many cases where an interrupt source would be synchronous with the CPU clock, and where hardware to allow precisely as much time as the 6502 required for an interrupt response would be cheaper than hardware which could allow one more cycle.

7
  • Like Stephen's answer, this is all very interesting, but doesn't answer the question - where are these instructions used in programs, where the documented instructions wouldn't suffice? Jun 11, 2019 at 11:30
  • 1
    @TobySpeight: As mentioned, LAX(ind),y saves two cycles if code needs to do an immediate table-lookup on the fetched value. Additionally, the SAX opcode (store A & X) can save two or three cycles per store if many values need to be masked with the same constant. DCP can be handy if one needs to perform some operation for a certain range of steps within a loop. The NOP zp opcode reads an address and does nothing with it, and takes 3 cycles do execute--useful if one needs a three-cycle delay that does not disturb registers or flags.
    – supercat
    Jun 11, 2019 at 14:42
  • And where is that used? Jun 11, 2019 at 15:57
  • 2
    @TobySpeight: I've used those in a number of my games for the Atari 2600. In Toyshop Trouble, the code for each right-masked toy scan line takes 75 cycles without a branch. Without SAX available to handle masking, I wouldn't be able to do everything that needs to be done each 76-cycle scan line.
    – supercat
    Jun 11, 2019 at 16:38
  • 1
    @Eight-BitGuru: Interesting, though I think you mean "the original NMOS 6502s". The CMOS versions added a bunch of documented instructions but got rid of the undocumented ones.
    – supercat
    Sep 18, 2020 at 20:57
26

More “serious and modern” CPUs than the 6502, Z80 and PDP-8 have undocumented instructions too, and they don’t necessarily cause the CPU to stop.

The Intel x86 line has quite a few instructions which were initially undocumented; one very famous instruction, which can not be replaced by documented instructions, is LOADALL, which has been discussed here already. This was used in MS-DOS (RAMDRIVE.SYS and HIMEM.SYS at least), OS/2, and no doubt a few other places; this is a known use of an undocumented instruction with no documented replacement.

Other examples of undocumented x86 instructions include:

  • AAD, “ASCII Adjust before Division”, which takes an undocumented 8-bit immediate operand n and calculates AH × n + AL, placing the result in AX (the documented form only mentions multiplication by 10, to convert a BCD value in AX to its non-BCD form);
  • AAM, “ASCII Adjust after Multiply”, which takes an undocumented 8-bit immediate operand n and calculates AL ÷ n, placing the quotient in AH and the remainder in AL (the documented form only mentions division by 10, to convert a non-BCD value in AX to its BCD form);
  • SALC, “Set AL on Carry”, which sets AL to FF if Carry is set, 0 otherwise.

The first two were well-known early on (for example, Robert L. Hummel’s The Processor and Coprocessor documents them, although it gets AAM slightly wrong), SALC not so much. They stayed officially undocumented for a very long time, well into the mid-nineties. All three can be replicated using other instructions, but they are useful in their own right because they don't affect the flags in the same way as their official counterparts. The NEC V20 and V30 clones followed the documentation and ignore the operand to AAD and AAM, so code using the undocumented forms wouldn’t give the expected result on these CPUs.

The Intel 8086 and 8088 also had some unintentional, undocumented instructions, probably corresponding to bits ignored by the instruction decoder. For example, the 0x60–0x6F opcodes map to 0x70–0x7F. These alias opcodes have since been re-purposed, so 8086 code relying on them doesn’t work on newer CPUs!

Another 8086/8088-specific, undocumented instruction is MOV CS, op which updates CS from op and causes an unconditional long jump (without changing the instruction pointer). This didn’t survive for long, even C-MOS 8086 and 8088 CPUs no longer support it. Unfortunately some software relies on this: the PC booter game President’s Choice issues MOV CS, AX, and fails on any CPU which doesn’t support the instruction; it has to be replaced by a far jump.

You’ll find lots more information on these and other instructions on Robert Collins’ old Undocumented OpCodes site, in 86bugs.lst, and in the more recent investigation conducted by Michal Necasek and Raúl Gutiérrez Sanz.

4
  • These instructions very much look like they were not unintended side effects of the instruction decoder (as in the 6502 or Z80).
    – dirkt
    Oct 21, 2016 at 5:57
  • @dirkt agreed, but I didn't get the impression the question was referring specifically to opcodes that were side effects of the instruction decoder. Oct 21, 2016 at 7:56
  • 3
    @dirkt: For AAD and AAM, I would guess that the Intel designers early on didn't know whether it would be more cheaper to have microcode for multiply/divide by ten, multiply/divide by the supplied operand but only iterate four times, or multiply/divide by the supplied operand, iterating the same number of times as a byte multiply. If the former would have been cheaper, the 0A byte in the opcode would have been superfluous, and if they'd known they were going to do the latter there would have been no reason not to document the opcode.
    – supercat
    Oct 21, 2016 at 16:13
  • 3
    @supercat: Intel's current documentation does document the possibility of using immediates other than 10 with AAM: felixcloutier.com/x86/aam is an extract from Intel's official PDFs. Contrary to what Intel says ("the instruction must be hand coded in machine code") good assemblers like NASM and FASM even support writing aam 7 or whatever. I don't know when this became documented, but Intel did at least retroactively decide to do so, maybe after deciding not to special-case that divisor with a multiplicative inverse in microcode or special hardware. Nov 6, 2019 at 4:49
8

Not sure that this really counts as using undocumented op-codes, but might be sufficiently related to be of interest...

On the 8086, there are often two ways of encoding the same instruction (especially for register-register operations). At least one shareware package (the A86/A386 assembler written by Eric Isaacson) uses this facility to "watermark" the files it creates. From the documentation:

A86 takes advantage of situations in which more than one set of opcodes can be generated for the same instruction. [Cites MOV AX,BX as an example]. Both forms are absolutely identical in functionality and execution speed. A86 adopts an unusual mix of choices in such situations. This creates a code-generation "footprint" that occupies no space in your program file, but will enable me to tell, and to demonstrate in a court of law, if a non-trivial object file has been produced by A86.

Ignoring a lot of extraneous detail1, the layout of many two-byte 8086 instructions is as follows:

           +-------------------------------+
      bit: | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
           +-------------------------------+
byte 1     |        opcode         | d | w |
           +-------------------------------+
byte 2     |  mod  |    reg    |    r/m    |
           +-------------------------------+

where:

  • opcode specifies the instruction,
  • d is the direction:
    • d=0 data moves from operand specified by r/m to operand specified by reg.
    • d=1 data moves from operand specified by reg to operand specified by r/m.
  • w is the size (=0 for byte; =1 for word).
  • mod is a par of "mode" bits that specify addressing mode and how the r/m field is used. For our purposes, =11 means a two-register instruction.
  • reg / r/m indicate a pair of registers, where 000 means AX and 011 means BX among others.

With this in mind, a 16-bit register-to-register move instruction (e.g. MOV AX,BX) can be encoded in two different ways:

            byte 1                                byte 2
          +-----------------------+---+---+     +-------+-----------+-----------+
          |        opcode         | d | w |     |  mod  |    reg    |    r/m    |
          +-----------------------+---+---+     +-------+-----------+-----------+
 89  D8   | 1   0   0   0   1   0 | 0 | 1 |     | 1   1 | 0   1   1 | 0   0   0 |
          +-----------------------+---+---+     +-------+-----------+-----------+
 8B  C3   | 1   0   0   0   1   0 | 1 | 1 |     | 1   1 | 0   0   0 | 0   1   1 |
          +-----------------------+---+---+     +-------+-----------+-----------+

Note that d is flipped between the two forms, and the values of reg and r/m are swapped.


1 These details were taken from course notes from the University of Maine (PDF) which also include the missing, extraneous stuff I have skipped over.

9
  • 1
    "to demonstrate in a court of law, if a non-trivial object file has been produced by A86" that's quite the assholish thing to do!
    – Lorraine
    Jun 11, 2019 at 11:28
  • 4
    @Wilson To be somewhat fair, and put it in context, it was shareware, available on try-before-you-buy terms, dating back to 1984. I've no idea whether the author ever pursued anyone for selling software written with an unlicensed copy, or whether this was just "scare" tactics. Although quirky, it was extremely fast and (from memory) about a tenth of the price of MASM (which, at the time, had some very restrictive license conditions of its own).
    – TripeHound
    Jun 11, 2019 at 11:50
  • 2
    @StephenKitt Fully agree, hence my first line. It was just a nugget of info about unusual use of op-codes, stashed away for 20+ years that seemed close-enough in spirit to the question to be worth adding. Most specifically relating to the "is if there is a known use of" part.
    – TripeHound
    Jun 11, 2019 at 13:39
  • 1
    I was curious what the watermark was, and it appears that A86 varies its encoding between instruction operands, but always uses the same encoding for the same register pair; thus MOV AX, BX is always 8B C3, and MOV AX, CX is always 89 C8 (the opposite form). Jun 11, 2019 at 13:53
  • 1
    @Chris Only if you can reliably tell the difference between 8B C3 being the instruction MOV AX, BX (that could be swapped) and some random data (that can't).
    – TripeHound
    Jun 12, 2019 at 21:01

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .