NASM Syntax Rules
NASM Basics: NASM uses Intel syntax (destination, source) and is case-insensitive for instructions/registers but case-sensitive for labels and symbols.
Intel Syntax
Syntax
Intel vs AT&T Syntax
; NASM (Intel Syntax): destination, source
mov rax, rbx ; Copy RBX to RAX
mov [rax], 42 ; Store 42 at address in RAX
; AT&T Syntax (GAS): source, destination
; movq %rbx, %rax ; Same operation
label: instruction operands ; comment
_start: mov rax, 60 ; syscall number for exit
Sections
.text Section (Code)
section .text
global _start
_start:
; Executable code goes here
mov rax, 60 ; exit syscall
xor rdi, rdi ; return 0
syscall
Save & Compile: exit.asm
Linux
nasm -f elf64 exit.asm -o exit.o
ld exit.o -o exit
./exit
macOS (change _start → _main, use syscall 0x2000001 for exit)
nasm -f macho64 exit.asm -o exit.o
ld -macos_version_min 10.13 -e _main -static exit.o -o exit
Windows (use ExitProcess API instead of syscall 60)
nasm -f win64 exit.asm -o exit.obj
link /subsystem:console /entry:_start exit.obj /out:exit.exe
.data Section (Initialized Data)
section .data
msg db "Hello, World!", 10 ; String with newline
len equ $ - msg ; Calculate length
num dd 12345 ; 32-bit integer
pi dq 3.14159 ; 64-bit float
.bss Section (Uninitialized Data)
section .bss
buffer resb 256 ; Reserve 256 bytes
count resd 1 ; Reserve 1 dword (4 bytes)
array resq 100 ; Reserve 100 qwords (800 bytes)
Data Directives
Define Data (db, dw, dd, dq)
Reference
Data Definition Directives
| Directive | Size | Example |
| db | 1 byte | db 0x41, 'A', 65 |
| dw | 2 bytes (word) | dw 0x1234 |
| dd | 4 bytes (dword) | dd 0x12345678 |
| dq | 8 bytes (qword) | dq 0x123456789ABCDEF0 |
Reserve Space (resb, resw, resd, resq)
Reserve directives allocate uninitialized space in the .bss section:
section .bss
buffer resb 256 ; Reserve 256 bytes
count resd 1 ; Reserve 1 dword (4 bytes)
array resq 100 ; Reserve 100 qwords (800 bytes)
flags resw 16 ; Reserve 16 words (32 bytes)
; .bss is NOT stored in the executable file!
; The OS zeros this memory when loading the program
Define vs Reserve:
db, dw, dd, dq — Define data with initial values (goes in .data or .rodata)
resb, resw, resd, resq — Reserve uninitialized space (goes in .bss)
; Calculating sizes
struc Point
.x: resd 1
.y: resd 1
endstruc
section .bss
points resb Point_size * 100 ; Array of 100 Point structures
; Point_size is automatically defined by NASM
Labels, Scope & Symbols
Global Labels
; Global labels are visible to the linker
global _start ; Export symbol (visible to linker)
global my_function ; Another exported symbol
extern printf ; Import symbol from another object
extern malloc
_start: ; Define the label
call my_function
; ...
my_function:
push rbp
; ...
ret
Local Labels (Dot Prefix)
; Local labels start with a dot and belong to the previous global label
process_array:
xor rcx, rcx ; i = 0
.loop: ; Local to process_array
cmp rcx, rax
jge .done ; Jump to process_array.done
; Process element...
inc rcx
jmp .loop
.done:
ret
process_string:
; Can reuse .loop and .done names!
.loop: ; Local to process_string (different from above)
; ...
.done:
ret
Anonymous Labels ($$ and $)
; $ = current address, $$ = section start
section .text
message db "Hello", 0
msg_len equ $ - message ; Length calculation
; Boot sector example
times 510 - ($ - $$) db 0 ; Pad to 510 bytes
dw 0xAA55 ; Boot signature
Symbol Visibility for Shared Libraries
; Control symbol visibility for dynamic linking
global public_api:function ; Visible, typed as function
global internal_helper:function hidden ; Hidden from dynamic linker
; In ELF output, you can also use:
global my_data:data
Exercise: Label Scope
; What labels are accessible where?
func_a:
jmp .loop ; Valid - same function
func_a.loop:
jmp func_b.loop ; Valid - fully qualified name
ret
func_b:
jmp .loop ; Valid - different .loop
.loop:
ret
Constants & Equates
EQU — Assemble-Time Constants
; EQU creates a constant that cannot be changed later
BUFFER_SIZE equ 4096
MAX_ITEMS equ 100
NULL equ 0
; EQU with expressions
HEADER_SIZE equ 16
DATA_OFFSET equ HEADER_SIZE + 4
; System call numbers
SYS_READ equ 0
SYS_WRITE equ 1
SYS_EXIT equ 60
; Use them like constants
mov rax, SYS_WRITE
mov rdi, BUFFER_SIZE
%define — Preprocessor Macros
; %define creates text substitution (like C #define)
%define STDIN 0
%define STDOUT 1
%define STDERR 2
; Can include expressions
%define KB(n) ((n) * 1024)
%define MB(n) ((n) * 1024 * 1024)
mov rax, KB(4) ; Expands to ((4) * 1024) = 4096
; String constants
%define NEWLINE 10
%define GREETING "Hello, World!", NEWLINE, 0
; How GREETING expands:
; When you write: db GREETING
; NASM substitutes: db "Hello, World!", 10, 0
;
; This produces 15 bytes in memory:
; 48 65 6C 6C 6F 2C 20 57 6F 72 6C 64 21 ("Hello, World!")
; 0A (10 = newline '\n')
; 00 (0 = null terminator)
;
; The comma-separated list works because db accepts multiple values:
; db "text", byte, byte → string bytes followed by individual bytes
;
; This is a common pattern for defining printable C-style strings:
; %define CR 13 ; Carriage return '\r'
; %define LF 10 ; Line feed '\n'
; %define CRLF CR, LF ; Windows-style line ending
; %define NULL 0 ; Null terminator
;
; Usage in .data section:
; section .data
; msg db GREETING ; "Hello, World!\n\0" (15 bytes)
; err db "Error", CRLF, 0 ; "Error\r\n\0" (8 bytes)
EQU vs %define
| Aspect |
EQU |
%define |
| Evaluation time |
Assembly time (once) |
Preprocessor (text substitution) |
| Can redefine? |
No |
Yes (with %undef first) |
| Parameters? |
No |
Yes (macro-like) |
| Use case |
Numeric constants |
Flexible substitutions |
Best Practice: Use equ for simple numeric constants (syscall numbers, sizes). Use %define for parametric macros or when you need text substitution.
Macros
Single-Line Macros (%define)
; Simple text substitution
%define EXIT_SUCCESS 0
%define EXIT_FAILURE 1
; Parametric macros
%define SYSCALL(n) mov rax, n
%define ALIGN16(x) (((x) + 15) & ~15)
SYSCALL(60) ; Expands to: mov rax, 60
mov rdi, ALIGN16(13) ; Expands to: mov rdi, (((13) + 15) & ~15) = 16
Multi-Line Macros (%macro / %endmacro)
; Basic multi-line macro: syscall wrapper
%macro exit 1 ; Name 'exit', takes 1 parameter
mov rax, 60 ; sys_exit
mov rdi, %1 ; First parameter
syscall
%endmacro
; Usage
exit 0 ; Expands to: mov rax, 60 / mov rdi, 0 / syscall
exit EXIT_FAILURE ; Works with constants too
Macro with Multiple Parameters
; Write string to file descriptor
%macro write 3 ; fd, buffer, length
mov rax, 1 ; sys_write
mov rdi, %1 ; fd
mov rsi, %2 ; buffer
mov rdx, %3 ; length
syscall
%endmacro
; Usage
write STDOUT, message, msg_len
write 2, error_msg, err_len ; Write to stderr
Local Labels in Macros
; Use %%label for labels local to each macro expansion
%macro repeat_char 2 ; char, count
mov rcx, %2
%%loop:
mov al, %1
; ... print character ...
loop %%loop ; Each expansion gets unique %%loop
%endmacro
; Multiple uses don't conflict:
repeat_char 'A', 5 ; %%loop becomes ..@1.loop
repeat_char 'B', 3 ; %%loop becomes ..@2.loop
Macro Overloading
; Same name, different parameter counts
%macro print 1 ; print buffer (null-terminated)
mov rsi, %1
; ... calculate length and print ...
%endmacro
%macro print 2 ; print buffer, length
mov rsi, %1
mov rdx, %2
; ... print ...
%endmacro
; NASM selects based on argument count
print message ; Calls 1-parameter version
print buffer, 100 ; Calls 2-parameter version
Exercise: Create a Stack Frame Macro
; Create macros for function prologue/epilogue
%macro prologue 1 ; local_bytes
push rbp
mov rbp, rsp
sub rsp, %1 ; Reserve stack space
%endmacro
%macro epilogue 0
leave ; mov rsp, rbp; pop rbp
ret
%endmacro
; Usage:
my_function:
prologue 32 ; Reserve 32 bytes
; ... function body ...
epilogue
Conditional Assembly
Conditionally include or exclude code at assembly time, useful for multi-platform or debug/release builds.
%if / %elif / %else / %endif
; Compile-time conditionals based on expressions
%define TARGET_BITS 64
%if TARGET_BITS == 64
mov rax, [rbx] ; 64-bit code
%elif TARGET_BITS == 32
mov eax, [ebx] ; 32-bit code
%else
%error "Unsupported target"
%endif
%ifdef / %ifndef (Check if Defined)
; Check if a symbol is defined
%define DEBUG 1
%ifdef DEBUG
; Include debug output
call debug_print
call dump_registers
%endif
%ifndef RELEASE
; Extra checks for non-release builds
call validate_input
%endif
; Can also use command line: nasm -DDEBUG program.asm
Platform-Specific Code
; Assemble different code for Linux vs Windows
%ifdef LINUX
%define SYS_WRITE 1
%define SYS_EXIT 60
%elifdef WINDOWS
; Windows uses different approach (WinAPI)
extern WriteFile
extern ExitProcess
%else
%error "Define LINUX or WINDOWS"
%endif
; Build: nasm -DLINUX -f elf64 program.asm
; or: nasm -DWINDOWS -f win64 program.asm
%ifid / %ifnum / %ifstr (Type Checking)
; Check argument types in macros
%macro safe_mov 2
%ifnum %2
mov %1, %2 ; Immediate value
%else
lea %1, [%2] ; Memory reference (assume address)
%endif
%endmacro
safe_mov rax, 42 ; Expands to: mov rax, 42
safe_mov rax, my_buffer ; Expands to: lea rax, [my_buffer]
Repetition: %rep / %endrep
; Generate repeated code or data
%assign i 0
%rep 10
db i ; Generates: db 0, db 1, ..., db 9
%assign i i+1
%endrep
; Unrolled loop
%rep 4
add rax, [rsi]
add rsi, 8
%endrep
%assign vs EQU: Use %assign for mutable preprocessor variables (can be reassigned). Use equ for fixed constants that never change.
Producing ELF Binaries
NASM Output Formats (-f)
| Format |
Option |
Use Case |
| ELF64 |
-f elf64 |
Linux 64-bit executable/object |
| ELF32 |
-f elf32 or -f elf |
Linux 32-bit |
| Win64 |
-f win64 |
Windows 64-bit COFF object |
| Win32 |
-f win32 |
Windows 32-bit COFF object |
| Binary |
-f bin |
Raw binary (boot sectors, firmware) |
| Mach-O 64 |
-f macho64 |
macOS 64-bit |
Complete Build Example (Linux)
# Simple standalone program
nasm -f elf64 hello.asm -o hello.o
ld hello.o -o hello
./hello
# With debug symbols
nasm -f elf64 -g -F dwarf hello.asm -o hello.o
ld hello.o -o hello
gdb ./hello
# Linking with libc
nasm -f elf64 program.asm -o program.o
gcc program.o -o program -no-pie # Use GCC to link with C runtime
# Or with ld (specify C library path)
ld -dynamic-linker /lib64/ld-linux-x86-64.so.2 \
-lc program.o -o program
Complete Build Example (Windows)
# Windows with MSVC linker
nasm -f win64 hello_win.asm -o hello_win.obj
link /SUBSYSTEM:CONSOLE /ENTRY:main hello_win.obj kernel32.lib
# Windows with GoLink (simpler)
nasm -f win64 hello_win.asm -o hello_win.obj
golink /console /entry main hello_win.obj kernel32.dll
Complete Build Example (macOS)
macOS Key Differences:
- Object format:
macho64 (Mach-O) instead of elf64
- Entry point: Labels must be prefixed with
_ (e.g., _main not _start)
- Syscall numbers: Add
0x2000000 offset (e.g., write = 0x2000004, exit = 0x2000001)
- Apple Silicon (M1/M2/M3/M4): Uses ARM64 natively — x86-64 NASM code runs via Rosetta 2
- No 32-bit support: macOS Catalina+ removed all 32-bit binary execution
# Simple standalone program (Intel Mac or Rosetta 2 on Apple Silicon)
nasm -f macho64 hello.asm -o hello.o
ld -macos_version_min 10.13 -e _main -static hello.o -o hello
./hello
# On Apple Silicon — verify Rosetta is running your binary
file hello # Should show: Mach-O 64-bit executable x86_64
arch -x86_64 ./hello # Force x86_64 execution explicitly
# With debug symbols (use lldb on macOS, not gdb)
nasm -f macho64 -g hello.asm -o hello.o
ld -macos_version_min 10.13 -e _main -static hello.o -o hello
lldb ./hello
# Linking with libc (use _main as entry, gcc handles C runtime)
nasm -f macho64 program.asm -o program.o
gcc program.o -o program # GCC/Clang links with libc automatically
# Note: macOS gcc is actually Clang — no -no-pie needed
# Disassemble with macOS-native tools
otool -t -v hello # Disassemble .text section
otool -l hello # Show load commands (Mach-O segments)
nm hello # List symbols
macOS Syscall Number Reference
| Syscall | Linux (RAX) | macOS (RAX) | Args |
| exit | 60 | 0x2000001 | RDI = status |
| fork | 57 | 0x2000002 | (none) |
| read | 0 | 0x2000003 | RDI=fd, RSI=buf, RDX=len |
| write | 1 | 0x2000004 | RDI=fd, RSI=buf, RDX=len |
| open | 2 | 0x2000005 | RDI=path, RSI=flags, RDX=mode |
| close | 3 | 0x2000006 | RDI=fd |
| mmap | 9 | 0x20000C5 | RDI=addr, RSI=len, RDX=prot, ... |
macOS syscall = 0x2000000 + BSD_syscall_number. Full list: /usr/include/sys/syscall.h or search XNU syscalls.
Apple Silicon Makefile (Rosetta 2)
# Makefile for macOS (Intel & Apple Silicon via Rosetta 2)
ASM = nasm
ASMFLAGS = -f macho64
LD = ld
LDFLAGS = -macos_version_min 10.13 -e _main -static
SRC = hello.asm
OBJ = $(SRC:.asm=.o)
BIN = hello
all: $(BIN)
$(BIN): $(OBJ)
$(LD) $(LDFLAGS) $(OBJ) -o $(BIN)
%.o: %.asm
$(ASM) $(ASMFLAGS) $< -o $@
debug: ASMFLAGS += -g
debug: clean all
lldb ./$(BIN)
disasm: $(BIN)
otool -t -v $(BIN)
ndisasm: $(OBJ)
ndisasm -b 64 $(OBJ)
clean:
rm -f $(OBJ) $(BIN)
.PHONY: all clean debug disasm ndisasm
Listing Files and Debugging
# Generate listing file (shows assembled bytes)
nasm -f elf64 -l listing.lst program.asm
# Listing file shows:
# Line Address Machine Code Source
# 10 00000000 B802000000 mov eax, 2
# 11 00000005 48C7C102000000 mov rcx, 2
# Generate map file (symbol addresses)
ld -Map=program.map program.o -o program
Quick Reference: Build Commands
# Linux standalone (no libc)
nasm -f elf64 prog.asm -o prog.o && ld prog.o -o prog
# Linux with libc
nasm -f elf64 prog.asm -o prog.o && gcc prog.o -o prog -no-pie
# Linux shared library
nasm -f elf64 -DPIC lib.asm -o lib.o
gcc -shared lib.o -o libmylib.so
# Boot sector (raw binary)
nasm -f bin boot.asm -o boot.bin
qemu-system-x86_64 -drive format=raw,file=boot.bin
# Debug build
nasm -f elf64 -g -F dwarf prog.asm -o prog.o
ld prog.o -o prog && gdb ./prog
Continue the Series
Part 4: Instruction Encoding & Binary Layout
Understand how instructions are encoded into machine code.
Read Article
Part 6: Complete Assembler Comparison
Compare MASM, NASM, and GAS syntax for Windows and cross-platform development.
Read Article
Part 7: Memory Addressing Modes
Master x86 addressing modes including RIP-relative addressing.
Read Article