67 A1 FFFFFFFF
isn't using a disp32
addressing mode, so the Mod/RM section of the documentation doesn't apply.
Intel's x86 manual vol.1 says:
All 16-bit and 32-bit address calculations are zero-extended in IA-32e mode to form 64-bit addresses. Address calculations are first truncated to the effective address size of the current mode (64-bit mode or compatibility mode), as overridden by any address-size prefix. The result is then zero-extended to the full 64-bit address width. [...] A 32-bit address generated in 64-bit mode can access only the low 4 GBytes of the 64-bit mode effective addresses.
This applies to the special moffs
absolute addressing forms of mov
as well as to regular ModR/M addressing modes like mov eax, [edi]
instead of mov eax, [rdi]
.
Note that the moffs8/16/32/64
naming shows the operand-size, not the address size (e.g. mov al, moffs8
). There isn't a different term for a 32-bit address size moffs
in 64-bit mode.
The address-size prefix changes the A1
opcode from a 64-bit immediate address to a 32-bit, i.e. it changes the length of the rest of the instruction (unlike ModR/M addressing mode in 64-bit mode, which are always disp0/8/32). This actually causes LCP stalls on Skylake, according to my testing, for a32 mov eax, [abs buf]
(NASM chooses to use the moffs encoding for that case, because with the a32
override specified, it's shorter than ModR/M + disp32)
See also Does a Length-Changing Prefix (LCP) incur a stall on a simple x86_64 instruction? for much more detail about LCP stalls in general, including with 67h
address-size prefixes.
Anyway, this means that disassembling it as mov eax, [0xFFFFFFFF]
is wrong (at least in NASM syntax), because that assembles back into an instruction that does something different.
The correct YASM/NASM syntax that will assemble back to that machine code is
a32 mov eax, [0xFFFFFFFF]
NASM also accepts mov eax, [a32 0xFFFFFFFF]
, but YASM doesn't.
GNU as
also provides a way to express it (without using .byte
):
addr32 mov 0xffffffff,%eax
movl 0x7FFFFFFF, %eax # 8B mod/rm disp32
movl 0xFFFFFFFF, %eax # A1 64bit-moffs32: Older GAS versions may have required the movabs mnemonic to force a moffs encoding
movabs 0x7FFFFF, %eax # A1 64b-moffs32: movabs forces MOFFS
movabs 0xFFFFFFFF, %rax # REX A1 64b-moffs64
movabs 0xFFFF, %ax # 66 A1 64b-moffs64: operand-size prefix
.byte 0x67, 0xa1, 0xff, 0xff, 0xff, 0xff # disassembles to addr32 mov 0xffffffff,%eax
# and that syntax works as assembler input:
addr32 mov 0xffffffff,%eax # 67 A1 FF FF FF FF: 32b-moffs32
With NASM/YASM, there's no way to force a 32-bit MOFFS encoding in a way that refuses assemble with a register other than AL/AX/EAX/RAX. a32 mov [0xfffffff], cl
assembles to 67 88 0c 25 ff ff ff 0f addr32 mov BYTE PTR ds:0xfffffff,cl
(a ModR/M + disp32 encoding of mov r/m8, r8
).
You can write mov eax, [qword 0xffff...]
to get the moffs64
encoding, but there's no way to require a 32-bit moffs encoding.
Agner Fog's objconv
disassembler gets it wrong (disassembling the machine code produced with GNU as
from the block above). objconv
appears to assume sign-extension. (It puts the machine code in comments as prefixes: opcode, operands
)
; Note: Absolute memory address without relocation
mov eax, dword [abs qword 7FFFFFH] ; 0033 _ A1, 00000000007FFFFF
...
; Note: Absolute memory address without relocation
mov eax, dword [0FFFFFFFFFFFFFFFFH] ; 0056 _ 67: A1, FFFFFFFF
ndisasm -b64
also disassembles incorrectly, to code that doesn't even work the same way:
00000073 A1FFFF7F00000000 mov eax,[qword 0x7fffff]
-00
...
00000090 67A1FFFFFFFF mov eax,[0xffffffff]
I would have expected a disassembly like mov eax, [qword 0xffffffff]
, if it's not going to use the a32
keyword. That would assemble to a 64-bit moffs that references the same address as the original, but is longer. Probably this was overlooked when adding AMD64 support to ndisasm, which already existed before AMD64.