I have a workaround now using RemedyBG and adding a custom panic to invoke in my root.zig:
pub fn panic(msg: []const u8, _: ?*@import(“std”).builtin.StackTrace, _: ?usize) noreturn {
std.debug.print(“Panic: {s}\n”, .{msg});
@breakpoint();
std.process.exit(1);
}
I tried doing PDB data handling myself, then switching to Dwarf data handling by forcing that to be used, but I got the filename, but never fn or line number that I could use. I did get the PDB line numbers and files from the PDB method, some maybe I did something wrong here:
pub fn panic(msg: const u8, stack_trace: ?*std.builtin.StackTrace, ret_addr: ?usize) noreturn {
if (ret_addr) |addr| {
std.debug.print(“\n!!! CRASH ADDRESS: 0x{x} !!!\n”, .{addr});
}
// 1. Create stderr writer with buffer
var stderr_buf: [4096]u8 = undefined;
var stderr_writer = std.fs.File.stderr().writer(&stderr_buf);
const stderr = &stderr_writer.interface;
stderr.print("\n--- PANIC: {s} ---\n", .{msg}) catch {};
// 2. Load debug info
const debug_info: *std.debug.SelfInfo = std.debug.getSelfDebugInfo() catch |err| {
stderr.print("Unable to open debug info: {s}\n", .{@errorName(err)}) catch {};
stderr.flush() catch {};
std.process.exit(100);
};
const tty_config = std.io.tty.detectConfig(std.fs.File.stderr());
std.debug.print("Stack Track:\n", .{});
// 3. Use builtin stack trace if available, otherwise walk manually
if (stack_trace) |st| {
std.debug.print("Builtin Stack Trace: Stack Count: {} Index: {}\n", .{ st.instruction_addresses.len, st.index });
// const frame_count: usize = @min(st.index, st.instruction_addresses.len);
const frame_count: usize = st.instruction_addresses.len;
var frame_index: usize = 0;
std.debug.print("Frame Count: {} Frame Index: {}\n", .{ frame_count, frame_index });
while (frame_index < frame_count) : (frame_index += 1) {
std.debug.print("Frame Index: {}\n", .{frame_index});
if (st.instruction_addresses.len <= frame_index) {
std.debug.print("Error: going out of bounds, breaking\n", .{});
break;
}
const return_address = st.instruction_addresses[frame_index];
const address = return_address -| 1;
// Try getting symbol directly. Direct version of printSourceAtAddress()
const module: *std.debug.SelfInfo.Module = debug_info.getModuleForAddress(address) catch {
std.debug.print(" 0x{x} (no module)\n", .{address});
continue;
};
const relative_address = address - module.base_address;
// Module contains dwarf field - need to find compile unit first
const module_name = debug_info.getModuleNameForAddress(address) orelse "???";
std.debug.print(" {s}: 0x{x}\n", .{ module_name, address });
// Debug: check what's in the module for Windows
std.debug.print(" {s}: 0x{x} - dwarf null: {}, pdb null: {}\n", .{
module_name,
address,
module.dwarf == null,
module.pdb == null,
});
if (module.pdb) |*pdb| {
// Try each PDB module safely
var found_line = false;
for (0..pdb.modules.len) |i| {
// SAFETY: Catch all errors including panics from bounds violations
const pdb_mod = pdb.getModule(i) catch continue;
if (pdb_mod == null) continue;
// SAFETY: This can panic from array out of bounds - we need to check first
// Check if module has valid data before trying line lookup
if (pdb_mod.?.subsect_info.len == 0) continue;
if (pdb_mod.?.checksum_offset == null) continue;
const line_info = pdb.getLineNumberInfo(pdb_mod.?, relative_address) catch |e| {
// Expected errors - just continue to next module
std.debug.print("Error: {}\n", .{e});
continue;
};
std.debug.print(" {s}:{d}:{d}\n", .{
line_info.file_name,
line_info.line,
line_info.column,
});
found_line = true;
break;
}
if (!found_line) {
// Fallback to section contribution
for (pdb.sect_contribs) |contrib| {
const sect_start = contrib.offset;
const sect_end = sect_start + contrib.size;
if (relative_address >= sect_start and relative_address < sect_end) {
if (contrib.module_index < pdb.modules.len) {
std.debug.print(" {s} +0x{x}\n", .{
pdb.modules[contrib.module_index].obj_file_name,
relative_address - sect_start,
});
break;
}
}
}
}
}
}
} else {
if (ret_addr != null) {
std.debug.print("Manual Stack Trace: ret_addr: {}\n", .{ret_addr.?});
} else {
std.debug.print("Manual Stack Trace: ret_addr: null\n", .{});
}
// Manual stack walking fallback
const first_address = ret_addr orelse @returnAddress();
var it: std.debug.StackIterator = .init(first_address, null);
while (it.next()) |return_address| {
const address = return_address -| 1;
std.debug.printSourceAtAddress(
debug_info,
stderr,
address,
tty_config,
) catch |err| {
stderr.print(" [0x{x} resolution failed: {s}]\n", .{ address, @errorName(err) }) catch {};
continue;
};
}
}
std.debug.print("End Stack Trace\n", .{});
stderr.flush() catch {};
std.process.exit(101);
}