6

Is it possible to use the 8-bit registers (al, ah, bl, bh, r8b) in indexed addressing modes in x86-64? For example:

mov ecx, [rsi + bl]
mov edx, [rdx + dh * 2]

In particular, this would let you use the bottom 8-bits of a register as a 0-255 offset, which could be useful for some kernels.

I poured over the Intel manuals and they aren't explicit on the matter, but all the examples they give only have 32-bit or 64-bit base and index registers. In 32-bit code I only saw 16 or 32-bit registers. Looking at the details of mod-r/m and SIB byte encoding also seems to point towards "no" but that's complex enough with enough corner cases that I'm not sure I got it right.

I'm mostly interested in the x86-64 behavior, but of course if it's possible in 32-bit mode only I'd like to know.

As an add-on question too small and related to deserve another post - can 16-bit registers be used for base or index? E.g., mov rax, [rbx + cx]. My investigation pointed towards basically the same answer as above: probably not.

Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
BeeOnRope
  • 60,350
  • 16
  • 207
  • 386
  • 3
    It's not possible, **both the registers have to be the same size as the address-size**, so that also rules out `[reg16 + disp32]`. You need a `movzx`. The tables that show the encoding really do enumerate *all* possible encodings. – Peter Cordes Aug 26 '16 at 19:36
  • Yeah the tables show stuff like rax/eax/ax/al though. So it kept my hopes up. You can count the bits and see that only three are available to select the register to guess that only one size is available, but you have to check for size changing bits across all the various bytes, and read the details of all the REX prefixes, etc. But yeah, I was already pretty sceptical. – BeeOnRope Aug 26 '16 at 19:39
  • 2
    The addressing mode tables won't show `al`, because there are no 8-bit addressing modes. I understand the motivation for the question, though: when I was new to x86, I kept wondering if there were addressing modes I didn't know about. But it turns out there's only `[base + idx*scale + disp8/disp32]`, or any subset of that omitting one or two of those three components. (Plus RIP-relative in 64-bit mode). That's why [I wrote this answer](http://stackoverflow.com/a/34058400/224132), which might make a good Docs topic at this point. – Peter Cordes Aug 26 '16 at 20:28
  • 1
    Right. I never did find a good table for the 64-bit addressing modes in the Intel docs though. There is table 2-3 in section 2.1.5 of Vol 2A of the current Intel dev manual, which covers the 32-bit case, but I never found a corresponding table for the 64-bit case. I was further thrown off early in my search by [this page](http://www.c-jump.com/CIS77/CPU/x86/X77_0110_scaled_indexed.htm) which clearly indicates a `[disp + reg8 + reg32*scale]` addressing mode, which is exactly what I want (in 32-bit mode). It seems like it was a typo though and they meant `disp8 + reg32 ...` instead. – BeeOnRope Aug 26 '16 at 20:51
  • Yeah, nasty typo I guess. x86-64's only major change to addressing modes was repurposing a redundancy to create RIP-relative. Otherwise REX bits just extend the existing base and/or index register fields. (So `[r13]` is like `[rbp]`; both require a zero `disp8`, because the pure base=rbp ModRM encoding is the escape-code for a SIB byte, IIRC) – Peter Cordes Aug 26 '16 at 21:05
  • 4
    Well, there *is* the curious case of `xlat` (or `xlatb`). It *does* (implicitly) use the 8-bit register `al` as the index into a table implicitly pointed to by `[r/e]bx`. Unfortunately it's a horrible waste of encoding space and can only load into the `al` register. – EOF Aug 26 '16 at 22:05
  • @PeterCordes I think your comments above are good enough to constitute an answer. Make one and I'll accept it, if not I'll compose one myself. – BeeOnRope Sep 02 '16 at 01:40
  • Ok, I'll copy those comments into an answer soon. – Peter Cordes Sep 02 '16 at 01:54
  • Actually, go ahead and write up something yourself if you have time. I don't really have anything to add to my comment. My [existing addressing-mode answer](http://stackoverflow.com/a/34058400/224132) is still pretty complete. It's already pretty long, but maybe it would be worth adding something to it about register sizes. If you have any ideas there, please make an edit. – Peter Cordes Sep 02 '16 at 08:40
  • @PeterCordes - I wrote something up below, focusing mostly on what you _can_ do using the address size prefix. I'll add something to your answer on the other question too. – BeeOnRope Oct 05 '16 at 20:18

1 Answers1

7

No, you cannot use 8-bit or 16-bit registers in addressing calculations in 64-bit mode, nor can you use 8-bit registers in 32-bit mode. You can use 16-bit registers in 32-bit mode, and 32-bit registers in 64-bit mode, via use of the 0x67 address size prefix byte.

(But using a narrower register makes the whole address narrow, not a 16-bit index relative to a 32-bit array address. Any registers need to be the same width as the address, which you normally want to match the mode you're in, unless you have stuff in the low 16 or low 32 bits of address space.)

This table summarizes well the various options for operand and address sizes. The general pattern is that the default address size is the same as the current mode (i.e., 32-bits in 32-bit mode, 64-bits in 64-bit mode)1, and then if the 0x67 prefix is included, the address size is changed to half the usual size (i.e., 16-bits in 32-bit mode, 32-bits in 64-bit mode).

Here's an excerpt of the full table linked above showing 64-bit long-mode behavior only, for various values of the REX.W, 0x66 operand and 0x67 address size prefixes:

REX.W 0x66 prefix (operand) 0x67 prefix (address) Operand size (footnote 2) Address size
0 No No 32-bit 64-bit
0 No Yes 32-bit 32-bit
0 Yes No 16-bit 64-bit
0 Yes Yes 16-bit 32-bit
1 Ignored No 64-bit 64-bit
1 ignored Yes 64-bit 32-bit

1 That might seem obvious, but it's the opposite to the way operand sizes work in 64-bit mode: most default to 32-bits, even in 64-bit mode, and a REX prefix is needed to promote them to 64-bits.

2 Some instructions default to 64-bit operand size without any REX prefix, notably push, pop, call and conditional jumps, and as Peter points out below, this leads to the odd situation where at least some of these instructions (push and pop included) can't be encoded to use 32-bit operands, but can use 16-bit operands (with the 0x66 prefix).

Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
BeeOnRope
  • 60,350
  • 16
  • 207
  • 386
  • 1
    Note that in long mode, 32-bit push/pop aren't encodable. The wording in the Intel manual implies that REX.W=0 could modify the operand-size, but it actually faults with an illegal instruction. PUSHW is still encodable with an operand-size prefix, though. Anyway, PUSH/POP don't just default to 64-bit operand-size, they're special. – Peter Cordes Oct 05 '16 at 21:05
  • Updated the footnote. Too bad, because I thought `push` and `pop` were better examples than what's left (call/jcc) since it's kind of obvious since the "operand" to call/jcc is conceptually an address (even if it behaves as an operand for the purposes of encoding). – BeeOnRope Oct 05 '16 at 21:16
  • 1
    You went too far. The operand-size prefix still works. `push ax` and `push rax` are encodable, it's only `push eax` that isn't. I hadn't ever tried modifying a CALL instruction, though. [Intel's table](http://www.felixcloutier.com/x86/CALL.html) says `CALL r/m32` is not encodeable in long mode. However, far calls can still use different operand sizes. But `CALL m16:64` needs a REX prefix, and the default is `CALL m16:32`. So it's not an example of a 64-bit default that's overrideable to 32. – Peter Cordes Oct 05 '16 at 21:28
  • Bleh, I changed it back (the original wording was still correct, even if it didn't actually mention that 32-bit wasn't encodable with REX.W), with a note about no 32-bit version for push/pop. The footnote doesn't actually say that the `REX.W` bit has the reverse-from-usual functionality: that it allows you to change the 64-bit default to 32-bit, but perhaps it is implied? I'm too lazy to check other instructions in the osdev wiki link to see if any behave like that. It seems correct as is. – BeeOnRope Oct 05 '16 at 21:38
  • I further clarified the weird [64-bit, 32-bit, 16-bit] -> [yes, no, yes] operand size case you pointed out in the footnote. – BeeOnRope Oct 05 '16 at 21:40
  • 1
    @Vlad your edit is completely broken. SE has supported [tables](https://meta.stackexchange.com/q/356997/230282) for a while so you can just use it – phuclv Jan 15 '21 at 10:48