Learning Assembly Language (Intel Syntax)

What You Will Learn

  • The difference between x86 (32-bit) and x86-64 (64-bit) registers
  • How arithmetic, comparison, and memory instructions work
  • How the stack is used for function calls
  • How Linux and Windows differ in function calling conventions
  • How to write and trace assembly code

X86

This is a 32-bit register architecture.

Registers

  • Floating point values: XMM0 to XMM15
  • General Purpose: EAX, EBX, ECX, EDX, ESI, EDI, EBP, and ESP
    • EAX: Accumulator register (for arithmetic)
    • EBX: Base register (sometimes return value and arguments to syscalls)
    • ECX: Counter register
    • EDX: Data register

Bits and Bytes

Unit Size
Bit 1 binary digit
Nibble 4 bits (one hex digit)
Byte 8 bits (two hex digits)
Word 2 bytes
Double Word (DWORD) 4 bytes
Quad Word (QWORD) 8 bytes

Common Terms

  • An immediate value (IM) is a constant like the number 12 — not a memory address or register.
  • A register refers to something like RAX, RBX, R12, AL, etc.
  • A memory address is a location in memory such as 0x7FFF842B.

Operands

MUL

Takes one operand and multiplies it with the value in EAX. Stores the result in EDX:EAX.

mov EAX, 25
mov EBX, 5
mul EBX ; Multiplies EAX (25) with EBX (5)

DIV

Takes one operand and divides EAX by it. Stores the quotient in EAX and the remainder in EDX.

mov EAX, 18
mov EBX, 3
div EBX ; Divides EAX (18) by EBX (3)

CMP

Compares two operands and sets the appropriate flags depending on the result.

mov RAX, 8
cmp RAX, 5

Dereferencing

mov rax, [rdi]    ; Move the value AT the address in rdi into rax
mov [rax], rdi    ; Move rdi into the memory address stored in rax

NOP

NOP stands for No Operation. This instruction does nothing. It is usually used for padding or as a placeholder.

REPT

.rept 10
    nop
.endr

Example

mov RAX, x          ; move variable from memory into register
cmp RAX, 4          ; compare immediate value with register, set flags
jne 0x7FFF842B      ; jump to address if not equal
call func1
ret

Stack

The stack grows downward in memory. PUSH decrements RSP (the stack pointer) and writes to it. POP reads from RSP and increments it.

Stack Diagram

Arrays

Arrays store multiple pieces of the same data type sequentially in memory. If an array of 5 integers starts at address 0x4000, each integer is 4 bytes:

int numbers[4] = {0, 1, 2, 3};

In memory:

0x4000: 0   ; 4 bytes
0x4004: 1
0x4008: 2
0x400C: 3

Classes

mov RAX, 0x4000     ; RAX = address of the object (age field, offset 0)
lea RBX, [RAX+0x4]  ; RBX = address of height
lea RCX, [RAX+0x8]  ; RCX = address of name
mov [RAX], 0x32     ; age = 50
mov [RBX], 0x48     ; height = 72
mov [RCX], 0x424F42 ; name = "BOB"

X86-64

In 64-bit mode, the general-purpose registers are 64 bits wide: RAX, RBX, RCX, RDX, RSI, RDI, RBP, RSP, and R8–R15.

Floating point: YMM0 to YMM15 (256-bit wide, can hold 4 × 64-bit or 8 × 32-bit values).

Register Subdivisions

+----------------------------------------+
|                   rax                  |
+--------------------+-------------------+
                     |        eax        |
                     +---------+---------+
                               |   ax    |
                               +----+----+
                               | ah | al |
                               +----+----+
=================================================
%rax   %eax   %ax   %al
%rcx   %ecx   %cx   %cl
%rdx   %edx   %dx   %dl
%rbx   %ebx   %bx   %bl
%rsi   %esi   %si   %sil
%rdi   %edi   %di   %dil
%rsp   %esp   %sp   %spl
%rbp   %ebp   %bp   %bpl
%r8    %r8d   %r8w  %r8b
%r9    %r9d   %r9w  %r9b
...
%r15   %r15d  %r15w %r15b

Function Calls — Linux Calling Convention

Arguments are passed in registers in this order:

Register Argument
RDI First
RSI Second
RDX Third
RCX Fourth
R8 Fifth
R9 Sixth

If there are more than six arguments, the rest are pushed onto the stack. The caller must ensure the stack is 16-byte aligned before the call.

Function Calls — Windows Calling Convention

func1(int a, int b, int c, int d, int e, int f);
// a in RCX, b in RDX, c in R8, d in R9, e and f pushed on stack

Windows uses a shadow space of 32 bytes — space for the callee to optionally save the first 4 registers.

WriteFile windows asm signature:
BOOL WriteFile(
  HANDLE       hFile,
  LPCVOID      lpBuffer,
  DWORD        nNumberOfBytesToWrite,
  LPDWORD      lpNumberOfBytesWritten,
  LPOVERLAPPED lpOverlapped
);

Loop Example (Calculate Average)

; rdi = memory address of first quad word
; rsi = n (number of elements)
; rax = computed average
mov rcx, 0

loop:
    cmp rsi, rcx
    je _end
    add rax, [rdi + rcx * 8]
    inc rcx
    jmp loop
_end:
    div rsi

Function Call and Return

0x1021  mov rax, 0x400000
0x1028  call rax          ; pushes 0x102a onto the stack, jumps to 0x400000
0x102a  mov [rsi], rax

; "ret" pops the top value off the stack and jumps to it (returns to 0x102a)

Setting Up the Stack Frame

mov rbp, rsp
sub rsp, 0x14       ; allocate local space
mov eax, 1337
mov [rbp-0x8], eax
mov rsp, rbp        ; restore stack
ret

ARM

Refer to: Arm64 Assembly (Topic79)

Also: https://github.com/proflamyt/300days-of-hacking/tree/main/Topic79

Example — Solving mx + b

imul rdi, rsi   ; rdi = rdi * rsi
add rdi, rdx    ; rdi = rdi + rdx
mov rax, rdi    ; return value in rax

Division

; rax = rdi / rsi, rdx = remainder
mov rax, rdi
div rsi

Modulus

; rdi % rsi
mov rax, rdi
div rsi
xor rax, rax
mov rax, rdx

Resources


This site uses Just the Docs, a documentation theme for Jekyll.