Pré-processador do NASM
Aprendendo a usar o pré-processador do NASM
O NASM tem um pré-processador de código baseado no pré-processador da linguagem C. O que ele faz basicamente é interpretar instruções específicas do pré-processador para gerar o código fonte final, que será de fato compilado pelo NASM para o código de máquina. É por isso que tem o nome de pré-processador, já que ele processa o código fonte antes do NASM compilar o código.
As diretivas interpretadas pelo pré-processador são prefixadas pelo símbolo % e dão um poder absurdo para a programação diretamente em Assembly no nasm. Abaixo irei listar as mais básicas e o seu uso.

%define

%define nome "valor"
%define nome(arg1, arg2) arg1 + arg2
Assim como a diretiva #define do C, essa diretiva é usada para definir macros de uma única linha. Seja lá aonde o nome do macro for citado no código fonte ele expandirá para exatamente o conteúdo que você definiu para ele como se você estivesse fazendo uma cópia.
E assim como no C é possível passar argumentos para um macro usando de uma sintaxe muito parecida com uma função. Exemplo de uso:
%define teste mov eax, 31
teste
teste
As linhas 2 e 3 irão expandir para a instrução mov eax, 31 como se tivesse feito uma cópia do valor definido para o macro. Podemos também é claro escrever um macro como parte de uma instrução, por exemplo:
%define addr [ebx*2 + 4]
mov eax, addr
Isso irá expandir a instrução na linha 2 para mov eax, [ebx*2 + 4]
A diferença entre definir um macro dessa forma e definir uma constante é que a constante recebe uma expressão matemática e expande para o valor do resultado. Enquanto o macro expande para qualquer coisa que você definir para ele.
O outro uso do macro, que é mais poderoso, é passando argumentos para ele assim como se é possível fazer em C. Para isso basta definir o nome do macro seguido dos parênteses e, dentro dos parênteses, os nomes dos argumentos que queremos receber separados por vírgula.
No valor definido para o macro os nomes desses argumentos irão expandir para qualquer conteúdo que você passe como argumento na hora que chamar um macro. Veja por exemplo o mesmo macro acima porém desta vez dando a possibilidade de escolher o registrador:
%define addr(reg) [reg*2 + 4]
mov eax, addr(ebx)
mov eax, addr(esi)
A linha 2 irá expandir para: mov eax, [ebx*2 + 4]. A linha 3 irá expandir para: mov eax, [esi*2 + 4].

%undef

%undef nome_do_macro
Simplesmente apaga um macro anteriormente declarado por %define.

%macro

%macro nome NÚMERO_DE_ARGUMENTOS
; Código aqui
%endmacro
Além dos macros de uma única linha existem também os macros de múltiplas linhas que podem ser definidos no NASM.
Após a especificação do nome que queremos dar ao macro podemos especificar o número de argumentos passados para ele. Caso não queira receber argumentos no macro basta definir esse valor para zero. Exemplo:
%macro sum5 0
mov ebx, 5
add eax, ebx
%endmacro
sum5
sum5
O %endmacro sinaliza o final do macro e todas as instruções inseridas entre as duas diretivas serão expandidas quando o macro for citado.
Para usar argumentos com um macro de múltiplas linhas difere de um macro definido com %define, ao invés do uso de parênteses o macro recebe argumentos seguindo a mesma sintaxe de uma instrução e separando cada um dos argumentos por vírgula. Para usar o argumento dentro do macro basta usar %n, onde n seria o número do argumento que começa contando em 1.
%macro sum 2
mov ebx, %2
add %1, ebx
%endmacro
sum esi, edi
sum ebp, eax
Também é possível fazer com que o último argumento do macro expanda para todo o conteúdo passado, mesmo que contenha vírgula. Para isso basta adicionar um + ao número de argumentos. Por exemplo:
%macro example 2+
inc %1
mov %2
%endmacro
example eax, ebx, ecx
example ebx, esi, edi, edx
A linha 6 expandiria para as instruções:
inc eax
mov ebx, ecx
Enquanto a linha 7 iria acusar erro já que na linha 3 dentro do macro a instrução expandiu para mov esi, edi, edx, o que está errado.
É possível declarar mais de um macro com o mesmo nome desde que cada um deles tenham um número diferente de argumentos recebidos. O exemplo abaixo é totalmente válido:
%macro example 1
mov rax, %1
%endmacro
%macro example 2
mov rax, %1
add rax, %2
%endmacro
example 1
example 2, 3

Rótulos dentro de um macro

