I was glancing around at some long-abandoned x86 assembly code from an old Linux box when I noticed a rather odd `icebp` command I had seen before but never really ever took the time to actually investigate. Since it appeared in other locations, I assumed it had something to do debugging or some odd x86-specific flag behaviour.
For context, here is a snip of the code in question:
loopne 0x1192
add %al,(%eax)
adc (%eax),%al
add %al,(%eax)
icebp
adc %al,(%eax)
add %cl,-0x07(%eax)
add %al,(%eax)
add %dl,(%edx)
add %al,(%eax)
add %ah,(%edx)
or $0x0,%al
The code itself is nothing exceptional, basically just a loop if a certain address (i.e. variable) is null. However, there were a handful of those `icebp` lines present throughout the code, and they didn't seem to do much; at least, nothing obvious.
A quick search led me to a rather dated, but interesting website about x86 undocumented assembly operation codes. The concerning page can be found here. Quoting from the text present on that page:
An undocumented op code that will make debugging run-time code on an ICE easier.
Seems to be related to the x86 "in-circuit emulator," which apparently was a oft-used method of debugging x86 code a number of years ago. I personally don't know that much about it, and frankly there aren't more than half-dozen pages with any meaningful content about it. I suppose that's not exactly that surprising with it being an "undocumented feature" and all.
Anyhow, this instruction inserts a breakpoint into the code, hence the "bp" at the end of "icebp" instruction. I guess this could be useful if you just want to stop at some point and check the flags. This program was large, so from a situational point-of-view it could make sense.
With that in mind, I thought I'd fire it up by assembling it via the binutils "as" command, but I kept getting errors like the following:
atnx_93.as: Assembler messages:
atnx_93.as:217: Error: no such instruction: `icebp'
I also attempted to enforce 32-bit mode via the `--32` argument option. Sadly no luck, so maybe binutils just no longer support it? Something to look into at a later date, I suppose.
Since "as" was unable to build it, I figured perhaps some other, more widely used assembler could do it. After doing a hasty "apt-get install fasm" I attempted to build it. With fasm, it did not seem to complain and that part of the program ran as intended after being linked.
So if binutils no longer support it, then maybe there is some way of updating the code such that the original behaviour is retained?
I was starting to wonder if maybe anyone else had noticed this behaviour. After a bit more searching, I came across a rather neat mailing list conversation between two Linux kernel developers fixing the non-masked interrupt (NMI) code here. It's a bit funny, so I'll quote it here:
This instruction is awesome. Binutils can disassemble it (it's called "icebp") but it can't assemble it.
By all means, read the whole thread, it's kinda interesting. As an aside, I should perhaps mention that in 2015/2016 there was a concerted effort to remove chunks of x86 assembler from a number of locations in the kernel. I seem to recall the argument being that several of the original long-time maintainers retiring, but feel free to correct me if this is wrong.
Anyway, in the thread one of the maintainers, Boris Petkov, was curious about it and decided to dig deeper. He comes to the conclusion that it really isn't all that special, and it can be replaced with an `int 0x01` if need-be. Boris ends by mentioning:
If I'd had to guess, it isn't documented because of the proprietary ICE aspect. And no one uses ICEs anymore so it is going to be forgotten with people popping off and on and asking about the undocumented opcode.
It appears he didn't have any more luck than I did, but the bit of time it took to find meaningful answers about it made me want to write a blog post, if only to satisfy the possible curiousity of others.
Heading back to my code, I did a simple find-and-replace in vim by means of the %s command. Here is what the snip in question looks like afterwards:
loopne 0x1192
add %al,(%eax)
adc (%eax),%al
add %al,(%eax)
int $0x01
adc %al,(%eax)
add %cl,-0x07(%eax)
add %al,(%eax)
add %dl,(%edx)
add %al,(%eax)
add %ah,(%edx)
or $0x0,%al
Afterwards the program assembled and linked correctly, and also appeared to run "properly"; I mean it still breaks once it gets to that code, but it's for debugging so that's sort of the point. With all of the above noted, I think I can call this an improvement. At the very least I would say that it was interesting to get to play around with x86 instructions again.