Cheatsheet Assembly
Linguagem de baixo nível com acesso direto ao hardware (x86/NASM)
Assembly
Básico
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 16db/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
syscallsys_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
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, 5db = 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 32STRUC/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 .nextPonteiros 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], 0BSS 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 elementosArrays 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
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=0INC/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
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 automaticamenteJMP 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 .loopLOOP = 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 elseIF/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
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 bytesCALL 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
retVariá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, eaxFunçõ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
retPrologue: 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 stackRecursã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-pieextern 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
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=02000sys_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 ponteirossys_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
syscallsys_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
syscallSyscalls 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 NULsys_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
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)
retstrlen 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.14printf: 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
retstrcpy 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 corretamenteAssembly 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_strcmpstrcmp compara byte a byte até diferença ou NUL. Retorna 0 (iguais), negativo (s1movzx 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_loopCMPSB 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=str1
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
retitoa 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
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.