Raymond Chen explains how x86 opcodes ud0 and ud1 could appear to take parameters despite being 'undefined' — they occupied decoder slots that had no assigned meaning yet still went through operand parsing before an invalid-opcode fault was raised.
<p>Gunnar Dalsnes wondered <a href="https://devblogs.microsoft.com/oldnewthing/20260910-00/?p=112689/&commentid=144727#comment-144727"> how <code>ud0</code> and <code>ud1</code> could have parameters if they were undefined</a>? “Does it mean they were not completely undefined, just undocumented and not completely implemented?”</p>
<p>The opcodes <code>ud0</code> and <code>ud1</code> have no definition, but that’s not the same as being architecturally an “undefined instruction”. They live in a purgatory where they were not assigned a meaning, but were also not officially declared to be meaningless.</p>
<p>Originally, these byte sequences went through the instruction decoder and happened to slip through a few cracks before somebody finally noticed, “Wait a second, I don’t know how to execute this.”</p>
<p>You can see this when you look at the instructions that are encoded as <code>00001111 11111xxx</code>, as of the Pentium III.</p>
<table class="cp3" style="border-collapse: collapse;" border="1" cellspacing="0" cellpadding="3">
<tbody>
<tr>
<th>Bits</th>
<th>Bytes</th>
<th>Opcode</th>
<th>Operand 1</th>
<th>Operand 2</th>
<th>Meaning</th>
</tr>
<tr>
<td><tt>00001111 11111000</tt></td>
<td><tt>0F F8</tt></td>
<td><tt>PSUBB</tt></td>
<td>mm</td>
<td>mm/m64</td>
<td>Subtract packed bytes</td>
</tr>
<tr>
<td><tt>00001111 11111001</tt></td>
<td><tt>0F F9</tt></td>
<td><tt>PSUBW</tt></td>
<td>mm</td>
<td>mm/m64</td>
<td>Subtract packed words</td>
</tr>
<tr>
<td><tt>00001111 11111010</tt></td>
<td><tt>0F FA</tt></td>
<td><tt>PSUBD</tt></td>
<td>mm</td>
<td>mm/m64</td>
<td>Subtract packed dwords</td>
</tr>
<tr>
<td><tt>00001111 11111011</tt></td>
<td><tt>0F FB</tt></td>
<td colspan="4" align="center">No meaning assigned</td>
</tr>
<tr>
<td><tt>00001111 11111100</tt></td>
<td><tt>0F FC</tt></td>
<td><tt>PADDB</tt></td>
<td>mm</td>
<td>mm/m64</td>
<td>Add packed bytes</td>
</tr>
<tr>
<td><tt>00001111 11111101</tt></td>
<td><tt>0F FD</tt></td>
<td><tt>PADDW</tt></td>
<td>mm</td>
<td>mm/m64</td>
<td>Add packed words</td>
</tr>
<tr>
<td><tt>00001111 11111110</tt></td>
<td><tt>0F FE</tt></td>
<td><tt>PADDD</tt></td>
<td>mm</td>
<td>mm/m64</td>
<td>Add packed dwords</td>
</tr>
<tr>
<td><tt>00001111 11111111</tt></td>
<td><tt>0F FF</tt></td>
<td colspan="4" align="center">No meaning assigned</td>
</tr>
</tbody>
</table>
<p>The byte sequences <code>0F FB</code> and <code>0F FF</code> had yet to be assigned a meaning. You can see how the instruction decoder could take a shortcut and say, “Well, all the instructions in this range, or at least all the ones that I care about, take an mm registers and an mm/m64 operand, so I’ll just save myself some transistors and decode all of them with two parameters (mm, mm/m64).” And then after decoding, it would use bit 3 to decide whether to set up the arithmetic unit for an add or subtract, and it would use bits 0 and 1 to decide how to subdivide the bits into saturating units.</p>
<p>And if you gave it a <code>0F FF</code>, it would be only in that last step that the decoder would realize “Oh dear, I don’t know what to do with a bit combination of <tt>11</tt>. I’ll raise an invalid opcode instruction.”</p>
<p>The invalid opcode instruction got raised <i>after</i> the operands were parsed.</p>
<p>You can see the trouble that <code>0F FF</code> created when those empty slots started to get filled in by the SSE instructions.</p>
<table class="cp3" style="border-collapse: collapse;" border="1" cellspacing="0" cellpadding="3">
<tbody>
<tr>
<th>Bits</th>
<th>Bytes</th>
<th>Opcode</th>
<th>Operand 1</th>
<th>Operand 2</th>
<th>Meaning</th>
</tr>
<tr>
<td><tt>00001111 11111000</tt></td>
<td><tt>0F F8</tt></td>
<td><tt>PSUBB</tt></td>
<td>mm</td>
<td>mm/m64</td>
<td>Subtract packed bytes</td>
</tr>
<tr>
<td><tt>00001111 11111001</tt></td>
<td><tt>0F F9</tt></td>
<td><tt>PSUBW</tt></td>
<td>mm</td>
<td>mm/m64</td>
<td>Subtract packed words</td>
</tr>
<tr>
<td><tt>00001111 11111010</tt></td>
<td><tt>0F FA</tt></td>
<td><tt>PSUBD</tt></td>
<td>mm</td>
<td>mm/m64</td>
<td>Subtract packed dwords</td>
</tr>
<tr style="background-color: color(from currentcolor srgb r g b /15%);">
<td><tt>00001111 11111011</tt></td>
<td><tt>0F FB</tt></td>
<td><tt>PSUBQ</tt></td>
<td>mm</td>
<td>mm/m64</td>
<td>Subtract packed qwords</td>
</tr>
<tr>
<td><tt>00001111 11111100</tt></td>
<td><tt>0F FC</tt></td>
<td><tt>PADDB</tt></td>
<td>mm</td>
<td>mm/m64</td>
<td>Add packed bytes</td>
</tr>
<tr>
<td><tt>00001111 11111101</tt></td>
<td><tt>0F FD</tt></td>
<td><tt>PADDW</tt></td>
<td>mm</td>
<td>mm/m64</td>
<td>Add packed words</td>
</tr>
<tr>
<td><tt>00001111 11111110</tt></td>
<td><tt>0F FE</tt></td>
<td><tt>PADDD</tt></td>
<td>mm</td>
<td>mm/m64</td>
<td>Add packed dwords</td>
</tr>
<tr style="background-color: color(from currentcolor srgb r g b /15%);">
<td><tt>00001111 11111111</tt></td>
<td><tt>0F FF</tt></td>
<td colspan="4" align="center">I want to put <tt>PADDQ</tt> here but I can’t</td>
</tr>
</tbody>
</table>
<p>the natural place to put the <code>PADDQ</code> instruction is <code>0F FF</code>, but people had already been using <code>0F FF</code> with the expectation that it raises an illegal instruction exception. Making it a valid instruction would break those programs, so Intel had to move <code>PADDQ</code> to the rather awkward location <code>0F D4</code>.</p>
<p><b>Bonus chatter</b>: Undefined instructions with parameters are actually not uncommon. For example, on AArch64, <a title="The AArch64 processor (aka arm64), part 19: Miscellaneous instructions" href="https://devblogs.microsoft.com/oldnewthing/20220822-00/?p=107032"> there is a range of 65,536 instructions set aside as permanently undefined</a>, so the <code>udf</code> instruction takes a 16-bit immediate to specify which invalid opcode you want. The PDP-10 reserved opcode 000 as a permanently illegal instruction, and it carries a register and a memory address as parameters. (Because <i>all</i> PDP-10 instructions carry a register and a memory address as parameters.)</p>
<p>There are also so-called “unofficial opcodes” which are instructions that are not part of the instruction set architecture, but for which people reverse-engineered a consistent behavior and began to rely on it. (The 6502 processor is <a title="How MOS 6502 Illegal Opcodes really work" href="https://www.pagetable.com/31"> well-known in nerd circles for having undergone this type of analysis</a>.) The <code>0F FF</code> is one of these “unofficial opcodes” that was popular enough that Intel felt pressure to maintain backward compatibility with it, even though it was never architecturally documented or supported.</p>
<p><b>Bonus bonus chatter</b>: It appears that the mnemonic <code>ud1</code> was <a href="https://github.com/netwide-assembler/nasm/blob/nasm-0.98.x/CHANGES#L827-L831"> introduced by the nasm assembler</a>:</p>
<pre>* Added the following new instructions: SYSENTER, SYSEXIT, FXSAVE,
FXRSTOR, UD1, UD2 (the latter two are two opcodes that Intel
guarantee will never be used; one of them is documented as UD2 in
Intel documentation, the other one just as "Undefined Opcode" --
<span style="border: solid 1px currentcolor;">calling it UD1 seemed to make sense</span>.)
</pre>
<p>It seems obvious that <code>ud1</code> is also the name that Intel gave internally to that legacy instruction. Otherwise, there would be no need to call the new one <code>ud2</code>!</p>
<p>The post <a href="https://devblogs.microsoft.com/oldnewthing/20261007-00/?p=112759/">How can undefined opcodes <CODE>ud0</CODE> and <CODE>ud1</CODE> have parameters? How undefined were they?</a> appeared first on <a href="https://devblogs.microsoft.com/oldnewthing">The Old New Thing</a>.</p>
The article explains the history and reasoning behind the naming of the x86 undefined instruction 'ud2', tracing its evolution from unofficial opcodes to Intel's official implementation due to Hyrum's Law.
Raymond Chen explains why compilers emit int3 versus ud2 for unreachable code: int3 marks padding where no code should exist (invalid function pointers), while ud2 marks paths that violate language-level undefined behavior.
A detailed examination of the conditional tests used in the Intel 8087 floating-point coprocessor's microcode, part of a reverse-engineering effort to understand its algorithms.