Debugar Segfaults em Zig — Resolver Segmentation Faults
Segmentation faults (segfaults) ocorrem quando o programa tenta acessar memória inválida. Em Zig, o modo Debug detecta muitas dessas situações antes que se tornem segfaults, mas algumas podem escapar. Este guia ajuda a diagnosticar e corrigir.
Causas Comuns de Segfault
1. Use-After-Free
Acessar memória que já foi liberada:
// PERIGO: use-after-free
fn exemplo(allocator: std.mem.Allocator) !void {
const dados = try allocator.alloc(u8, 100);
allocator.free(dados);
// dados[0] = 42; // SEGFAULT! Memória já liberada
// CORRETO: não acessar após free
const dados2 = try allocator.alloc(u8, 100);
defer allocator.free(dados2); // free acontece ao sair do escopo
dados2[0] = 42; // OK
}
Detecção: Use GeneralPurposeAllocator em Debug — ele preenche memória liberada com um padrão especial e detecta use-after-free.
2. Dangling Pointer (Ponteiro para Stack)
Retornar ponteiro para variável local:
// PERIGO: ponteiro para stack que já não existe
fn perigoso() *i32 {
var x: i32 = 42;
return &x; // SEGFAULT quando usado! x já morreu
}
// CORRETO: alocar no heap
fn correto(allocator: std.mem.Allocator) !*i32 {
const ptr = try allocator.create(i32);
ptr.* = 42;
return ptr; // OK — vive no heap até ser liberado
}
3. Null Pointer Dereference
// Em interop com C, ponteiros podem ser null
const c = @cImport(@cInclude("lib.h"));
const ptr: ?*c.struct_data = c.get_data();
// ptr.*.campo; // SEGFAULT se ptr for null!
// CORRETO: verificar antes
if (ptr) |p| {
const valor = p.*.campo;
_ = valor;
} else {
std.debug.print("Ponteiro null retornado\n", .{});
}
4. Buffer Overflow
// PERIGO: escrever além do buffer
var buffer: [10]u8 = undefined;
// Em Debug mode, Zig detecta e dá panic
// Em Release, pode causar segfault ou corrupção silenciosa
// CORRETO: verificar limites
if (indice < buffer.len) {
buffer[indice] = valor;
}
5. Alignment Incorreto
// PERIGO: cast para tipo com alignment diferente
fn perigoso(bytes: [*]u8) *u32 {
// Se bytes não está alinhado a 4 bytes:
return @ptrCast(@alignCast(bytes)); // Pode dar panic ou segfault
}
// CORRETO: verificar alignment
fn correto(bytes: [*]align(1) u8) ?*align(1) const u32 {
if (@intFromPtr(bytes) % @alignOf(u32) != 0) return null;
return @ptrCast(bytes);
}
Ferramentas de Debugging
GDB (Linux)
# Compilar em Debug
zig build
# Rodar com GDB
gdb ./zig-out/bin/meu-app
# Dentro do GDB:
(gdb) run # Executar
(gdb) bt # Backtrace quando crashar
(gdb) bt full # Backtrace com variáveis
(gdb) frame 0 # Ir para frame do crash
(gdb) info registers # Ver registradores
(gdb) print *ptr # Inspecionar ponteiro
(gdb) x/10x ptr # Examinar memória
(gdb) watch *ptr # Parar quando memória mudar
LLDB (macOS)
lldb ./zig-out/bin/meu-app
(lldb) run
(lldb) bt
(lldb) frame variable
(lldb) memory read ptr
AddressSanitizer (ASan)
# Compilar com sanitizer
zig build-exe src/main.zig -fsanitize=address
# Rodar — ASan reporta erros detalhados
./main
# =================================================================
# ==12345==ERROR: AddressSanitizer: heap-use-after-free
# ...com stack trace detalhado
ASan detecta: use-after-free, buffer overflow, stack overflow, memory leaks e double-free.
Valgrind (Linux)
zig build
valgrind --tool=memcheck --leak-check=full ./zig-out/bin/meu-app
Debugging Passo a Passo
1. Reproduzir o Segfault
# Compilar em Debug
zig build
# Verificar se o crash é reproduzível
./zig-out/bin/meu-app
2. Obter o Stack Trace
# Se o stack trace não aparece, use GDB
gdb -ex run -ex bt -ex quit ./zig-out/bin/meu-app
# Ou habilitar core dumps
ulimit -c unlimited
./zig-out/bin/meu-app
gdb ./zig-out/bin/meu-app core
3. Localizar o Código Problemático
// Adicione checkpoints
std.debug.print("Checkpoint 1\n", .{});
// ...código suspeito...
std.debug.print("Checkpoint 2\n", .{});
4. Isolar com Teste Mínimo
test "reproduzir segfault" {
const allocator = std.testing.allocator;
// Código mínimo que reproduz o problema
const dados = try allocator.alloc(u8, 10);
defer allocator.free(dados);
// ...operação que causa segfault...
}
Segfaults em Interop com C
Interop com C é a causa mais comum de segfaults em Zig:
const c = @cImport(@cInclude("lib.h"));
// CUIDADO com ponteiros C:
// 1. Sempre verificar null
// 2. Não assumir lifetime
// 3. Não assumir tamanho de buffers
fn usar_lib_c() void {
const resultado = c.funcao_c();
// Verificar null
if (resultado == null) {
std.debug.print("Erro: retornou null\n", .{});
return;
}
// Verificar tamanho antes de criar slice
const len = c.get_size(resultado);
if (len == 0) return;
// Criar slice seguro
const slice = resultado.?[0..@intCast(len)];
_ = slice;
}
Prevenção
- Use modo Debug durante desenvolvimento — detecta a maioria dos problemas
- Use
GeneralPurposeAllocator— detecta use-after-free e double-free - Sempre verifique ponteiros C antes de dereferenciar
- Use
deferpara liberar recursos no momento certo - Evite
@ptrCasta menos que tenha certeza do alignment e tipo - Rode com ASan periodicamente para pegar problemas sutis
Veja Também
- Crash em Runtime — Outros tipos de crashes
- Ler Stack Traces — Interpretar stack traces
- Memory Leak — Detectar vazamentos
- Linkar Biblioteca C — Interop com C
- FAQ Memória — Gerenciamento de memória