Assembly
Table of Contents
- Syntaxes
- Registers
- Memory & Addressing Modes
- Program Sections
- Data Directives
- Instructions
- The Stack
- System Calls (Linux int 0x80)
- Assembling & Linking
- Calling Conventions
- Example Programs
Syntaxes
There are 2 types of assembly language syntax in common use:
Intel
- Used by NASM, MASM, and in Intel documentation
- Destination operand comes first:
mov dst, src - No prefix on register names:
eax,ebx - Immediate values have no prefix:
mov eax, 5 - Memory references use square brackets:
mov eax, [ebx] - Size specified on the operand when ambiguous:
mov DWORD PTR [ebx], 5
mov eax, 1 ; move immediate 1 into eax
mov ebx, eax ; copy eax into ebx
add eax, 5 ; eax = eax + 5
mov eax, [ebx] ; load value at address in ebx into eax
AT&T
- Used by GAS (GNU Assembler β default for
.sfiles withgcc/as) - Source operand comes first:
mov src, dst - Registers prefixed with
%:%eax,%ebx - Immediate values prefixed with
$:$5,$0x80 - Memory references use parentheses:
(%ebx) - Size suffix on the mnemonic (b=byte, w=word, l=long/32-bit, q=quad/64-bit):
movbβ 8-bitmovwβ 16-bitmovlβ 32-bitmovqβ 64-bit
movl $1, %eax # move immediate 1 into eax
movl %eax, %ebx # copy eax into ebx
addl $5, %eax # eax = eax + 5
movl (%ebx), %eax # load value at address in ebx into eax
Side-by-side comparison
| Operation | Intel (NASM) | AT&T (GAS) |
|---|---|---|
| Move immediate | mov eax, 5 | movl $5, %eax |
| Move register | mov ebx, eax | movl %eax, %ebx |
| Load from memory | mov eax, [ebx] | movl (%ebx), %eax |
| Store to memory | mov [ebx], eax | movl %eax, (%ebx) |
| Add immediate | add eax, 10 | addl $10, %eax |
| Jump | jmp label | jmp label |
Registers
Registers are small, fast storage areas on the CPU. In IA-32 (x86-32) architecture there are:
- 8 general-purpose 32-bit registers
- 6 16-bit segment registers
- 1 32-bit EFLAGS register
- 1 32-bit instruction pointer (EIP)
- Several control registers (CR0βCR4)
Registers are grouped into categories: general-purpose, control, and segment.
General-Purpose Registers
Data Registers
| Register | Name | Common use |
|---|---|---|
EAX | Accumulator | Arithmetic, function return values, syscall number |
EBX | Base | General storage; syscall arg 1 (Linux int 0x80) |
ECX | Counter | Loop counters; syscall arg 2 |
EDX | Data | I/O port operations, extended precision; syscall arg 3 |
Index Registers
| Register | Name | Common use |
|---|---|---|
ESI | Source Index | Source address for string/memory operations |
EDI | Destination Index | Destination address for string/memory operations |
Pointer Registers
| Register | Name | Common use |
|---|---|---|
EIP | Instruction Pointer | Address of the next instruction to execute β not directly writable; modified by jmp, call, ret |
ESP | Stack Pointer | Points to the top of the stack (current stack address); decremented by push, incremented by pop |
EBP | Base Pointer | Points to the base of the current stack frame; used for stable access to local variables and parameters |
Control Registers
Control registers govern processor operation modes. Not directly accessible in user-space programs β require ring 0 (kernel) privilege.
| Register | Purpose |
|---|---|
CR0 | System control flags: PE (bit 0) enables protected mode; PG (bit 31) enables paging; WP (bit 16) write-protect |
CR1 | Reserved |
CR2 | Page Fault Linear Address β holds the address that caused the last page fault |
CR3 | Page Directory Base Register β physical address of the page directory; used by the MMU for virtual-to-physical address translation |
CR4 | Extended feature enable: PSE (4 MB pages), PAE (physical address extension), VME, etc. |
Segment Registers
Segment registers hold 16-bit segment selectors β they index into the Global Descriptor Table (GDT) or Local Descriptor Table (LDT) to define memory segments. In modern 32-bit flat-memory Linux programs, most segments cover the entire 4 GB address space and act as a formality.
| Register | Name | Role |
|---|---|---|
CS | Code Segment | Points to the segment containing executable code; determines privilege level (CPL) |
DS | Data Segment | Default segment for data read/write operations |
ES | Extra Segment | Extra segment; used by string instructions as the destination segment |
FS | F Segment | General purpose; used by OS for thread-local storage (TLS) on Linux/Windows |
GS | G Segment | General purpose; used by OS for per-CPU data on Linux |
SS | Stack Segment | Points to the segment containing the current stack |
EFLAGS Register
EFLAGS is a 32-bit register where individual bits are status flags set/cleared by arithmetic and logic instructions. Conditional jumps read these flags.
| Flag | Bit | Name | Set when⦠|
|---|---|---|---|
CF | 0 | Carry | Unsigned overflow / borrow occurred |
PF | 2 | Parity | Least-significant byte of result has even number of 1-bits |
AF | 4 | Auxiliary Carry | Carry out of bit 3 (used for BCD arithmetic) |
ZF | 6 | Zero | Result is zero |
SF | 7 | Sign | Result is negative (MSB = 1) |
TF | 8 | Trap | Single-step debugging mode |
IF | 9 | Interrupt Enable | Maskable interrupts are enabled |
DF | 10 | Direction | String operations go highβlow (set) or lowβhigh (clear) |
OF | 11 | Overflow | Signed overflow occurred |
Register size sub-divisions
Each 32-bit general-purpose register can be accessed in smaller pieces:
31 16 15 8 7 0
βββββββββββββββ¬βββββββββββ¬ββββββββββ
β (upper) β AH β AL β β 8-bit halves of AX
β (upper) β AX β β 16-bit
β EAX β β 32-bit
βββββββββββββββββββββββββββββββββββ
| 32-bit | 16-bit | High 8-bit | Low 8-bit |
|---|---|---|---|
EAX | AX | AH | AL |
EBX | BX | BH | BL |
ECX | CX | CH | CL |
EDX | DX | DH | DL |
ESI | SI | β | β |
EDI | DI | β | β |
ESP | SP | β | β |
EBP | BP | β | β |
In 64-bit mode (x86-64) the registers extend to RAX, RBX, etc. and eight additional registers R8βR15 are added.
Memory & Addressing Modes
Assembly accesses memory through several addressing modes. Examples shown in AT&T syntax.
| Mode | AT&T syntax | Meaning |
|---|---|---|
| Immediate | $5 | The literal value 5 |
| Register | %eax | Value stored in EAX |
| Direct / absolute | 0x804a000 | Value at that fixed address |
| Register indirect | (%eax) | Value at the address stored in EAX |
| Base + displacement | 4(%eax) | Value at address EAX + 4 |
| Indexed | (%eax, %ebx) | Value at address EAX + EBX |
| Scaled indexed | (%eax, %ebx, 4) | Value at address EAX + EBXΓ4 |
| Base + scaled index + displacement | 8(%eax, %ebx, 4) | Value at address EAX + EBXΓ4 + 8 |
The general form is: displacement(base, index, scale) β base + index * scale + displacement
Scale must be 1, 2, 4, or 8 (matching byte, word, dword, qword element sizes).
movl (%eax), %ebx # ebx = memory[eax]
movl 4(%esp), %eax # eax = memory[esp + 4] (second stack slot)
movl (%eax, %ecx, 4), %edx # edx = memory[eax + ecx*4] (array indexing)
Program Sections
A GAS (AT&T) source file is organized into sections:
| Section | Directive | Purpose |
|---|---|---|
| Code | .section .text | Executable instructions; read-only at runtime |
| Initialized data | .section .data | Global/static variables with initial values |
| Uninitialized data | .section .bss | Global/static variables zero-initialized by the OS; no space in binary |
| Read-only data | .section .rodata | String literals and constants |
.section .data
message: .ascii "Hello, World!\n" # initialized string
counter: .long 0 # 32-bit integer initialized to 0
.section .bss
buffer: .space 256 # 256 bytes of zero-initialized space
.section .text
.global _start
_start:
# code here
.global _start makes the _start symbol visible to the linker. The linker requires this as the program entry point when not using the C runtime.
Data Directives
Use these directives inside .data or .bss to reserve and initialize storage:
| Directive | Size | Description |
|---|---|---|
.byte | 1 byte | 8-bit value(s) |
.word | 2 bytes | 16-bit value(s) |
.long | 4 bytes | 32-bit value(s) |
.quad | 8 bytes | 64-bit value(s) |
.ascii | n bytes | String β no null terminator |
.asciz | n+1 bytes | String with null terminator (\0) |
.space n | n bytes | Allocate n bytes, zero-initialized |
.fill n, size, val | varies | Fill n copies of val at size bytes each |
.equ name, val | β | Define a symbolic constant (no storage) |
.section .data
num: .long 42
pi: .long 0x40490FDB # IEEE 754 float for pi (approx)
msg: .asciz "hello\n" # null-terminated
arr: .long 1, 2, 3, 4, 5 # array of five 32-bit integers
.equ STDIN, 0
.equ STDOUT, 1
.equ SYS_EXIT, 1
.equ SYS_WRITE, 4
Instructions
Data Movement
| Mnemonic (AT&T) | Operation | Notes |
|---|---|---|
movl src, dst | dst = src | Copy between registers, memory, or immediates. Cannot move memoryβmemory directly. |
leal src, dst | dst = address of src | Load Effective Address β computes the address without dereferencing it. Useful for pointer arithmetic and fast multiplication. |
pushl src | esp -= 4; mem[esp] = src | Push onto the stack |
popl dst | dst = mem[esp]; esp += 4 | Pop from the stack |
xchgl a, b | a β b | Swap two registers (or register and memory) atomically |
movzbl src, dst | dst = zero-extend(src) | Move byte to long, zero-extending to 32 bits |
movsbl src, dst | dst = sign-extend(src) | Move byte to long, sign-extending to 32 bits |
movl $10, %eax # eax = 10
movl %eax, %ebx # ebx = eax
leal 4(%eax), %ecx # ecx = eax + 4 (address calculation, no memory access)
leal (%eax, %ebx, 4), %edx # ecx = eax + ebx*4
xchgl %eax, %ebx # swap eax and ebx
Arithmetic
| Mnemonic (AT&T) | Operation | Notes |
|---|---|---|
addl src, dst | dst = dst + src | Sets CF, ZF, SF, OF |
subl src, dst | dst = dst - src | Sets CF, ZF, SF, OF |
incl dst | dst = dst + 1 | Does not set CF |
decl dst | dst = dst - 1 | Does not set CF |
negl dst | dst = -dst | Twoβs complement negation |
imull src, dst | dst = dst * src | Signed multiply (2-operand form) |
mull src | edx:eax = eax * src | Unsigned multiply β result in EDX:EAX |
imull src | edx:eax = eax * src | Signed multiply β result in EDX:EAX |
divl src | eax = edx:eax / src; edx = remainder | Unsigned divide β dividend in EDX:EAX, zero-extend: xorl %edx, %edx before dividing |
idivl src | eax = edx:eax / src; edx = remainder | Signed divide β sign-extend EAX into EDX:EAX using cdq first |
movl $10, %eax
addl $5, %eax # eax = 15
subl $3, %eax # eax = 12
imull $4, %eax # eax = 48
movl $100, %eax
movl $7, %ecx
xorl %edx, %edx # zero-extend eax into edx:eax for unsigned divide
divl %ecx # eax = 14 (quotient), edx = 2 (remainder)
Logical
| Mnemonic (AT&T) | Operation | Notes |
|---|---|---|
andl src, dst | dst = dst & src | Bitwise AND; clears OF, CF |
orl src, dst | dst = dst | src | Bitwise OR |
xorl src, dst | dst = dst ^ src | Bitwise XOR; xorl %eax, %eax is the canonical way to zero a register |
notl dst | dst = ~dst | Bitwise NOT (oneβs complement) |
testl src, dst | dst & src (set flags only) | AND without storing result β used to check bits or test for zero |
cmpl src, dst | dst - src (set flags only) | Subtract without storing result β sets flags for conditional jumps |
xorl %eax, %eax # eax = 0 (faster than movl $0, %eax)
movl $0xFF, %ebx
andl $0x0F, %ebx # ebx = 0x0F (mask lower nibble)
testl %eax, %eax # set ZF if eax == 0
cmpl $10, %ecx # compare ecx to 10; sets flags for je/jl/jg etc.
Shift & Rotate
| Mnemonic (AT&T) | Operation | Notes |
|---|---|---|
shll $n, dst | dst = dst << n | Logical (unsigned) left shift; fills with 0 |
shrl $n, dst | dst = dst >> n | Logical (unsigned) right shift; fills with 0 |
sarl $n, dst | dst = dst >> n | Arithmetic right shift; sign-extends (preserves sign) |
roll $n, dst | Rotate left n bits | Bit shifted out wraps to the other end |
rorl $n, dst | Rotate right n bits |
Shift by a variable amount using %cl:
movl $1, %eax
shll $3, %eax # eax = 8 (1 << 3; multiply by 8)
shrl $1, %eax # eax = 4 (divide by 2, unsigned)
movb $2, %cl
shll %cl, %eax # eax = eax << 2 (variable shift)
Control Flow
Unconditional jump
jmp label # jump to label (near, relative)
jmp *%eax # indirect jump β jump to address in eax
Comparison + conditional jump
Always pair cmpl (or testl) with a conditional jump:
cmpl $10, %eax # compute eax - 10, set flags
je equal # jump if ZF=1 (eax == 10)
jne not_equal # jump if ZF=0 (eax != 10)
jl less # jump if SFβ OF (signed less than)
jle less_or_eq # jump if ZF=1 or SFβ OF
jg greater # jump if ZF=0 and SF=OF (signed greater)
jge greater_or_eq # jump if SF=OF
jb below # unsigned less than (CF=1)
jbe below_or_eq # unsigned less or equal
ja above # unsigned greater than
jae above_or_eq # unsigned greater or equal
Call and return
call function # push EIP (return address) onto stack, jump to function
ret # pop return address from stack into EIP
Loop instruction
movl $5, %ecx
loop_top:
# loop body
loop loop_top # decrement ecx; jump to loop_top if ecx != 0
loop is equivalent to decl %ecx; jnz loop_top but only reads ECX.
Stack Operations
The stack is a LIFO (Last In, First Out) region of memory that grows downward (toward lower addresses) on x86.
High addresses
βββββββββββββββ
β stack frame β β EBP (base of current frame)
β saved EBP β
β local vars β
β ... β
β top value β β ESP (always points here)
βββββββββββββββ
Low addresses
pushdecrements ESP by the operand size, then writes the value to[ESP]popreads the value at[ESP], then increments ESP
Standard function prologue / epilogue
function:
pushl %ebp # save caller's base pointer
movl %esp, %ebp # establish new stack frame
subl $16, %esp # allocate 16 bytes for local variables
# function body
# locals accessed as -4(%ebp), -8(%ebp), etc.
# caller's args accessed as 8(%ebp), 12(%ebp), etc.
movl %ebp, %esp # restore stack pointer (deallocate locals)
popl %ebp # restore caller's base pointer
ret # return to caller
The enter and leave instructions compress the prologue/epilogue:
enter $16, $0 # equivalent to: pushl %ebp; movl %esp, %ebp; subl $16, %esp
leave # equivalent to: movl %ebp, %esp; popl %ebp
Stack arithmetic example (from stack.asm)
pushl $10 # push 10; esp -= 4
pushl $20 # push 20; esp -= 4
movl (%esp), %eax # eax = 20 (top of stack)
addl 4(%esp), %eax # eax = 20 + 10 = 30
addl $8, %esp # pop both values (esp += 8)
System Calls (Linux int 0x80)
On 32-bit Linux, system calls are made by:
- Loading the syscall number into
EAX - Loading arguments into
EBX,ECX,EDX,ESI,EDI(in order) - Executing
int $0x80β software interrupt 0x80 switches to kernel mode
The return value is placed in EAX after the interrupt.
Common syscalls (IA-32 Linux)
| EAX | Name | EBX | ECX | EDX |
|---|---|---|---|---|
| 1 | exit | exit code | β | β |
| 2 | fork | β | β | β |
| 3 | read | fd | buffer ptr | count |
| 4 | write | fd | buffer ptr | count |
| 5 | open | filename ptr | flags | mode |
| 6 | close | fd | β | β |
| 45 | brk | new break addr | β | β |
Exit example
movl $1, %eax # syscall: exit
xorl %ebx, %ebx # exit code 0 (xor is faster than movl $0)
int $0x80
Write βHello, World!β example
.section .data
msg: .asciz "Hello, World!\n"
len = . - msg # '.' is current address; len = length of msg
.section .text
.global _start
_start:
movl $4, %eax # syscall: write
movl $1, %ebx # fd: stdout (1)
leal msg, %ecx # pointer to message
movl $len, %edx # number of bytes to write
int $0x80
movl $1, %eax # syscall: exit
xorl %ebx, %ebx # exit code 0
int $0x80
Assembling & Linking
GAS (GNU Assembler) β AT&T syntax, .s files
# Assemble to object file
as program.s -o program.o
# Link to executable
ld program.o -o program
# Or combine with gcc (handles linking for you)
gcc -m32 -nostdlib program.s -o program
NASM β Intel syntax, .asm files
# Assemble to ELF32 object file
nasm -f elf32 program.asm -o program.o
# Link
ld -m elf_i386 program.o -o program
Flags reference
| Flag | Purpose |
|---|---|
-m32 | Compile/link for 32-bit target (on 64-bit host) |
-nostdlib | Do not link against C standard library (use for bare _start programs) |
-f elf32 | NASM: output format = 32-bit ELF |
-m elf_i386 | ld: link for 32-bit ELF |
-g | Include debug info (for use with GDB) |
-o outfile | Specify output filename |
Debugging with GDB
gcc -m32 -nostdlib -g program.s -o program
gdb ./program
(gdb) break _start # set breakpoint at entry
(gdb) run # start execution
(gdb) info registers # display all registers
(gdb) x/10xw $esp # examine 10 words on stack in hex
(gdb) stepi # step one instruction
(gdb) disas # disassemble current function
Calling Conventions
The cdecl convention is standard for C on 32-bit x86 Linux:
- Caller pushes arguments onto the stack right-to-left (last arg first)
- Caller executes
call function(pushes return address, jumps) - Callee sets up a stack frame (
push %ebp; mov %esp, %ebp) - Callee uses
%eax(and%edxfor 64-bit values) for return value - Callee restores
%ebx,%esi,%edi,%ebp,%esp(callee-saved) %eax,%ecx,%edxare caller-saved (callee may clobber them)- Caller cleans up arguments from the stack after
ret
# Calling a C function: int result = add(3, 5);
pushl $5 # push second argument
pushl $3 # push first argument
call add # call; return value will be in %eax
addl $8, %esp # caller cleans up 2 Γ 4 bytes of args
# %eax now holds the return value
Register save/restore summary
| Register | Who saves? | Notes |
|---|---|---|
EAX | Caller | Return value |
ECX | Caller | Often used as scratch |
EDX | Caller | High half of return for 64-bit values |
EBX | Callee | Must be preserved across calls |
ESI | Callee | Must be preserved |
EDI | Callee | Must be preserved |
EBP | Callee | Frame pointer β must be preserved |
ESP | Both | Caller sets up args; callee must restore before ret |
Example Programs
program1.s β register moves and exit
.section .text
.global _start
_start:
movl $42, %eax # load 42 into eax
movl %eax, %ebx # copy eax -> ebx
movl %ebx, %ecx # copy ebx -> ecx
movl $1, %eax # syscall: exit
xorl %ebx, %ebx # exit code 0
int $0x80
Demonstrates movl between registers and the exit syscall. xorl %ebx, %ebx is the idiomatic zero β it is shorter and faster than movl $0, %ebx.
program2.asm β Intel syntax minimal exit
.global start
.intel_syntax
.section .text
_start:
mov %eax, 1 # syscall: exit
mov %ebx, 0 # exit code 0
int 0x80
Same logic in Intel syntax (GAS Intel mode). Note: GAS Intel syntax still uses % on register names unlike NASM.
stack.asm β stack arithmetic
.section .text
.global _start
_start:
pushl $10 # push 10 onto stack (esp = esp - 4)
pushl $20 # push 20 onto stack (esp = esp - 4)
movl (%esp), %eax # eax = top of stack = 20
addl 4(%esp), %eax # eax += *(esp+4) = 10 β eax = 30
addl $8, %esp # pop both values (esp = esp + 8)
movl $1, %eax # syscall: exit
xorl %ebx, %ebx # exit code 0
int $0x80
Demonstrates push/pop mechanics, stack-relative addressing (4(%esp)), and manual stack cleanup. After the two pushes, the stack looks like:
esp+0 β [ 20 ]
esp+4 β [ 10 ]
addl $8, %esp discards both values β equivalent to two popl instructions without actually reading them into registers.
String operations overview
# Copy 10 dwords from source to destination
.section .data
source: .long 1, 2, 3, 4, 5, 6, 7, 8, 9, 10
dest: .space 40
.section .text
.global _start
_start:
leal source, %esi # esi = source address
leal dest, %edi # edi = destination address
movl $10, %ecx # count = 10 dwords
cld # clear DF: increment esi/edi after each step
rep movsl # copy ecx dwords from [esi] to [edi]
movl $1, %eax
xorl %ebx, %ebx
int $0x80
rep movsl repeats movsl (move dword from [ESI] to [EDI], increment both) until ECX reaches zero.
String Operations (reference)
| Instruction | Operation | Notes |
|---|---|---|
movsl | mem[edi] = mem[esi]; esi += 4; edi += 4 | Copy dword; direction depends on DF |
movsb | Same but byte | |
stosl | mem[edi] = eax; edi += 4 | Store EAX into memory at EDI |
lodsl | eax = mem[esi]; esi += 4 | Load dword from ESI into EAX |
scasl | eax - mem[edi]; edi += 4 | Compare EAX to memory at EDI (set flags) |
cmpsb | Compare mem[esi] to mem[edi]; advance both | Used for strcmp-style loops |
rep | Repeat next string op ECX times | |
repe/repz | Repeat while ZF=1 (equal) | Used with cmps/scas |
repne/repnz | Repeat while ZF=0 (not equal) | repne scasb finds a byte in memory |
cld | Clear DF (forward direction) | Call before string ops |
std | Set DF (reverse direction) | Rarely used |