Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Sign or Zero Extension of address in 64bit mode for MOV moffs32?

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 ?

like image 514
vitsoft Avatar asked Jun 06 '16 19:06

vitsoft


1 Answers

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.

like image 54
Peter Cordes Avatar answered Nov 02 '22 08:11

Peter Cordes