---
title: "Depuração e Profiling em Zig: GDB, LLDB, Valgrind, perf e Tracy"
url: "https://ziglang.com.br/artigos/zig-depuracao-profiling-tracy-valgrind-perf/"
markdown_url: "https://ziglang.com.br/artigos/zig-depuracao-profiling-tracy-valgrind-perf.MD"
description: "Guia prático para depurar e perfilar programas Zig em 2026: modos de build, stack traces, GDB/LLDB, Valgrind, perf, flamegraphs, Tracy, allocators e checklist de produção."
date: "2026-04-23"
author: ""
---

# Depuração e Profiling em Zig: GDB, LLDB, Valgrind, perf e Tracy

Guia prático para depurar e perfilar programas Zig em 2026: modos de build, stack traces, GDB/LLDB, Valgrind, perf, flamegraphs, Tracy, allocators e checklist de produção.


Depurar Zig é diferente de depurar uma aplicação escondida atrás de runtime pesado. O binário mostra mais do que você espera: stack trace, símbolo DWARF, erro de allocator, endereço de falha, custo de função, syscall lenta e diferença real entre `Debug`, `ReleaseSafe`, `ReleaseFast` e `ReleaseSmall`. Essa transparência é uma vantagem enorme, desde que você use as ferramentas certas.

Este guia reúne um fluxo prático para **depuração e profiling em Zig** em 2026. Vamos cobrir modos de build, stack traces, GDB e LLDB, Valgrind, `perf`, flamegraphs, Tracy, detecção de vazamento de memória, leitura de core dump e o que levar para produção sem transformar observabilidade em overhead permanente. Ele complementa os guias de [benchmarking em Zig](/artigos/zig-benchmarking-medir-performance/), [observabilidade em Zig](/artigos/zig-observabilidade/), [OpenTelemetry em Zig](/artigos/zig-opentelemetry-traces-metricas-logs/), [eBPF para observabilidade Linux](/artigos/zig-ebpf-observabilidade-rede-linux/) e [servidor HTTP em produção](/artigos/zig-http-server-producao/).

## Resposta rápida: qual ferramenta usar?

| Problema | Primeira ferramenta | Por quê |
|---|---|---|
| Crash local ou `panic` | Stack trace do Zig + GDB/LLDB | Mostra arquivo, linha, frames e variáveis sem instrumentação pesada |
| Vazamento ou uso inválido de memória | `GeneralPurposeAllocator` em Debug, depois Valgrind/ASan quando fizer sentido | Pega leak simples cedo e confirma comportamento nativo fora do teste |
| Função lenta em CPU | `perf record` + flamegraph | Mede o binário real no Linux, inclusive syscalls e bibliotecas C |
| Latência intermitente | Tracy ou logs estruturados com IDs de operação | Mostra timeline, zonas e correlação entre etapas |
| Regressão após refatoração | Benchmark pequeno + `ReleaseFast`/`ReleaseSafe` comparados | Evita otimizar ruído ou modo de build errado |
| Incidente em produção | logs, métricas, health checks, core dump e símbolos | Investiga sem recompilar às cegas |

A regra prática é simples: comece pelo mecanismo mais barato que responde à pergunta. Não abra Tracy para um erro óbvio de índice. Não rode benchmark sintético para um problema de DNS. Não use `ReleaseFast` para investigar UB que só aparece quando as verificações de segurança somem.

## Modos de build mudam o diagnóstico

Antes de culpar a ferramenta, confirme o modo de compilação. Zig muda bastante entre desenvolvimento, produção segura e otimização agressiva.

| Modo | Uso recomendado | O que você ganha | O que você perde |
|---|---|---|---|
| `Debug` | desenvolvimento e reprodução local | safety checks, stack traces completos, asserts úteis | performance realista |
| `ReleaseSafe` | produção conservadora e staging | otimização com várias verificações de segurança | parte do detalhe de debug, dependendo da configuração |
| `ReleaseFast` | hot path já validado | máxima performance | checks removidos; bugs podem virar corrupção silenciosa |
| `ReleaseSmall` | binário mínimo, embarcado, CLI distribuída | tamanho reduzido | menos informação para inspeção |

Para depurar, compile primeiro em `Debug`:

```bash
zig build -Doptimize=Debug
zig build test -Doptimize=Debug
```

Para comparar comportamento de produção:

```bash
zig build -Doptimize=ReleaseSafe
zig build -Doptimize=ReleaseFast
```

Se um bug só aparece em `ReleaseFast`, trate como sinal de comportamento indefinido, dado não inicializado, corrida, lifetime errado ou pressuposto quebrado. Reproduza em `ReleaseSafe` com asserts extras antes de tentar adivinhar pela performance.

## Stack traces e erros explícitos

Zig costuma entregar stack traces legíveis quando o binário mantém símbolos e quando o erro passa por caminhos rastreáveis. Isso já resolve muitos problemas sem debugger interativo.

Exemplo mínimo:

```zig
const std = @import("std");

fn divide(a: i32, b: i32) i32 {
    return @divTrunc(a, b);
}

pub fn main() void {
    const value = divide(10, 0);
    std.debug.print("valor={d}\n", .{value});
}
```

Em `Debug`, o erro aponta para a linha relevante. Em projetos maiores, preserve nomes de funções descritivos e evite engolir erros com `catch unreachable` fora de limites muito controlados. Um `try` que propaga erro com contexto claro costuma ser mais útil que uma mensagem genérica no topo.

Quando capturar erro para logar, registre operação, entrada sanitizada, duração e causa. Para serviços HTTP, use o mesmo ID de requisição nos logs e nas métricas. Esse padrão conversa com o guia de [observabilidade em Zig](/artigos/zig-observabilidade/) e evita que depuração local vire uma disciplina separada da produção.

## GDB e LLDB para inspeção interativa

Zig gera DWARF, então GDB e LLDB funcionam bem para inspeção de frames, variáveis, ponteiros e chamadas C. O fluxo típico é:

```bash
zig build-exe src/main.zig -O Debug -femit-bin=zig-debug-demo

gdb ./zig-debug-demo
# ou
lldb ./zig-debug-demo
```

Com GDB:

```gdb
break main
run
bt
frame 2
info locals
print minha_struct
next
step
continue
```

Com LLDB:

```lldb
breakpoint set --name main
run
thread backtrace
frame variable
next
step
continue
```

Dicas práticas:

- mantenha um caso de reprodução pequeno antes de abrir o debugger;
- coloque breakpoints em fronteiras: parser, alocação, chamada externa, handler HTTP, função de serialização;
- use watchpoints quando uma variável muda sem você saber onde;
- confirme se o binário inspecionado é o mesmo que falhou;
- ao depurar código com C, instale símbolos das bibliotecas quando possível.

Para aplicações servidoras, também vale iniciar o processo com configuração local mínima e chamar uma rota específica com `curl`. Isso reduz ruído e facilita repetir o mesmo passo após cada hipótese.

## Allocators como ferramenta de depuração

Em Zig, allocator não é detalhe: é parte do contrato. Isso torna vazamentos e lifetimes mais visíveis.

Durante desenvolvimento, use `GeneralPurposeAllocator` e verifique `deinit()`:

```zig
const std = @import("std");

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer {
        const leaked = gpa.deinit();
        if (leaked == .leak) {
            std.debug.print("vazamento detectado\n", .{});
        }
    }

    const allocator = gpa.allocator();
    const buffer = try allocator.alloc(u8, 1024);
    defer allocator.free(buffer);

    @memset(buffer, 0);
}
```

Use `ArenaAllocator` para lifetimes de requisição, parsing ou jobs curtos. Isso simplifica limpeza quando todos os objetos morrem juntos. Mas não use arena como desculpa para esconder crescimento ilimitado: em serviço long-running, arena por requisição deve ser destruída ao fim da operação.

Checklist de memória:

- quem aloca também documenta quem libera;
- buffers de resposta têm limite explícito;
- parsing de JSON define tamanho máximo;
- cache tem política de expiração;
- testes exercitam caminhos de erro, não só sucesso;
- logs não imprimem segredos nem buffers enormes.

## Valgrind, sanitizers e confirmação externa

O allocator de Zig pega muitos vazamentos em desenvolvimento, mas Valgrind ainda é útil para confirmar comportamento nativo, especialmente quando há interoperabilidade com C.

```bash
zig build -Doptimize=Debug
valgrind --leak-check=full --show-leak-kinds=all ./zig-out/bin/app
```

Quando o projeto usa bibliotecas C, rode um teste que passe pela fronteira C/Zig. Vazamento pode estar em uma função de inicialização, em callback, em buffer retornado por C ou em caminho de erro que pula limpeza.

Sanitizers também podem ajudar dependendo da versão do Zig, target e dependências. Se o build com sanitizer ficar instável ou não suportado para o target, não trate isso como bloqueio para investigar: volte para `Debug`, `ReleaseSafe`, Valgrind e teste de reprodução menor.

## perf para CPU real no Linux

Quando a pergunta é CPU, `perf` costuma ser o melhor primeiro passo em Linux. Ele mede o binário real, com otimização real, no kernel real.

```bash
zig build -Doptimize=ReleaseFast
perf record -F 99 -g -- ./zig-out/bin/app --bench
perf report
```

Para flamegraph:

```bash
perf script > perf.out
# depois gere o flamegraph com a ferramenta que você usa no ambiente
```

O que procurar:

- função aparecendo larga no topo sem motivo claro;
- parser ou formatação consumindo mais CPU que a regra de negócio;
- alocação em hot path;
- syscalls frequentes demais;
- lock ou espera que parece CPU por amostragem ruim;
- diferença grande entre entrada pequena e entrada real.

Não otimize o primeiro frame chamativo sem confirmar a hipótese. Às vezes a função aparece porque tudo passa por ela; o problema real está no número de chamadas, não no custo de cada chamada. Adicione contadores ou métricas antes de reescrever arquitetura.

## Tracy para timeline e zonas