Usar um rótulo dentro de um macro é problemático porque se o macro for usado mais de uma vez estaremos redefinindo o mesmo rótulo já que seu nome nunca muda.
Para não ter esse problema existem os rótulos locais de um macro que será expandido para um nome diferente, definido pelo NASM, a cada uso do macro. A sintaxe é simples, basta prefixar o nome do rótulo com %%. Exemplo:
; Repare como o código abaixo ficaria mais simples usando SETcc
; ou até mesmo CMOVcc.
%macro compare 2
cmp %1, %2
je %%is_equal
mov eax, 0
jmp %%end
%%is_equal:
mov eax, 1
%%end:
%endmacro
compare eax, edx

%unmacro

%unmacro nome NÚMERO_DE_ARGUMENTOS
Apaga um macro anteriormente definido com %macro. O número de argumentos especificado deve ser o mesmo utilizado na hora de declarar o macro.

Compilação condicional

Assim como o pré-processador do C, o NASM também suporta diretivas de código condicional. A sintaxe básica é:
%if<condição>
; Código 1
%elif<condição>
; Código 2
%else
; Código 3
%endif
Onde o código dentro da diretiva %if só é compilado se a condição for atendida. Caso não seja é possível usar a diretiva %elif para fazer o teste de uma nova condição. Enquanto o código na diretiva %else é expandido caso nenhuma das condições anteriormente testadas sejam atendidas. Por fim é usado a diretiva %endif para indicar o fim da diretiva %if.
É possível passar para %if e %elif uma expressão matemática afim de testar o resultado de um cálculo com uma constante ou algo semelhante. Se o valor for diferente de zero a expressão será considerada verdadeira e o bloco de código será expandido no código de saída.
Também é possível inverter a lógica das instruções adicionando um 'n', fazendo com que o bloco seja expandido caso a condição não seja atendida. Exemplo:
CONST equ 5
%ifn CONST * 2 > 7
call is_smallest
%else
call is_bigger
%endif
Além do %if básico também podemos usar variantes que verificam por uma condição específica ao invés de receber uma expressão e testar seu resultado.

%ifdef e %elifdef

%ifdef nome_do_macro
%elifdef nome_do_macro
Essas diretivas verificam se um macro de linha única foi declarado por um %define anteriormente. É possível também usar essas diretivas em forma de negação adicionando o 'n' após o 'if'. Ficando: %ifndef e %elifndef, respectivamente.

%ifmacro e %elifmacro

%ifmacro nome_do_macro
%elifmacro nome_do_macro
Mesmo que %ifdef porém para macros de múltiplas linhas declarados por %macro. E da mesma que as diretivas anteriores também têm suas versões em negação: %ifnmacro e %elifnmacro.

%error e %warning

%error "Mensagem de erro"
%warning "Mensagem de alerta"
Usando diretivas condicionais as vezes queremos acusar um erro ou emitir um alerta no console para indicar alguma mensagem no processo de compilação de algum projeto.
%error imprime a mensagem como um erro e finaliza a compilação, enquanto %warning emite a mensagem como um alerta e a compilação continua normalmente. Podemos por exemplo acusar um erro caso um determinado macro necessário para o código não esteja definido:
%ifndef macro_importante
%ifdef macro_substituto
%warning "Macro importante não foi definido"
%else
%error "Macro importante e o seu substituto não foram definidos"
%endif
%endif

%include

%include "nome do arquivo.ext"
Essa diretiva tem o uso parecido com a diretiva #include da linguagem C e ela faz exatamente a mesma coisa: Copia o conteúdo do arquivo passado como argumento para o exato local aonde ela foi utilizada no arquivo fonte. Seria como você manualmente abrir o arquivo, copiar todo o conteúdo dele e depois colar no código fonte.
Assim como fazemos em um header file incluído por #include na linguagem C é importante usar as diretivas condicionais para evitar a inclusão duplicada de um mesmo arquivo. Por exemplo:
arquivo.asm
%ifndef _ARQUIVO_ASM
%define _ARQUIVO_ASM
; Código aqui
%endif
Dessa forma quando incluirmos o arquivo pela primeira vez o macro _ARQUIVO_ASM será declarado. Se ele for incluído mais uma vez o macro já estará declarado e o %ifndef da linha 1 terá uma condição falsa e portanto não expandirá o conteúdo dentro de sua diretiva.
É importante fazer isso para evitar a redeclaração de macros, constantes ou rótulos. Bem como também evita que o mesmo código fique duplicado.