programming4 min read

Assembly Tutorial: Learn Low-Level Programming from Scratch (2026)

Assembly Tutorial: Learn Low-Level Programming from Scratch (2026)

Published:  |  Category: Programming  |  Reading time: ~15 min
Assembly Tutorial: Learn Low-Level Programming from Scratch (2026)

Assembly is the closest humans get to talking directly to the CPU without machine code. I learned assembly to understand what higher-level languages actually do under the hood. There is no better way to understand pointers, stack frames, calling conventions, and instruction pipelines than reading assembly. Writing assembly in production is rare, but reading it for debugging and optimization is an essential skill.

This tutorial covers x86-64 assembly, the dominant architecture in desktops and servers. The principles transfer to ARM, RISC-V, and other ISAs.

Registers and Data Movement

x86-64 has general-purpose registers (RAX, RBX, RCX, RDX, RSI, RDI, R8-R15), each 64 bits wide. Lower 32 bits are accessible as EAX, etc. The mov instruction copies data between registers and memory. Addressing forms: mov reg, [base + index*scale + offset]. The direction is AT&T: mov src, dst or Intel: mov dst, src.

; Intel syntax
mov rax, 42        ; immediate to register
mov rbx, rax       ; register to register
mov rcx, [rdi]     ; memory to register
mov [rsp+8], rax   ; register to memory

; Address calculation
mov rax, [rbx + rcx*4 + 16]

; Move with zero-extension
movzx eax, byte [rsi]

Stack Operations

The stack grows downward: RSP points to the top. push decrements RSP and stores a value; pop loads and increments RSP. The stack stores local variables, return addresses, and saved registers. The call instruction pushes the return address and jumps; ret pops the address and jumps back.

; Stack frame
my_func:
    push rbp        ; save base pointer
    mov rbp, rsp    ; set frame pointer
    sub rsp, 32     ; allocate 32 bytes locals

    mov [rbp-8], 42 ; local variable

    ; ... function body

    mov rsp, rbp    ; restore stack
    pop rbp         ; restore base pointer
    ret

Calling Conventions

The System V AMD64 ABI is standard on Linux/macOS: first six integer args go in RDI, RSI, RDX, RCX, R8, R9; float args go in XMM0-XMM7; additional args go on the stack. RAX holds the return value. Registers RBX, RBP, R12-R15 are callee-saved (must be preserved); others are caller-saved. Windows x64 uses RCX, RDX, R8, R9 for args.

; System V calling convention
; int add(int a, int b, int c)
; a in rdi, b in rsi, c in rdx
add:
    mov rax, rdi
    add rax, rsi
    add rax, rdx
    ret

; Caller
    mov rdi, 10
    mov rsi, 20
    mov rdx, 30
    call add
    ; result in rax (60)

Interrupts and Syscalls

On Linux, system calls use the syscall instruction. The syscall number goes in RAX, arguments in RDI, RSI, RDX, R10, R8, R9. RAX gets the return value. Common syscalls: 0 (read), 1 (write), 60 (exit), 2 (open). int 0x80 is the legacy 32-bit interface. Syscalls are the kernel's API.

; Linux write syscall
; write(1, msg, len)
section .data
    msg db "Hello, World!", 10
    len equ $ - msg

section .text
global _start
_start:
    mov rax, 1       ; sys_write
    mov rdi, 1       ; stdout
    mov rsi, msg     ; buffer
    mov rdx, len     ; length
    syscall

    mov rax, 60      ; sys_exit
    xor rdi, rdi     ; status 0
    syscall

Addressing Modes

Addressing modes determine how memory operands are computed. Immediate: mov rax, 42. Register: mov rax, rbx. Direct: mov rax, [addr]. Base+offset: mov rax, [rbp+16]. Indexed: mov rax, [rbx + rcx*8]. RIP-relative: lea rax, [rip + offset] — essential for position-independent code.

; Various addressing modes
mov rax, [0x1000]         ; direct
mov rax, [rbp + 8]        ; base + offset
mov rax, [rbx + rcx]      ; base + index
mov rax, [rbx + rcx*4]    ; base + index*scale
mov rax, [rbx + rcx*4 + 16] ; base + index*scale + offset

; LEA computes address without dereference
lea rax, [rbx + rcx*8]    ; rax = rbx + rcx*8 (arithmetic)

Macros and Conditional Assembly

NASM and MASM provide macros for code generation and readability. %define creates single-line macros; %macro/%endmacro for multi-line. %if, %elif, %else enable conditional assembly. This is how assembly libraries provide platform-specific code and how operating system kernels handle architecture variants.

; NASM macros
%define SYS_WRITE 1
%define SYS_EXIT  60

%macro print 2
    mov rax, SYS_WRITE
    mov rdi, 1
    mov rsi, %1
    mov rdx, %2
    syscall
%endmacro

%ifidni __OUTPUT_FORMAT__, elf64
    %define GLOBAL global
%endif

section .data
    msg db "Hello", 0

section .text
    GLOBAL _start
_start:
    print msg, 5
    mov rax, SYS_EXIT
    xor rdi, rdi
    syscall

Frequently Asked Questions

Why learn assembly today?

For debugging (reading compiler output, crash dumps), reverse engineering, embedded systems, and understanding performance at the hardware level.

Intel vs AT&T syntax?

Intel: op dst, src. AT&T: op src, dst with % prefix for registers. Intel is more readable; AT&T is default on Unix tools.

What is the difference between mov and lea?

mov dereferences the address to load/store data. lea computes the effective address without memory access, useful for arithmetic.

What are callee-saved registers?

Registers that a function must preserve for the caller: RBX, RBP, R12-R15 on x86-64. Push them on entry, pop before return.

Originally published on Ayodhyyya. Last updated June 1, 2026.