Trilha 07 · Assembly e baixo nível

Registradores e CPU state

A CPU tem um punhado de variáveis ultra-rápidas embutidas no chip — os registradores. Entendê-los é o primeiro passo para ler qualquer código assembly.

① Intuição

Variáveis dentro da CPU

Quando você escreve int x = 5;, o compilador escolhe onde guardar esse valor. Na maior parte do tempo, ele vai para um registrador — uma célula de memória embutida diretamente no chip, a centímetros dos transistores que fazem os cálculos. Acessar um registrador leva zero ciclos extras; acessar a RAM leva 200–300 ciclos.

Em x86-64, existem 16 registradores de uso geral de 64 bits: RAX, RBX, RCX, RDX, RSI, RDI, RSP, RBP, R8–R15. Cada um tem "versões menores" que acessam parte do mesmo registrador físico — EAX acessa os 32 bits inferiores de RAX, AX os 16, AL os 8 inferiores.

Por que tão poucos? Cada registrador precisa de fiação dedicada para cada unidade funcional da CPU (ALU, load/store, branch). Com 16 registradores, a CPU pode fazer quase qualquer coisa; com 8 (x86-32), o compilador precisa fazer malabarismo; com 32 (ARM/RISC-V), a frequência de clock fica um pouco menor. É um equilíbrio de design.
② Visualização interativa

Explorador de registradores

Selecione um registrador, defina um valor (decimal ou 0x... hexadecimal) e veja a representação binária e os sub-registradores atualizarem em tempo real. Clique nas flags para alternás-las.

EAX

Acumulador — valor de retorno de funções, resultado de MUL/DIV

hex: 0x00000000
dec: 0
sig: 0
FLAGS — clique para alternar
REPRESENTAÇÃO BINÁRIA (32 bits)
00000000000000000000000000000000
bit 31 (MSB)bit 0 (LSB)
REGISTRADORES PARCIAIS
EAX0x000000000 · 32-bit (todo)
AX0x00000 · 16-bit (baixo)
AH0x000 · 8-bit (bits 15–8)
AL0x000 · 8-bit (bits 7–0)

Escrever em AL muda apenas os bits 7–0 de EAX — os outros bits ficam intactos.

③ Explicação técnica

Convenções e o registrador de flags

// x86-64: os registradores de uso geral (64-bit)
// Cada registrador tem versões menores que compartilham bits

// RAX (64-bit)
//  └─ EAX  (bits 31–0)
//      └─ AX   (bits 15–0)
//          ├─ AH  (bits 15–8)
//          └─ AL  (bits 7–0)

// Convenção de chamada x86-64 Linux (System V ABI):
// Argumentos:  RDI, RSI, RDX, RCX, R8, R9
// Retorno:     RAX (e RDX para 128-bit)
// Callee-saved: RBX, RBP, R12–R15 (função deve preservar)
// Caller-saved: RAX, RCX, RDX, RSI, RDI, R8–R11 (pode sobrescrever)

// Exemplo: tradução de C → x86-64
// int resultado = add(3, 5);
mov edi, 3       ; 1º argumento → EDI (parte de RDI)
mov esi, 5       ; 2º argumento → ESI
call add          ; chama a função
; resultado em EAX após o retorno

EFLAGS — o registrador de status

// EFLAGS: registrador de 32 bits com bits de status

// Bit 0 — CF (Carry Flag)
add al, 0xFF    ; se al=1: resultado=0x100, CF=1 (carry)

// Bit 6 — ZF (Zero Flag)
sub eax, eax    ; EAX=0 → ZF=1
je  destino     ; salta SE ZF=1

// Bit 7 — SF (Sign Flag) = cópia do bit 31 do resultado
mov eax, -1    ; EAX = 0xFFFFFFFF → SF=1

// Bit 11 — OF (Overflow Flag): overflow com sinal
mov eax, 0x7FFFFFFF  ; INT_MAX com sinal
add eax, 1           ; → 0x80000000 = -2147483648: OF=1 !

// Diferença CF vs OF:
// CF = carry em aritmética sem sinal (unsigned)
// OF = overflow em aritmética com sinal (signed)
// A mesma instrução pode setar ambos, só um, ou nenhum.
Sub-registradores e side effects em x86-64: escrever em EAX zera automaticamente os 32 bits superiores de RAX — comportamento especificado na arquitetura para simplificar extensão de 32 para 64 bits. Mas escrever em AX, AH ou AL não afeta os bits superiores. Esse comportamento surpreende muitos: movzx eax, al (zero-extend) é diferente de simplesmente usar al diretamente.
④ Projeto para programar

Inspetor de registradores com GDB

Mini projeto: compile qualquer programa C simples com gcc -O0 -g -o prog prog.c, execute com gdb ./prog, coloque um breakpoint (break main) e use info registers para ver todos os registradores. Use si (step instruction) para avançar uma instrução por vez e observar o que muda.

Projeto principal: escreva uma função em C que some os elementos de um array de inteiros. Compile com gcc -O0 -S para gerar o assembly. Identifique: qual registrador guarda o ponteiro do array? Qual guarda o contador do loop? Qual retorna o resultado? Compare com -O2.

Desafio extra: use Python com a biblioteca ctypes para chamar uma função assembly escrita diretamente (use mmap para memória executável). Escreva os bytes de uma instrução ret (0xC3) e chame a função — ela retorna o valor de RAX do Python em si.

⑤ Exercícios rápidos

Teste sua intuição

O que são registradores e por que são mais rápidos que a RAM?
O que acontece com RAX quando você escreve em EAX em x86-64?
Qual é a diferença entre CF (Carry Flag) e OF (Overflow Flag)?
⑥ Aplicações no mundo real

Onde você encontra isso

🐛

Depuração com GDB / LLDB

Todo debugger mostra o estado dos registradores na pausa. Quando você vê "Segmentation fault", o endereço inválido estava em RSP (pilha corrompida) ou em algum registrador de ponteiro. info registers no GDB imprime todos os 16 registradores + EFLAGS em hex e decimal.

🔐

Engenharia reversa e CTF

Ao descompilar malware ou resolver desafios de reverse engineering, você lê assembly diretamente. Entender que o valor de retorno está sempre em RAX (Linux) ou EAX (Windows 32-bit) é o primeiro passo para entender o que uma função desconhecida faz sem ter o código-fonte.

Otimizações SIMD

Registradores YMM (256-bit) e ZMM (512-bit) — extensões AVX2/AVX-512 — permitem operar em 8 floats ou 4 doubles simultaneamente. Bibliotecas como NumPy, OpenCV e codecs de vídeo (x264, AV1) usam intrinsics SIMD para atingir desempenho de GFLOPs em um único core.

← Trilha: Assembly e baixo nível Próxima: Conjunto de instruções →