Trilha 07 · Assembly e baixo nível

Assembly na prática

A melhor forma de entender assembly é executá-lo passo a passo e observar os registradores mudarem. Aqui você faz exatamente isso — sem precisar instalar nada.

① Intuição

Do código de alto nível ao código de máquina

Quando você escreve for (int i = 10; i > 0; i--), o compilador traduz isso para uma sequência de instruções de máquina: inicializa um registrador, compara, salta condicionalmente. Toda a lógica do seu programa — if, while, chamada de função, retorno — vira MOV, CMP, JNE, CALL, RET.

A tradução é mecânica: loops viram saltos para trás, if/else viram saltos condicionais, variáveis locais viram registradores ou posições na pilha. Entender essa tradução ajuda a prever performance, ler disassemblies e depurar crashes.

Assembly é portável no conceito, não no código: a lógica de "comparar e saltar se igual" existe em toda ISA (x86, ARM, RISC-V, MIPS). O que muda é o mnemônico (JE vs. BEQ vs. beq) e a convenção de chamada. Aprender a ler assembly de uma arquitetura te prepara para ler qualquer outra.
② Visualização interativa

Simulador de assembly passo a passo

Escolha um programa, clique em ▶ Iniciar e use ⏭ Passo para executar uma instrução por vez. Observe EAX, EBX, ECX, EDX e as flags atualizarem. Registradores que mudaram ficam destacados em azul.

CÓDIGO ASSEMBLY
1; Soma de 1 a 10: resultado em EAX
2mov ecx, 10 ; contador
3mov eax, 0 ; acumulador
4loop_inicio:
5 add eax, ecx ; soma += contador
6 dec ecx ; contador--
7 cmp ecx, 0 ; contador chegou a 0?
8 jne loop_inicio; não → volta
9hlt ; EAX = 55
REGISTRADORES
EAX0x000000000
EBX0x000000000
ECX0x000000000
EDX0x000000000
ESP0x0000FFF865528
FLAGS
ZF
0
CF
0
SF
0
OF
0
PILHA (topo → base)
vazia
Clique em ▶ Iniciar para começar
③ Explicação técnica

Loops e condicionais em assembly

; Tradução de um loop for em C → assembly x86

; C: for (int i = 10; i > 0; i--) soma += i;

mov ecx, 10        ; i = 10 (ECX é o contador clássico)
mov eax, 0         ; soma = 0 (EAX acumula)
loop_inicio:
  add eax, ecx     ; soma += i
  dec ecx          ; i-- (DEC atualiza ZF)
  jnz loop_inicio  ; se ZF=0 (ECX≠0) → volta
hlt               ; EAX = 55

; Note: o loop vai de 10→1; quando ECX chega a 0,
; DEC seta ZF=1 e JNZ não pula → sai do loop.
; JNZ é equivalente a JNE ("jump if not zero/equal")

Tradução de if/else

; Tradução de if/else em C → assembly

; C: if (a > b) resultado = a; else resultado = b;

mov eax, 42       ; a = 42
mov ebx, 73       ; b = 73
cmp eax, ebx      ; calcula flags de (a - b)
jg  eax_maior     ; se a > b → pula (ZF=0, SF=OF)
mov eax, ebx      ; else: resultado = b
eax_maior:         ; aqui eax = max(a, b)
hlt               ; EAX = 73

; O compilador com -O2 gera isso sem o JG:
;   cmp edi, esi
;   cmovg eax, edi   ← move condicional (sem branch!)
Endereçamento de labels: labels como loop_inicio: não ocupam espaço — são apenas marcadores. O assembler substitui o label pelo deslocamento relativo (para JMP/JE) ou pelo endereço absoluto (para CALL). Isso é por isso que mover código pode quebrar saltos hardcoded — um dos problemas resolvidos por Position Independent Code (PIC).
④ Projeto para programar

Escreva seu próprio assembly

Mini projeto: usando o simulador acima, escreva um programa que calcule o fatorial de 5 sem recursão (use um loop como o exemplo de soma). O resultado deve estar em EAX quando HLT executar. Dica: inicialize EAX=1, ECX=5, multiplique EAX por ECX a cada iteração com ADD (EAX += EAX * (ECX-1) não funciona; use INC/DEC e acumulação manual ou pense em outra forma).

Projeto principal: escreva em arquivo .s (sintaxe AT&T ou Intel) uma função assembly para calcular a soma dos quadrados de 1 a N. Compile com nasm ou gas, link e chame de um programa C. Meça a performance vs. a versão em C puro com clock_gettime.

Desafio extra: use o simulador para implementar o algoritmo de Euclides para MDC: enquanto B≠0: temp=B, B=A mod B, A=temp. O mod pode ser simulado com subtração repetida (se A>=B: A-=B; repita). Qual é o MDC de 48 e 36?

⑤ Exercícios rápidos

Teste sua intuição

Como um loop for em C é traduzido para assembly?
Como o JE sabe se os valores eram iguais? Depende exclusivamente do CMP anterior?
O que são labels em assembly (loop_inicio:) e como a CPU os usa?
⑥ Aplicações no mundo real

Onde você encontra isso

🕹️

Emuladores de console

Emuladores como PCSX2 (PS2) e Dolphin (GameCube/Wii) implementam a CPU do console em software: lêem instruções MIPS/PowerPC, decodificam e executam — exatamente o que o simulador acima faz, mas para outra ISA. Dolphin usa JIT (compilação just-in-time) para traduzir PowerPC → x86 em tempo real, executando 100× mais rápido que interpretação pura.

🔧

Exploits e buffer overflow

Exploits clássicos de buffer overflow sobrescrevem o endereço de retorno na pilha com o endereço de código do atacante. O payload é literalmente bytes de instrução assembly (shellcode) escrito na memória do processo. Mitigações modernas como ASLR, stack canaries e NX bit (No-eXecute) dificultam mas não eliminam esse vetor.

📱

Tradução binária (Rosetta 2)

Quando a Apple migrou para Apple Silicon (ARM), o Rosetta 2 traduziu apps x86-64 para ARM64. Primeiro uso: AOT (ahead-of-time) durante instalação, traduzindo todo o executável. Uso dinâmico: JIT para código gerado em runtime. Resultado: apps x86 rodando em ARM quase na mesma velocidade de apps nativos.

← Anterior: Conjunto de instruções Próxima: Pipeline de execução →