# std.debug (Zig 0.16.0) Debugging utilities: panic handling, assertions, stack traces, hex dumps, and value tracing. Primary Zig 0.16 release-note source: https://ziglang.org/download/0.16.0/release-notes.html Zig 0.16 reworked debug information and expanded target support for segfault handling/unwinding. For application-owned output, create a writer from the caller's `std.Io` (for example `std.Io.File.stderr().writer(io, &buf)`). Low-level `std.debug.print` and stack-dump helpers intentionally use the configured debug I/O path instead. ## Quick Reference | Function | Purpose | |----------|---------| | `print(fmt, args)` | Printf-style debug output to stderr | | `panic(fmt, args)` | Format message and abort | | `assert(bool)` | Crash if false (optimized out in ReleaseFast) | | `dumpCurrentStackTrace(addr)` | Print stack trace to stderr | | `dumpHex(bytes)` | Print hexdump to stderr | ## Debug Printing ```zig const std = @import("std"); // Quick debug output (64-byte buffer, auto-flush) std.debug.print("value: {}\n", .{x}); std.debug.print("name: {s}, count: {d}\n", .{name, count}); // Print without newline std.debug.print("loading...", .{}); ``` **Note:** `std.debug.print` silently ignores errors. For production logging, use `std.log`. ## Format Specifiers Format string syntax: `{[argument][specifier]:[fill][alignment][width].[precision]}`. Named arguments use square brackets, for example `{[name]s}`. ### Type Specifiers | Specifier | Types | Output | |-----------|-------|--------| | `{}` | any | Default formatting | | `{s}` | `[]const u8`, `[*:0]const u8` | String | | `{d}` | int, float, enum | Decimal | | `{b}` | int, enum | Binary | | `{o}` | int, enum | Octal | | `{x}` | int, float, `[]u8`, enum | Lowercase hex | | `{X}` | int, float, `[]u8`, enum | Uppercase hex | | `{c}` | u8, u21 | ASCII character | | `{u}` | u21 | Unicode codepoint | | `{e}` | float | Scientific notation | | `{*}` | pointer | Address (`Type@0x...`) | | `{f}` | has `format` method | Custom formatter | | `{any}` | any | Debug representation with depth limit | ### Examples ```zig std.debug.print("{d}\n", .{42}); // "42" std.debug.print("{x}\n", .{255}); // "ff" std.debug.print("{X}\n", .{255}); // "FF" std.debug.print("{b}\n", .{5}); // "101" std.debug.print("{o}\n", .{64}); // "100" std.debug.print("{s}\n", .{"hello"}); // "hello" std.debug.print("{c}\n", .{'A'}); // "A" std.debug.print("{*}\n", .{&value}); // "i32@7fff5fbff8a0" // Floats std.debug.print("{d}\n", .{3.14159}); // "3.14159" std.debug.print("{e}\n", .{1234.5}); // "1.2345e+03" std.debug.print("{x}\n", .{@as(f32, 1.0)}); // "0x1.0p0" // Hex dump of bytes std.debug.print("{x}\n", .{"hello"}); // "68656c6c6f" ``` ### Width and Alignment ```zig std.debug.print("{d:5}\n", .{42}); // " 42" (right-aligned, width 5) std.debug.print("{d:<5}\n", .{42}); // "42 " (left-aligned) std.debug.print("{d:^5}\n", .{42}); // " 42 " (center-aligned) std.debug.print("{d:0>5}\n", .{42}); // "00042" (zero-padded) std.debug.print("{s:_<10}\n", .{"hi"}); // "hi________" (custom fill) ``` ### Precision ```zig std.debug.print("{d:.2}\n", .{3.14159}); // "3.14" std.debug.print("{e:.3}\n", .{1234.5}); // "1.234e+03" std.debug.print("{x:.4}\n", .{@as(f32, 1.0)}); // "0x1.0000p0" ``` ### Named and Positional Arguments ```zig // Positional std.debug.print("{0} {1} {0}\n", .{"a", "b"}); // "a b a" // Named (with struct) std.debug.print("{[name]s}: {[value]d}\n", .{ .name = "x", .value = 42 }); // Runtime width/precision std.debug.print("{[value]d:[width]}\n", .{ .value = 42, .width = 5 }); std.debug.print("{[value]d:.[precision]}\n", .{ .value = 3.14159, .precision = 2 }); ``` ### Escape Braces ```zig std.debug.print("{{literal braces}}\n", .{}); // "{literal braces}" ``` ### Custom Format Method Types can implement a `format` method for `{f}`: ```zig const Point = struct { x: f32, y: f32, pub fn format(self: @This(), writer: *std.Io.Writer) std.Io.Writer.Error!void { try writer.print("({d:.2}, {d:.2})", .{ self.x, self.y }); } }; const p = Point{ .x = 1.5, .y = 2.5 }; std.debug.print("{f}\n", .{p}); // "(1.50, 2.50)" ``` ### Any Format (Debug Representation) ```zig const data = .{ .x = 1, .list = &[_]u8{ 1, 2, 3 } }; std.debug.print("{any}\n", .{data}); // Prints struct with depth-limited recursion ``` ## Assertions ```zig // Runtime assertion (triggers illegal instruction on failure) std.debug.assert(x > 0); std.debug.assert(ptr != null); // Debug/ReleaseSafe: generates check // ReleaseFast/ReleaseSmall: optimized away (undefined behavior if false) ``` ### Specialized Assertions ```zig // Assert slice is readable (checks memory mapping) std.debug.assertReadable(slice); // Assert pointer alignment std.debug.assertAligned(ptr, .@"16"); // 16-byte alignment ``` ## Panic ```zig // Formatted panic message std.debug.panic("invalid state: {}", .{state}); // With explicit return address std.debug.panicExtra(@returnAddress(), "error: {s}", .{msg}); ``` Panic prints message + stack trace to stderr, then aborts. ## Stack Traces ### Dump Current Stack ```zig // Print current stack trace to stderr std.debug.dumpCurrentStackTrace(.{}); // Skip frames until this address std.debug.dumpCurrentStackTrace(.{ .first_address = @returnAddress() }); ``` ### Dump to Writer ```zig var buf: [4096]u8 = undefined; var locked = try io.lockStderr(&buf, null); defer io.unlockStderr(); try std.debug.writeCurrentStackTrace(.{}, locked.terminal()); ``` ### Capture Stack Trace ```zig var addrs: [32]usize = undefined; const trace = std.debug.captureCurrentStackTrace(.{}, &addrs); // Later: print captured trace std.debug.dumpStackTrace(&trace); ``` `StackUnwindOptions` can also carry a target-specific `cpu_context.Native` pointer and opt into unsafe fallback unwinding. The stack iterator itself is an implementation detail; use `captureCurrentStackTrace`, `writeCurrentStackTrace`, and `dumpCurrentStackTrace` as the public interface. ## Hex Dump ```zig const data = "Hello, World!\x00\x01\x02"; // Quick dump to stderr std.debug.dumpHex(data); // Output: // 7fff5fbff8a0 48 65 6C 6C 6F 2C 20 57 6F 72 6C 64 21 00 01 02 Hello, World!... // Fallible dump to an existing terminal abstraction try std.debug.dumpHexFallible(terminal, data); ``` `dumpHexFallible` accepts `std.Io.Terminal`, not a bare writer, because it uses terminal color/mode operations. Use `.no_color` when constructing a terminal for a non-terminal sink. Output format: - Address (lowercase hex) - 16 bytes per line (uppercase hex) - ASCII representation (`.` for non-printable, special chars for `\n`, `\r`, `\t`) ## Value Tracing Track where values originate and mutate during debugging: ```zig const Trace = std.debug.Trace; // Pre-configured: 2 traces, 4 stack frames const MyStruct = struct { value: u32, trace: Trace = .init, fn setValue(self: *@This(), v: u32) void { self.value = v; self.trace.add("setValue called"); } }; var s = MyStruct{ .value = 0 }; s.setValue(42); s.trace.dump(); // Prints stack traces with notes ``` ### Configurable Trace ```zig // Custom configuration: 4 trace slots, 8 stack frames per trace const MyTrace = std.debug.ConfigurableTrace(4, 8, true); var trace: MyTrace = .init; trace.add("first mutation"); trace.addAddr(@returnAddress(), "with explicit address"); // Check if tracing is enabled if (MyTrace.enabled) { trace.dump(); } // Use in format strings std.debug.print("trace: {}", .{trace}); ``` The predefined `std.debug.Trace` is enabled only in Debug mode. A custom `ConfigurableTrace` follows its explicit `is_enabled` argument; when disabled, its operations are no-ops and its storage is zero-sized. ## SafetyLock Debug helper to detect concurrent access violations: ```zig const SafetyLock = std.debug.SafetyLock; var lock: SafetyLock = .{}; fn criticalSection() void { lock.lock(); defer lock.unlock(); // ... protected code } fn checkNotLocked() void { lock.assertUnlocked(); // Panics if locked } ``` - `SafetyLock` follows runtime-safety mode: active in Debug and ReleaseSafe, and a no-op in ReleaseFast and ReleaseSmall. - `Trace` has the separate Debug-only default described above. ## Source Location ```zig const SourceLocation = std.debug.SourceLocation; const loc: SourceLocation = .{ .line = 42, .column = 10, .file_name = "src/main.zig", }; // Invalid/unknown location const unknown = SourceLocation.invalid; ``` ## Symbol Information ```zig const Symbol = std.debug.Symbol; // Resolved fields are optional because debug information may be incomplete. const sym: Symbol = .{ .name = "myFunction", .compile_unit_name = "main.zig", .source_location = .{ .line = 100, .column = 1, .file_name = "src/main.zig" }, }; // Unknown symbol const unknown: Symbol = .unknown; // all three fields are null ``` ## Segfault Handling ```zig // Check if platform supports segfault handling if (std.debug.have_segfault_handling_support) { // Attach handler (prints stack trace on SIGSEGV/SIGBUS/etc) std.debug.attachSegfaultHandler(); // Later: reset to default handler std.debug.resetSegfaultHandler(); } // Check if handler is enabled by default const enabled = std.debug.default_enable_segfault_handler; ``` **Note:** `maybeEnableSegfaultHandler()` is called automatically by the runtime if `std.options.enable_segfault_handler` is true. ## CPU Context Unwinding Signal handlers that already receive a native CPU context can pass it through `std.debug.StackUnwindOptions.context` to `captureCurrentStackTrace`, `writeCurrentStackTrace`, or `dumpCurrentStackTrace`. There are no public `std.debug.ThreadContext`, `getContext`, `copyContext`, or `dumpStackTraceFromBase` helpers in Zig 0.16; native context capture is target- and signal-handler-specific under `std.debug.cpu_context`. ## Valgrind Detection ```zig if (std.debug.inValgrind()) { // Running under Valgrind - may want different behavior std.debug.print("Valgrind detected\n", .{}); } ``` ## Debug Info Access ```zig // Get debug info for current executable const info = try std.debug.getSelfDebugInfo(); // Public methods are target-specific through SelfInfo. A broadly available // operation is resolving the owning module name; returned storage is owned by // SelfInfo rather than by the caller. const module_name = try info.getModuleName(io, address); std.debug.print("module: {s}\n", .{module_name}); ``` ## Constants ```zig // Whether runtime safety checks are enabled std.debug.runtime_safety // deprecated; reflects stdlib mode, not necessarily the caller's module // Whether platform can produce stack traces std.debug.sys_can_stack_trace // false on WASM, MIPS, etc. ``` ## Submodules | Module | Purpose | |--------|---------| | `std.debug.Dwarf` | DWARF debug info parser | | `std.debug.Pdb` | Windows PDB debug info parser | | `std.debug.SelfInfo` | Debug info for current executable | | `std.debug.Coverage` | Code coverage support | | `std.debug.cpu_context` | Target-specific native CPU context definitions | ## FullPanic Create custom panic handler with formatted safety messages: ```zig pub const panic = std.debug.FullPanic(myPanicFn); fn myPanicFn(msg: []const u8, ret_addr: ?usize) noreturn { // Custom panic handling (log to file, send telemetry, etc.) std.posix.abort(); } // Now safety checks use myPanicFn with descriptive messages: // - "sentinel mismatch: expected X, found Y" // - "index out of bounds: index N, len M" // - "attempt to unwrap error: ErrorName" // etc. ``` ## Locking stderr For multi-line debug output without interleaving: ```zig // Lock stderr and clear any progress indicators. The returned object exposes // a file writer and terminal; unlock performs the final flush automatically. var lock_buf: [256]u8 = undefined; const locked = std.debug.lockStderr(&lock_buf); defer std.debug.unlockStderr(); // Safe to write multiple lines try locked.file_writer.interface.writeAll("Line 1\n"); try locked.file_writer.interface.writeAll("Line 2\n"); ``` The matching function is spelled `unlockStderr`: ```zig var buf: [256]u8 = undefined; const locked = std.debug.lockStderr(&buf); defer std.debug.unlockStderr(); try locked.file_writer.interface.print("Complex output: {}\n", .{value}); ```