</>
CodeLearn Pro
Start Learning Free โ†’

Assembly Language โ€” Complete Tutorial

7 modules ยท x86-64 ยท NASM syntax ยท Annotated examples

01

rogram Structure

Every NASM Assembly program is divided into sections. The .data section holds initialised data, .bss holds uninitialised variables, and .text holds your executable instructions. Execution starts at the _start label.

example_01.asm
NASM x86-64
; โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
; hello.asm  โ€”  Minimal NASM x86-64 program
; Build:  nasm -f elf64 hello.asm -o hello.o
;         ld hello.o -o hello
; Run:    ./hello
; โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€

section .data
    msg     db  "Hello, Assembly!", 10  ; string + newline (ASCII 10)
    msglen  equ $ - msg                 ; length = current pos - start

section .bss
    buf     resb 64                     ; reserve 64 bytes (uninitialised)

section .text
    global _start                       ; entry point for the linker

_start:
    ; write(1, msg, msglen)
    mov rax, 1          ; syscall: sys_write
    mov rdi, 1          ; fd: stdout
    mov rsi, msg        ; pointer to buffer
    mov rdx, msglen     ; byte count
    syscall             ; call into the kernel

    ; exit(0)
    mov rax, 60         ; syscall: sys_exit
    xor rdi, rdi        ; exit code: 0  (XOR reg,reg = fastest zero)
    syscall

โ–ถ Output

Hello, Assembly!

Key Notes

  • โ€ขNASM uses Intel syntax: destination comes FIRST (mov dst, src)
  • โ€ขdb = define byte, dw = define word (2 bytes), dd = dword (4), dq = qword (8)
  • โ€ขequ is a compile-time constant โ€” not stored in memory
  • โ€ขresb/resw/resd/resq reserve uninitialised space in .bss
02

egisters

Registers are tiny, ultra-fast storage slots inside the CPU. x86-64 has 16 general-purpose 64-bit registers. Each has 32-bit, 16-bit, and 8-bit sub-registers you can address independently.

example_02.asm
NASM x86-64
; Register sizes and sub-registers
; โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
; 64-bit   32-bit   16-bit   8-bit hi   8-bit lo
;  RAX      EAX       AX       AH         AL
;  RBX      EBX       BX       BH         BL
;  RCX      ECX       CX       CH         CL
;  RDX      EDX       DX       DH         DL
;  RSI      ESI       SI       โ€”          SIL
;  RDI      EDI       DI       โ€”          DIL
;  RBP      EBP       BP       โ€”          BPL
;  RSP      ESP       SP       โ€”          SPL
;  R8โ€“R15   R8Dโ€“R15D  R8Wโ€“R15W โ€”          R8Bโ€“R15B

section .text
    global _start

