ADRIANOLAUREANO← Artigos
SÉRIE VIGIA · 02/03X86_64STACK TRACE

O que existe dentro de um programa pausado? Registradores, memória e stack traces

O Vigia já congelou o processo. Agora precisamos responder: onde ele está, quais valores carrega e quem chamou a função atual?

ARTIGO 28 · VIGIA 0.2 · ESTADO, MEMÓRIA E STACK

17registradores exibidos

3formas de inspeção

1stack trace simbólico

VIGIA · CONGELANDO UM INSTANTE DA EXECUÇÃO
CÓDIGO-FONTE42 soma += item;
43● mostre(soma);
44 retorne soma;
VIGIA 0.2controle · inspeção · significado
CPU PAUSADARIP 0x4011a7
RSP 0x7fff…
CC → instrução original

VOCABULÁRIO VISUAL

As peças que tornam a pausa observável.

01Registradormemória imediata dentro da CPU
02RSPtopo atual da stack
03RBPbase do frame corrente
04ABIcontrato binário entre funções
05Unwindreconstrução das chamadas ativas
06Endiannessordem dos bytes de um valor
A CPU só conhece bytes e endereços; o Vigia reconstrói o significado por camadas.
01
Pausar preserva um instante executável.
Estado de uma thread

O kernel guarda registradores da thread, memória do processo e motivo da parada. Memória é compartilhada entre threads; registradores e stack corrente pertencem a cada thread. O Vigia enumera /proc/PID/task e permite thread N, embora os fixtures usem uma thread.

02
Três ponteiros contam grande parte da história.
RIP, RSP e RBP
RIP  → próxima instrução
RSP  → topo atual da stack
RBP  → base do frame, quando preservado
RAX  → retorno inteiro comum
RDI, RSI, RDX, RCX, R8, R9 → primeiros argumentos inteiros
Os papéis de argumentos e retorno vêm da ABI System V AMD64, não do nome do registrador.
(vigia) registers
rip  0x0000000000401172  <nivel_tres+18>
rsp  0x00007fffffffe210
rbp  0x00007fffffffe240
rdi  0x0000000000000007

Isso retoma registradores e chamadas dos artigos de CPU e funções, agora observados num processo real.

03
Endereço é coordenada, não permissão.
Leitura validada e endianness

Antes de ler, o Vigia consulta mapas do processo, valida overflow de endereço + tamanho e recusa regiões ilegíveis. Leituras usam palavras e remontam bytes. No x86_64, o byte menos significativo vem primeiro na memória.

endereço  00 01 02 03 04 05 06 07
bytes     78 56 34 12 00 00 00 00
valor     0x0000000012345678  ← little-endian
A ordem de exibição do número é diferente da ordem dos bytes na memória.
(vigia) examine 0x7fffffffe210 32
7fffffffe210: 40 e2 ff ff ff 7f 00 00 84 11 40 00 00 00 00 00
04
A stack não guarda uma lista pronta de funções.
Frames e endereços de retorno

Com frame pointers, cada frame começa com o RBP anterior e, logo acima, o endereço de retorno. Seguimos essa cadeia apenas enquanto os endereços são alinhados, crescentes dentro da stack e os retornos caem em regiões executáveis.

endereços maiores
[retorno para main]       frame nivel_um
[RBP anterior] ◀ RBP
[retorno para nivel_um]   frame nivel_dois
[RBP anterior]
[retorno para nivel_dois] frame nivel_tres
                         ◀ RSP
endereços menores
A stack cresce para endereços menores nesta arquitetura.
fn unwind_frame(pid: Pid, rbp: u64) -> Result<Frame> {
    let previous_rbp = read_u64(pid, rbp)?;
    let return_addr = read_u64(pid, rbp + 8)?;
    validate_frame(previous_rbp, return_addr)?;
    Ok(Frame { previous_rbp, return_addr })
}
05
Um endereço ganha nome e deslocamento.
Stack trace simbólico

Ordenamos símbolos de função por endereço. Para cada retorno, encontramos o maior símbolo que não ultrapassa o endereço e mostramos o deslocamento.

(vigia) backtrace
#0 0x401172 nivel_tres+18
#1 0x4011a4 nivel_dois+31
#2 0x4011ce nivel_um+24
#3 0x40120a main+42

Parâmetros ainda vivos podem estar nos registradores da ABI. Locais do fixture ficam em offsets conhecidos de RBP, mas o Vigia não generaliza: otimização pode movê-los, eliminá-los ou mantê-los apenas em registradores.

06
Quando RBP desaparece, a trilha muda.
Omissão de frame pointer e unwind

-fomit-frame-pointer, tail calls e inlining quebram a cadeia simples. Executáveis podem trazer regras de unwind em .eh_frame. O Vigia 0.2 reconhece o limite e usa regras simples da seção; não tenta implementar todas as expressões.

BuildResultado
-O0 -fno-omit-frame-pointercadeia RBP completa
-O2frames podem sumir ou fundir
sem símbolosendereços sem nomes
07
Vigia 0.2 vê a máquina, mas ainda não a linha.
O próximo mapa

Agora lemos registradores, memória, instruções e frames e produzimos stack trace simbólico. Porém 0x401172 não contém “fixture.c:17”. Essa ponte depende de informações deixadas pelo compilador: DWARF.

Fontes: System V AMD64 ABI e proc_pid_maps(5).

PROJETO COMPLETO · ESTADO EXATO DESTE CAPÍTULO

Baixe o Vigia 0.2.

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.

Baixar Vigia 0.2 (.zip)Linux x86_64 · Rust · ELF · processos-fixture

O que este artigo fez você pensar?

Dúvidas, experiências e contrapontos ajudam a próxima pessoa a enxergar o assunto por outro ângulo.

Todos passam por moderação. Ao enviar, você concorda com a política de privacidade.

Receba os próximos artigos.

Uma mensagem quando uma nova investigação estiver pronta. Só isso.