x86_64

keywords: x86_64, x86, abi

  • 64bit synonyms: x86_64, x64, amd64, intel 64
  • 32bit synonyms: x86, ia32, i386
  • ISA type: CISC
  • Endianness: little

Registers

General purpose register

bytes
[7:0]      [3:0]   [1:0]   [1]   [0]     desc
----------------------------------------------------------
rax        eax     ax      ah    al      accumulator
rbx        ebx     bx      bh    bl      base register
rcx        ecx     cx      ch    cl      counter
rdx        edx     dx      dh    dl      data register
rsi        esi     si      -     sil     source index
rdi        edi     di      -     dil     destination index
rbp        ebp     bp      -     bpl     base pointer
rsp        esp     sp      -     spl     stack pointer
r8-15      rNd     rNw     -     rNb

Special register

bytes
[7:0]      [3:0]     [1:0]      desc
---------------------------------------------------
rflags     eflags    flags      flags register
rip        eip       ip         instruction pointer

FLAGS register

rflags
bits    desc                            instr        comment
--------------------------------------------------------------------------------------------------------------
   [21]   ID   identification                        ability to set/clear -> indicates support for CPUID instr
   [18]   AC   alignment check                       alignment exception for PL 3 (user), requires CR0.AM
[13:12] IOPL   io privilege level
   [11]   OF   overflow flag
   [10]   DF   direction flag           cld/std      increment (0) or decrement (1) registers in string operations
    [9]   IF   interrupt enable         cli/sti
    [7]   SF   sign flag
    [6]   ZF   zero flag
    [4]   AF   auxiliary carry flag
    [2]   PF   parity flag
    [0]   CF   carry flag

Change flag bits with pushf / popf instructions:

pushfd                          // push flags (4bytes) onto stack
or dword ptr [esp], (1 << 18)   // enable AC flag
popfd                           // pop flags (4byte) from stack

There is also pushfq / popfq to push and pop all 8 bytes of rflags.

Model Specific Register (MSR)

rdmsr     // Read MSR register, effectively does EDX:EAX <- MSR[ECX]
wrmsr     // Write MSR register, effectively does MSR[ECX] <- EDX:EAX

Size directives

Explicitly specify size of the operation.

mov  byte ptr [rax], 0xff    // save 1 byte(s) at [rax]
mov  word ptr [rax], 0xff    // save 2 byte(s) at [rax]
mov dword ptr [rax], 0xff    // save 4 byte(s) at [rax]
mov qword ptr [rax], 0xff    // save 8 byte(s) at [rax]

Addressing

mov qword ptr [rax], rbx         // save val in rbx at [rax]
mov qword ptr [imm], rbx         // save val in rbx at [imm]
mov rax, qword ptr [rbx+4*rcx]   // load val at [rbx+4*rcx] into rax

rip relative addressing:

lea rax, [rip+.my_str]       // load addr of .my_str into rax
...
.my_str:
.asciz "Foo"

Load effective address:

mov rax, 2
lea r11, [rax + 3]   // r11 <- 5

String instructions

The operand size of a string instruction is defined by the instruction suffix b | w | d | q.

Source and destination registers are modified according to the direction flag (DF) in the flags register

  • DF=0 increment src/dest registers
  • DF=1 decrement src/dest registers

Following explanation assumes byte operands with DF=0:

movsb   // move data from string to string
        // ES:[DI] <- DS:[SI]
        // DI <- DI + 1
        // SI <- SI + 1

lodsb   // load string
        // AL <- DS:[SI]
        // SI <- SI + 1

stosb   // store string
        // ES:[DI] <- AL
        // DI <- DI + 1

cmpsb   // compare string operands
        // DS:[SI] - ES:[DI]    ; set status flag (eg ZF)
        // SI <- SI + 1
        // DI <- DI + 1

scasb   // scan string
        // AL - ES:[DI]         ; set status flag (eg ZF)
        // DI <- DI + 1

String operations can be repeated:

rep     // repeat until rcx = 0
repz    // repeat until rcx = 0 or while ZF = 0
repnz   // repeat until rcx = 0 or while ZF = 1

Example: Simple memset

// memset (dest, 0xaa /* char */, 0x10 /* len */)

lea di, [dest]
mov al, 0xaa
mov cx, 0x10
rep stosb

Time stamp counter - rdtsc

