DevTools

Cheatsheet Assembly

Linguagem de baixo nível com acesso direto ao hardware (x86/NASM)

Voltar às linguagens
Assembly
80 cards encontrados
Categorias:
Versões:

Básico


10 cards
Registos x86-64
; Registos de 64 bits (GPR):
; RAX RBX RCX RDX RSI RDI RSP RBP
; R8  R9  R10 R11 R12 R13 R14 R15

; Sub-registos:
; EAX (32b) | AX (16b) | AH/AL (8b)

; Propósito:
; RAX - retorno de funções
; RCX - contador (loops)
; RSP - stack pointer
; RBP - base pointer (frame)
; RSI/RDI - source/destination

Registos são a memória mais rápida da CPU. RAX é o acumulador (retorno). RSP aponta para o topo da stack. R8-R15 são exclusivos de 64 bits. Aceder a EAX zera os 32 bits superiores.

Diretivas NASM
; Definir dados:
db  0x41            ; 1 byte ('A')
dw  1000            ; 2 bytes (word)
dd  3.14            ; 4 bytes (float)
dq  123456789       ; 8 bytes (qword)

; Reservar espaço:
resb 16             ; 16 bytes (bss)
resw 8              ; 8 words
resq 1              ; 1 qword

; Constantes:
MAX: equ 100
TAM: equ $ - inicio

; Alinhamento:
align 16

db/dw/dd/dq definem dados de 1/2/4/8 bytes. resb/resw/resq reservam espaço em BSS. equ cria constantes. align alinha para performance.

Flags (EFLAGS/RFLAGS)
; Flags principais:
; ZF - Zero Flag (resultado = 0)
; CF - Carry Flag (overflow unsigned)
; SF - Sign Flag (bit de sinal)
; OF - Overflow Flag (overflow signed)
; PF - Parity Flag (paridade baixa)

; Instruções que afetam flags:
add rax, rbx    ; ZF, CF, SF, OF
sub rax, rbx    ; ZF, CF, SF, OF
cmp rax, rbx    ; como sub, descarta resultado
test rax, rax   ; como and, descarta (ZF/SF)

; Ver flags:
pushfq          ; push flags para stack
pop rax         ; flags em rax

Flags são bits que indicam resultados. ZF=1 se zero. CF=1 se carry/borrow. SF=1 se negativo. OF=1 se overflow signed. CMP e TEST só afetam flags.

Estrutura NASM
; ficheiro.asm — estrutura básica NASM
section .data       ; dados inicializados
    msg: db "Olá", 10
    len: equ $ - msg

section .bss        ; dados não inicializados
    buffer: resb 64

section .text       ; código
    global _start

_start:
    ; instruções aqui
    mov rax, 60     ; sys_exit
    xor rdi, rdi    ; código 0
    syscall

.data tem dados com valor inicial. .bss reserva espaço (zero). .text contém o código. global _start exporta o ponto de entrada. section organiza o binário.

Modos de endereçamento
mov rax, [rbx]          ; indireto (conteúdo de rbx)
mov rax, [rbx + 8]      ; deslocamento
mov rax, [rbx + rcx*4]  ; base + índice*escala
mov rax, [rbx + rcx*4 + 16] ; completo

; RIP-relative (64-bit):
mov rax, [rel variavel]

; Imediato:
mov rax, 42             ; valor direto

; Registo:
mov rax, rbx            ; registo → registo

Endereçamento: [reg] = indireto, [reg+N] = deslocamento, [base+idx*escala] = arrays. RIP-relative é padrão em 64-bit para dados globais. Escalas: 1, 2, 4, 8.

Comentários e convenções
; Comentário de linha (ponto e vírgula)

/* Comentário multi-linha
   (suportado pelo NASM) */

; Convenções de nomes:
; _start      → ponto de entrada (Linux)
; main        → ponto de entrada (C)
; snake_case  → funções e labels
; CONSTANTES  → equ em maiúsculas

; Labels:
loop_inicio:    ; label local
.fim:           ; label local (ponto)
@@:             ; label anónimo
jmp @B          ; salta para @@ anterior

; inicia comentário. Labels terminam com :. .nome são locais (scope entre labels). @@ é anónimo (@F = forward, @B = backward). Convenção: snake_case para símbolos.

Compilar e executar (NASM)
; Montar (assembly → objeto):
nasm -f elf64 programa.asm -o programa.o

; Linkar (objeto → executável):
ld programa.o -o programa

; Executar:
./programa

; Com GCC (se usar funções C):
nasm -f elf64 -g -F dwarf main.asm
gcc main.o -o app -no-pie

; Ver código máquina:
objdump -d programa

nasm -f elf64 gera objeto 64-bit Linux. ld faz linking sem libc. gcc se precisar de printf/malloc. -g -F dwarf adiciona debug info. objdump -d desassembla.

MOV e transferência
mov rax, 42         ; imediato → registo
mov rax, rbx        ; registo → registo
mov rax, [mem]      ; memória → registo
mov [mem], rax      ; registo → memória

; Variantes com extensão:
movzx eax, byte [x]  ; zero-extend (8→32)
movsx eax, byte [x]  ; sign-extend (8→32)
movsxd rax, eax      ; sign-extend (32→64)

; LEA (endereço, não conteúdo):
lea rax, [rbx + rcx*4 + 10]

MOV copia dados (não move!). movzx estende com zeros. movsx estende com sinal. LEA calcula endereço sem aceder à memória — útil para aritmética rápida.

Hello World
section .data
    msg: db "Olá, Mundo!", 10
    len: equ $ - msg

section .text
    global _start

_start:
    mov rax, 1          ; sys_write
    mov rdi, 1          ; stdout (fd=1)
    mov rsi, msg        ; ponteiro para string
    mov rdx, len        ; comprimento
    syscall

    mov rax, 60         ; sys_exit
    xor rdi, rdi        ; exit code 0
    syscall

sys_write (rax=1) imprime. rdi=fd (1=stdout), rsi=buffer, rdx=tamanho. syscall invoca o kernel. sys_exit (rax=60) termina. $ - msg calcula comprimento.

Tamanhos e sufixos
; Tamanhos explícitos:
mov byte [x], 0x41     ; 1 byte
mov word [x], 0x1234   ; 2 bytes
mov dword [x], 0xABCD  ; 4 bytes
mov qword [x], 0xFF    ; 8 bytes

; Em registo (implícito):
mov al, 1     ; 8-bit
mov ax, 1     ; 16-bit
mov eax, 1    ; 32-bit (zera upper 32!)
mov rax, 1    ; 64-bit

; NASM usa byte/word/dword/qword ptr
; MASM usa PTR: mov [x], DWORD PTR 0

Sufixos: byte=1, word=2, dword=4, qword=8 bytes. Escrever em EAX zera os 32 bits superiores de RAX automaticamente. Em NASM, o tamanho vem do registo ou é explícito.

Dados e Memória


10 cards
Definir variáveis
section .data
    ; Strings:
    nome:   db "Ana", 0        ; null-terminated
    msg:    db "Olá", 10       ; com newline

    ; Numéricos:
    idade:  db 30              ; byte
    ano:    dw 2024            ; word (2 bytes)
    pop:    dd 7000000         ; dword (4 bytes)
    big:    dq 9999999999      ; qword (8 bytes)

    ; Float:
    pi:     dd 3.14159         ; float 32-bit
    e:      dq 2.718281828     ; double 64-bit

    ; Arrays:
    vetor:  dd 1, 2, 3, 4, 5

db = byte/string, dw = word, dd = dword/float, dq = qword/double. Strings são arrays de bytes. 0 no fim = null-terminated. Arrays são dados contíguos.

Structs em Assembly
; Simular struct com offsets:
; struct Pessoa { char nome[32]; int idade; }
STRUC Pessoa
    .nome:   resb 32
    .idade:  resd 1
    .size:
ENDSTRUC

section .bss
    p: resb Pessoa.size

section .text
    ; Acesso:
    mov dword [p + Pessoa.idade], 30
    lea rdi, [p + Pessoa.nome]

    ; Offset manual:
    ; nome  = offset 0
    ; idade = offset 32

STRUC/ENDSTRUC definem structs no NASM. Campos têm offsets automáticos (Pessoa.idade). Pessoa.size dá o tamanho total. Acesso via [base + offset]. Equivale a structs em C.

Aritmética de ponteiros
; Ponteiro em rdi, percorrer array:
mov rdi, arr        ; rdi = &arr[0]
mov rcx, 5          ; 5 elementos

.loop:
    mov eax, [rdi]  ; ler elemento atual
    add eax, 1      ; processar
    mov [rdi], eax  ; escrever de volta
    add rdi, 4      ; próximo (dword = +4)
    dec rcx
    jnz .loop

; Alternativa com índice:
xor rcx, rcx
.next:
    mov eax, [arr + rcx*4]
    inc rcx
    cmp rcx, 5
    jl .next

Ponteiros avançam pelo tamanho do tipo: +4 para dword, +8 para qword. Alternativa: índice com [base + idx*escala]. Ambos equivalentes. Ponteiro é mais natural em Assembly.

BSS (reservar espaço)
section .bss
    ; Reservar sem inicializar (zero):
    buffer:   resb 256        ; 256 bytes
    contador: resd 1          ; 1 dword (4 bytes)
    matriz:   resq 100        ; 100 qwords (800 bytes)
    array:    resw 10         ; 10 words

; Vantagens do BSS:
; • Não ocupa espaço no binário
; • Inicializado a zero pelo OS
; • Mais rápido que .data para buffers

; Acesso:
mov byte [buffer], 'A'
mov dword [contador], 0

BSS reserva espaço sem valor inicial (zero pelo OS). resb/resw/resd/resq reservam 1/2/4/8 bytes por elemento. Não aumenta o binário. Ideal para buffers e arrays grandes.

Pilha (push/pop)
; PUSH: decrementa RSP, escreve
push rax         ; RSP -= 8; [RSP] = RAX
push rbx
push rcx

; POP: lê, incrementa RSP
pop rcx          ; RCX = [RSP]; RSP += 8
pop rbx
pop rax

; Ver topo sem remover:
mov rax, [rsp]   ; peek

; Reservar espaço local:
sub rsp, 32      ; 32 bytes para locais
; ... usar [rsp], [rsp+8], etc.
add rsp, 32      ; libertar (epilogue)

PUSH coloca na stack (RSP -= 8). POP remove (RSP += 8). LIFO: último a entrar, primeiro a sair. Stack cresce para baixo (endereços menores). sub rsp, N reserva espaço para variáveis locais.

Dados alinhados
section .data
    align 16            ; alinhar a 16 bytes
    vec4: dd 1.0, 2.0, 3.0, 4.0

    align 32            ; para AVX
    vec8: dd 1.0, 2.0, 3.0, 4.0
          dd 5.0, 6.0, 7.0, 8.0

section .bss
    alignb 16
    buffer: resb 128

; Alinhar stack (obrigatório em chamadas):
and rsp, -16        ; alinhar RSP a 16 bytes
; ... chamar funções ...

; Porquê: SIMD requer alinhamento
; movaps falha se não alinhado!

align 16 alinha dados a 16 bytes (SSE). align 32 para AVX. and rsp, -16 alinha a stack antes de chamadas. movaps falha com segfault se desalinhado. Performance: cache lines de 64 bytes.

Arrays e indexação
section .data
    arr: dd 10, 20, 30, 40, 50

section .text
    ; Acesso por índice:
    mov rax, [arr]            ; arr[0] = 10
    mov rax, [arr + 4]        ; arr[1] = 20
    mov rax, [arr + 8]        ; arr[2] = 30

    ; Com índice variável (i em rcx):
    mov rcx, 2
    mov eax, [arr + rcx*4]   ; arr[2] = 30

    ; Escrita:
    mov dword [arr + rcx*4], 99  ; arr[2] = 99

    ; Tamanho:
    len: equ ($ - arr) / 4   ; 5 elementos

Arrays são memória contígua. [arr + idx*4] para dword (4 bytes por elemento). Escala: *1 (byte), *2 (word), *4 (dword), *8 (qword). $ - arr dá tamanho em bytes.

Acesso a memória
; Ler da memória:
mov rax, [variavel]        ; 8 bytes
mov eax, [variavel]        ; 4 bytes
mov al, byte [variavel]    ; 1 byte

; Escrever na memória:
mov [variavel], rax
mov byte [buffer + 5], 'X'

; Endereçamento RIP-relative (64-bit):
mov rax, [rel contador]    ; posição-independente

; Com deslocamento negativo:
mov rax, [rbp - 8]        ; variável local
mov rax, [rbp + 16]       ; argumento

Colchetes [] = acesso à memória. Sem colchetes = valor imediato ou registo. [rel var] é RIP-relative (código posicional-independente). [rbp-N] para locais, [rbp+N] para argumentos.

LEA (carregar endereço)
; LEA calcula endereço SEM aceder à memória:
lea rax, [rbx + rcx*4 + 8]

; Uso para aritmética (rápido!):
lea rax, [rax + rax*4]    ; rax = rax * 5
lea rax, [rax*8 + rax]    ; rax = rax * 9
lea rax, [rax + rax*2]    ; rax = rax * 3

; vs MOV (acede memória):
mov rax, [rbx + rcx*4]    ; carrega VALOR
lea rax, [rbx + rcx*4]    ; carrega ENDEREÇO

; Ponteiro para variável:
lea rdi, [rel msg]        ; endereço de msg

LEA calcula endereço sem ler memória. Mais rápido que MOV para aritmética (1 ciclo). Multiplica por constantes: [rax+rax*4] = ×5. Não afeta flags. Muito usado para &variavel.

Constantes e EQU
; Constantes (não ocupam memória):
MAX:     equ 100
NEWLINE: equ 10
NULL:    equ 0
STDIN:   equ 0
STDOUT:  equ 1
STDERR:  equ 2

; Cálculo em compilação:
TAM:     equ 1024 * 4
OFFSET:  equ 8 + 16

; Uso:
mov rcx, MAX
cmp rax, TAM

; %define (macro, redefinível):
%define VERSAO 2

equ define constantes em tempo de montagem (sem memória). Substituídas textualmente. %define é redefinível (macro). Ideal para syscalls, tamanhos e offsets. Não confundir com variáveis em .data.

Aritmética e Lógica


10 cards
ADD e SUB
add rax, 10         ; rax += 10
add rax, rbx        ; rax += rbx
sub rax, 5          ; rax -= 5
sub rax, rcx        ; rax -= rcx

; Flags afetadas: ZF, CF, SF, OF
add rax, rbx
; ZF=1 se resultado=0
; CF=1 se overflow unsigned
; SF=1 se bit 63=1 (negativo)
; OF=1 se overflow signed

; ADD com memória:
add dword [contador], 1
sub qword [saldo], rax

ADD soma, SUB subtrai. Ambos afetam flags: ZF (zero), CF (carry), SF (sinal), OF (overflow). ADD reg, mem e ADD mem, reg são válidos.

AND, OR, XOR, NOT
and rax, 0xFF       ; máscara (manter 8 bits)
and rax, -16        ; alinhar a 16 (limpar 4 bits)

or rax, 0x01        ; definir bit 0
or rax, rbx         ; combinar bits

xor rax, rax        ; zerar (mais rápido que mov!)
xor eax, eax        ; zera RAX inteiro (32→64)

not rax             ; inverter todos os bits

; Testar bit:
test rax, 0x04      ; bit 2 está definido?
jnz .bit_set        ; ZF=0 se bit=1

AND = máscara, OR = definir bits, XOR = toggle/zerar, NOT = inverter. xor rax,rax é a forma mais rápida de zerar (1 byte, 1 ciclo). TEST = AND sem guardar resultado.

ADC e SBB (carry)
; ADC: soma + carry flag
; Para soma de 128 bits:
mov rax, [num1]
add rax, [num2]       ; soma low
mov rdx, [num1+8]
adc rdx, [num2+8]     ; soma high + carry

; SBB: subtrai + borrow
mov rax, [num1]
sub rax, [num2]       ; sub low
mov rdx, [num1+8]
sbb rdx, [num2+8]     ; sub high - borrow

; Uso: aritmética de precisão arbitrária
; (números maiores que 64 bits)

ADC = ADD + Carry Flag. SBB = SUB + Borrow. Usados para aritmética multi-precisão (128+ bits). Padrão: ADD no low, ADC no high. Essencial para criptografia e big numbers.

MUL e IMUL
; MUL (unsigned): resultado em RDX:RAX
mov rax, 100
mov rbx, 200
mul rbx         ; RDX:RAX = RAX * RBX

; IMUL (signed, mais versátil):
imul rax, rbx           ; rax *= rbx
imul rax, rbx, 10       ; rax = rbx * 10
imul rax, rcx           ; rax = rax * rcx

; 32-bit: resultado em EDX:EAX
mov eax, 50000
mov ebx, 60000
mul ebx         ; EDX:EAX = EAX * EBX

; Overflow: CF=OF=1 se resultado não cabe

MUL = unsigned, resultado em RDX:RAX (128 bits). IMUL = signed, mais flexível (2 ou 3 operandos). Para multiplicar por potências de 2, usa SHL (mais rápido).

Shifts (SHL, SHR, SAR)
shl rax, 1      ; rax *= 2 (shift left)
shl rax, 4      ; rax *= 16
shr rax, 1      ; rax /= 2 (unsigned)
shr rax, 3      ; rax /= 8

sar rax, 1      ; shift aritmético (preserva sinal)
; sar -8, 1 = -4 (correto para negativos)
; shr -8, 1 = valor enorme (errado!)

; Rotação:
rol rax, 1      ; rotate left (bit sai → entra)
ror rax, 4      ; rotate right

; SHLD/SHRD: shift duplo
shld rax, rbx, 4  ; shift left com bits de rbx

SHL = ×2^n, SHR = ÷2^n (unsigned). SAR preserva sinal (para negativos). ROL/ROR rotacionam bits. Shifts são muito mais rápidos que MUL/DIV para potências de 2.

BSR, BSF, POPCNT
; Bit Scan:
bsf rcx, rax      ; índice do primeiro bit 1 (low)
bsr rcx, rax      ; índice do último bit 1 (high)
; ZF=1 se input=0

; Popcount (contar bits 1):
popcnt rcx, rax   ; rcx = nº de bits a 1
; Requer: -mpopcnt ou CPUID check

; Contar zeros:
lzcnt rcx, rax    ; leading zeros
tzcnt rcx, rax    ; trailing zeros (= bsf)

; Extrair bit:
bt rax, 5         ; CF = bit 5 de rax
bts rax, 5        ; define bit 5
btr rax, 5        ; limpa bit 5
btc rax, 5        ; toggle bit 5

BSF/BSR encontram primeiro/último bit 1. POPCNT conta bits ativos. LZCNT/TZCNT contam zeros. BT/BTS/BTR manipulam bits individuais. Úteis para bitmaps e flags.

DIV e IDIV
; DIV (unsigned):
; Divide RDX:RAX por operando
; Quociente → RAX, Resto → RDX
mov rax, 100
xor rdx, rdx       ; ZERAR RDX antes!
mov rbx, 7
div rbx            ; rax=14, rdx=2

; IDIV (signed):
mov rax, -100
cqo                ; sign-extend RAX → RDX:RAX
mov rbx, 7
idiv rbx           ; rax=-14, rdx=-2

; 32-bit: cdq antes de div
; Cuidado: div por 0 = #DE exception!

DIV divide RDX:RAX por operando. Quociente em RAX, resto em RDX. Sempre zerar RDX (xor rdx,rdx) ou sign-extend (cqo). Divisão por 0 causa exceção.

CMP e TEST
; CMP: subtrai sem guardar (só flags)
cmp rax, 10
; ZF=1 se rax==10
; CF=1 se rax<10 (unsigned)
; SF≠OF se rax<10 (signed)

cmp rax, rbx
je  .igual      ; ZF=1
jl  .menor      ; SF≠OF (signed)
ja  .maior      ; CF=0 e ZF=0 (unsigned)

; TEST: AND sem guardar (só flags)
test rax, rax   ; rax é zero?
jz  .nulo       ; ZF=1 se rax=0

test rax, 1     ; é ímpar?
jnz .impar      ; bit 0 = 1

CMP compara (faz SUB sem guardar). TEST verifica bits (faz AND sem guardar). Ambos só afetam flags. Seguidos de jumps condicionais. test rax,rax + jz = verificar null/zero.

INC, DEC e NEG
inc rax         ; rax++ (não afeta CF!)
dec rcx         ; rcx-- (não afeta CF!)

