Como o debugger encontra linhas e variáveis? Do DWARF ao stepping no código-fonte
A CPU parou num endereço. O editor mostra arquivo, linha, função e variáveis. De onde veio esse significado que não existe nos bytes da instrução?
42 soma += item;
43● mostre(soma);
44 retorne soma;RIP 0x4011a7
RSP 0x7fff…
CC → instrução originalVOCABULÁRIO VISUAL
As peças que tornam a pausa observável.
| Dado | Responde |
|---|---|
| Tabela de símbolos ELF | onde começa uma função global? |
| DWARF line table | qual arquivo e linha correspondem ao endereço? |
| DWARF DIEs | quais funções, escopos, tipos e variáveis existem? |
| Location expression | onde encontrar o valor neste ponto? |
DWARF é um conjunto de seções estruturadas no executável. O artigo do LLVM ao executável mostrou a geração de máquina; agora usamos os metadados opcionais deixados ao lado dela.
unidade de compilação fixture.c ├─ line program: endereço ↔ arquivo:linha └─ DIE subprogram nivel_tres ├─ intervalo [0x401160, 0x401184) ├─ parâmetro valor → DW_OP_reg5 └─ variável total → DW_OP_fbreg -20
A biblioteca gimli decodifica unidades e formatos. O Vigia continua responsável por selecionar unidade, aplicar base relocada, percorrer escopos e avaliar o subconjunto de expressões documentado.
Uma linha pode gerar várias faixas de instruções; várias linhas podem apontar ao mesmo endereço. break fixture.c:17 escolhe o primeiro endereço executável da linha e informa alternativas. No sentido inverso, buscamos a última linha cujo endereço não ultrapassa RIP.
fixture.c:17 ──▶ 0x40116a, 0x401174 0x401172 ──────▶ fixture.c:17 · nivel_tres endereço runtime = endereço DWARF + base PIE
| Comando | Estratégia |
|---|---|
stepi | PTRACE_SINGLESTEP uma instrução |
step | single-step até mudar linha; entra em chamadas |
next | breakpoint temporário após a chamada; não entra |
finish | breakpoint temporário no endereço de retorno |
linha 17: total = soma(a, b);
mov edi,... mov esi,... call soma mov [rbp-4],eax
step → entra em soma
next → pausa após call, ainda na função atual
stepi → avança somente mov ediO Vigia encontra o DIE da função, entra nos escopos que contêm RIP e avalia localizações simples: DW_OP_regN, DW_OP_fbreg, endereço constante e pequenas somas. Location lists permitem receita diferente em faixas distintas.
valor em RIP A → RDI valor em RIP B → [RBP - 20] valor em RIP C → <otimizado/indisponível>
(vigia) variables
valor: i32 = 7 @ rdi
total: i32 = 21 @ [rbp-20]
temporario: <otimizado>$ vigia ./fixtures/chamadas
(vigia) break fixture.c:17
(vigia) run
fixture.c:17 · nivel_tres
15 int nivel_tres(int valor) {
16 int total = valor * 3;
17 ▶ return total;
(vigia) variables
valor = 7 · total = 21
(vigia) backtrace
#0 nivel_tres fixture.c:17
#1 nivel_dois fixture.c:23
(vigia) finish
fixture.c:23 · nivel_dois--format json publica eventos stopped, frames, scopes e variables num protocolo mínimo. O Debug Adapter Protocol padroniza a conversa entre IDE e adaptador. GDB/LLDB possuem seus próprios núcleos; adaptadores traduzem comandos. O Vigia demonstra a fronteira, sem declarar compatibilidade completa.
clique no editor → arquivo:linha → adaptador → Vigia consulta DWARF → endereço runtime → escreve CC → CPU executa INT 3 → kernel envia SIGTRAP → Vigia restaura e interpreta estado → JSON de frames/variáveis → editor desenha a pausa
Com -O0 -g -fno-omit-frame-pointer, linhas e variáveis são estáveis. Em -O2 -g, funções podem ser inline, linhas reordenadas e variáveis eliminadas. Sem -g, o Vigia continua depurando endereços e símbolos disponíveis, mas não inventa fonte.
O projeto cobre ELF/DWARF conhecido, expressões simples e x86_64. Não oferece depuração remota, core dumps, watchpoints de hardware, C++ completo ou todas as regras DWARF.
Fontes: DWARF Standard, Debug Adapter Protocol e documentação oficial do GDB sobre código otimizado.
O editor congela uma linha porque compilador, executável, kernel, CPU e debugger cooperam: DWARF encontra o endereço; 0xCC interrompe a instrução; ptrace entrega o estado; o Vigia reconstrói linhas, stack e variáveis.
PROJETO COMPLETO · ESTADO EXATO DESTE CAPÍTULO
Baixe o Vigia 1.0.
Pacote exclusivo com código Rust, fixture em C, opções de compilação, testes e limites documentados para Linux x86_64. O Vigia controla apenas filhos criados por ele.