Debugar código é uma habilidade essencial para qualquer desenvolvedor. No Zig, você tem acesso a ferramentas poderosas para identificar e corrigir bugs — desde técnicas simples de print debugging até debuggers profissionais como GDB e LLDB.
Neste tutorial completo, você vai aprender todas as técnicas de debugging disponíveis no Zig: como usar o std.debug para diagnósticos rápidos, depurar com GDB/LLDB, analisar stack traces, debugar problemas de memória e integrar tudo com seu IDE favorito.
Pré-requisito: Conhecimento básico de Zig. Se você está começando, confira nosso Guia para Iniciantes.
Visão Geral do Debugging em Zig
Filosofia do Zig: Debugabilidade
O Zig foi projetado com debugabilidade em mente:
- Stack traces limpas — Sem overhead de runtime pesado
- Modo Debug seguro — Checks de segurança ativados por padrão
- Sem malloc escondido — Você controla toda alocação
- Erros explícitos — Error types ao invés de exceções
- Comportamento definido — Undefined behavior é detectado em Debug
Modos de Build e Debugging
| Modo | Uso | Debug | Performance |
|---|---|---|---|
Debug | Desenvolvimento | ✅ Stack traces, bounds checks | Lento |
ReleaseSafe | Produção com segurança | ✅ Safety checks | Rápido |
ReleaseFast | Máxima velocidade | ❌ Sem checks | Máximo |
ReleaseSmall | Binários pequenos | ❌ Sem checks | Rápido |
💡 Dica: Sempre desenvolva no modo
Debugpara ter acesso completo a informações de debugging.
# Build com símbolos de debug (padrão)
zig build
# Build de release (sem símbolos de debug)
zig build -Doptimize=ReleaseFast
Ferramentas de Debugging Disponíveis
┌─────────────────────────────────────────────────────────────┐
│ Debugging em Zig │
├─────────────────────────────────────────────────────────────┤
│ │
│ Rápido e Simples Debugger Profissional │
│ ┌──────────────┐ ┌──────────────┐ │
│ │ std.debug │ │ GDB │ │
│ │ print │ │ LLDB │ │
│ │ dumpStackTrace│ │ VS Code │ │
│ │ assert │ │ Breakpoints │ │
│ └──────────────┘ └──────────────┘ │
│ │
│ Análise de Memória │
│ ┌──────────────┐ │
│ │ GeneralPurposeAllocator│ │
│ │ GPA.detectLeaks() │ │
│ │ Valgrind (Linux) │ │
│ └──────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
Print Debugging com std.debug
A forma mais rápida de debugar é usar std.debug.print. É simples, mas poderosa.
Básico: std.debug.print
const std = @import("std");
pub fn main() void {
const valor = 42;
// Print básico
std.debug.print("Valor: {d}\n", .{valor});
// Múltiplos valores
const nome = "Zig";
const versao = 0.15;
std.debug.print("{s} v{d}\n", .{nome, versao});
// Debug format (mostra estrutura completa)
const ponto = .{ .x = 10, .y = 20 };
std.debug.print("Ponto: {any}\n", .{ponto});
// Saída: Ponto: struct{comptime x: comptime_int = 10, comptime y: comptime_int = 20}
}
Formatos de Print Úteis
const std = @import("std");
pub fn main() void {
const num: i32 = 42;
const hex: u32 = 0xDEADBEEF;
const ptr: *i32 = @ptrFromInt(0x1000);
const bin: u8 = 0b10101010;
// Diferentes formatos
std.debug.print("Decimal: {d}\n", .{num}); // 42
std.debug.print("Hexadecimal: {x}\n", .{hex}); // deadbeef
std.debug.print("Hex uppercase: {X}\n", .{hex});// DEADBEEF
std.debug.print("Ponteiro: {*p}\n", .{ptr}); // 0x1000
std.debug.print("Binário: {b}\n", .{bin}); // 10101010
std.debug.print("Caractere: {c}\n", .{'A'}); // A
std.debug.print("String: {s}\n", .{"hello"}); // hello
std.debug.print("Any: {any}\n", .{num}); // 42
}
Asserts e Verificações
Asserts são essenciais para capturar bugs cedo:
const std = @import("std");
fn dividir(a: i32, b: i32) i32 {
// Assert básico
std.debug.assert(b != 0); // Panic se b == 0
return @divTrunc(a, b);
}
fn buscar(array: []const i32, indice: usize) i32 {
// Assert com mensagem customizada
std.debug.assert(indice < array.len);
return array[indice];
}
// Asserts em tempo de compilação
comptime {
std.debug.assert(@sizeOf(usize) >= @sizeOf(u32));
}
Dump de Stack Trace
Quando algo dá errado, veja onde:
const std = @import("std");
fn funcaoNivel3() void {
std.debug.print("Em funcaoNivel3\n", .{});
// Imprime stack trace atual
const trace = @import("builtin").StackTrace;
std.debug.dumpStackTrace(trace{});
}
fn funcaoNivel2() void {
funcaoNivel3();
}
fn funcaoNivel1() void {
funcaoNivel2();
}
pub fn main() void {
funcaoNivel1();
}
Saída típica:
Em funcaoNivel3
first render trace:
/home/user/projeto/src/main.zig:6:31: 0x1033b60 in funcaoNivel3 (main)
/home/user/projeto/src/main.zig:13:18: 0x1033b80 in funcaoNivel2 (main)
/home/user/projeto/src/main.zig:17:18: 0x1033ba0 in funcaoNivel1 (main)
/home/user/projeto/src/main.zig:21:17: 0x1033bc0 in main (main)
Print Condicional
Use comptime para prints de debug opcionais:
const std = @import("std");
const builtin = @import("builtin");
const debug_mode = builtin.mode == .Debug;
fn debugPrint(comptime fmt: []const u8, args: anytype) void {
if (debug_mode) {
std.debug.print("[DEBUG] " ++ fmt ++ "\n", args);
}
}
pub fn main() void {
debugPrint("Iniciando aplicação", .{});
debugPrint("Config carregada: {any}", .{.{
.host = "localhost",
.port = 8080,
}});
}
Debugging com GDB
GDB (GNU Debugger) é o debugger padrão para Linux. Funciona perfeitamente com código Zig.
Preparação: Build com Símbolos de Debug
# Build padrão já inclui símbolos
zig build
# Ou explicitamente
zig build-exe -ODebug src/main.zig
# Para C/C++ mixed projects
zig build -Doptimize=Debug
Comandos Básicos do GDB
# Iniciar GDB
gdb ./zig-out/bin/meu-programa
# Ou com arguments
gdb --args ./zig-out/bin/meu-programa arg1 arg2
Comandos essenciais:
| Comando | Abreviação | Descrição |
|---|---|---|
run | r | Iniciar execução |
break | b | Definir breakpoint |
continue | c | Continuar execução |
step | s | Entrar na função |
next | n | Próxima linha (pula função) |
finish | fin | Terminar função atual |
print | p | Imprimir valor |
backtrace | bt | Stack trace |
info locals | Variáveis locais | |
quit | q | Sair |
Exemplo Prático com GDB
Código:
const std = @import("std");
fn fatorial(n: u32) u32 {
if (n <= 1) return 1;
return n * fatorial(n - 1);
}
pub fn main() void {
const n = 5;
const resultado = fatorial(n);
std.debug.print("{d}! = {d}\n", .{n, resultado});
}
Sessão GDB:
# Build
zig build-exe -ODebug src/main.zig -o debug_app
# Iniciar GDB
gdb ./debug_app
# Dentro do GDB:
(gdb) break fatorial # Breakpoint na função
Breakpoint 1 at 0x2370
(gdb) run # Executar
Starting program: /home/user/debug_app
Breakpoint 1, fatorial (n=5) at src/main.zig:4
4 if (n <= 1) return 1;
(gdb) print n # Inspecionar valor
$1 = 5
(gdb) continue # Continuar
Continuing.
Breakpoint 1, fatorial (n=4) at src/main.zig:4
4 if (n <= 1) return 1;
(gdb) bt # Stack trace
#0 fatorial (n=4) at src/main.zig:4
#1 0x0000555555556379 in fatorial (n=5) at src/main.zig:6
#2 0x000055555555639e in main () at src/main.zig:11
(gdb) continue # Continuar até o fim
Continuing.
5! = 120
[Inferior 1 (process 12345) exited normally]
(gdb) quit
Breakpoints Condicionais
# Breakpoint apenas quando n == 0
(gdb) break fatorial if n == 0
# Breakpoint na linha 6 apenas quando n > 10
(gdb) break src/main.zig:6 if n > 10
# Breakpoint temporário (para na primeira vez)
(gdb) tbreak fatorial
Watchpoints
Monitore quando uma variável muda:
# Parar quando resultado mudar
(gdb) watch resultado
# Parar quando a memória em um endereço mudar
(gdb) watch *(int*)0x7fffffffe000
Inspecionando Structs
const Pessoa = struct {
nome: []const u8,
idade: u32,
ativo: bool,
};
const p = Pessoa{
.nome = "Maria",
.idade = 30,
.ativo = true,
};
(gdb) print p
$1 = {
nome = {
ptr = 0x555555556004 "Maria",
len = 5
},
idade = 30,
ativo = true
}
(gdb) print p.idade
$2 = 30
(gdb) print p.nome.ptr
$3 = (const u8 *) 0x555555556004 "Maria"
Debugging com LLDB
LLDB é o debugger padrão no macOS e parte do LLVM. Também funciona no Linux.
Comandos LLDB vs GDB
| GDB | LLDB | Descrição |
|---|---|---|
break | breakpoint set ou b | Definir breakpoint |
run | run ou r | Executar |
continue | continue ou c | Continuar |
step | step ou s | Entrar função |
next | next ou n | Próxima linha |
print | expr ou p | Imprimir valor |
backtrace | thread backtrace ou bt | Stack trace |
info locals | frame variable | Variáveis locais |
quit | quit ou q | Sair |
Sessão LLDB Exemplo
# Iniciar LLDB
lldb ./zig-out/bin/meu-programa
# Sessão:
(lldb) breakpoint set --name fatorial
Breakpoint 1: where = debug_app`fatorial + 4 at main.zig:4, address = 0x00002370
(lldb) run
Process 12345 launched
Process 12345 stopped
* thread #1, name = 'debug_app', stop reason = breakpoint 1.1
frame #0: 0x0000555555556370 debug_app`fatorial(n=5) at main.zig:4
1 const std = @import("std");
2
3 fn fatorial(n: u32) u32 {
-> 4 if (n <= 1) return 1;
5 return n * fatorial(n - 1);
6 }
(lldb) expr n
(unsigned int) $0 = 5
(lldb) thread backtrace
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
* frame #0: 0x100003720 debug_app`fatorial(n=5) at main.zig:4
frame #1: 0x100003784 debug_app`main at main.zig:11
frame #2: 0x100003d54 debug_app`start + 52
(lldb) continue
Process 12345 resuming
5! = 120
Process 12345 exited with status = 0
Stack Traces em Zig
Obtendo Stack Traces
Zig torna fácil obter informações de onde seu código está:
const std = @import("std");
fn funcaoC() void {
std.debug.print("Em funcaoC\n", .{});
// Capturar stack trace
var address_buffer: [10]usize = undefined;
const trace = std.debug.StackTrace{
.instruction_addresses = &address_buffer,
.index = 0,
};
// Em caso de erro, você pode capturar o trace
@panic("Erro intencional para demonstração");
}
fn funcaoB() void {
funcaoC();
}
fn funcaoA() void {
funcaoB();
}
pub fn main() void {
funcaoA();
}
Stack Trace de Erros
const std = @import("std");
fn podeFalhar() !void {
return error.ErroExemplo;
}
pub fn main() !void {
podeFalhar() catch |err| {
std.debug.print("Erro capturado: {s}\n", .{@errorName(err)});
// Em modo Debug, você tem stack trace automático
if (@errorReturnTrace()) |trace| {
std.debug.dumpStackTrace(trace.*);
}
return err;
};
}
Saída em modo Debug:
Erro capturado: ErroExemplo
/home/user/projeto/src/main.zig:5:5: 0x1032a0 in podeFalhar (main)
return error.ErroExemplo;
^
/home/user/projeto/src/main.zig:10:5: 0x1032f0 in main (main)
podeFalhar() catch |err| {
^
Debugging de Memória
GeneralPurposeAllocator (GPA)
O GeneralPurposeAllocator é seu aliado para detectar problemas de memória:
const std = @import("std");
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{
.safety = true, // Detecta use-after-free, double-free
.thread_safety = true, // Thread-safe
}){};
defer {
const deinit_status = gpa.deinit();
if (deinit_status == .leak) {
std.debug.print("⚠️ Memory leak detectado!\n", .{});
}
}
const allocator = gpa.allocator();
// Alocação
const ptr = try allocator.alloc(u8, 100);
defer allocator.free(ptr);
// Se você esquecer o free, o GPA detecta no deinit
}
Detectando Memory Leaks
const std = @import("std");
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
const allocator = gpa.allocator();
// ❌ Memory leak - esquecemos de liberar
const leaky = try allocator.alloc(u8, 100);
_ = leaky; // Nunca usamos free
// O deinit vai detectar
const status = gpa.deinit();
if (status == .leak) {
std.log.err("Memory leak detectado!", .{});
}
}
Saída:
error(gpa): memory address 0x7f3a4c000b90 leaked:
/home/user/projeto/src/main.zig:8:40: 0x103450 in main (main)
const leaky = try allocator.alloc(u8, 100);
^
Detectando Use-After-Free
const std = @import("std");
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
var ptr = try allocator.alloc(u8, 10);
allocator.free(ptr);
// ❌ Use-after-free - o GPA detecta
ptr[0] = 42; // Panic aqui!
}
Saída:
thread 12345 panic: Use of uninitialized memory
/home/user/projeto/src/main.zig:11:5: 0x1034a0 in main (main)
ptr[0] = 42;
^
Detectando Double-Free
const std = @import("std");
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
const ptr = try allocator.alloc(u8, 10);
allocator.free(ptr);
// ❌ Double-free - o GPA detecta
allocator.free(ptr); // Panic aqui!
}
Page Allocator para Valgrind
Para debugging avançado com Valgrind:
const std = @import("std");
pub fn main() !void {
// PageAllocator é compatível com Valgrind
var pa = std.heap.page_allocator;
const ptr = try pa.alloc(u8, 4096);
defer pa.free(ptr);
// Use valgrind para detectar problemas
// valgrind --leak-check=full ./meu-programa
}
Integração com VS Code
Configuração do Launch.json
Crie .vscode/launch.json:
{
"version": "0.2.0",
"configurations": [
{
"name": "Debug Zig",
"type": "lldb",
"request": "launch",
"program": "${workspaceFolder}/zig-out/bin/${input:program}",
"args": [],
"cwd": "${workspaceFolder}",
"preLaunchTask": "build"
}
],
"inputs": [
{
"id": "program",
"type": "pickString",
"description": "Qual programa debugar?",
"options": ["meu-programa"],
"default": "meu-programa"
}
]
}
Configuração de Build Tasks
.vscode/tasks.json:
{
"version": "2.0.0",
"tasks": [
{
"label": "build",
"type": "shell",
"command": "zig build",
"group": {
"kind": "build",
"isDefault": true
},
"presentation": {
"echo": true,
"reveal": "always",
"focus": false,
"panel": "shared"
},
"problemMatcher": ["$gcc"]
},
{
"label": "test",
"type": "shell",
"command": "zig build test",
"group": "test"
}
]
}
Extensão Zig para VS Code
Instale a extensão oficial:
- Abra VS Code
- Ctrl+Shift+X (extensões)
- Procure “Zig”
- Instale “Zig” (ziglang.org)
Features:
- Syntax highlighting
- Autocomplete
- Go to definition
- Format on save (
zig fmt) - Build tasks integradas
Breakpoints no VS Code
- Clique na margem esquerda para adicionar breakpoint (ponto vermelho)
- Pressione F5 para iniciar debugging
- Use F10 (step over), F11 (step into), Shift+F11 (step out)
- Variables panel mostra valores das variáveis
- Watch panel para monitorar expressões
Debugging de Erros Comuns
Erro: “attempt to use null value”
const std = @import("std");
pub fn main() void {
var ptr: ?*i32 = null;
// ❌ Erro: tentando dereferenciar null
ptr.?.* = 42; // Panic: attempt to use null value
}
Debug:
// ✅ Verifique antes de usar
if (ptr) |p| {
p.* = 42;
} else {
std.debug.print("ptr é null!\n", .{});
}
Erro: “index out of bounds”
const std = @import("std");
pub fn main() void {
const array = [5]i32{1, 2, 3, 4, 5};
const indice = 10;
// ❌ Erro: índice fora dos limites
const valor = array[indice]; // Panic aqui
}
Debug:
// ✅ Verifique bounds
std.debug.assert(indice < array.len);
const valor = array[indice];
Erro: “integer overflow”
const std = @import("std");
pub fn main() void {
var x: u8 = 255;
// ❌ Erro: overflow em modo Debug
x += 1; // Panic: integer overflow
}
Debug:
// ✅ Use funções saturadas ou checked
const y = std.math.add(u8, x, 1) catch |err| {
std.debug.print("Overflow! err={}\n", .{err});
return;
};
// Ou use wrapping se for intencional
x = @addWithOverflow(x, 1)[0];
Erro: “unreachable”
const std = @import("std");
fn parseNumero(input: []const u8) !i32 {
if (input.len == 0) return error.Vazio;
// ❌ Se chegar aqui com input inesperado
unreachable; // Panic!
}
pub fn main() !void {
try parseNumero("abc");
}
Debug:
fn parseNumero(input: []const u8) !i32 {
if (input.len == 0) return error.Vazio;
if (std.mem.eql(u8, input, "abc")) return error.Invalido;
// Agora unreachable é válido
unreachable;
}
Técnicas Avançadas de Debug
Custom Panic Handler
const std = @import("std");
pub fn panic(msg: []const u8, error_return_trace: ?*std.builtin.StackTrace, ret_addr: ?usize) noreturn {
std.debug.print("\n╔════════════════════════════════════╗\n", .{});
std.debug.print("║ PANIC DETECTADO! ║\n", .{});
std.debug.print("╚════════════════════════════════════╝\n", .{});
std.debug.print("Mensagem: {s}\n", .{msg});
if (error_return_trace) |trace| {
std.debug.print("\nStack trace:\n", .{});
std.debug.dumpStackTrace(trace.*);
}
std.process.exit(1);
}
pub fn main() void {
@panic("Erro de demonstração");
}
Logging Estruturado
const std = @import("std");
const LogLevel = enum {
debug,
info,
warn,
err,
};
fn log(comptime level: LogLevel, comptime fmt: []const u8, args: anytype) void {
const prefix = switch (level) {
.debug => "[DEBUG]",
.info => "[INFO]",
.warn => "[WARN]",
.err => "[ERROR]",
};
std.debug.print(prefix ++ " " ++ fmt ++ "\n", args);
}
pub fn main() void {
log(.info, "Aplicação iniciada", .{});
log(.debug, "Configuração carregada: {any}", .{{ .port = 8080 }});
log(.warn, "Conexão lenta detectada", .{});
}
Time-based Debugging
const std = @import("std");
var timer: std.time.Timer = undefined;
fn startTimer() void {
timer = std.time.Timer.start() catch unreachable;
}
fn logElapsed(comptime msg: []const u8) void {
const elapsed_ns = timer.read();
const elapsed_ms = @as(f64, @floatFromInt(elapsed_ns)) / 1_000_000.0;
std.debug.print("[TIME] {s}: {d:.2}ms\n", .{msg, elapsed_ms});
}
pub fn main() !void {
startTimer();
// Código a ser medido
var sum: u64 = 0;
for (0..1_000_000) |i| {
sum += i;
}
logElapsed("Loop de 1M iterações");
std.debug.print("Soma: {d}\n", .{sum});
}
Checklist de Debugging
Quando seu código não funciona:
- Build em modo Debug —
zig build(não ReleaseFast) - Adicione prints estratégicos — Antes e depois de operações suspeitas
- Verifique inputs — Print todos os argumentos recebidos
- Use asserts — Verifique pré-condições e pós-condições
- Verifique erros — Todos os
tryecatchestão corretos? - Use GPA — Detecte memory leaks automaticamente
- Leia stack traces — A última linha é onde o erro ocorreu
- Debugger — GDB/LLDB para investigação profunda
Comandos Úteis:
# Build com informações de debug
zig build
# Run com diagnósticos
zig build run
# Testes
zig build test
# GDB
gdb ./zig-out/bin/meu-programa
# LLDB
lldb ./zig-out/bin/meu-programa
# Valgrind (Linux)
valgrind --leak-check=full ./zig-out/bin/meu-programa
# Strace (system calls)
strace ./zig-out/bin/meu-programa
Resumo
| Situação | Ferramenta/Técnica |
|---|---|
| Debug rápido | std.debug.print |
| Stack trace | std.debug.dumpStackTrace |
| Verificações | std.debug.assert |
| Memory leaks | GeneralPurposeAllocator |
| Debug profundo | GDB ou LLDB |
| IDE integrado | VS Code + LLDB |
| Erros de memória | Valgrind (Linux) |
Próximos Passos
Agora que você domina debugging em Zig:
- 📊 Testes em Zig: Guia Completo — Escreva testes para prevenir bugs
- 💾 Gerenciamento de Memória em Zig — Entenda allocators em profundidade
- 🔧 Zig Build System — Configure builds otimizados
- 📚 Documentação de Debugging do Zig — Referência oficial
Tem dúvidas sobre debugging em Zig? Compartilhe com a comunidade!