Keyboard shortcuts

Press ← or β†’ to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

🏠 Back to Blog

Assembly

Table of Contents


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 .s files with gcc/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-bit
    • movw β€” 16-bit
    • movl β€” 32-bit
    • movq β€” 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

OperationIntel (NASM)AT&T (GAS)
Move immediatemov eax, 5movl $5, %eax
Move registermov ebx, eaxmovl %eax, %ebx
Load from memorymov eax, [ebx]movl (%ebx), %eax
Store to memorymov [ebx], eaxmovl %eax, (%ebx)
Add immediateadd eax, 10addl $10, %eax
Jumpjmp labeljmp 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

RegisterNameCommon use
EAXAccumulatorArithmetic, function return values, syscall number
EBXBaseGeneral storage; syscall arg 1 (Linux int 0x80)
ECXCounterLoop counters; syscall arg 2
EDXDataI/O port operations, extended precision; syscall arg 3

Index Registers

RegisterNameCommon use
ESISource IndexSource address for string/memory operations
EDIDestination IndexDestination address for string/memory operations

Pointer Registers

RegisterNameCommon use
EIPInstruction PointerAddress of the next instruction to execute β€” not directly writable; modified by jmp, call, ret
ESPStack PointerPoints to the top of the stack (current stack address); decremented by push, incremented by pop
EBPBase PointerPoints 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.

RegisterPurpose
CR0System control flags: PE (bit 0) enables protected mode; PG (bit 31) enables paging; WP (bit 16) write-protect
CR1Reserved
CR2Page Fault Linear Address β€” holds the address that caused the last page fault
CR3Page Directory Base Register β€” physical address of the page directory; used by the MMU for virtual-to-physical address translation
CR4Extended 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.

RegisterNameRole
CSCode SegmentPoints to the segment containing executable code; determines privilege level (CPL)
DSData SegmentDefault segment for data read/write operations
ESExtra SegmentExtra segment; used by string instructions as the destination segment
FSF SegmentGeneral purpose; used by OS for thread-local storage (TLS) on Linux/Windows
GSG SegmentGeneral purpose; used by OS for per-CPU data on Linux
SSStack SegmentPoints 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.

FlagBitNameSet when…
CF0CarryUnsigned overflow / borrow occurred
PF2ParityLeast-significant byte of result has even number of 1-bits
AF4Auxiliary CarryCarry out of bit 3 (used for BCD arithmetic)
ZF6ZeroResult is zero
SF7SignResult is negative (MSB = 1)
TF8TrapSingle-step debugging mode
IF9Interrupt EnableMaskable interrupts are enabled
DF10DirectionString operations go high→low (set) or low→high (clear)
OF11OverflowSigned 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-bit16-bitHigh 8-bitLow 8-bit
EAXAXAHAL
EBXBXBHBL
ECXCXCHCL
EDXDXDHDL
ESISIβ€”β€”
EDIDIβ€”β€”
ESPSPβ€”β€”
EBPBPβ€”β€”

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.

