Skip to content

Instantly share code, notes, and snippets.

@jtpaasch
Created April 1, 2019 12:34
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save jtpaasch/de6cf47234da7d6ac05d439223ee399c to your computer and use it in GitHub Desktop.
Save jtpaasch/de6cf47234da7d6ac05d439223ee399c to your computer and use it in GitHub Desktop.
Quick reminder of all the little things I forget.

Assembly (x86_64)

Useful quick references

General:

System calls:

Instruction syntax

Intel syntax:

 opcode destination, source

The AT&T (or GAS, for Gnu Assembler) syntax has source(s) first, then destination last.

Registers

There are sixteen, general purpose 64-bit registers:

R0 / RAX - [A]ccumulator register
R1 / RCX - [C]ounter
R2 / RDX - [D]ata register
R3 / RBX - [B]ase register
R4 / RSP - [S]tack pointer
R5 / RBP - [B]ase (of stack) pointer
R6 / RSI - [S]ource [I]ndex (for string operations)
R7 / RDI - [D]estination [I]ndex (for string operations)
R8
R9
R10
R11
R12
R13
R14
R15

Other registers:

RIP - [I]nstruction [P]ointer

Sizes:

  • Half-word/Byte: 8-bits
  • Word: 16-bits (2 bytes)
  • Double word: 32-bits (2 words = 4 bytes)
  • Quad word: 64-bits (2 double words = 4 words = 8 bytes)

Pieces of registers:

  • R0D / RAXD - 32-bits (double word) of R0.
  • R0W / RAXW - 16-bits (a word) of R0.
  • R0B / RAXB - 8-bits (a byte) of R0.

Some registers are broken down even further:

|<--- High byte                                       Low byte ---> |
+-------------------------------------------------------------------+
|                               RAX                                 |
+--------------------------------+----------------------------------+
|                                |               EAX                |
+--------------------------------+----------------+--------+--------+
|                                |                |       AX        |
+--------------------------------+----------------+--------+--------+
|                                |                |   AH   |   AL   |
+--------------------------------+----------------+--------+--------+
| 32 bits                        | 16 bits        | 8 bits | 8 bits |
+--------------------------------+----------------+--------+--------+

Examples:

  • AL - the lowest byte of RAX.
  • AH - the second to lowest byte of RAX.
  • AX - the lowest word of RAX.
  • EAX - the lowest double word of RAX.

Others: RBX, RCX, RDX, ESP, RBP, RSI, and RDI can be broken down by L, H, and E:

  • BL - the lowest byte of RBX.
  • CX - the lowest word of RCX.
  • EDX - the lowest double word of RDX.
  • SH - the second to lowest byte of RSP.
  • SP - the lowest word of RSP.
  • ESP - the lowest double word of RSP.
  • BP - the lowest byte of RBP.
  • EBP - the lowest double word of RBP.
  • SIL - the lowest byte of RSI.
  • DIL - the lowest byte of RDI.
  • ESI - the lowest double word of RSI.
  • EDI - the lowest double word of RDI.

The instruction pointer (RIP) can be broken down into IP (word), EIP (double word).

Assignment, addition, substraction

The mov instruction copies a value into a register.

mov rax, 15    ; Store 15 in RAX.
mov rcx, rax   ; Store the value in RAX in RCX.

Addition:

mov rax, 11    ; Store 11 in RAX.
mov rcx, 500   ; Store 500 in RCX.
add rax, rcx   ; Add the value in RCX to RAX. RAX now holds 511.
add rax, 10    ; Add 10 to RAX. RAX now holds 521.

Subtraction:

mov r15, 1337  ; Store 1355 in R15.
mov r12, 55    ; Store 55 in R12.
sub r15, r12   ; Subtract the value in R12 from R15. R15 now holds 1300.
sub r15, 301   ; Subtract 301 from R15. R15 now holds 999.

System calls

The (x86_64 ABI)[https://github.com/hjl-tools/x86-psABI/wiki/X86-psABI] says:

  • System call number is put in RAX.
  • Arguments are put in RDI, RSI, RDX, RCX, R8, and R9 (in that order).
  • The system call is invoked with the syscall instruction.
  • The return value is put in RAX.
    • Error? It returns -errno.

Find the system call number in the (system call table)[https://github.com/torvalds/linux/blob/master/arch/x86/entry/syscalls/syscall_64.tbl]. For example, the number for the write call is 1.

Find the man page for the system call to find the arguments. For example, write:

#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count);

With this, we can call write:

  • Put 1 into RAX to indicate that we want to call write.
  • Put 1 into RDI to indicate that fd is stdout (i.e., 1).
  • Put a string (e.g., from .rodata) into RSI (the buf argument).
  • Put the length of buf into RDX.
  • Invoke syscall.

For example, hello.s:

global _start

section .text

_start:
  mov rax, 1      ; Store 1 in RAX. 1 is the "write" sys call.
  mov rdi, 1      ; Store 1 in RDI. 1 is the STDOUT fd.
  mov rsi, msg    ; Store `msg` in RSI. This is the value to write.
  mov rdx, msglen ; Store msg length in RDX. This is how much to write.
  syscall         ; Invoke the call.

  mov rax, 60     ; Store 60 in RAX. 60 is the "exit" sys call.
  mov rdi, 0      ; Store 0 in RDI. 0 is the exit code.
  syscall         ; Invoke the call.

section .rodata
  msg: db "Hello, world!", 10
  msglen: equ $ - msg

A Makefile:

prog = hello

all: clean build

clean:
        rm -rf $(prog).o
        rm -rf $(prog)

build:
        nasm -f elf64 -o $(prog).o $(prog).s
        ld -o $(prog) $(prog).o

Build and run:

make
./hello
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment