Debugar Segfaults em Zig — Resolver Segmentation Faults

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

  1. Use modo Debug durante desenvolvimento — detecta a maioria dos problemas
  2. Use GeneralPurposeAllocator — detecta use-after-free e double-free
  3. Sempre verifique ponteiros C antes de dereferenciar
  4. Use defer para liberar recursos no momento certo
  5. Evite @ptrCast a menos que tenha certeza do alignment e tipo
  6. Rode com ASan periodicamente para pegar problemas sutis

Veja Também

Continue aprendendo Zig

Explore mais tutoriais e artigos em português para dominar a linguagem Zig.