static inline uint64_t rdtsc() {
  uint32_t eax, edx;
  asm volatile("rdtsc" : "=d"(edx), "=a"(eax)::);
  return (uint64_t)edx << 32 | eax;
}

Constant TSC behavior ensures that the duration of each clock tick is uniform and supports the use of the TSC as a wall clock timer even if the processor core changes frequency. This is the architectural behavior moving forward.

On linux one can check the constant_tsc cpu flag, to validate if the implemented TSC ticks with a constant frequency.

grep constant_tsc /proc/cpuinfo

SysV x86_64 ABI

Passing arguments to functions

  • Integer/Pointer arguments
    reg     arg
    -----------
    rdi       1
    rsi       2
    rdx       3
    rcx       4
    r8        5
    r9        6
    
  • Floating point arguments
    reg     arg
    -----------
    xmm0      1
      ..     ..
    xmm7      8
    
  • Additional arguments are passed on the stack. Arguments are pushed right-to-left (RTL), meaning next arguments are closer to current rsp.

Return values from functions

  • Integer/Pointer return values
    reg          size
    -----------------
    rax        64 bit
    rax+rdx   128 bit
    
  • Floating point return values
    reg            size
    -------------------
    xmm0         64 bit
    xmm0+xmm1   128 bit
    

Caller saved registers

Caller must save these registers if they should be preserved across function calls.

  • rax
  • rcx
  • rdx
  • rsi
  • rdi
  • rsp
  • r8 - r11

Callee saved registers

Caller can expect these registers to be preserved across function calls. Callee must must save these registers in case they are used.

  • rbx
  • rbp
  • r12r15

Stack

  • grows downwards
  • frames aligned on 16 byte boundary
    Hi ADDR
     |                +------------+
     |                | prev frame |
     |                +------------+ <--- 16 byte aligned (X & ~0xf)
     |       [rbp+8]  | saved RIP  |
     |       [rbp]    | saved RBP  |
     |       [rbp-8]  | func stack |
     |                | ...        |
     v                +------------+
    Lo ADDR
    

Function prologue & epilogue

  • prologue
    push rbp        // save caller base pointer
    mov rbp, rsp    // save caller stack pointer
    
  • epilogue
    mov rsp, rbp    // restore caller stack pointer
    pop rbp         // restore caller base pointer
    

    Equivalent to leave instruction.

Windows x64 ABI

Passing arguments to functions (ref)

A single argument is never spread across multiple registers.

  • Integer/Pointer arguments
    reg     arg
    -----------
    rcx       1
    rdx       2
    r8        3
    r9        4
    
  • Floating point arguments
    reg     arg
    -----------
    xmm0      1
      ..     ..
    xmm3      4
    
  • Additional arguments are passed on the stack. Arguments are pushed right-to-left (RTL), meaning next arguments are closer to current rsp. See example.

Return values from functions

  • Integer/Pointer return values
    reg          size
    -----------------
    rax        64 bit
    
  • Floating point return values
    reg            size
    -------------------
    xmm0         64 bit
    

Caller saved registers

Caller must save these registers if they should be preserved across function calls.

  • rax
  • rcx
  • rdx
  • r8 - r11
  • xmm0 - xmm5

Callee saved registers

Caller can expect these registers to be preserved across function calls. Callee must must save these registers in case they are used.

  • rbx
  • rbp
  • rdi
  • rsi
  • rsp
  • r12 - r15
  • xmm6 - xmm15

ASM skeleton

Small assembler skeleton, ready to use with following properties:

  • use raw Linux syscalls (man 2 syscall for ABI)
  • no C runtime (crt)
  • gnu assembler gas
  • intel syntax
# file: greet.s

    .intel_syntax noprefix

    .section .text, "ax", @progbits
    .global _start
_start:
    mov rdi, 1                      # fd
    lea rsi, [rip + greeting]       # buf
    mov rdx, [rip + greeting_len]   # count
    mov rax, 1                      # write(2) syscall nr
    syscall

    mov rdi, 0                      # exit code
    mov rax, 60                     # exit(2) syscall nr
    syscall

    .section .rdonly, "a", @progbits
greeting:
    .asciz "Hi ASM-World!\n"
greeting_len:
    .int .-greeting

Syscall numbers are defined in /usr/include/asm/unistd.h.

To compile and run:

> gcc -o greet greet.s -nostartfiles -nostdlib && ./greet
Hi ASM-World!

References