This document is meant to summarise differences between x86-64 and i386 assembly assuming that you already know well the i386 gas syntax. I will try to keep this document up to date until official documentation is available.
X86-64 defines eight new integer registers named r8-r15. These registers are encoded using special REX prefix and so using them in non-64-bit instruction implies instruction length growth by 1 byte. They are named as follows:
rXb for 8 bit register (containing the lowest byte of the 64-bit value) rXw for 16 bits rXd for 32 bits rX for 64 bitsWhere X stands for integer in the range of 8 to 16.
Original integer registers keeps their irregular names and the 64-bit versions of the 32-bit registers eax, edx, exc, ebx, esi, esi, edi, esp and ebp are now called rax, rdx, rcx, rbx, rsi, rdi, rsp and respectivetly rbp.
The new registers can be used in the same places as the old ones, except for implicit register usage. Some instructions implicitly use specific fixed registers, e.g. as shift counters, source and destination for string operations, etc.
Instructions with REX prefix change behaviour of 8-bit register parts so that all registers can be accessed as 8-bit registers. The upper halves (ah, dh, ch, bh) are replaced by lower halves of next 4 registers (sil, dil, spl, bpl). Then the rules described above are applied.
Unfortunatly some instructions require a REX prefix, so you can't use upper halves together with addresses requiring REX prefix:
addb %ah, (%r10) # Invalid instruction.
To write 64bit instructions, use 'q' as a suffix (q for 'quad-word'):
movl $1, %eax # 32-bit instruction movq $1, %rax # 64-bit instruction
Exceptions from this rule are instructions manipulating the stack (push, pop, call, ret, enter and leave) which are implicitly 64-bit and their 32-bit counterparts are not available anymore, yet their 16-bit counterparts are. So:
pushl %eax # Illegal instruction pushq %rax # 1 byte instruction encoded as pushl %eax in 32 bits pushq %r10 # 2 byte instruction encoded as pushl preceeded by REX.
Results of 32-bit operations are implicitly zero extended to 64-bit values. This differs from 16 and 8 bit operations, that don't affect the upper part of registers. This can be used for code size optimisations in some cases, such as:
movl $1, %eax # one byte shorter movq $1, %rax xorq %rax, %rax # three byte equivalent of mov $0,%rax andl $5, %eax # equivalent for andq $5, %eax
Immediate values inside instructions remain 32 bits and their value is sign extended to 64 bits before calculation. This means that:
addq $1, %rax # Valid instruction addq $0x7fffffff, %rax # As this addq $0xffffffffffffffff, %rax # as this one addq $0xffffffff, %rax # Invalid instruction addl $0xffffffff, %eax # Valid instruction
Only exception from this rule are the moves of constant to registers that have 64bit form. This means:
movl 1, %eax # 5 byte instruction movq 1, %rax # 7 byte instruction movq 0xffffffffffffffff, %rax # 7 byte instruction movq 0x1122334455667788, %rax # 10 byte instruction movq 0xffffffff, %rax # 10 byte instruction movl 0xffffffff, %eax # 5 byte instruction equivalent to above
You may write symbolic expressions as operands to both 64-bit and 32-bit operations. For 32-bit operations they result in zero extending relocations, while in 64-bit operations they result in sign extending ones.
movl $symb, %eax # 5 byte instruction movq $symb, %rax # 7 byte instruction
So in case you know that the symbol is in the first 32 bits, you should use 32bit instructions whenever possible.
To load a symbol as 64-bit value, you need to use movabs instruction, that is a synonym for mov only changes the default behaviour:
movandq %symb, %rax # 11 byte instruction
Similarly as immediates, the displacements are also sign extended and pretty much the same rules apply to them. X86-64 defines a special form of move instruction having 64-bit displacement and similarly, as for immediates, it is implicitly used when the value is known to not fit at compilation time and you need to use movabs to force a 64-bit relocation:
movl 0x1, %eax # load with 32bit sign extended relocation movl 0xffffffff, %eax # load with 64bit relocation movl symb, %eax # load with 32bit sign extended relocation movabsl symb, %eax # load with 64bit sign extended relocationLoads and stores with 64-bit displacement are available only for the eax instruction.
X86-64 defines a new instruction pointer relative addressing mode to simplify writing of position independent code. The original displacement-only addressing of are overwritten by this one and displacement only is now encoded by one of the redundant SIB form. This means that RIP relative addressing is actually cheaper than displacement only.
To encode this addressing, just write rip as yet another register:
movl $0x1, 0x10(%rip)will store the value 0x1 10 bytes after the end of the instruction.
Symbolic relocation will be implicitly RIP relative, so
movl $0x1, symb(%rip)Will write 0x1 to the address of symbol "symb".
FIXME: This looks particularly confusing in the Intel syntax [symb+rip] suggest different location than [symb]. Suggestions for better syntax with symbols?
You are recommended to use RIP relative addressing whenever possible to reduce code size.
The RIP relative branch instructions are still encoded equally to 32bit mode. This means that they are implicitly RIP relative and "*" is used to switch to absolute form.
(%rbp,index,scale)is not encodable and:
0(%rbp,index,scale)must be used.
This text was written by Jan Hubicka. Please send comments to email@example.com.