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.