Macros & Directives
Multiline Macros
A macro is a named block of source text that the assembler pastes in wherever you write its name, substituting the arguments you supply. It is a compile-time mechanism: the expansion is invisible at run time.
%macro write_str 2 ; name, number of parameters
mov rax, 1 ; syscall 1 = write
mov rdi, 1 ; fd 1 = stdout
mov rsi, %1 ; %1 = first argument: pointer to the string
mov rdx, %2 ; %2 = second argument: length
syscall
%endmacro
section .rodata
msg db "Macro magic", 10
section .text
global _start
_start:
write_str msg, 13 ; expands to the five instructions above
mov rax, 60
xor rdi, rdi
syscall
Parameters are referenced as %1, %2, and so on. Because the substitution is textual, a macro can accept anything — a number, a label, or even an expression.
Local Labels Inside Macros: %%
If a macro contains a label and you expand it twice, you get a duplicate-symbol error. Prefixing the label with %% makes it unique on each expansion.
%macro delay 1
mov rcx, %1
%%wait: ; %% generates a fresh name every time
dec rcx
jnz %%wait
%endmacro
; Both expansions coexist, because each %%wait becomes a different label:
delay 1000
delay 500
Macro or Subroutine?
| Macro | Subroutine (call) | |
|---|---|---|
| When it happens | Assembly time (text expansion) | Run time (a jump and a return) |
| Code size | Grows with each use | Written once |
| Run-time cost | None — no call, no return | One call plus one ret, plus stack use |
| Good for | Short sequences used a few times; constants; code generation | Larger routines used many times |
Choose a macro when the sequence is short and speed matters; choose a subroutine when the code is long enough that duplicating it would bloat the executable or hurt instruction-cache performance.
Conditional Assembly
The assembler can include or exclude whole blocks of source based on symbols you define. This is how one source file produces debug and release builds, or targets several platforms.
%define DEBUG 1
%ifdef DEBUG
; This block is assembled only when DEBUG is defined.
%macro trace 1
push rax
push rcx
push rdx
push rsi
push rdi
; ... write a diagnostic line ...
pop rdi
pop rsi
pop rdx
pop rcx
pop rax
%endmacro
%else
%macro trace 1
; nothing: trace calls compile away entirely in release builds
%endmacro
%endif
%ifndef VERSION
%define VERSION 1
%endif
Because the untaken branch is never assembled, a disabled trace macro expands to nothing at all — no instructions, no data, no cost. That is a cleaner arrangement than a run-time if (debug) check in a language without a preprocessor.
Constants: equ and %define
equ — a Value for the Assembler
equ binds a name to a constant expression. The name is not a label and occupies no storage; it disappears after assembly, replaced everywhere by its value.
BUFFER_SIZE equ 4096
SYS_WRITE equ 1
SYS_EXIT equ 60
STDOUT equ 1
; equ values can be used in any expression, including addresses:
msg_len equ end_of_msg - msg
%define — a Text Substitution
%define is more primitive: it replaces text, not a value, and it can take parameters. That power is also its danger — an unparenthesized substitution can change the meaning of an expression.
%define TWO 2
%define SQUARE(x) ((x) * (x)) ; ALWAYS parenthesize macro parameters
%define MAX(a, b) ((a) > (b) ? (a) : (b)) ; not valid in NASM — see note
mov rax, SQUARE(rbx) ; expands to: mov rax, ((rbx) * (rbx))
mov rcx, TWO * 3 ; expands to: mov rcx, 2 * 3
%define is a simple text substitution and does not evaluate conditional expressions the way the C preprocessor does. For anything involving conditions or loops, use %if and %rep instead of trying to emulate C macros.
Rule of thumb: use equ for numbers you compute with, and %define for token substitutions such as register aliases or names of system calls.
Includes, Repetition, and Structure
%include — Splitting a Project
Once a program outgrows one file, put the shared macros and constants in a header and include it. This is exactly the role of a C .h file.
; ---- file: syscalls.inc ----
%define SYS_WRITE 1
%define SYS_EXIT 60
%define STDOUT 1
%macro exit 1
mov rax, SYS_EXIT
mov rdi, %1
syscall
%endmacro
; ---- file: program.asm ----
%include "syscalls.inc" ; pull in the definitions above
section .text
global _start
_start:
; ... produce output ...
exit 0 ; expand the macro; argument 0 is the status
Search paths can be extended with -I on the command line: nasm -I include/ -f elf64 program.asm.
%rep — Compile-Time Repetition
%rep expands a block several times at assembly time. Unlike times, which repeats data, %rep repeats anything, and the counter %$ is available inside the block. That makes generated tables possible.
%assign i 0
%rep 16
dd i * i ; emit i squared, sixteen times: 0, 1, 4, 9, ... 225
%assign i i+1
%endrep
Summary
equdefines an assembly-time constant;%defineis a text substitution that also accepts parameters.%macro name n…%endmacrodefines a macro withnparameters, referenced as%1,%2, …- Use
%%labelinside macros to create a fresh local label on every expansion — otherwise the second expansion collides with the first. %if/%ifdef/%ifndefselect code at assembly time; the untaken branch is never assembled.- Macros expand into real instructions, so they cost nothing at run time; subroutines (
call) cost one call, but can be used many times for far less code size.
Next: Input/Output & Syscalls — making a program talk to the outside world.