Why is the x86 undefined instruction called ud2? Why 2?(devblogs.microsoft.com) |
Why is the x86 undefined instruction called ud2? Why 2?(devblogs.microsoft.com) |
Ah, finally an undefined instruction whose behaviour is consistent and architecturally guaranteed!
We also got UDB (D6), the one-byte variant that arrived with x86-64 for 64-bit mode.
And we have always had UDW (FF FF), aka group #5 (1st FF) with a modrm byte of mod=11b r/m=111b (/7) reg=111b (2nd FF) -- that one matters for memory with all bits set to 1, or for buses terminated to all 1 when no device claims an access.
But found it in wikipedia which is well structured and has detailed notes/comments on all instructions - https://en.wikipedia.org/wiki/List_of_x86_instructions
There is also UD2A and UD2B which just seem to be synonyms for UD2 and UD1 respectively? Google tells me that they are legacy tool specific mnemonics used in older GNU GCC/Binutils.
* https://groups.google.com/g/comp.lang.asm.x86/c/ErG5TJEDjiw/...
* https://github.com/netwide-assembler/nasm/blob/nasm-0.98.x/C...
The thing that never seems to stop tripping me up is the different “dup” codes that compile. At some point I really need to properly learn the difference between dup_x2 and dup2_x1 and dup2_x2.
[1] not out of like an ethical objection, just my career has involved almost no reverse engineering and it’s also never been a path I have been super interested in to pursue on my own.
[2] e.g. if two competing chunks of code emit the same bytecode, you don’t need to pull out JMH. My go to example for this is using if statements vs switches, which will usually emit the same code so performance arguments are moot.
Something like:
if (x == 5) {
doSomething()
} else if (x == 6) {
doSomethingElse()
}
This can be trivially converted into a switch and generally is. Run a release build and check me if you'd like.Because the rest of the 0F Fx line also has a ModRM. Ditto for 0F Bx.
So if your 0F FF is at the end of a page, and the next page is not present, you sometimes got an invalid opcode exception and you sometimes got an access violation.
This reminds me of some related information on instruction length and decoding of "undefined" instructions I have filed away from a long time ago; sadly this stuff is disappearing from the Internet, but both the Archive and I still remember:
https://web.archive.org/web/20160721202526/http://pferrie.ho...
Such information is very important for emulation accuracy and some security contexts.
Given who this is and the overall quality of his output over the years, I'm willing to trust it isn't pure guesswork - and anyway, I'd trust his guesswork over many other people's absolute facts.
The first hard-drive in a system was made C: to reserve A: and B: in part because there was software out there that assumed A: and B: were floppy drives and could cause problems if something else was allocated to those signifiers. There are many things that are due to long forgotten compatibility issues like this (try naming file LPT1 under Windows to see another). Another reason is that the BIOS on many PCs was just hard-wired to assume two floppy drives so DOS would see that even if there were no drives really there.
It was a surprise for me as a reader. When I came to this sentence I assumed that 0f ff would become #1 and 0fb9 -- #2. But no, Intel counts from zero, so there is a third ud.
Though, yes, convention does play a role. On ARMv8 you get both SVC <imm> and BRK <imm>. SVC and BRK raise different exception codes (which satisfies the "easy to distinguish requirement) but in principle you could just use BRK with a well-known immediate and eliminate the need for SVC since BRK's immediate is reported in the exception status register. And, anyways, if you have an SVC instruction and a BRK instruction, you may as well use the SVC instruction for syscalls since it's right there.
So you can invoke the handler of the invalid instruction exception with the INT instruction, but as another poster mentioned, the INT instruction alone is not enough for this, but you need to setup the stack in such a way so that it will contain the information expected by the exception handler, which requires multiple instructions.
This kind of invocation may be acceptable when you write a test program for the invalid instruction exception handler, but it is not acceptable when you want to initialize some guard memory with values that will trigger the exception, to signal that your program has attempted to execute instructions from an area that should not be executable. Setting a memory area as non-executable through the access rights has only page granularity, so it is not useful when a page must contain both some executable code and some non-executable data.
If Intel had not defined an official opcode that is guaranteed to remain unused forever, to be able to reliably trigger the invalid instruction exception, the workaround would have been for the user to reserve one of the 256 interrupt vectors for the invocation through software of the invalid instruction exception. For that vector, a simple handler could have been used, which would have setup the stack in the right way, before jumping to the invalid instruction handler.
But this workaround would have had the disadvantage that any chosen interrupt vector could have conflicted with some choice made by the hardware designers of some computers, so it would have been required for it to be a configurable parameter of the operating system kernel, and also of the user applications that need it, like compilers, unless it would have been standardized by some organization.
Just reserving an opcode at Intel and AMD was simpler, with no other requirements for standardization or changes in the existing software.
(exceptions that do push an error code couldn't be emulated at all using INT, since the error code is the last thing pushed by the CPU, after flags and return address)
Wait, what? The x86 CPUs construct the stack frame themselves before jumping to the interrupt handler, otherwise e.g. INT3 wouldn't work.
exception handling requires that some unrelated region of memory is initialized and intact and ready to do the right thing, whatever that is, and that region is outside the scope of your control, it belongs to the operating system or the the embedded ROM, and it may not have been laid out to take care of your case.
assembly/machine code is operating at a lower layer: "I don't know what larger thing I'm a part of, but I know I need to stop."
Some operating systems used them for syscalls.
INT Ib INT1 INT3 INTO BOUND
2. you might know everything about the environments and that's how you know that different environments use different interrupt tables, but you want a code snippet that will not rely on that, so you use this guaranteed opcode.
The reason to want a true UDF imm with an immediate comes down to it being pretty solidly guaranteed that it's going to turn into your language/OS equivalent of a SIGILL insn. In theory an OS could by convention allocate some subset of BRK space for arbitrary userspace purposes, but in practice none did, so trying to use BRK gets you dumped into a debugger, or doesn't have consistent behaviour. It's nice for userspace to have something that doesn't need active OS support.
Even if you only had one floppy, it was both A and B, so you could say "copy a:DOC.TXT b:" and it would read the doc and then ask you to insert the floppy you're considering B (and depending on file size and available memory, sometimes switch back and forth a few more times)
There was a lot of experimentation back in those days. The Wikipedia’s page on floppy disks is surprisingly long!
IBM PCs have never supported 8" floppy drives, but it was easy to make an adapter between IBM PC floppy cables and 8" floppy drives, so there have existed IBM PC clones from countries where 5¼" floppies were scarce (e.g. Eastern Europe), which supported the attachment of 8" floppy drives.
The original 5¼" floppies had only the size advantage, but they had both a lower capacity and a lower speed than 8" floppies, so attaching 8" floppy drives would have been a higher performance option in the beginning.
Only after the IBM PC/AT introduced the high-density 1.2 Mbyte floppy disks, the 5¼" format matched (actually very slightly exceeded) the performance of the old 8" floppies.
But in the context of drive lettering, although CP/M supported 8" floppies, MS-DOS never did. MS-DOS adopted the naming scheme, but not the boot style.
They were relatively rare, but there absolutely were DOS-era machines without a floppy drive – found in schools, corporations, government agencies.
The most common implementation used Novell NetWare's RPL protocol to boot using an Ethernet card with an installed RPL BOOT ROM. The machine didn't need any disk drives (hard disk or floppy) at all, it loaded MS-DOS off a NetWare server, which was also used for all files or data.
You could also install a hard disk for local storage, without installing a floppy drive, but the more common configuration was no disks at all.
Also, the original IBM PC supported external floppy drives; as the 80s went on, external floppy connectors became increasingly rare, but if you specifically wanted an ISA FDC card with an external connector, they were still making new ones into the 1990s. (These didn't use anything like USB; they took the signals of the internal floppy ribbon cable, and surfaced them on a port on the rear of the card.) So, you could have a machine with a hard disk but without a floppy most of the time, and you'd plug in an external drive when you needed it, and you could keep it locked in a cupboard out of the hands of the plebian users the rest of the time. I doubt many people did this – diskless workstations were a lot less work – but probably somebody did at some point.
IBM PCs, even into the PS/2 days, would boot into Cassette BASIC if you didn't have a floppy drive or boot disk inserted: https://en.wikipedia.org/wiki/IBM_BASIC
My high school had the PS/2 Model 25 SX, introduced in 1992, without hard drives. All of the software ran off of a Novell NetWare file server, but required a bootable floppy disk. If you removed the floppy disk, they would boot into the built-in BASIC from the machine's ROM.