I'm not much of an x86 person but on other architectures you can raise software interrupts/exceptions. Does x86 not have this or did those facilities not cover enough use cases?
It's basically a convention. The alternative is to raise interrupts of course, but that might be application specific, or use other invalid instructions than the designated one, but they might work differently on other processor types.
There's a bit of convention and practicality The only thing you really need is that your "fatal error" instruction and "syscall" instruction can be reasonably discriminated without needing to set registers at the call site. Needing to set register to identify a fatal error is not great for code size, especially in languages that generate a lot of them (memory safe languages, mostly).
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.
ARMv8 also gives you a a UDF imm, for a guaranteed undefined insn with an immediate payload.
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.
Maybe because if the code wants to call the invalid opcode interrupt handler (INT6), it needs extra code to populate the flags and registers expected by that handler, whereas actually triggering an invalid opcode exception will get all those parameters populated automatically.
Already since Intel 8086, x86 has the instruction "INT vector_number", whose purpose is to allow software to invoke directly any of the many kinds of exception handlers or hardware interrupt handlers that are specified by the ISA or implemented by the hardware designer, which are normally invoked when various conditions arise, as determined by software execution or by I/O events.
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.
> 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,
Wait, what? The x86 CPUs construct the stack frame themselves before jumping to the interrupt handler, otherwise e.g. INT3 wouldn't work.
I expect that UD2 stops instruction fetching (beyond the current block) and conversion to µops. A software interrupt or supervisor call should probably do neither because most of the time, these instructions eventually return and continue executing the next instruction.
An interrupt or SYSCALL instruction could do anything, which includes remapping or overwriting the memory location it returns to. So no, these instructions can't be prefetched in any case.
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."
It's common to use int3 for some of the scenarios mentioned in the article. (Like non-reachable code) This instruction is often used to trigger a break in the debugger.
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.
What the instruction does is well documented, and the history of other invalid instructions being used for the same purpose in the past I'm guessing there is also well known, though a quick search doesn't turn up any official Intel documentation on the matter.
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.
> It’s called ud2 because the 0F FF variant was retroactively named ud0, and the 0F B9 variant was retroactively named ud1, leaving ud2 as the recommended undefined opcode.
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.
If you look at the intel instruction manual, you do see UD0 and UD1. This isn't specific to Microsoft. You would probably have to ask the cpu engineers who made the decision to add those instructions in?
Thus finally the 0F FF believers were rewarded by being give the honor of op code UD0 making it the one and true original invalid opcode permanently disgracing the 0F B9 adherents with the shame of UD1.
Tangential, but I almost never read assembly [1], but I do read Java bytecode pretty frequently, primarily because doing that can sometimes be a good substitute for benchmarking [2], which I do not enjoy.
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.
28 comments
[ 0.30 ms ] story [ 38.1 ms ] threadINT Ib INT1 INT3 INTO BOUND
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.
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.
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.
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."
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.
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.
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.
Ah, finally an undefined instruction whose behaviour is consistent and architecturally guaranteed!
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.