'Unable to dump stack trace: InvalidBlockIndex' on Win 10, Zig 0.15.1/2

A few weeks ago I got stack traces, I took a break and came back and no stack traces. I have no idea what happened. I’ve been working on this project for 13 months, and always had stack traces. I upgraded to 0.15.1 in Nov/Dec it was working until later Jan, I came back to the project yesterday and no more stack traces:

thread 21540 panic: attempt to use null value
Unable to dump stack trace: InvalidBlockIndex

I have no idea how to continue my project without this, and I have no idea what caused it to stop. I know Win 10 keeps updating even though they say it’s EOL, and I saw my terminal change colors, did they break it? How do I get it back? I can’t continue my project without stack traces.

I’ve found stack traces to be very inconsistent on Windows with 0.15.x.

Adding a std.debug.print to the failing function can make them work again, assuming you know where it is panicing. Which makes it sound like stack / memory corruption, but I’ve had the same experience across several applications.

Unfortunately, I don’t have better advice than change some code, recompile and see if you get a stack trace.

I’ve never had a problem with a stack trace before. Ive gotten them consistently until now. I dont know how anyone could work without them. Print closures everywhere is monumental wasteful effort.

Adding prints around the problem didn’t force a stack. I guess have to change OSes to finish this work. Not going to be easy to launch games on Windows when I cant debug on Windows.

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);

}

I was testing one of my binaries on win 11 and I was not able to see a stack trace as well, but my issue was that I did not copy .pdb file to a location where I copied my .exe binary.
I have almost no experience with windows so I do not know if it’s relevant but I wanted to share what happened to me.

Thanks. I have my .pdb and .exe in the same directory. This worked for 13 months, but some hidden Win10 update around Feb 7-16 broke it. At least the RemedyBG works, so the PDB is fine it just won’t give me run time stack traces in the EXE, but RemedyBG has the stack and line number info, so I can at least finish my work.

I’ve read they are starting to move Win11 updates into Win10, and that CodeImport has changed with new meta-data, which is what broke it, but I don’t know the details.

1 Like

Hi there—I think this is an issue with either the Zig standard library’s PDB parsing, or the PDB files being emitted by LLVM. It’s very reminiscent of another issue I already had to work around in the standard library (because it kind of seems that LLVM is just ignoring some of the rules of the PDB format).

Would it be possible for someone experiencing this issue to send me the PDB file with which you’re getting these errors? Ideally the corresponding EXE too, but that’s not too important. I’ll try to take a look at that soon if someone can upload it somewhere.

Sure, here is a link to a .tar.gz for Win10 with EXE and PDB:

Thanks!