_start:
    ; โ”€โ”€ Moving immediate values โ”€โ”€
    mov rax, 1000          ; RAX = 1000
    mov eax, 42            ; EAX = 42  โ†’ upper 32 bits of RAX zeroed!
    mov ax,  300           ; AX  = 300 โ†’ upper 48 bits of RAX unchanged
    mov al,  5             ; AL  = 5   โ†’ only lowest byte changed
    mov ah,  7             ; AH  = 7   โ†’ byte 1 changed

    ; โ”€โ”€ Register-to-register โ”€โ”€
    mov rbx, rax           ; RBX = RAX
    mov rcx, rbx           ; RCX = RBX

    ; โ”€โ”€ Special registers โ”€โ”€
    ; RSP  โ€” stack pointer (top of stack)
    ; RBP  โ€” base pointer  (stack frame base)
    ; RIP  โ€” instruction pointer (you can't MOV into it directly)
    ; RFLAGS โ€” status flags (set by arithmetic/compare ops)

    ; exit
    mov rax, 60
    xor rdi, rdi
    syscall

Key Notes

  • โ€ขWriting to EAX (32-bit) automatically zeros the upper 32 bits of RAX โ€” watch out!
  • โ€ขWriting to AX, AL, AH does NOT zero the rest of RAX
  • โ€ขRSP always points to the top of the stack โ€” avoid corrupting it
  • โ€ขRIP (instruction pointer) cannot be set directly with MOV
03

rithmetic Instructions

ADD, SUB, IMUL, IDIV, and INC/DEC perform signed arithmetic. Every arithmetic instruction updates the RFLAGS register, which is then tested by conditional jump instructions.

example_03.asm
NASM x86-64
section .text
    global _start

_start:
    ; โ”€โ”€ Basic arithmetic โ”€โ”€
    mov rax, 50
    mov rbx, 13

    add rax, rbx        ; RAX = 63    (50 + 13)
    sub rax, 10         ; RAX = 53    (63 - 10)
    inc rax             ; RAX = 54    (53 + 1)
    dec rbx             ; RBX = 12    (13 - 1)

    ; โ”€โ”€ Multiply: result in RDX:RAX โ”€โ”€
    ; imul with 2 operands: dst *= src
    imul rax, rbx       ; RAX = 54 * 12 = 648

    ; imul with 3 operands: dst = src1 * imm
    imul rcx, rax, 2    ; RCX = 648 * 2 = 1296

    ; โ”€โ”€ Divide: dividend in RDX:RAX โ”€โ”€
    ; quotient โ†’ RAX, remainder โ†’ RDX
    xor rdx, rdx        ; clear RDX before divide!
    mov rbx, 10
    idiv rbx            ; RAX = 1296 / 10 = 129, RDX = 6

    ; โ”€โ”€ Flags demo โ”€โ”€
    mov rax, 0
    sub rax, 1          ; RAX = -1 (wraps), sets SF (sign flag) and CF (carry)

    ; exit
    mov rax, 60
    xor rdi, rdi
    syscall

Key Notes

  • โ€ขALWAYS xor rdx, rdx before idiv to clear the high half of the dividend
  • โ€ขimul/idiv operate on SIGNED integers; mul/div are their unsigned counterparts
  • โ€ขADD and SUB update: ZF (zero), SF (sign), CF (carry), OF (overflow)
  • โ€ขNEG rax negates the value: rax = -rax
04

MP & Conditional Jumps

CMP subtracts two values and discards the result, but updates RFLAGS. Conditional jump instructions then branch based on those flags. This is how Assembly implements if/else and loops.

example_04.asm
NASM x86-64
section .data
    big_msg   db "RAX is bigger", 10
    big_len   equ $ - big_msg
    small_msg db "RBX is bigger", 10
    small_len equ $ - small_msg

section .text
    global _start

_start:
    mov rax, 42
    mov rbx, 17

    ; โ”€โ”€ Signed comparison โ”€โ”€
    cmp rax, rbx          ; computes rax - rbx, sets flags
    jg  .rax_bigger       ; jump if greater (signed)
    jl  .rbx_bigger       ; jump if less    (signed)
    je  .equal            ; jump if equal   (ZF=1)
    jmp .done             ; unconditional jump

.rax_bigger:
    mov rax, 1
    mov rdi, 1
    mov rsi, big_msg
    mov rdx, big_len
    syscall
    jmp .done

.rbx_bigger:
    mov rax, 1
    mov rdi, 1
    mov rsi, small_msg
    mov rdx, small_len
    syscall
    jmp .done

.equal:
    ; handle equal case

.done:
    ; โ”€โ”€ Simple loop: count from 1 to 5 โ”€โ”€
    mov rcx, 1            ; counter

.loop:
    cmp rcx, 5
    jg  .loop_end         ; exit when counter > 5
    inc rcx
    jmp .loop

.loop_end:
    mov rax, 60
    xor rdi, rdi
    syscall

โ–ถ Output

RAX is bigger

Key Notes

  • โ€ขjg/jl/jge/jle are SIGNED: use ja/jb/jae/jbe for UNSIGNED comparisons
  • โ€ขje (jump if equal) tests ZF=1; jne tests ZF=0
  • โ€ขjz and je are identical opcodes โ€” just different mnemonics
  • โ€ขLOOP rcx label is a shorthand that decrements RCX and jumps if RCX โ‰  0
05

emory & Addressing

Bracket notation reads or writes memory. You can address memory directly, through a register, or with base+index*scale+displacement โ€” giving you full pointer arithmetic.

example_05.asm
NASM x86-64
section .data
    numbers dq 10, 20, 30, 40, 50  ; array of 5 qwords (8 bytes each)
    count   equ 5

section .bss
    result  resq 1          ; one 64-bit slot

section .text
    global _start

_start:
    ; โ”€โ”€ Direct memory access โ”€โ”€
    mov rax, [numbers]      ; load first element (10)
    mov [result], rax       ; store back

    ; โ”€โ”€ Register-indirect โ”€โ”€
    mov rsi, numbers        ; RSI = address of array
    mov rax, [rsi]          ; *rsi โ†’ 10
    mov rax, [rsi + 8]      ; *(rsi+8) โ†’ 20  (next qword)
    mov rax, [rsi + 16]     ; โ†’ 30

    ; โ”€โ”€ Base + index*scale โ”€โ”€
    mov rcx, 3              ; index 3
    mov rax, [numbers + rcx*8]  ; numbers[3] = 40
    ; scale must be 1, 2, 4, or 8

    ; โ”€โ”€ Stack operations โ”€โ”€
    push 0xDEAD             ; RSP -= 8, then [RSP] = 0xDEAD
    push 0xBEEF
    pop  rax                ; RAX = 0xBEEF, RSP += 8
    pop  rbx                ; RBX = 0xDEAD

    ; โ”€โ”€ LEA: load effective address (no memory read) โ”€โ”€
    lea rdx, [numbers + rcx*8]   ; RDX = address, not value

    mov rax, 60
    xor rdi, rdi
    syscall

Key Notes

  • โ€ขSquare brackets [ ] mean "dereference" โ€” like * in C
  • โ€ขLEA (Load Effective Address) computes an address WITHOUT reading memory โ€” great for pointer arithmetic
  • โ€ขThe stack grows DOWNWARD: push decrements RSP, pop increments it
  • โ€ขScale in [base + index*scale] must be 1, 2, 4, or 8 โ€” nothing else is valid
06

inux System Calls

System calls are how Assembly talks to the Linux kernel: file I/O, process control, networking. Arguments go into specific registers, the syscall number into RAX, then the syscall instruction transfers control to the kernel.

example_06.asm
NASM x86-64
; Linux x86-64 syscall calling convention:
;   RAX = syscall number
;   RDI = arg 1
;   RSI = arg 2
;   RDX = arg 3
;   R10 = arg 4
;   R8  = arg 5
;   R9  = arg 6
;   Return value โ†’ RAX

section .data
    prompt  db "Enter your name: ", 0
    plen    equ $ - prompt - 1
    hello   db "Hello, ", 0
    hlen    equ $ - hello - 1
    newline db 10

section .bss
    name    resb 32         ; buffer for user input

section .text
    global _start

_start:
    ; sys_write(1, prompt, plen)
    mov rax, 1
    mov rdi, 1
    mov rsi, prompt
    mov rdx, plen
    syscall

    ; sys_read(0, name, 32)
    mov rax, 0              ; syscall: sys_read
    mov rdi, 0              ; fd: stdin
    mov rsi, name
    mov rdx, 32
    syscall
    mov rbx, rax            ; save bytes read (includes newline)

    ; print "Hello, "
    mov rax, 1
    mov rdi, 1
    mov rsi, hello
    mov rdx, hlen
    syscall

    ; print the name (rbx bytes)
    mov rax, 1
    mov rdi, 1
    mov rsi, name
    mov rdx, rbx
    syscall

    ; sys_exit(0)
    mov rax, 60
    xor rdi, rdi
    syscall

โ–ถ Output

Enter your name: Alice
Hello, Alice

Key Notes

  • โ€ขCommon syscall numbers (x86-64 Linux): read=0, write=1, open=2, close=3, exit=60
  • โ€ขThe full table: /usr/include/asm/unistd_64.h or man 2 syscall
  • โ€ขOn error, RAX returns a negative errno value (e.g. -1 = EPERM)
  • โ€ขNever use int 0x80 (32-bit) with 64-bit code โ€” use syscall
07

rocedures & Calling Convention

Functions in Assembly are just labels you CALL and RET from. The System V AMD64 ABI defines which registers pass arguments (RDI, RSI, RDX, RCX, R8, R9), which are caller-saved vs callee-saved, and how the stack frame is set up.

example_07.asm
NASM x86-64
; add_numbers(a, b) โ†’ rax
; ABI: first arg = RDI, second = RSI, return value = RAX

section .text
    global _start

; โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
; int64_t add_numbers(int64_t a, int64_t b)
; โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
add_numbers:
    push rbp                ; save caller's frame pointer
    mov  rbp, rsp           ; establish our stack frame

    mov  rax, rdi           ; rax = a
    add  rax, rsi           ; rax += b  โ†’ return value

    pop  rbp                ; restore frame pointer
    ret                     ; return to caller (pops RIP from stack)

; โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
; int64_t multiply(int64_t a, int64_t b)
; โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
multiply:
    mov  rax, rdi
    imul rax, rsi
    ret

; โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
_start:
    ; call add_numbers(10, 32)
    mov rdi, 10             ; first argument
    mov rsi, 32             ; second argument
    call add_numbers        ; RAX = 42

    ; use result as first arg to multiply
    mov rdi, rax            ; a = 42
    mov rsi, 2              ; b = 2
    call multiply           ; RAX = 84

    ; exit with computed value as exit code (clamped to 8-bit)
    mov rdi, rax
    mov rax, 60
    syscall

Key Notes

  • โ€ขArgument registers (caller-saved): RDI RSI RDX RCX R8 R9
  • โ€ขReturn value: RAX (or RAX:RDX for 128-bit)
  • โ€ขCallee-saved (you must restore): RBX, RBP, R12โ€“R15
  • โ€ขAlways pair PUSH with POP โ€” an unbalanced stack corrupts RET

You've covered the core of Assembly! ๐ŸŽ‰

Next steps: practice in the Playground, explore C for high-level interop, or dive into C++ for systems programming.