Tracy é valioso quando você precisa enxergar tempo por zona, frame, thread ou operação. Ele é mais intrusivo que `perf`, mas muito bom para responder perguntas como: “qual etapa de uma requisição atrasou?”, “o parser está bloqueando?”, “o worker passa tempo esperando IO?”

Use Tracy quando:

- o problema é latência, não apenas CPU total;
- você precisa comparar fases de uma operação;
- há múltiplas threads ou workers;
- a sequência temporal importa.

Instrumente zonas pequenas e nomeadas. Evite marcar cada linha. O excesso de zona aumenta overhead e transforma a visualização em ruído. Bons nomes são de domínio: `parse_config`, `fetch_api`, `render_template`, `write_cache`, `handle_request`, `flush_metrics`.

Em produção, mantenha essa instrumentação desligada por padrão ou limitada a builds especiais. Para sinal contínuo, prefira logs estruturados, métricas e traces leves como discutido no guia de [OpenTelemetry em Zig](/artigos/zig-opentelemetry-traces-metricas-logs/).

## Core dumps e falhas que só aparecem fora da sua máquina

Alguns bugs só aparecem no host real. Nesses casos, configure core dump e preserve o binário com símbolos correspondente ao commit em produção.

Fluxo de investigação:

1. registrar commit, versão do Zig, target, flags e ambiente;
2. guardar binário e core dump;
3. abrir com GDB ou LLDB;
4. extrair backtrace, thread atual e variáveis locais;
5. reproduzir localmente com a menor entrada possível;
6. adicionar teste ou assert antes do fix.

Exemplo com GDB:

```bash
gdb ./zig-out/bin/app core.1234
(gdb) bt
(gdb) info threads
(gdb) thread apply all bt
```

Nunca dependa apenas de “reiniciou e parou de acontecer”. Se o processo caiu, transforme a queda em caso reproduzível, teste ou pelo menos alerta mais preciso.

## Debug local e observabilidade de produção devem se encontrar

A fronteira entre debugging e observabilidade é prática. Debugger responde “o que acontece agora nesta execução?”. Observabilidade responde “o que aconteceu em muitas execuções, inclusive quando ninguém estava olhando?”.

Para serviços Zig, um conjunto mínimo saudável inclui:

- logs estruturados com nível, operação e ID de correlação;
- métricas de latência, taxa de erro, fila, memória e conexões;
- health check separado de readiness;
- build info exposto de forma segura;
- limites claros para body, resposta, timeout e concorrência;
- core dumps ou crash reports controlados;
- alerta baseado em sintoma, não em ruído interno.

Esse conjunto reduz a necessidade de entrar no servidor para “dar uma olhada”. Quando um incidente acontece, você já sabe qual rota, qual versão, qual dependência e qual sintoma merecem depuração local.

## Checklist antes de chamar de otimizado

Antes de declarar vitória em performance:

- o teste usa dados parecidos com produção?
- o modo de build é o modo que você pretende comparar?
- há benchmark antes e depois?
- `perf` confirma a hipótese?
- alocação em hot path foi medida, não presumida?
- a mudança mantém legibilidade e segurança?
- o ganho compensa a complexidade?
- `zig build test` continua passando?
- logs, métricas e limites continuam intactos?

Zig facilita otimizações profundas, mas também facilita micro-otimizações desnecessárias. O melhor fluxo é medir, mudar pouco, medir de novo e registrar o motivo. Para um binário de produção, previsibilidade vale tanto quanto velocidade bruta.

## Perguntas frequentes

### Devo depurar sempre em Debug?

Comece em `Debug`, porque os checks e stack traces ajudam muito. Depois confirme em `ReleaseSafe` ou `ReleaseFast` se o bug depende de otimização. Problemas que só aparecem em `ReleaseFast` merecem suspeita extra de comportamento indefinido, dado não inicializado ou lifetime incorreto.

### Valgrind substitui o GeneralPurposeAllocator?

Não. Use o `GeneralPurposeAllocator` durante desenvolvimento e testes porque ele integra naturalmente com Zig. Use Valgrind para confirmação externa, interoperabilidade com C e investigações em que você quer observar o processo nativo por fora.

### perf ou Tracy?

Use `perf` para CPU real e amostragem no Linux. Use Tracy para timeline, zonas, threads e latência por etapa. Em muitos casos, os dois se complementam: `perf` aponta onde a CPU foi embora; Tracy mostra quando e em qual fase isso aconteceu.

### Posso deixar instrumentação pesada em produção?

Evite. Logs, métricas e traces leves podem ficar sempre ligados com limites de cardinalidade e amostragem. Profiling pesado, Tracy detalhado e dumps amplos devem ser acionados com cuidado, em ambiente controlado ou build específico.

### Como isso se conecta com observabilidade?

Depuração corrige um caso. Observabilidade ajuda a encontrar e priorizar os casos. Um serviço Zig bem instrumentado entrega logs, métricas e health checks suficientes para você reproduzir localmente o problema certo, em vez de adivinhar pelo sintoma errado.