ModeAT&T syntaxMeaning
Immediate$5The literal value 5
Register%eaxValue stored in EAX
Direct / absolute0x804a000Value at that fixed address
Register indirect(%eax)Value at the address stored in EAX
Base + displacement4(%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 + displacement8(%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:

SectionDirectivePurpose
Code.section .textExecutable instructions; read-only at runtime
Initialized data.section .dataGlobal/static variables with initial values
Uninitialized data.section .bssGlobal/static variables zero-initialized by the OS; no space in binary
Read-only data.section .rodataString 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:

DirectiveSizeDescription
.byte1 byte8-bit value(s)
.word2 bytes16-bit value(s)
.long4 bytes32-bit value(s)
.quad8 bytes64-bit value(s)
.asciin bytesString β€” no null terminator
.ascizn+1 bytesString with null terminator (\0)
.space nn bytesAllocate n bytes, zero-initialized
.fill n, size, valvariesFill 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)OperationNotes
movl src, dstdst = srcCopy between registers, memory, or immediates. Cannot move memory→memory directly.
leal src, dstdst = address of srcLoad Effective Address β€” computes the address without dereferencing it. Useful for pointer arithmetic and fast multiplication.
pushl srcesp -= 4; mem[esp] = srcPush onto the stack
popl dstdst = mem[esp]; esp += 4Pop from the stack
xchgl a, ba ↔ bSwap two registers (or register and memory) atomically
movzbl src, dstdst = zero-extend(src)Move byte to long, zero-extending to 32 bits
movsbl src, dstdst = 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)OperationNotes
addl src, dstdst = dst + srcSets CF, ZF, SF, OF
subl src, dstdst = dst - srcSets CF, ZF, SF, OF
incl dstdst = dst + 1Does not set CF
decl dstdst = dst - 1Does not set CF
negl dstdst = -dstTwo’s complement negation
imull src, dstdst = dst * srcSigned multiply (2-operand form)
mull srcedx:eax = eax * srcUnsigned multiply β€” result in EDX:EAX
imull srcedx:eax = eax * srcSigned multiply β€” result in EDX:EAX
divl srceax = edx:eax / src; edx = remainderUnsigned divide β€” dividend in EDX:EAX, zero-extend: xorl %edx, %edx before dividing
idivl srceax = edx:eax / src; edx = remainderSigned 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)OperationNotes
andl src, dstdst = dst & srcBitwise AND; clears OF, CF
orl src, dstdst = dst | srcBitwise OR
xorl src, dstdst = dst ^ srcBitwise XOR; xorl %eax, %eax is the canonical way to zero a register
notl dstdst = ~dstBitwise NOT (one’s complement)
testl src, dstdst & src (set flags only)AND without storing result β€” used to check bits or test for zero
cmpl src, dstdst - 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)OperationNotes
shll $n, dstdst = dst << nLogical (unsigned) left shift; fills with 0
shrl $n, dstdst = dst >> nLogical (unsigned) right shift; fills with 0
sarl $n, dstdst = dst >> nArithmetic right shift; sign-extends (preserves sign)
roll $n, dstRotate left n bitsBit shifted out wraps to the other end
rorl $n, dstRotate 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
  • push decrements ESP by the operand size, then writes the value to [ESP]
  • pop reads 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:

  1. Loading the syscall number into EAX
  2. Loading arguments into EBX, ECX, EDX, ESI, EDI (in order)
  3. 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)

EAXNameEBXECXEDX
1exitexit codeβ€”β€”
2forkβ€”β€”β€”
3readfdbuffer ptrcount
4writefdbuffer ptrcount
5openfilename ptrflagsmode
6closefdβ€”β€”
45brknew 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

FlagPurpose
-m32Compile/link for 32-bit target (on 64-bit host)
-nostdlibDo not link against C standard library (use for bare _start programs)
-f elf32NASM: output format = 32-bit ELF
-m elf_i386ld: link for 32-bit ELF
-gInclude debug info (for use with GDB)
-o outfileSpecify 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:

  1. Caller pushes arguments onto the stack right-to-left (last arg first)
  2. Caller executes call function (pushes return address, jumps)
  3. Callee sets up a stack frame (push %ebp; mov %esp, %ebp)
  4. Callee uses %eax (and %edx for 64-bit values) for return value
  5. Callee restores %ebx, %esi, %edi, %ebp, %esp (callee-saved)
  6. %eax, %ecx, %edx are caller-saved (callee may clobber them)
  7. 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

RegisterWho saves?Notes
EAXCallerReturn value
ECXCallerOften used as scratch
EDXCallerHigh half of return for 64-bit values
EBXCalleeMust be preserved across calls
ESICalleeMust be preserved
EDICalleeMust be preserved
EBPCalleeFrame pointer β€” must be preserved
ESPBothCaller 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)

InstructionOperationNotes
movslmem[edi] = mem[esi]; esi += 4; edi += 4Copy dword; direction depends on DF
movsbSame but byte
stoslmem[edi] = eax; edi += 4Store EAX into memory at EDI
lodsleax = mem[esi]; esi += 4Load dword from ESI into EAX
scasleax - mem[edi]; edi += 4Compare EAX to memory at EDI (set flags)
cmpsbCompare mem[esi] to mem[edi]; advance bothUsed for strcmp-style loops
repRepeat next string op ECX times
repe/repzRepeat while ZF=1 (equal)Used with cmps/scas
repne/repnzRepeat while ZF=0 (not equal)repne scasb finds a byte in memory
cldClear DF (forward direction)Call before string ops
stdSet DF (reverse direction)Rarely used