authorgravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2025-09-18 12:54:33+01:00
committergravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2025-09-30 13:44:55+01:00
log9434bab3134edadae7ae7e575f6b025cafc6a59a
tree141c3ad4208426f1cb4e613ce92a7fd7f57ea044
parent23d6381e8b5fb63bc9cba5cc5c78b7946ca4746a
signaturelock-open Commit is signed but in an unrecognized format.

std: work around crash parsing LLVM PDB

This crash exists on master, and seems to have existed since 2019; I think it's just very rare and depends on the exact binary generated. In theory, a stream block should always be a "data" block rather than a FPM block; the FPMs use blocks `1, 4097, 8193, ...` and `2, 4097, 8194, ...` respectively. However, I have observed LLVM emitting an otherwise valid PDB which maps FPM blocks into streams. This is not a bug in `std.debug.Pdb`, because `llvm-pdbutil` agrees with our stream indices. I think this is arguably an LLVM bug; however, we don't really lose anything from just weakening this check. To be fair, MSF doesn't have an explicit specification, and LLVM's documentation (which is the closest thing we have) does not explicitly state that FPM blocks cannot be mapped into streams, so perhaps this is actually valid. In the rare case that LLVM emits this, previously, stack traces would have been completely useless; now, stack traces will work okay.

1 files changed, 18 insertions(+), 18 deletions(-)

lib/std/debug/Pdb.zig+18-18
......@@ -413,8 +413,7 @@ const Msf = struct {
413413 return error.InvalidDebugInfo;
414414 if (superblock.free_block_map_block != 1 and superblock.free_block_map_block != 2)
415415 return error.InvalidDebugInfo;
416 const file_len = try file_reader.getSize();
417 if (superblock.num_blocks * superblock.block_size != file_len)
416 if (superblock.num_blocks * superblock.block_size != try file_reader.getSize())
418417 return error.InvalidDebugInfo;
419418 switch (superblock.block_size) {
420419 // llvm only supports 4096 but we can handle any of these values
......@@ -428,6 +427,7 @@ const Msf = struct {
428427
429428 try file_reader.seekTo(superblock.block_size * superblock.block_map_addr);
430429 const dir_blocks = try gpa.alloc(u32, dir_block_count);
430 errdefer gpa.free(dir_blocks);
431431 for (dir_blocks) |*b| {
432432 b.* = try file_reader.interface.takeInt(u32, .little);
433433 }
......@@ -451,25 +451,25 @@ const Msf = struct {
451451 const streams = try gpa.alloc(MsfStream, stream_count);
452452 errdefer gpa.free(streams);
453453
454 for (streams, 0..) |*stream, i| {
455 const size = stream_sizes[i];
454 for (streams, stream_sizes) |*stream, size| {
456455 if (size == 0) {
457456 stream.* = .empty;
458 } else {
459 const blocks = try gpa.alloc(u32, size);
460 errdefer gpa.free(blocks);
461 for (blocks) |*block| {
462 const block_id = try directory.interface.takeInt(u32, .little);
463 const n = (block_id % superblock.block_size);
464 // 0 is for pdb.SuperBlock, 1 and 2 for FPMs.
465 if (block_id == 0 or n == 1 or n == 2 or block_id * superblock.block_size > file_len)
466 return error.InvalidBlockIndex;
467 block.* = block_id;
468 }
469 const buffer = try gpa.alloc(u8, 64);
470 errdefer gpa.free(buffer);
471 stream.* = .init(superblock.block_size, file_reader, blocks, buffer);
457 continue;
458 }
459 const blocks = try gpa.alloc(u32, size);
460 errdefer gpa.free(blocks);
461 for (blocks) |*block| {
462 const block_id = try directory.interface.takeInt(u32, .little);
463 // Index 0 is reserved for the superblock.
464 // In theory, every page which is `n * block_size + 1` or `n * block_size + 2`
465 // is also reserved, for one of the FPMs. However, LLVM has been observed to map
466 // these into actual streams, so allow it for compatibility.
467 if (block_id == 0 or block_id >= superblock.num_blocks) return error.InvalidBlockIndex;
468 block.* = block_id;
472469 }
470 const buffer = try gpa.alloc(u8, 64);
471 errdefer gpa.free(buffer);
472 stream.* = .init(superblock.block_size, file_reader, blocks, buffer);
473473 }
474474
475475 const end = directory.logicalPos();