Skip to main content

AMD64 Assembly Quick Reference

··6 mins

For Prof Tuck’s cs4310, Spring 2025.

This uses AT&T syntax (per norms for the GNU C compiler).

Registers #

In AMD64, we have 16 registers that can each hold 64-bits (8 bytes):

Group 1:

  • %rax - accumulator: arithmetic source & destination
  • %rcx - counter: loop index
  • %rdx - data: arithmetic source
  • %rbx - base: historically used for segmented addressing

Group 2:

  • %rsi - source index
  • %rdi - destination index
  • %rsp - stack pointer
  • %rbp - base pointer

Group 3:

  • %r8, %r9, …, %r15

Groups 1 and 2 are the extended form of the old 16 bit registers from the Intel 8086, while Group 3 was added specifically for AMD64.

Shorter parts of these registers are also accessible by the hisorical names:

{% striped() %}

64-bit register Low 32-bits Low 16 bits Low 8 bits Byte 7*
%rax %eax %ax %al %ah
%rsi %esi %si %sil n/a
%r8 %r8d %r8w %r8b n/a
{% end %}

The names for the other registers follow the pattern within the same group.

For the Group 1 registers, the high and low bytes of the low 16-bits be accessed by dedicated names (%ah, %al, %ch, %cl, etc).

There are a couple of other registers worth remembering:

  • %rip - Instruction pointer. Points to next instruction to execute. Not readable.
  • %flags - (%eflags, %rflags) - Each bit means something specific. Not writable.

Instructions #

Arithmetic Instructions #

{% striped() %}

Instruction Description
mov %rxx, %ryy Copy data from register %rxx to %ryy
—————– ——————————————-
inc %rxx Add one to %rxx
dec %rxx Subtract one from rxx
neg %rxx Negate %rxx
not %rxx Bitwise NOT %rxx
—————– ——————————————-
add %rxx, %ryy %ryy += %rxx
sub %rxx, %ryy %ryy -= %rxx
or %rxx, %ryy %ryy = %rxx OR %ryy (bitwise)
and %rxx, %ryy %ryy = %rxx AND %ryy (bitwise)
—————– ——————————————-
imul %rxx %rdx:%rax = %rax * %rxx
imul %rxx, %ryy %ryy = %ryy * %rxx (truncated to 64-bits)
idiv %rxx %rax = %rdx:%rax / %rxx; %rdx = remainder
{% end %}

Flow Control and Logic #

Note: The argument order for “cmp” is backwards here.

  ## This is "%rbx > %rax"?
  cmp %rax, %rbx
  jg rbx_is_bigger

{% striped() %}

Instruction Description
cmp %ryy, %rxx Compares the two registers, updating the flags register (note: order seems backwards)
—————- ————————————————————–
je label Jump if equal (if previous cmp set equal flag; %rxx == %ryy)
jne label Jump if not equal (%rxx != %ryy)
jl label Jump if less than (%rxx < %ryy)
jle label <=
jg label >
jge label >=
—————- ————————————————————–
sete %rzz Set %rzz if %rxx == %ryy in the previous cmp, else clear it.
setg %rzz Set %rzz if %rxx > %ryy
setl %rzz Set %rzz if %rxx < %ryy
{% end %}

Function Call and Stack #

{% striped() %}

Instruction Description
push %rxx Copy %rxx to stack @ %rsp, move down %rsp
pop %rxx Copy from stack @ %rsp to %rxx, move up %rsp
enter $NN, $0 Allocate a stack frame with NN bytes of space
leave Deallocate a stack frame
call label Push $rip and jump to the address of the “label” function
{% end %}

Memory and Immediate Arguments #

In addition to operating on registers, many instructions can accept alternate modes that operate on data in memory or on constant values.

{% striped() %}

Example Instruction Description
add (%rcx), %rdx %rdx = %rdx + (value at address in %rcx)
add $10, %rdx %rdx = %rdx + 10
addq $10, 2(%e10, %e11, 2) (the value at %e10+2*%e11) += 10
add -16(%rsp), %rax %rax += the value 16 bytes below where %rsp points
{% end %}

There’s a special instruction, lea, that calcuates an address as if it were going to access an argument in memory but gives you the address as its output.

{% striped() %}

Example Instruction Description
lea -16(%rsp), %rax %rax = %rsp - 16
{% end %}

Instruction suffixes: Instructions can have a single letter suffix added to indicate hte size of the value operated on: b, w, l, q for 1, 2, 4, 8 bytes.

{% striped() %}

Example Instruction Description
movw $10, (%rdx) Move a 16-bit (2 byte, short) int to the address in %rdx
movq %10, (%rdx) Move a 64-bit (8 byte, long) int to the address in %rdx
{% end %}

Calling a Function #

To call a function with the “call” instruction, you must first:

  • Put arguments in the appropriate registers / stack.
    • %rdi, %rsi, %rdx, %rcx, %r8, %r9, then the stack
  • Think about “caller save”/“temporary” registers.
    • That’s all the data registers except %rbx and %r12-%r15.
    • Assume calling any function corrupts them.
    • You can push these before the call and pop after, but a different allocation with stack / callee-save might work better.
  • Make sure %rsp points to an address divisible by 16 bytes.
    • This is not true when you enter a function, because call pushes a return address.
    • Pushing %ebp (e.g. with “enter $0,…”) corrects this.
    • Make sure you do an even number of pushes / reserve stack space in multiples of 16 bytes.

Once the function returns, your result will be in %rax. An optional second result is returned in %rdx.

Writing a Function #

These registers are callee-saved. If you want to use them, save them to the stack in your prologue and restore them in your epilogue:

  • %rbx, %r12-%r15

The stack registers (%rsp, %rbp) are technically callee save, but this is handled by the standard use of “enter” and “leave” calls.

Register Allocation #

  • Function arguments: %rdi, %rsi, %rdx, %rcx, %r8, %r9
  • Temporary registers: %r10, %r11, (argument registers)
  • Safe registers: %r12, %r13, %r14, %r15, %rbx

How to map variables / values to locations:

  • Local variables that get their address taken should go on the stack.
  • Local variables that get used before and after a function call should go in a safe (callee-saved) register.
  • Local variables that don’t need to survive a function call can go in temporary registers.
  • Temporary values should go in temporary registers.

The caller-save strategy, where temporary registers are pushed/popped around a function call to preserve them can be used but tends to be more annoying than using safe registers.

Compiling ASM with GCC #

$ gcc -no-pie -o foo foo.s
  • By default, program starts at _start
  • If you declare “.global main”, program will start at main.
  • The “-no-pie” flag is needed for Ubuntu 18.04. This disables ASLR for the binary, which hurts security.

Compiling ASM without libc #

$ gcc -nostdlib -no-pie -o foo foo.s
OR
$ as -o foo.o foo.s
$ ld -o foo foo.o

Using GDB with ASM #

  • Compile with -g
  • Use break to set breakpoints on labels or line numbers.
  • Print registers with p $rax or p/x $rax.
  • Print all registers with info reg

External Resources #