Depuração de código C++: como parar de ter medo de segmentation fault e amar o GDB
Você já passou três horas procurando um bug que no final era um break esquecido em um switch ou um ; extra depois de um if? Se sim — bem-vindo ao clube. A depuração de C++ é uma arte que beira o masoquismo. Mas, como qualquer arte, pode ser dominada.
Neste artigo, vamos abordar não apenas "como executar o GDB", mas técnicas reais que transformam a depuração de um jogo de adivinhação em um trabalho de detetive digno de Sherlock Holmes. Falaremos sobre sanitizers modernos, depuração reversiva e até mesmo como capturar um bug que só aparece "na sexta-feira à tarde".
1. Clássico do gênero: GDB e LLDB
Vamos começar pelo fundamento. O GNU Debugger (GDB) e seu concorrente LLDB são os canivetes suíços do depurador. Mas a maioria os usa com apenas 10% de suas capacidades. Vamos corrigir isso.
Pontos de parada com condições
Você coloca um ponto de parada em um loop de 10.000 iterações e aperta continue 9.999 vezes? Pare com isso. Use breakpoints condicionais:
(gdb) break main.cpp:42 if i == 5000Isso vai economizar horas de vida. No LLDB, a sintaxe é semelhante:
(lldb) breakpoint set --file main.cpp --line 42 --condition 'i == 5000'Watchpoints — capturando a mudança de uma variável
Imagine: você tem uma variável que muda em algum lugar desconhecido. Coloque um watchpoint:
(gdb) watch my_variable(gdb) rwatch my_variable # dispara na leitura(gdb) awatch my_variable # dispara tanto na leitura quanto na escritaAgora o programa vai parar exatamente no momento em que alguém tocar na sua variável. Mágico, não é?
2. Reverse Debugging: viagem no tempo
O pior pesadelo de um programador: o programa travou, mas você não sabe como ele chegou a esse estado. A depuração reversiva (reverse debugging) permite "rebobinar" a execução.
No GDB, você precisa do suporte a record:
(gdb) record(gdb) continue... o programa travou ...(gdb) reverse-step # volta um passo(gdb) reverse-continue # continua no sentido inversoEm ferramentas comerciais (como UndoDB ou rr da Mozilla), isso funciona ainda melhor. O rr (Record and Replay) é uma ferramenta gratuita que grava a execução do programa e depois permite reproduzi-la várias vezes:
$ rr record ./my_program$ rr replay(gdb) continue... encontrou o local da falha ...(gdb) reverse-stepDica: use o rr para encontrar condições de corrida (data races) — ele reproduz deterministicamente cada execução.
3. Sanitizers: vacina contra undefined behavior
O C++ moderno (a partir do Clang e GCC 4.8+) possui ferramentas integradas que nossos pais nem sonhavam. O AddressSanitizer (ASan), o UndefinedBehaviorSanitizer (UBSan) e o ThreadSanitizer (TSan) são itens obrigatórios.
Exemplo: capturando vazamento de memória
// compile com: g++ -fsanitize=address -g main.cpp -o main#include <iostream>
void leak() { int* p = new int(42); // esqueceu o delete}
int main() { leak(); return 0;}Ao executar, vemos:
==12345==ERROR: LeakSanitizer: detected memory leaks
Direct leak of 4 byte(s) in 1 object(s) allocated from: #0 0x7f... in operator new(unsigned long) #1 0x400... in leak() main.cpp:5 #2 0x400... in main main.cpp:10O ASan mostrará a pilha de chamadas exata, incluindo o número da linha onde a alocação ocorreu. Chega de "a memória está vazando em algum lugar".
UBSan contra comportamento indefinido
// compile com: g++ -fsanitize=undefined -g main.cppint main() { int x = INT_M