Part 1: Assembly Language
Welcome to this comprehensive tutorial on Assembly language, the most low-level programming language that
humans typically write. Assembly (often abbreviated asm) is not a single language but a family of languages, each
corresponding to a specific computer architecture. Unlike high-level languages that abstract away hardware
details, Assembly gives you direct control over the CPU registers, memory, and instructions. This tutorial focuses
on x86-64 Assembly, the most widely used architecture in desktop and server computing.
1.1 Why Learn Assembly?
Learning Assembly gives you a deep understanding of how computers actually work at the hardware level. It helps
you understand what compilers do, how memory is laid out, and why certain code patterns are faster than others.
Assembly is essential for reverse engineering, malware analysis, writing device drivers, implementing operating
system kernels, optimizing performance-critical code, and understanding security vulnerabilities like buffer
overflows. Even if you never write production Assembly, understanding it makes you a better programmer in every
other language.
1.2 Prerequisites and Tools
To write and run Assembly, you need an assembler and a linker. The most common assembler on Linux is NASM
(Netwide Assembler), which uses Intel syntax. On macOS, you can use NASM as well, though macOS uses a
different calling convention. GNU Assembler (GAS) uses AT&T syntax by default. We use NASM with Intel syntax
throughout this tutorial because it is more readable. You also need a linker (ld on Linux, ld on macOS) or you can
link using gcc/clang, which is often easier.
# Install NASM on macOS
$ brew install nasm
# Install NASM on Ubuntu
$ sudo apt install nasm
# Verify
$ nasm --version
NASM version 2.16.01
# Assemble and link (Linux)
$ nasm -f elf64 [Link] -o hello.o
$ ld hello.o -o hello
$ ./hello
# Assemble and link (macOS)
$ nasm -f macho64 [Link] -o hello.o
$ ld hello.o -o hello -l System -syslibroot $(xcrun -sdk macosx --show-sdk-path) -e _main
$ ./hello
1.3 Registers
Registers are small, fast storage locations inside the CPU. In x86-64, there are 16 general-purpose registers. The
most commonly used are RAX, RBX, RCX, RDX (general data), RSI and RDI (source and destination indices),
RSP (stack pointer), RBP (base pointer), and R8 through R15 (extended registers). RAX typically holds return
Page 1
Part 1: Assembly Language
values. Each 64-bit register has smaller aliases: EAX is the lower 32 bits, AX is 16 bits, and AL/AH are 8-bit
portions.
; 64-bit register layout for RAX:
; +--------+--------+--------+--------+
; | RAX (64-bit) |
; +--------+--------+--------+--------+
; | EAX (32-bit) |
; +--------+--------+
; | AX (16-bit) |
; +----+---+----+
; | AH | AL(8b) |
; +----+---+----+
; Common registers:
; RAX - Accumulator, return values
; RBX - Base, general purpose
; RCX - Counter for loops (REP, LOOP)
; RDX - Data, I/O operations
; RSI - Source index for string ops
; RDI - Destination index for string ops
; RSP - Stack pointer (top of stack)
; RBP - Base pointer (frame pointer)
; R8-R15 - Extended general purpose
1.4 Hello World in Assembly
Let us write our first Assembly program. On Linux, we use system calls directly. The write syscall (number 1)
writes data to a file descriptor. The exit syscall (number 60) terminates the program. We pass syscall numbers in
RAX and arguments in RDI, RSI, RDX, R10, R8, R9 in that order.
; [Link] - Linux x86-64
section .data
msg db 'Hello, World!', 0xA ; message + newline
msg_len equ $ - msg ; length (current addr - msg addr)
section .text
global _start
_start:
; write(1, msg, msg_len)
mov rax, 1 ; syscall: write
mov rdi, 1 ; fd: stdout
mov rsi, msg ; buffer address
mov rdx, msg_len; byte count
syscall ; invoke kernel
; exit(0)
mov rax, 60 ; syscall: exit
mov rdi, 0 ; exit code 0
syscall
Page 2
Part 1: Assembly Language
; Assemble and run:
; nasm -f elf64 [Link] -o hello.o
; ld hello.o -o hello && ./hello
1.5 Data Section and Variables
Assembly programs are organized into sections. The .data section holds initialized variables. The .bss section
holds uninitialized variables (reserved memory). The .text section holds the actual instructions. The .rodata section
(read-only data) holds constants. Variables are essentially labels pointing to memory addresses.
section .data
; Initialized data
name db 'Alice', 0 ; null-terminated string
age dd 30 ; 32-bit integer (double word)
pi dq 3.14159 ; 64-bit float (quad word)
numbers dd 10, 20, 30, 40 ; array of 4 ints
section .bss
; Uninitialized (reserved) data
buffer resb 256 ; 256 bytes
counter resd 1 ; 1 double word (4 bytes)
big_array resq 100 ; 100 quad words (800 bytes)
section .rodata
; Read-only data (constants)
prompt db 'Enter: ', 0
fmt db '%d', 0 ; format string
1.6 Instructions: Moving Data
The mov instruction copies data between registers and memory. The lea (load effective address) instruction
computes an address without accessing memory. Assembly uses various addressing modes: immediate, register,
direct memory, and indirect addressing with offsets and scaling for arrays.
; mov examples
mov rax, 42 ; immediate to register
mov rbx, rax ; register to register
mov [counter], rax ; register to memory (label)
mov rcx, [counter] ; memory to register
; lea - compute address (no memory access)
lea rax, [counter] ; rax = address of counter
lea rbx, [rax + 8] ; rbx = rax + 8 (pointer math)
; Array indexing (numbers is an array of dword)
; numbers[i] = [numbers + i*4]
mov rsi, 2 ; index = 2
mov eax, [numbers + rsi*4] ; eax = numbers[2] (30)
; Stack operations
Page 3
Part 1: Assembly Language
push rax ; push rax onto stack
pop rbx ; pop from stack into rbx
1.7 Arithmetic and Logic
Assembly provides arithmetic instructions (add, sub, mul, div, inc, dec) and logical instructions (and, or, xor, not,
shl, shr). The mul and div instructions have specific behaviors regarding the RAX and RDX registers. The cmp
instruction compares two values and sets flags, which are then used by conditional jump instructions.
; Arithmetic
mov rax, 10
add rax, 5 ; rax = 15
sub rax, 3 ; rax = 12
inc rax ; rax = 13
dec rax ; rax = 12
; Multiplication (unsigned: mul)
mov rax, 10
mov rbx, 3
mul rbx ; RDX:RAX = RAX * RBX = 30
; Division (unsigned: div)
mov rax, 100
xor rdx, rdx ; clear RDX (high part of dividend)
mov rbx, 4
div rbx ; RAX = 100/4 = 25, RDX = 100%4 = 0
; Logic
mov rax, 0xFF
and rax, 0x0F ; rax = 0x0F
xor rax, rax ; rax = 0 (common zeroing idiom)
shl rax, 4 ; shift left 4 bits (multiply by 16)
shr rax, 2 ; shift right 2 bits (divide by 4)
1.8 Control Flow: Jumps and Loops
Assembly has no structured if-else or for loops. Instead, you use labels and conditional/unconditional jumps. The
cmp instruction compares two operands and sets CPU flags. Conditional jumps like je (jump if equal), jne (jump if
not equal), jl (jump if less), jg (jump if greater) use these flags. jmp is an unconditional jump. Loops are built by
combining cmp and jumps.
; if-else example: if (rax >= 10) print "yes" else print "no"
cmp rax, 10
jl .else_branch
; if body: rax >= 10
mov rdi, yes_msg
jmp .end_if
.else_branch:
mov rdi, no_msg
.end_if:
Page 4
Part 1: Assembly Language
; For loop: print numbers 0 to 4
mov rcx, 0 ; counter
.loop_start:
cmp rcx, 5
jge .loop_end
; loop body: print rcx
push rcx ; save counter (print may clobber)
; ... print rcx ...
pop rcx ; restore counter
inc rcx
jmp .loop_start
.loop_end:
; While loop equivalent: countdown from 10
mov rax, 10
.while_start:
cmp rax, 0
jle .while_end
dec rax
jmp .while_start
.while_end:
; Common conditional jumps:
; je/jz - equal/zero
; jne/jnz- not equal/not zero
; jl/jge - less / greater-or-equal (signed)
; jb/jae - below / above-or-equal (unsigned)
; jg/jle - greater / less-or-equal (signed)
; ja/jbe - above / below-or-equal (unsigned)
1.9 Functions and the Call Stack
Functions in Assembly use the call and ret instructions. call pushes the return address onto the stack and jumps to
the function. ret pops the return address and jumps back. The calling convention defines how arguments are
passed and who saves registers. The System V AMD64 ABI (used on Linux/macOS) passes the first 6 integer
args in RDI, RSI, RDX, RCX, R8, R9, and the return value in RAX. You must preserve RBP, RBX, and R12-R15
(callee-saved).
; Function: add_two(a, b) -> a + b
; Args: rdi = a, rsi = b, return in rax
add_two:
mov rax, rdi
add rax, rsi
ret
; Function with stack frame (prologue/epilogue)
; int sum_array(int* arr, int n)
sum_array:
push rbp ; save old base pointer
mov rbp, rsp ; set new base pointer
Page 5
Part 1: Assembly Language
; args: rdi = arr, rsi = n
xor eax, eax ; sum = 0
xor ecx, ecx ; i = 0
.loop:
cmp ecx, esi ; if i >= n
jge .done
add eax, [rdi + rcx*4] ; sum += arr[i]
inc ecx
jmp .loop
.done:
; rax already holds return value
pop rbp ; restore base pointer
ret
; Calling a function
mov rdi, 3 ; first arg
mov rsi, 4 ; second arg
call add_two ; rax = 7
1.10 Interfacing with C
In practice, most Assembly is written as functions called from C programs. This lets you use Assembly for
performance-critical functions while writing the rest in C. You assemble the Assembly file into an object file and link
it with your C code. The C function can be declared with extern.
; [Link] - a function callable from C
; int square(int n); -> returns n * n
section .text
global square
square:
mov eax, edi ; eax = n (first arg in edi)
imul eax, eax ; eax = n * n
ret
// main.c - calling Assembly from C
#include <stdio.h>
#include <stdio.h>
extern int square(int n);
int main() {
printf("Square of 7 = %d\n", square(7));
return 0;
}
; Build (Linux):
; nasm -f elf64 [Link] -o math.o
; gcc main.c math.o -o program
; ./program
Page 6
Part 1: Assembly Language
; Output: Square of 7 = 49
1.11 Summary of Part 1
Assembly is challenging but rewarding. We covered the x86-64 architecture, registers, sections (data, bss, text),
system calls, data movement, arithmetic, logic, control flow with jumps and labels, functions and the calling
convention, and interfacing with C. Assembly teaches you exactly how a CPU executes instructions, how memory
is addressed, and how high-level constructs like loops and function calls are implemented at the lowest level. In
Part 2, we move to JavaScript, a high-level language that powers the modern web.
Page 7