Let's have an instruction MOV EAX,[0xFFFFFFFF]
encoded in 64bit mode as 67A1FFFFFFFF
(effective address-size is toggled by 67 prefix from default 64 to 32 bits).
Intel's instruction reference manual (doc Order Number: 325383-057US from December 2015) on page Vol. 2A 2-11 says:
2.2.1.3 Displacement
Addressing in 64-bit mode uses existing 32-bit ModR/M and SIB encodings. The ModR/M and SIB sizes do not change. They remain 8 bits or 32 bits and are sign-extended to 64 bits.
This suggests that 32bit displacement should be sign-extended but I am not sure if this concerns special moffs addressing mode as well. On the next page Intel says:
2.2.1.6 RIP-Relative Addressing
RIP-relative addressing is enabled by 64-bit mode, not by a 64-bit address-size. The use of the address-size prefix does not disable RIP-relative addressing. The effect of the address-size prefix is to truncate and zero-extend the computed effective address to 32 bits.
This suggests that in relative addressing mode the disp32 is sign-extended to 64 bit, added to RIP and then truncated and zero-extended. Hovever I am not sure if the same rule applies to absolute addressing mode, which is the case of MOV moffs operations.
What address will be EAX loaded from, A) FFFFFFFFFFFFFFFF or B) 00000000FFFFFFFF ?
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With