Ferramentas de Debug em Zig — Depuração e Análise de Erros

Ferramentas de Debug em Zig — Depuração e Análise de Erros

O Zig foi projetado com depuração de primeira classe em mente. O compilador inclui verificações de segurança em tempo de execução que detectam bugs comuns como acesso fora de limites, uso de ponteiros inválidos e overflow de inteiros. Combinado com suporte nativo a GDB/LLDB e stack traces detalhados, o Zig oferece uma experiência de debugging superior a C e C++.

Safety Checks Integrados

Em builds de debug (o padrão), o Zig ativa verificações automáticas:

pub fn main() void {
    // Detecção de overflow
    var x: u8 = 255;
    x += 1; // Panic em debug: integer overflow

    // Acesso fora de limites
    var arr = [_]u8{ 1, 2, 3 };
    _ = arr[5]; // Panic em debug: index out of bounds

    // Ponteiro nulo
    var ptr: ?*u32 = null;
    _ = ptr.?; // Panic em debug: attempt to use null value

    // Unreachable
    unreachable; // Panic em debug: reached unreachable code
}

Modos de Build e Safety

ModoSafety ChecksOtimizaçãoUso
DebugTodos ativosNenhumaDesenvolvimento
ReleaseSafeTodos ativosOtimizadoProdução segura
ReleaseFastDesativadosMáximaPerformance máxima
ReleaseSmallDesativadosTamanho mínimoEmbarcados

Stack Traces Detalhados

O Zig gera stack traces informativos automaticamente em panics:

fn funcaoA() !void {
    try funcaoB();
}

fn funcaoB() !void {
    try funcaoC();
}

fn funcaoC() !void {
    return error.AlgoErrado;
}

pub fn main() !void {
    try funcaoA();
}

// Saída em caso de erro não tratado:
// error: AlgoErrado
// src/main.zig:10:5: funcaoC
// src/main.zig:6:5: funcaoB
// src/main.zig:2:5: funcaoA
// src/main.zig:14:5: main

std.debug para Informações de Runtime

const std = @import("std");

pub fn debugInfo() void {
    // Imprimir stack trace atual
    std.debug.dumpCurrentStackTrace();

    // Informações do return address
    const addr = @returnAddress();
    std.debug.print("Return address: 0x{x}\n", .{addr});

    // Assert com mensagem
    std.debug.assert(1 + 1 == 2);

    // Print formatado para debug
    std.debug.print("Valor: {}, Tipo: {s}\n", .{ 42, @typeName(u32) });
}

GDB com Zig

O Zig gera informações de debug DWARF completas, compatíveis com GDB:

# Compilar em modo debug
zig build

# Iniciar GDB
gdb ./zig-out/bin/minha-app

# Comandos úteis no GDB
(gdb) break main                    # Breakpoint na main
(gdb) break src/utils.zig:42       # Breakpoint na linha 42
(gdb) run                           # Executar
(gdb) next                          # Próxima linha
(gdb) step                          # Entrar na função
(gdb) print variavel                # Imprimir variável
(gdb) print @as(u32, valor)         # Print com tipo Zig
(gdb) backtrace                     # Stack trace
(gdb) info locals                   # Variáveis locais
(gdb) watch variavel                # Watchpoint
(gdb) continue                      # Continuar execução

Configuração do GDB para Zig

Crie um .gdbinit para melhor experiência:

# .gdbinit
set print pretty on
set pagination off
set confirm off

# Zig-specific
define zig-slice
    print {ptr = $arg0.ptr, len = $arg0.len}
end

define zig-optional
    if $arg0 == 0
        printf "null\n"
    else
        print *$arg0
    end
end

LLDB com Zig

O LLDB também funciona bem com Zig:

# Iniciar LLDB
lldb ./zig-out/bin/minha-app

# Comandos LLDB
(lldb) breakpoint set --file main.zig --line 10
(lldb) run
(lldb) thread step-over                # next
(lldb) thread step-in                  # step
(lldb) frame variable                  # locals
(lldb) expression variavel             # print
(lldb) thread backtrace                # backtrace

Debug no VS Code

Configure o VS Code para debug com o ZLS:

// .vscode/launch.json
{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "Debug Zig",
            "type": "lldb",
            "request": "launch",
            "program": "${workspaceFolder}/zig-out/bin/minha-app",
            "args": [],
            "cwd": "${workspaceFolder}",
            "preLaunchTask": "zig build",
            "sourceLanguages": ["zig"]
        },
        {
            "name": "Debug Testes",
            "type": "lldb",
            "request": "launch",
            "program": "${workspaceFolder}/zig-out/bin/test",
            "args": [],
            "cwd": "${workspaceFolder}",
            "preLaunchTask": "zig build test"
        }
    ]
}

Técnicas de Debug Avançadas

Comptime Debug

// Mensagens de erro em tempo de compilação
fn validarTipo(comptime T: type) void {
    const info = @typeInfo(T);
    if (info != .Struct) {
        @compileError("Esperado struct, recebido " ++ @typeName(T));
    }

    // Log em comptime
    @compileLog("Analisando tipo:", @typeName(T));
    @compileLog("Número de campos:", info.Struct.fields.len);
}

Sanitizers

O Zig integra sanitizers do LLVM:

# Address Sanitizer (detecta uso após free, buffer overflow)
zig build -Dsanitize-address

# Undefined Behavior Sanitizer
zig build -Dsanitize-undefined

# Thread Sanitizer (detecta data races)
zig build -Dsanitize-thread

Debug Allocator

Use o GeneralPurposeAllocator com todas as verificações:

var gpa = std.heap.GeneralPurposeAllocator(.{
    .safety = true,
    .stack_trace_frames = 16,      // Stack traces detalhados
    .enable_memory_limit = true,   // Limite de memória
    .requested_memory_limit = 1024 * 1024 * 100, // 100MB
}){};

// No deinit, reportar leaks
defer {
    const check = gpa.deinit();
    if (check == .leak) {
        @panic("Memory leak detectado!");
    }
}

Panic Handler Customizado

pub const panic = struct {
    pub fn call(msg: []const u8, error_return_trace: ?*std.builtin.StackTrace, ret_addr: ?usize) noreturn {
        // Log customizado
        const stderr = std.io.getStdErr().writer();
        stderr.print("\n=== PANIC ===\n{s}\n", .{msg}) catch {};

        if (error_return_trace) |trace| {
            std.debug.dumpStackTrace(trace.*);
        }

        // Gerar core dump ou enviar telemetria
        std.posix.abort();
    }
}.call;

Ferramentas Externas

Valgrind

# Executar com Valgrind para detecção de memória
valgrind --leak-check=full ./zig-out/bin/minha-app

# Cachegrind para análise de cache
valgrind --tool=cachegrind ./zig-out/bin/minha-app

strace

# Rastrear chamadas de sistema
strace -f ./zig-out/bin/minha-app

# Apenas I/O
strace -e trace=read,write,open,close ./zig-out/bin/minha-app

Boas Práticas

  1. Desenvolva em modo Debug: As verificações de segurança encontram bugs cedo
  2. Use ReleaseSafe em produção: Mantém verificações com otimização
  3. Teste com FailingAllocator: Garanta tratamento de OutOfMemory
  4. Configure GDB/LLDB no IDE: Debug visual é mais produtivo
  5. Implemente panic handlers: Para logging e telemetria em produção

Próximos Passos

Explore as ferramentas de profiling para otimização, os frameworks de teste para prevenção de bugs, e os alocadores customizados para diagnóstico de memória. Consulte nossos tutoriais para práticas de debugging.

Continue aprendendo Zig

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