neg rax         ; rax = -rax (two's complement)
; neg: CF=0 se input=0, CF=1 caso contrário

; Alternativas que afetam CF:
add rax, 1      ; como inc, mas afeta CF
sub rax, 1      ; como dec, mas afeta CF

; Uso típico:
mov rcx, 10
.loop:
    ; processar
    dec rcx
    jnz .loop   ; ZF=1 quando rcx=0

INC/DEC incrementam/decrementam sem afetar CF (útil após ADC/SBB). NEG nega (complemento para 2). Em loops, DEC + JNZ é o padrão. ADD/SUB 1 se precisar de CF.

Conversões de tipo
; Extensão com sinal:
movsx eax, byte [x]    ; int8 → int32
movsx rax, dword [y]   ; int32 → int64
movsxd rax, eax        ; int32 → int64
cwde                   ; AX → EAX (sign)
cdqe                   ; EAX → RAX (sign)

; Extensão com zero:
movzx eax, byte [x]    ; uint8 → uint32
movzx rax, word [y]    ; uint16 → uint64

; Preparar divisão:
xor rdx, rdx           ; zera RDX (unsigned)
cqo                    ; RDX:RAX = sign-extend (signed)
cdq                    ; EDX:EAX = sign-extend (32-bit)

MOVSX estende com sinal (negativos corretos). MOVZX estende com zeros (unsigned). CQO/CDQ preparam RDX para divisão. Sempre usar extensão correta para evitar bugs.

Controlo de Fluxo


10 cards
JMP (incondicional)
; Salto incondicional:
jmp .fim            ; label local
jmp inicio          ; label global
jmp rax             ; indireto (endereço em rax)
jmp [rax]           ; indireto via memória

; Labels:
inicio:
    ; código
.fim:
    ; código

; Curto vs longo:
jmp short .perto    ; -128 a +127 bytes (2 bytes)
jmp near .longe     ; ±2GB (5 bytes)
; NASM escolhe automaticamente

JMP salta incondicionalmente (como goto). jmp label = direto. jmp reg = indireto (tabelas de salto, vtables). short = 2 bytes (limitado). Labels locais com . prefixo.

Instrução LOOP
; LOOP: decrementa RCX, salta se != 0
mov rcx, 5
.loop:
    ; corpo (executa 5 vezes)
    mov rax, rcx
    loop .loop      ; rcx--; if rcx!=0 jmp

; Variantes:
loope .loop     ; loop se RCX!=0 E ZF=1
loopz .loop     ; sinónimo de LOOPE
loopne .loop    ; loop se RCX!=0 E ZF=0
loopnz .loop    ; sinónimo de LOOPNE

; Nota: LOOP é lento em CPUs modernas!
; Preferir: dec rcx / jnz .loop

LOOP = DEC RCX + JNZ implícito. LOOPE/LOOPNE verificam ZF também. Em CPUs modernas, LOOP é mais lento que DEC+JNZ (microcode). Usar só por legibilidade em código educacional.

Ternário (CMOV)
; rax = (rbx > 10) ? 1 : 0

; Com jumps:
    cmp rbx, 10
    jle .zero
    mov rax, 1
    jmp .fim
.zero:
    xor rax, rax
.fim:

; Com CMOV (sem branch, mais rápido):
    xor rax, rax        ; rax = 0 (default)
    mov rcx, 1
    cmp rbx, 10
    cmovg rax, rcx      ; se >, rax = 1

; CMOVcc: move se condição (sem salto!)
; cmovl, cmovg, cmove, cmovne, cmova...

CMOVcc = move condicional sem branch. Evita misprediction do branch predictor. cmovg = move if greater. Ideal para ternários simples. Sem branch = pipeline não para. Prefere para expressões simples.

Jumps condicionais (igualdade)
cmp rax, 10

je  .igual      ; jump if equal (ZF=1)
jne .diferente  ; jump if not equal (ZF=0)

; Sinónimos:
jz  .zero       ; jump if zero (ZF=1) = JE
jnz .nao_zero   ; jump if not zero (ZF=0) = JNE

; Após TEST:
test rax, rax
jz  .nulo       ; rax == 0?
jnz .valido     ; rax != 0?

; Verificar bit:
test rax, 0x04
jnz .bit2_set   ; bit 2 definido?

JE/JNE = igual/diferente (após CMP). JZ/JNZ = zero/não-zero (após TEST). São sinónimos (mesmo opcode, ZF). Usar o nome que faz mais sentido no contexto.

IF/ELSE em Assembly
; if (rax > 10) { rbx = 1 } else { rbx = 0 }

    cmp rax, 10
    jle .else       ; se rax <= 10, vai para else

.then:
    mov rbx, 1
    jmp .fim        ; salta o else!

.else:
    mov rbx, 0

.fim:
    ; continua aqui

; Padrão: condição NEGADA salta para else
; jmp .fim no then evita executar else

IF/ELSE: nega a condição e salta para .else. No bloco then, JMP .fim para não cair no else. Labels com . são locais. Equivale a if (cond) {...} else {...} em C.

SETcc (byte condicional)
; SETcc: define byte para 0 ou 1
cmp rax, rbx
sete al         ; al = 1 se igual, 0 senão
movzx rax, al   ; estender para 64 bits

setl cl         ; cl = 1 se signed less
setg dl         ; dl = 1 se signed greater
seta bl         ; bl = 1 se unsigned above
setne sil       ; sil = 1 se não igual

; Booleano → inteiro:
xor eax, eax
cmp rcx, 0
setnz al        ; al = (rcx != 0) ? 1 : 0

; Combinar com aritmética:
; rax += (rbx > 5)
cmp rbx, 5
setg cl
movzx rcx, cl
add rax, rcx

SETcc define byte (AL, CL, etc.) para 0 ou 1 conforme flags. Seguido de MOVZX para usar como inteiro. Sem branches. Útil para contar condições e booleanos. Variantes: SETE, SETL, SETG, SETA.

Jumps condicionais (comparação)
; SIGNED (após CMP):
jl  .menor      ; jump if less (SF≠OF)
jle .menor_igual ; less or equal (ZF=1 ou SF≠OF)
jg  .maior      ; jump if greater (ZF=0 e SF=OF)
jge .maior_igual ; greater or equal (SF=OF)

; UNSIGNED (após CMP):
jb  .abaixo     ; jump if below (CF=1)
jbe .abaixo_igual ; below or equal (CF=1 ou ZF=1)
ja  .acima      ; jump if above (CF=0 e ZF=0)
jae .acima_igual ; above or equal (CF=0)

; Mnemónicos: L/G = signed, B/A = unsigned

Signed: JL/JG/JLE/JGE. Unsigned: JB/JA/JBE/JAE. L/G para negativos corretos. B/A para endereços/tamanhos. Sempre após CMP.

Switch/Case (tabela de saltos)
section .data
    jump_table: dq .case0, .case1, .case2, .case3

section .text
    ; rax = índice (0-3)
    cmp rax, 3
    ja  .default        ; fora do range

    jmp [jump_table + rax*8]  ; salto indireto

.case0:
    mov rbx, 10
    jmp .fim
.case1:
    mov rbx, 20
    jmp .fim
.case2:
    mov rbx, 30
    jmp .fim
.default:
    mov rbx, 0
.fim:

Switch usa tabela de endereços + JMP [tabela + idx*8]. Verifica range antes (CMP + JA). Cada case termina com JMP .fim (evita fall-through). O(1) para qualquer case. Como vtable.

Loops com DEC/JNZ
; Loop N vezes (padrão):
mov rcx, 10         ; contador
.loop:
    ; corpo do loop
    mov rax, rcx    ; usar contador
    dec rcx         ; rcx--
    jnz .loop       ; repete se rcx != 0

; Loop com índice crescente:
xor rcx, rcx        ; i = 0
.next:
    mov rax, [arr + rcx*4]
    ; processar
    inc rcx
    cmp rcx, 10
    jl .next        ; enquanto i < 10

; Loop infinito:
.inf:
    jmp .inf        ; ou: jmp $

Padrão: DEC + JNZ (countdown). Alternativa: INC + CMP + JL (countup). JMP $ = loop infinito (salta para si). Countdown é mais eficiente (menos instruções).

Curto-circuito (&&, ||)
; if (a > 0 && b > 0) { ... }
    cmp rax, 0
    jle .fim        ; se a<=0, FALSO (curto-circuito)
    cmp rbx, 0
    jle .fim        ; se b<=0, FALSO
    ; ambos verdadeiros:
    ; corpo do if
.fim:

; if (a == 1 || b == 2) { ... }
    cmp rax, 1
    je  .verdadeiro  ; se a==1, VERDADEIRO (curto-circuito)
    cmp rbx, 2
    je  .verdadeiro  ; se b==2, VERDADEIRO
    jmp .fim
.verdadeiro:
    ; corpo do if
.fim:

&&: salta para fim na primeira condição falsa. ||: salta para corpo na primeira verdadeira. Curto-circuito: segunda condição só é avaliada se necessário. Mesmo padrão que compiladores C geram.

Funções e Stack


10 cards
CALL e RET
; CALL: push RIP, salta para função
call minha_funcao     ; chama
; ... continua aqui após RET

; RET: pop RIP, volta ao caller
minha_funcao:
    mov rax, 42       ; valor de retorno
    ret               ; volta

; CALL indireto:
call rax              ; endereço em registo
call [rax]            ; endereço em memória

; RET com limpeza (stdcall, raro em x64):
ret 8                 ; pop + remove 8 bytes

CALL guarda o endereço de retorno na stack e salta. RET faz pop desse endereço e volta. RAX é o registo de retorno. Em x86-64, CALL é sempre relativo (±2GB).

Variáveis locais
func:
    push rbp
    mov rbp, rsp
    sub rsp, 48       ; 6 qwords locais

    ; Locais (offsets negativos de RBP):
    mov [rbp-8], rdi      ; local1 = arg1
    mov [rbp-16], 0       ; local2 = 0
    mov qword [rbp-24], 100 ; local3 = 100

    ; Array local:
    ; [rbp-32] a [rbp-48] = 2 qwords

    ; Acesso:
    mov rax, [rbp-8]      ; ler local1
    add rax, [rbp-24]     ; local1 + local3

    leave
    ret

Variáveis locais ficam em [rbp-N] (offsets negativos). Reservar com sub rsp, N (múltiplo de 16!). O compilador aloca tudo de uma vez. Acesso relativo a RBP (estável mesmo com push/pop).

Funções variádicas
; printf(fmt, ...) — args variáveis
; RAX = nº de registos XMM usados (0-8)

section .data
    fmt: db "x=%d, y=%f", 10, 0

section .text
    extern printf

    mov rdi, fmt        ; formato
    mov esi, 42         ; int arg
    movq xmm0, [pi]    ; double arg
    mov eax, 1          ; 1 registo XMM usado!
    call printf

; AL (RAX low) = contagem de XMM args
; Se esquecer → crash ou lixo!
; Para inteiros puros: xor eax, eax

Funções variádicas (printf, scanf) exigem AL = número de args em XMM. Esquecer causa crash. Inteiros em RDI, RSI, RDX... Floats em XMM0-7. Sempre xor eax,eax se sem floats.

Stack frame (prologue/epilogue)
minha_funcao:
    ; Prologue:
    push rbp          ; guardar frame anterior
    mov rbp, rsp      ; novo frame pointer
    sub rsp, 32       ; espaço para locais

    ; Corpo:
    mov [rbp-8], rdi   ; local 1 (arg1)
    mov [rbp-16], rsi  ; local 2 (arg2)

    ; Epilogue:
    mov rsp, rbp      ; libertar locais
    pop rbp           ; restaurar frame
    ret

; Alternativa (leave = mov rsp,rbp + pop rbp):
    leave
    ret

Prologue: push rbp + mov rbp, rsp + sub rsp, N. Epilogue: leave + ret. RBP é estável durante a função. Locais em [rbp-N], args em [rbp+16] e acima.

Recursão
; fatorial(n): se n<=1 retorna 1, senão n*fatorial(n-1)
fatorial:
    cmp rdi, 1
    jle .base           ; caso base

    push rdi            ; salvar n (caller-saved!)
    dec rdi             ; n - 1
    call fatorial       ; recursão
    pop rdi             ; restaurar n

    imul rax, rdi       ; n * fatorial(n-1)
    ret

.base:
    mov rax, 1
    ret

; Cuidado: stack overflow com N grande!
; Cada chamada usa ~16+ bytes de stack

Recursão: salvar registos necessários com PUSH antes do CALL (são caller-saved!). Restaurar com POP após. Caso base evita recursão infinita. Cada chamada consome stack — limite prático ~8MB.

Chamar funções C (extern)
; Declarar função externa:
extern printf
extern malloc
extern strlen

section .data
    msg: db "Valor: %d", 10, 0

section .text
    global main

main:
    push rbp
    mov rbp, rsp

    ; printf("Valor: %d", 42)
    mov rdi, msg        ; arg1: formato
    mov esi, 42         ; arg2: valor
    xor eax, eax        ; 0 xmm args
    call printf wrt ..plt  ; PIC (Linux)

    ; return 0
    xor eax, eax
    pop rbp
    ret

; Linkar: gcc main.o -o app -no-pie

extern declara funções C. Chamar com argumentos em RDI, RSI, RDX... wrt ..plt para código PIC (position-independent). Linkar com gcc (não ld). xor eax,eax antes de variádicas.

Argumentos (System V AMD64)
; Convenção Linux/macOS (System V):
; Inteiros/ponteiros: RDI, RSI, RDX, RCX, R8, R9
; Floats: XMM0-XMM7
; Retorno: RAX (int), XMM0 (float)
; Extras: na stack (direita → esquerda)

; Exemplo: func(a, b, c, d, e, f, g)
; a=RDI, b=RSI, c=RDX, d=RCX, e=R8, f=R9
; g → [rsp] (na stack)

; Windows (Microsoft x64):
; RCX, RDX, R8, R9 (só 4 em registos!)
; Shadow space: 32 bytes obrigatórios

Linux: 6 args inteiros em RDI, RSI, RDX, RCX, R8, R9. Retorno em RAX. Windows usa RCX, RDX, R8, R9 + shadow space. Floats em XMM0-7.

Leaf function (sem CALL)
; Leaf = não chama outras funções
; Não precisa de prologue completo!

; Versão mínima:
soma:
    lea rax, [rdi + rsi]  ; rax = a + b
    ret

; Com red zone (128 bytes livres):
; Abaixo de RSP, sem push, seguro em leaf
leaf_func:
    mov [rsp-8], rdi    ; red zone!
    mov [rsp-16], rsi
    ; processar sem sub rsp
    ret

; Red zone: 128 bytes abaixo de RSP
; Só seguro se NÃO chamar funções
; (signal handlers podem corromper)

Leaf function não faz CALL — pode omitir prologue. Red zone: 128 bytes abaixo de RSP são seguros em leaf (System V). Sem push/pop = mais rápido. Ideal para funções simples (getters, cálculos).

Callee-saved vs Caller-saved
; Callee-saved (preservar se usar!):
; RBX, RBP, R12, R13, R14, R15
; (também RSP implicitamente)

; Caller-saved (podem ser alterados):
; RAX, RCX, RDX, RSI, RDI, R8-R11

; Exemplo: função que usa RBX
func:
    push rbx          ; preservar!
    mov rbx, rdi      ; usar rbx
    call outra_func   ; rbx seguro
    mov rax, rbx      ; ainda válido
    pop rbx           ; restaurar
    ret

; Se não preservar → bug intermitente!

Callee-saved: RBX, RBP, R12-R15 — a função chamada DEVE preservar. Caller-saved: RAX, RCX, RDX, RSI, RDI, R8-R11 — podem ser destruídos por CALL. Sempre push/pop dos callee-saved.

Alinhamento de stack
; ABI exige RSP alinhado a 16 bytes
; ANTES de CALL (após CALL, RSP%16 = 8)

; Verificar:
; rsp % 16 == 0 antes de call → correto

; Se desalinhado:
sub rsp, 8          ; alinhar (se necessário)
call func
add rsp, 8          ; restaurar

; Ao entrar na função (após CALL):
; RSP % 16 == 8 (o CALL fez push de 8)
; push rbp → RSP % 16 == 0 ✓

; Porquê: movaps/movdqa exigem alinhamento
; SSE/AVX falham com stack desalinhada

Stack DEVE estar alinhada a 16 bytes antes de CALL. Após CALL, RSP%16=8. O prologue (push rbp) realinha. Se fizer sub rsp, N, N deve ser múltiplo de 16. movaps causa segfault se desalinhado.

System Calls


10 cards
Syscall (visão geral)
; Linux x86-64 syscalls:
; RAX = número da syscall
; RDI, RSI, RDX, R10, R8, R9 = argumentos
; syscall → invoca kernel
; Retorno em RAX (negativo = erro)

; Syscalls comuns:
; 0  = read
; 1  = write
; 2  = open
; 3  = close
; 39 = getpid
; 60 = exit
; 63 = uname

; Tabela completa:
; /usr/include/asm/unistd_64.h

syscall invoca o kernel Linux. Número em RAX, args em RDI, RSI, RDX, R10, R8, R9. Retorno em RAX (negativo = errno). Destrói RCX e R11.

sys_open e sys_close
section .data
    filename: db "/tmp/teste.txt", 0

section .text
    ; open(filename, O_WRONLY|O_CREAT, 0644)
    mov rax, 2              ; sys_open
    lea rdi, [rel filename]
    mov rsi, 0x241          ; O_WRONLY|O_CREAT|O_TRUNC
    mov rdx, 0644o          ; permissões (octal)
    syscall
    ; rax = file descriptor (ou -errno)

    mov rdi, rax            ; guardar fd

    ; ... escrever no ficheiro ...

    ; close(fd)
    mov rax, 3              ; sys_close
    ; rdi já tem o fd
    syscall

; Flags: O_RDONLY=0, O_WRONLY=1, O_RDWR=2
; O_CREAT=0100, O_TRUNC=01000, O_APPEND=02000

sys_open (rax=2): path, flags, modo. Retorna fd (≥0) ou -errno. sys_close (rax=3): fd em rdi. Flags: O_RDONLY=0, O_WRONLY=1, O_CREAT=0100. Sempre fechar fds!

sys_execve
section .data
    path: db "/bin/echo", 0
    arg0: db "echo", 0
    arg1: db "Olá!", 0

section .text
    ; execve(path, argv, envp)
    mov rax, 59             ; sys_execve
    lea rdi, [rel path]     ; filename

    ; argv = {"echo", "Olá!", NULL}
    lea rsi, [rel argv]     ; ponteiro para array

    ; envp = NULL (sem ambiente)
    xor rdx, rdx

    syscall
    ; Se retornar → ERRO! (execve não volta)

section .data
    argv: dq arg0, arg1, 0  ; array de ponteiros

sys_execve (59) substitui o processo por outro. rdi=path, rsi=argv (array de ponteiros, NULL-terminated), rdx=envp. Se retornar, é erro. Nunca volta em sucesso. Combinar com fork.

sys_write (imprimir)
section .data
    msg: db "Olá, Mundo!", 10
    len: equ $ - msg

section .text
    global _start
_start:
    ; write(1, msg, len)
    mov rax, 1          ; sys_write
    mov rdi, 1          ; fd = stdout
    lea rsi, [rel msg]  ; buffer
    mov rdx, len        ; tamanho
    syscall

    ; rax = bytes escritos (ou -errno)
    cmp rax, 0
    jl  .erro           ; verificar erro

    mov rax, 60         ; sys_exit
    xor rdi, rdi
    syscall
.erro:
    mov rax, 60
    mov rdi, 1          ; exit code 1
    syscall

sys_write (rax=1): rdi=fd (1=stdout, 2=stderr), rsi=buffer, rdx=tamanho. Retorna bytes escritos. Verificar rax < 0 para erros. Não adiciona newline automaticamente.

sys_mmap (alocar memória)
; mmap(NULL, size, PROT, FLAGS, fd, offset)
mov rax, 9              ; sys_mmap
xor rdi, rdi            ; addr = NULL (kernel escolhe)
mov rsi, 4096           ; size = 4KB
mov rdx, 3              ; PROT_READ|PROT_WRITE
mov r10, 0x22           ; MAP_PRIVATE|MAP_ANONYMOUS
mov r8, -1              ; fd = -1 (anónimo)
xor r9, r9              ; offset = 0
syscall
; rax = endereço da memória (ou -errno)

; Usar:
mov [rax], byte 'A'    ; escrever

; Libertar: munmap(addr, size)
mov rax, 11             ; sys_munmap
mov rsi, 4096
syscall

; PROT: READ=1, WRITE=2, EXEC=4
; MAP: SHARED=0x01, PRIVATE=0x02, ANON=0x20

sys_mmap (rax=9) aloca memória. MAP_ANONYMOUS = sem ficheiro. Retorna endereço ou -errno. sys_munmap (rax=11) liberta. Equivale a malloc (que usa mmap internamente). Páginas de 4096 bytes.

Tratamento de erros
; Syscall retorna -errno em RAX
; (valores negativos: -1 a -4095)

mov rax, 2          ; sys_open
lea rdi, [rel path]
xor esi, esi        ; O_RDONLY
syscall

cmp rax, 0
jl  .erro           ; negativo = erro
mov rdi, rax        ; fd válido
jmp .continuar

.erro:
    neg rax         ; rax = errno positivo
    ; errno comuns:
    ; 2  = ENOENT (ficheiro não existe)
    ; 13 = EACCES (permissão negada)
    ; 9  = EBADF (fd inválido)

    ; Escrever erro no stderr:
    mov rax, 1      ; sys_write
    mov rdi, 2      ; stderr
    lea rsi, [rel errmsg]
    mov rdx, errmsg_len
    syscall

Syscalls retornam -errno em RAX (negativo = erro). Verificar com cmp rax, 0 + jl. neg rax dá o errno positivo. Comuns: 2=ENOENT, 13=EACCES, 9=EBADF. Sempre verificar em produção!

sys_read (ler input)
section .bss
    buffer: resb 256

section .text
    ; read(0, buffer, 256)
    mov rax, 0          ; sys_read
    mov rdi, 0          ; fd = stdin
    lea rsi, [rel buffer]
    mov rdx, 256        ; max bytes
    syscall

    ; rax = bytes lidos (inclui \n)
    ; rax = 0 → EOF
    ; rax < 0 → erro

    ; Remover newline:
    lea rdi, [rel buffer]
    add rdi, rax        ; posição do \n
    dec rdi
    mov byte [rdi], 0   ; substituir por NUL

sys_read (rax=0): rdi=fd (0=stdin), rsi=buffer, rdx=máximo. Retorna bytes lidos (inclui \n). rax=0 = EOF. Buffer em BSS. Remover newline substituindo por NUL.

sys_getpid e sys_getuid
; getpid() → PID do processo
mov rax, 39         ; sys_getpid
syscall
; rax = PID (sempre positivo)

; getppid() → PID do pai
mov rax, 110        ; sys_getppid
syscall

; getuid() → user ID
mov rax, 102        ; sys_getuid
syscall
; rax = UID (0=root)

; getgid() → group ID
mov rax, 104        ; sys_getgid
syscall

; gettid() → thread ID
mov rax, 186        ; sys_gettid
syscall

; Útil para: logs, nomes de ficheiros temp,
; verificação de permissões, debugging

sys_getpid (39) retorna o PID. sys_getuid (102) retorna UID (0=root). Sem argumentos — só RAX com o número. Úteis para logs, ficheiros temporários e verificação de permissões.

sys_exit
; Terminar programa:
mov rax, 60         ; sys_exit
mov rdi, 0          ; exit code (0=sucesso)
syscall

; Exit codes convencionais:
; 0   = sucesso
; 1   = erro genérico
; 2   = uso incorreto (bash)
; 126 = sem permissão
; 127 = comando não encontrado
; 128+N = morto por sinal N
; 139 = segfault (128+11)

; Alternativa (libc):
; call exit (não retorna)
; Nunca retorna — código após é inalcançável

sys_exit (rax=60): rdi = código de saída. 0=sucesso, 1=erro. O kernel termina o processo e liberta recursos. Código após syscall de exit nunca executa. Em C: equivale a exit(code).

sys_fork e sys_wait4
; fork() → cria processo filho
mov rax, 57         ; sys_fork
syscall
; rax = 0 → processo filho
; rax > 0 → processo pai (rax = PID filho)
; rax < 0 → erro

test rax, rax
jz  .filho

.pai:
    ; esperar pelo filho
    mov rax, 61         ; sys_wait4
    mov rdi, -1         ; qualquer filho
    xor rsi, rsi        ; status (NULL)
    xor rdx, rdx        ; options
    xor r10, r10        ; rusage (NULL)
    syscall
    jmp .fim

.filho:
    ; código do filho
    mov rax, 60
    xor rdi, rdi
    syscall
.fim:

sys_fork (57) duplica o processo. Retorna 0 no filho, PID no pai. sys_wait4 (61) espera pelo filho. Padrão: verificar rax==0 para branch filho/pai. Filho deve fazer exit ou execve.

Strings


10 cards
REP MOVSB (copiar)
; Copiar N bytes de RSI → RDI
section .data
    src: db "Hello, World!", 0

section .bss
    dst: resb 64

section .text
    lea rsi, [rel src]   ; origem
    lea rdi, [rel dst]   ; destino
    mov rcx, 14          ; nº de bytes
    rep movsb            ; copia RCX bytes

; REP: repete até RCX=0
; MOVSB: move byte [RSI]→[RDI], inc ambos
; Direção: DF=0 (cld) → crescente
;          DF=1 (std) → decrescente

; Variantes: rep movsw (word), rep movsq (qword)

REP MOVSB copia RCX bytes de [RSI] para [RDI]. REP decrementa RCX até 0. CLD = direção crescente (padrão). Equivale a memcpy. Variantes: MOVSW (2B), MOVSQ (8B).

strlen manual
; strlen: contar bytes até NUL
; Input: RDI = ponteiro para string
; Output: RAX = comprimento
my_strlen:
    xor eax, eax        ; contador = 0
.loop:
    cmp byte [rdi + rax], 0
    je  .fim            ; encontrou NUL
    inc rax
    jmp .loop
.fim:
    ret

; Alternativa com REPNE SCASB:
my_strlen2:
    push rdi
    mov al, 0
    mov rcx, -1
    cld
    repne scasb
    pop rax
    sub rax, rdi        ; negativo
    neg rax             ; positivo
    dec rax             ; -1 (NUL)
    ret

strlen conta bytes até NUL. Método manual: loop com CMP byte, 0. Método rápido: repne scasb (hardware otimizado). Input em RDI, retorno em RAX. Não inclui o NUL no count.

printf com argumentos
extern printf

section .data
    fmt1: db "Nome: %s, Idade: %d", 10, 0
    fmt2: db "Pi = %.2f", 10, 0
    nome: db "Ana", 0

section .text
    ; printf("Nome: %s, Idade: %d", "Ana", 30)
    lea rdi, [rel fmt1]     ; formato
    lea rsi, [rel nome]     ; %s → ponteiro
    mov edx, 30             ; %d → inteiro
    xor eax, eax            ; 0 xmm args
    call printf wrt ..plt

    ; printf("Pi = %.2f", 3.14)
    lea rdi, [rel fmt2]
    movq xmm0, [pi_val]    ; %f → XMM0
    mov eax, 1              ; 1 xmm arg!
    call printf wrt ..plt

section .data
    pi_val: dq 3.14

printf: formato em RDI, args em RSI, RDX, RCX... Floats em XMM0-7. EAX = nº de args XMM (obrigatório!). %s = ponteiro, %d = int. String com NUL no fim.

LODSB / STOSB
; LODSB: carrega byte de [RSI] → AL, RSI++
lea rsi, [rel texto]
lodsb               ; al = [rsi], rsi++
lodsb               ; al = próximo byte

; STOSB: escreve AL → [RDI], RDI++
lea rdi, [rel buffer]
mov al, 'A'
stosb               ; [rdi] = 'A', rdi++

; REP STOSB: preencher memória (memset!)
lea rdi, [rel buffer]
mov al, 0           ; byte de preenchimento
mov rcx, 256        ; 256 bytes
rep stosb           ; memset(buffer, 0, 256)

; REP STOSQ: mais rápido (8 bytes/iter)
mov rax, 0
mov rcx, 32         ; 32*8 = 256 bytes
rep stosq

LODSB lê byte de [RSI] para AL. STOSB escreve AL em [RDI]. REP STOSB = memset. REP STOSQ preenche 8 bytes por iteração (mais rápido). Sempre CLD antes.

strcpy manual
; strcpy: copiar até NUL (inclusive)
; Input: RDI = destino, RSI = origem
my_strcpy:
    push rdi            ; guardar início (retorno)
.loop:
    lodsb               ; al = [rsi], rsi++
    stosb               ; [rdi] = al, rdi++
    test al, al         ; é NUL?
    jnz .loop           ; se não, continua
    pop rax             ; retorno = início dst
    ret

; Alternativa com REP:
; (requer strlen primeiro)
my_strcpy2:
    push rdi
    push rsi
    call my_strlen      ; rax = len (rsi)
    inc rax             ; incluir NUL
    mov rcx, rax
    pop rsi
    pop rdi
    rep movsb           ; copiar tudo
    ret

strcpy copia bytes até NUL (inclusive). LODSB+STOSB em loop. Retorna ponteiro do destino (convenção C). Alternativa: strlen + rep movsb. Cuidado: buffer destino deve ser suficiente!

Strings multibyte (UTF-8)
section .data
    ; UTF-8: "Olá" = 4 bytes (á = 2 bytes)
    msg: db "Ol", 0xC3, 0xA1, 0
    ; 0xC3 0xA1 = 'á' em UTF-8

    ; Emoji: "😀" = 4 bytes
    emoji: db 0xF0, 0x9F, 0x98, 0x80, 0

    ; Tamanho em bytes vs caracteres:
    len_bytes: equ $ - msg - 1  ; bytes
    ; caracteres ≠ bytes para acentos!

section .text
    ; write não sabe UTF-8 — só bytes
    mov rax, 1
    mov rdi, 1
    lea rsi, [rel msg]
    mov rdx, 5          ; 5 BYTES (não chars)
    syscall
    ; Terminal interpreta UTF-8 corretamente

Assembly trabalha com bytes, não caracteres. UTF-8: acentos = 2 bytes, emoji = 4 bytes. sys_write envia bytes — o terminal interpreta UTF-8. strlen conta bytes, não caracteres. Cuidado com indexação!

SCASB (procurar byte)
; SCASB: compara AL com [RDI], RDI++
; Usado para strlen e strchr

; strlen: contar até NUL
lea rdi, [rel string]
mov al, 0           ; procurar NUL
mov rcx, -1         ; máximo (infinito)
cld                 ; direção crescente
repne scasb         ; repete enquanto != AL

; RCX = -(strlen+2)
not rcx             ; inverter bits
dec rcx             ; strlen = ~rcx - 1
; ou: neg rcx; sub rcx, 2

; strchr: procurar caractere
mov al, 'o'         ; procurar 'o'
repne scasb
jnz .nao_encontrado
; rdi-1 = posição do caractere

SCASB compara AL com [RDI]. REPNE = repete enquanto NÃO igual. Para strlen: procurar NUL com repne scasb. Resultado: NOT RCX - 1 = comprimento. REPE = enquanto igual.

strcmp manual
; strcmp: comparar strings
; Retorno: 0=iguais, <0 se s1<s2, >0 se s1>s2
my_strcmp:
.loop:
    mov al, [rdi]       ; byte de s1
    mov bl, [rsi]       ; byte de s2
    cmp al, bl
    jne .diff           ; diferentes
    test al, al
    jz  .equal          ; ambos NUL → iguais
    inc rdi
    inc rsi
    jmp .loop
.diff:
    movzx eax, al
    movzx ebx, bl
    sub eax, ebx        ; negativo/positivo
    ret
.equal:
    xor eax, eax        ; 0 = iguais
    ret

; Uso:
; lea rdi, [rel str1]
; lea rsi, [rel str2]
; call my_strcmp

strcmp compara byte a byte até diferença ou NUL. Retorna 0 (iguais), negativo (s1s2). movzx para unsigned antes de sub. Loop com inc em ambos os ponteiros.

CMPSB (comparar strings)
; CMPSB: compara [RSI] com [RDI], ambos++
; Usado para strcmp/memcmp

; Comparar N bytes:
lea rsi, [rel str1]
lea rdi, [rel str2]
mov rcx, 10         ; comparar 10 bytes
cld
repe cmpsb          ; repete enquanto iguais

; Resultado:
jz  .iguais         ; todos iguais (RCX=0)
; RSI-1 e RDI-1 = posição da diferença
; Flags: CF/SF indicam ordem

; strcmp completo (até NUL):
lea rsi, [rel str1]
lea rdi, [rel str2]
.cmp_loop:
    mov al, [rsi]
    cmp al, [rdi]
    jne .diferentes
    test al, al
    jz  .iguais     ; ambos NUL
    inc rsi
    inc rdi
    jmp .cmp_loop

CMPSB compara [RSI] com [RDI]. REPE = repete enquanto iguais. Para strcmp: loop até diferença ou NUL. Flags após última comparação indicam ordem (JB=str1memcmp.

Imprimir número (itoa)
; Converter inteiro → string decimal
; Input: RAX = número, RDI = buffer
itoa:
    push rdi
    mov rcx, 0          ; contador de dígitos
    mov rbx, 10
.div_loop:
    xor rdx, rdx
    div rbx             ; rax /= 10, resto em rdx
    add dl, '0'         ; ASCII do dígito
    push rdx            ; guardar (ordem inversa!)
    inc rcx
    test rax, rax
    jnz .div_loop
; Escrever dígitos (invertidos):
    pop rax
    mov [rdi], al
    inc rdi
    dec rcx
    jnz .write_loop-1   ; (loop)
; NUL:
    mov byte [rdi], 0
    pop rdi
    ret

itoa converte inteiro para string. Divide por 10 repetidamente. Resto + "0" = dígito ASCII. Dígitos saem invertidos → usar stack para reordenar. Terminar com NUL. Alternativa: printf via libc.

Dicas e Técnicas


10 cards
Otimizações básicas
; Zerar registo (mais rápido):
xor eax, eax        ; 1 byte, 1 ciclo
; vs: mov rax, 0    ; 7 bytes, mais lento

; Multiplicar por constante:
lea rax, [rax + rax*4]  ; *5 (1 ciclo)
shl rax, 3              ; *8 (1 ciclo)
; vs: imul rax, 5       ; 3 ciclos

; Trocar registos:
xchg rax, rbx       ; 1 instrução
; vs: push/pop ou temp

; NOP (alinhamento):
nop                 ; 1 byte
; multi-byte NOP para alinhamento:
nop dword [rax]     ; 4 bytes (não executa)

; Branch hint (documentar):
; likely: jmp curto para frente
; unlikely: jmp para trás

xor eax,eax é a forma mais rápida de zerar (1 ciclo, 2 bytes). LEA para multiplicar por constantes. XCHG troca sem temporário. NOP para alinhamento. Evitar branches desnecessários.

Macros NASM
; Macro sem parâmetros:
%macro newline 0
    mov rax, 1
    mov rdi, 1
    lea rsi, [rel .nl]
    mov rdx, 1
    syscall
    jmp %%fim
    .nl: db 10
    %%fim:
%endmacro

; Macro com parâmetros:
%macro print_str 2
    mov rax, 1
    mov rdi, 1
    lea rsi, [%1]
    mov rdx, %2
    syscall
%endmacro

; Uso:
    newline
    print_str msg, msg_len

; %% = label local à macro (evita conflito)

%macro nome N define macro com N parâmetros. %1, %2 = argumentos. %%label = label local (evita duplicados). Expandida em montagem (sem overhead). Ideal para código repetitivo (prints, asserts).

Boas práticas
; 1. Sempre CLD antes de string ops
cld

; 2. Preservar callee-saved
push rbx            ; se usar rbx
; ...
pop rbx

; 3. Stack alinhada a 16 antes de CALL
and rsp, -16        ; se necessário

; 4. Verificar retorno de syscalls
syscall
test rax, rax
js  .erro

; 5. Usar LEA para aritmética
lea rax, [rdi + rsi*4]

; 6. XOR para zerar (não MOV 0)
xor eax, eax

; 7. Labels locais com ponto
.loop:
.fim:

; 8. Comentários em cada bloco lógico

Regras: CLD antes de string ops. Preservar callee-saved (RBX, R12-15). Stack alinhada a 16. Verificar erros de syscall. LEA para aritmética. XOR para zerar. Labels locais com .. Comentar blocos.

Branchless programming
; abs(x) sem branch:
mov rax, rdi        ; x
mov rcx, rdi
sar rcx, 63         ; rcx = 0 ou -1 (máscara)
xor rax, rcx        ; inverte se negativo
sub rax, rcx        ; +1 se negativo

; min(a, b) sem branch:
mov rax, rdi        ; a
mov rcx, rsi        ; b
cmp rax, rcx
cmovg rax, rcx      ; se a>b, rax=b

; max(a, b):
mov rax, rdi
cmp rax, rsi
cmovl rax, rsi      ; se a<b, rax=b

; Vantagem: sem misprediction do CPU
; Branch predictor erra em dados aleatórios

Branchless evita JMP condicionais (misprediction penalty ~15 ciclos). CMOV para min/max. Máscara com SAR 63 para abs. Ideal para dados imprevisíveis. Branches são bons quando previsíveis (loops).

Debugging com GDB
; Compilar com debug info:
; nasm -f elf64 -g -F dwarf prog.asm
; gcc prog.o -o prog -no-pie -g

; GDB:
; gdb ./prog
; (gdb) break _start
; (gdb) run
; (gdb) info registers
; (gdb) x/10xw $rsp    ; 10 words na stack
; (gdb) x/s $rdi       ; string em rdi
; (gdb) stepi           ; 1 instrução
; (gdb) nexti           ; 1 instrução (skip call)
; (gdb) display $rax    ; mostrar sempre

; NASM: breakpoints com int3
int3                ; breakpoint por software
; (gdb) continue → para aqui

; Ver flags:
; (gdb) print $eflags

Compilar com -g -F dwarf para debug. GDB: stepi = 1 instrução, info registers = registos. x/10xw $rsp examina stack. int3 = breakpoint no código. display $rax mostra a cada passo.

x86 vs ARM vs RISC-V
; x86-64 (Intel/AMD):
; • CISC: instruções complexas, tamanho variável
; • 16 registos GPR (RAX-R15)
; • Flags (EFLAGS) afetadas por ops
; • Little-endian

; ARM64 (Apple M, Raspberry Pi):
; • RISC: instruções simples, tamanho fixo (4B)
; • 31 registos (X0-X30)
; • Sem flags (comparação explícita)
; • Bi-endian (geralmente little)

; RISC-V:
; • RISC open-source, modular
; • 32 registos (x0-x31, x0=zero)
; • Extensões: M(mult), F(float), V(vector)

; Conceitos são transferíveis:
; stack, call/ret, loops, syscalls

x86 = CISC (instruções complexas, tamanho variável). ARM = RISC (simples, 4 bytes fixos). RISC-V = open-source modular. Conceitos (stack, registos, loops) são universais. x86 domina servers/desktops. ARM domina mobile.

SSE (operações vetoriais)
; SSE: 128-bit (XMM0-XMM15)
; 4 floats ou 2 doubles por registo

; Carregar (requer alinhamento 16):
movaps xmm0, [dados]    ; 4 floats alinhados
movups xmm0, [dados]    ; não alinhado (lento)

; Operações:
addps xmm0, xmm1       ; 4 floats soma
mulps xmm0, xmm1       ; 4 floats multiplica
subps xmm0, xmm1       ; 4 floats subtrai

; Scalar (1 float):
addss xmm0, xmm1       ; só o float baixo

; Comparar:
cmpps xmm0, xmm1, 0   ; equal (4 comparações)

; Converter:
cvtsi2ss xmm0, eax     ; int → float
cvtss2si eax, xmm0     ; float → int

SSE processa 4 floats simultaneamente (SIMD). XMM0-15 = 128 bits. movaps requer alinhamento 16. Sufixos: ps = packed (4), ss = scalar (1). 4x mais rápido que escalar para vetores.

Linking e ELF
; Montar:
nasm -f elf64 main.asm -o main.o

; Linkar (sem libc):
ld main.o -o main

; Linkar (com libc):
gcc main.o -o main -no-pie

; Múltiplos ficheiros:
nasm -f elf64 a.asm -o a.o
nasm -f elf64 b.asm -o b.o
gcc a.o b.o -o app -no-pie

; Ver símbolos:
nm main             ; listar símbolos
readelf -s main.o   ; tabela de símbolos
objdump -d main     ; desassemblar

; Declarar externo:
extern printf       ; vem de outro ficheiro
global main         ; exportar para linker

nasm -f elf64 gera objeto ELF. ld = link sem libc (só syscalls). gcc = link com libc (printf, malloc). extern importa, global exporta. -no-pie evita PIE (mais simples).

AVX (256-bit)
; AVX: 256-bit (YMM0-YMM15)
; 8 floats ou 4 doubles por registo

; Operações (prefixo V):
vaddps ymm0, ymm1, ymm2   ; 8 floats soma
vmulps ymm0, ymm1, ymm2   ; 8 floats mult
vsubps ymm0, ymm1, ymm2   ; 8 floats sub

; Carregar:
vmovaps ymm0, [dados]     ; alinhado (32 bytes)
vmovups ymm0, [dados]     ; não alinhado

; Broadcast (1 valor → todos):
vbroadcastss ymm0, [val]  ; 1 float → 8

; IMPORTANTE: limpar upper bits!
vzeroupper                  ; antes de código SSE
; Evita penalidade de transição SSE↔AVX

; Alinhar dados:
align 32
dados: dd 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0

AVX usa YMM (256 bits) = 8 floats. Prefixo V nas instruções. vzeroupper OBRIGATÓRIO ao misturar com SSE (evita penalidade). Alinhar dados a 32 bytes. 8x throughput vs escalar.

Performance e profiling
; RDTSC: timestamp counter (ciclos de CPU)
rdtsc               ; EDX:EAX = ciclos
shl rdx, 32
or rax, rdx         ; rax = 64-bit counter
mov [start], rax

; ... código a medir ...

rdtsc
shl rdx, 32
or rax, rdx
sub rax, [start]    ; rax = ciclos gastos

; Dicas de performance:
; 1. Evitar branches (cmov, setcc)
; 2. Alinhar loops a 16 bytes
; 3. Unroll loops pequenos
; 4. Prefetch dados (prefetcht0 [rax])
; 5. Evitar dependências (ILP)

; Ferramentas: perf stat, perf record
; perf stat ./programa

RDTSC lê o contador de ciclos da CPU. Medir antes/depois para profiling. Dicas: evitar branches, alinhar loops, unroll, prefetch. perf stat mostra IPC, cache misses. ILP: instruções independentes em paralelo.