How to Learn x86-64 Assembly Language for Practical Use: A Step-by-Step Guide
The Problem
I wanted to learn assembly language. But when I tried, I ran into a wall.
I opened an x86-64 assembly tutorial. It showed instructions like mov, push, pop. But I had no context. Why does the stack grow downward? What are these registers? Why do arguments go in different registers?
Then I tried reading a disassembly of a simple C program. Hundreds of instructions. I couldn’t tell what was my code and what was runtime setup.
I was trying to learn assembly without understanding the machine that runs it.
The Solution
The most effective path to learn x86-64 assembly is:
C Fundamentals → C to Assembly Translation → Register Fundamentals →Essential Instructions → Calling Conventions → System Calls → PracticeYou don’t start with assembly. You start with C, then use the compiler as your teacher.
Phase 1: Build the C Foundation
Before touching assembly, I needed to understand what assembly actually does. C is the bridge.
Pointers—The Key Concept
int x = 42;int *ptr = &x; // ptr holds memory addressprintf("%d\n", *ptr); // dereference to get valueIn assembly, everything is pointers. You’re always working with addresses and values at those addresses. Understanding C pointers makes assembly natural.
Memory Layout
void example() { int stack_var = 10; // Stack - automatic int *heap_var = malloc(4); // Heap - manual *heap_var = 20; free(heap_var);}In assembly, you see the stack and heap directly. You manipulate the stack pointer. You understand why memory leaks happen.
Struct Layout and Padding
struct Data { char a; // 1 byte + 3 padding int b; // 4 bytes char c; // 1 byte + 3 padding}; // Total: 12 bytes, not 6This matters in assembly because you access struct members by offset. The padding affects those offsets.
Phase 2: Use the Compiler as Teacher
The best way to learn assembly is to see what the compiler generates.
Compile C to Assembly
# Compile C to assemblygcc -S -O0 program.c -o program.s
# Use Intel syntax (easier to read)gcc -S -O0 -masm=intel program.c -o program.s
# Disassemble existing programsobjdump -d program | lessI started with simple functions and examined their assembly:
int add(int a, int b) { return a + b;}Becomes:
add: mov eax, edi ; First argument (a) -> eax add eax, esi ; Add second argument (b) ret ; Return (result in eax)This taught me more than any tutorial. I could see exactly how C maps to assembly.
Phase 3: x86-64 Register Fundamentals
x86-64 has many registers, but you only need to know the essential ones.
General Purpose Registers
┌──────────────────────────────────────────────────────────────┐│ Essential Registers │├──────────────────────────────────────────────────────────────┤│ RAX/EAX │ Accumulator │ Return values, arithmetic ││ RBX/EBX │ Base │ Preserved across calls ││ RCX/ECX │ Counter │ Loops, shifts ││ RDX/EDX │ Data │ I/O, arithmetic extension ││ RSI/ESI │ Source │ String operations ││ RDI/EDI │ Destination │ String operations ││ R8-R15 │ Additional │ Extra general purpose (64-bit) │├──────────────────────────────────────────────────────────────┤│ RSP/ESP │ Stack pointer │ Top of stack ││ RBP/EBP │ Base pointer │ Stack frame reference ││ RIP │ Instruction │ Next instruction address ││ RFLAGS │ Status flags │ Zero, carry, overflow, etc. │└──────────────────────────────────────────────────────────────┘Argument Passing (System V AMD64 ABI)
On Linux, function arguments go in registers:
Integer arguments: RDI, RSI, RDX, RCX, R8, R9Return value: RAXThis is different from 32-bit x86 where arguments were passed on the stack.
Phase 4: Essential Instructions
x86-64 has over 1000 instructions. You only need about 50 for practical work.
Data Movement
mov rax, rbx ; Copy rbx to raxmov rax, [rbx] ; Load from memory at rbxmov [rbx], rax ; Store rax to memory at rbxlea rax, [rbx+rcx] ; Load effective addressArithmetic
add rax, rbx ; rax = rax + rbxsub rax, rbx ; rax = rax - rbximul rax, rbx ; rax = rax * rbx (signed)inc rax ; rax++dec rax ; rax--Logic
and rax, rbx ; Bitwise ANDor rax, rbx ; Bitwise ORxor rax, rbx ; Bitwise XORshl rax, 3 ; Shift left 3 bitsshr rax, 3 ; Shift right 3 bitsControl Flow
cmp rax, rbx ; Compare (sets flags)je label ; Jump if equaljne label ; Jump if not equaljg label ; Jump if greaterjl label ; Jump if lessjmp label ; Unconditional jumpStack Operations
push rax ; Push rax onto stackpop rbx ; Pop top of stack into rbxcall function ; Push return address, jump to functionret ; Pop return address, jump backPhase 5: Calling Conventions
Understanding how functions communicate is essential.
The Linux x86-64 System V ABI
; Arguments: RDI, RSI, RDX, RCX, R8, R9; Return: RAX
; Example: Calling printfsection .data msg db "Hello, %s!", 10, 0 name db "World", 0
section .text global main extern printf
main: push rbp ; Save base pointer mov rbp, rsp ; Set up stack frame
lea rdi, [rel msg] ; First arg: format string lea rsi, [rel name] ; Second arg: string xor eax, eax ; Clear eax (printf uses AL for varargs) call printf ; Call printf
xor eax, eax ; Return 0 pop rbp retCallee-Saved vs Caller-Saved
This matters when you write your own functions:
Callee-saved (must preserve): RBX, RBP, R12-R15Caller-saved (can clobber): RAX, RCX, RDX, RSI, RDI, R8-R11Phase 6: System Calls
Assembly lets you call the kernel directly.
Linux x86-64 System Calls
; Linux x86-64 syscall numbers; sys_write = 1, sys_exit = 60
section .data msg db "Hello, Assembly!", 10 len equ $ - msg
section .text global _start
_start: ; sys_write(fd, buf, count) mov rax, 1 ; syscall: write mov rdi, 1 ; fd: stdout lea rsi, [rel msg] ; buf: message mov rdx, len ; count: length syscall ; Make system call
; sys_exit(0) mov rax, 60 ; syscall: exit xor rdi, rdi ; status: 0 syscall ; Make system callPractical Projects
Once I understood the basics, I applied the knowledge:
Project 1: Hello World
; Build: nasm -f elf64 hello.asm && ld -o hello hello.o; Run: ./hello
section .data msg db "Hello, x86-64 Assembly!", 10 len equ $ - msg
section .text global _start
_start: mov rax, 1 ; write syscall mov rdi, 1 ; stdout lea rsi, [rel msg] mov rdx, len syscall
mov rax, 60 ; exit syscall xor rdi, rdi syscallProject 2: Array Sum
; int sum_array(int *arr, int count)section .textglobal sum_array
sum_array: xor eax, eax ; Result = 0 test rsi, rsi ; Check if count == 0 jz .done
xor ecx, ecx ; Counter i = 0
.loop: add eax, [rdi + rcx*4] ; Add arr[i] to result inc ecx cmp ecx, esi jl .loop
.done: retProject 3: Debug with GDB
# Compile with debug infonasm -f elf64 -g -F dwarf program.asmld -o program program.o
# Debuggdb ./program
# Inside GDB:(gdb) break _start(gdb) run(gdb) info registers(gdb) stepi(gdb) x/10x $rsp # Show 10 hex values at stack pointerCommon Mistakes
Mistake 1: Learning 32-bit Assembly
Many tutorials still teach 32-bit x86. This wastes time:
; DON'T learn this (32-bit)push ebpmov ebp, espmov eax, [ebp+8] ; Arguments on stackpop ebpret
; LEARN this (64-bit)mov rax, rdi ; Arguments in registersretMistake 2: Trying to Learn All Instructions
Focus on the 20% you’ll use 80% of the time:
- Data movement (MOV, LEA, PUSH, POP)
- Arithmetic (ADD, SUB, IMUL, INC, DEC)
- Logic (AND, OR, XOR, SHL, SHR)
- Control flow (CMP, JMP, Jcc, CALL, RET)
Mistake 3: Ignoring Calling Conventions
; WRONG: Random register usagemy_function: mov [rax], rbx ; Clobbering RAX without saving ret ; RAX should have return value!
; CORRECT: Follow conventionsmy_function: push rbx ; Save callee-saved register mov rax, rdi ; First argument in RDI add rax, rsi ; Second argument in RSI pop rbx ; Restore callee-saved register ret ; Return value in RAXMistake 4: Not Using Tools
Use the right tools:
nasm -f elf64 program.asm # Assemblerld -o program program.o # Linkergdb ./program # Debuggerobjdump -d program # DisassemblerSummary
In this post, I showed how to learn x86-64 assembly for practical use. The key points:
- Start with C—Pointers and memory management map directly to assembly
- Use the compiler as teacher—
gcc -Sshows you how C becomes assembly - Learn essential registers—RAX, RDI, RSI, RSP, RBP, RIP
- Focus on 50 instructions—Not all 1000+ instructions
- Understand calling conventions—How functions pass arguments and return values
- Practice with real programs—Debug with GDB, write actual code
The path: C fundamentals → C to assembly translation → registers → instructions → calling conventions → system calls → projects.
Final Words + More Resources
My intention with this article was to help others share my knowledge and experience. If you want to contact me, you can contact by email: Email me
Here are also the most important links from this article along with some further resources that will help you in this scope:
- 👨💻 Computer Systems: A Programmer's Perspective
- 👨💻 NASM Assembler
- 👨💻 GDB Documentation
- 👨💻 System V AMD64 ABI
Oh, and if you found these resources useful, don’t forget to support me by starring the repo on GitHub!
Comments