# 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. When debug output writes to stderr/stdout directly, use `std.Io.File.stderr().writer(io, &buf)` / `std.Io.File.stdout().writer(io, &buf)` rather than old `std.io` or `std.fs.File` APIs. ## 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: `{[arg]:[fill][alignment][width][.precision][specifier]}` ### 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}: {value}\n", .{ .name = "x", .value = 42 }); // Runtime width/precision std.debug.print("{d:[width]}\n", .{ .width = 5, 42 }); std.debug.print("{d:.[precision]}\n", .{ .precision = 2, 3.14159 }); ``` ### 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(null); // Skip frames until this address std.debug.dumpCurrentStackTrace(@returnAddress()); ``` ### Dump to Writer ```zig var buf: [4096]u8 = undefined; var stderr = std.Io.File.stderr().writer(io, &buf); try std.debug.dumpCurrentStackTraceToWriter(null, &stderr.interface); ``` ### Capture Stack Trace ```zig var addrs: [32]usize = undefined; var trace: std.builtin.StackTrace = .{ .instruction_addresses = &addrs, .index = 0, }; std.debug.captureStackTrace(@returnAddress(), &trace); // Later: print captured trace std.debug.dumpStackTrace(trace); ``` ### StackIterator Walk the stack manually: ```zig var it = std.debug.StackIterator.init(@returnAddress(), null); defer it.deinit(); while (it.next()) |return_address| { const addr = return_address -| 1; std.debug.print("0x{x}\n", .{addr}); } ``` ## 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!... // Dump to writer var buf: [256]u8 = undefined; var aw: std.io.Writer.Allocating = .init(allocator); defer aw.deinit(); try std.debug.dumpHexFallible(&aw.writer, .no_color, data); ``` 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}); ``` In release builds (`enabled = false`), all trace operations are no-ops with zero size. ## 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 } ``` - In Debug/ReleaseSafe: actively tracks lock state - In ReleaseFast/ReleaseSmall: all methods are no-ops ## 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; // Symbol with resolved source location 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 = .{}; // name = "???", compile_unit_name = "???" ``` ## 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. ## Thread Context Platform-specific CPU register state for stack unwinding: ```zig const ThreadContext = std.debug.ThreadContext; var ctx: ThreadContext = undefined; if (std.debug.getContext(&ctx)) { // ctx now contains register state std.debug.dumpStackTraceFromBase(&ctx, stderr); } // Copy context (handles internal pointers) var ctx_copy: ThreadContext = undefined; std.debug.copyContext(&original_ctx, &ctx_copy); ``` ## 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(); // Get symbol at address const symbol = try info.getSymbolAtAddress(allocator, address); defer if (symbol.source_location) |sl| allocator.free(sl.file_name); std.debug.print("{s}:{d}: {s}\n", .{ symbol.source_location.?.file_name, symbol.source_location.?.line, symbol.name, }); ``` ## Constants ```zig // Whether runtime safety checks are enabled std.debug.runtime_safety // true in Debug/ReleaseSafe // Whether platform can produce stack traces std.debug.sys_can_stack_trace // false on WASM, MIPS, etc. // Whether platform has ucontext_t std.debug.have_ucontext ``` ## 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.MemoryAccessor` | Safe memory access for unwinding | | `std.debug.Coverage` | Code coverage support | ## 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 std.debug.lockStdErr(); defer std.debug.unlockStdErr(); // Safe to write multiple lines var buf: [256]u8 = undefined; var stderr = std.Io.File.stderr().writer(io, &buf); try stderr.interface.writeAll("Line 1\n"); try stderr.interface.writeAll("Line 2\n"); try stderr.interface.flush(); ``` Or with a writer: ```zig var buf: [256]u8 = undefined; const writer = std.debug.lockStderrWriter(&buf); defer std.debug.unlockStderrWriter(); try writer.print("Complex output: {}\n", .{value}); ```