https://llvm.org/docs/PDB/MsfFile.html
const Msf = struct
const Msf = struct {
directory: MsfStream,
streams: []MsfStream,
fn init(gpa: Allocator, file_reader: *File.Reader) !Msf {
const superblock = try file_reader.interface.takeStruct(pdb.SuperBlock, .little);
if (!std.mem.eql(u8, &superblock.file_magic, pdb.SuperBlock.expect_magic))
return error.InvalidDebugInfo;
if (superblock.free_block_map_block != 1 and superblock.free_block_map_block != 2)
return error.InvalidDebugInfo;
if (superblock.num_blocks * superblock.block_size != try file_reader.getSize())
return error.InvalidDebugInfo;
switch (superblock.block_size) {
// llvm only supports 4096 but we can handle any of these values
512, 1024, 2048, 4096 => {},
else => return error.InvalidDebugInfo,
}
const dir_block_count = blockCountFromSize(superblock.num_directory_bytes, superblock.block_size);
if (dir_block_count > superblock.block_size / @sizeOf(u32))
return error.UnhandledBigDirectoryStream; // cf. BlockMapAddr comment.
try file_reader.seekTo(superblock.block_size * superblock.block_map_addr);
const dir_blocks = try gpa.alloc(u32, dir_block_count);
errdefer gpa.free(dir_blocks);
for (dir_blocks) |*b| {
b.* = try file_reader.interface.takeInt(u32, .little);
}
var directory_buffer: [64]u8 = undefined;
var directory = MsfStream.init(superblock.block_size, file_reader, dir_blocks, &directory_buffer);
const begin = directory.logicalPos();
const stream_count = try directory.interface.takeInt(u32, .little);
const stream_sizes = try gpa.alloc(u32, stream_count);
defer gpa.free(stream_sizes);
// Microsoft's implementation uses @as(u32, -1) for inexistent streams.
// These streams are not used, but still participate in the file
// and must be taken into account when resolving stream indices.
const nil_size = 0xFFFFFFFF;
for (stream_sizes) |*s| {
const size = try directory.interface.takeInt(u32, .little);
s.* = if (size == nil_size) 0 else blockCountFromSize(size, superblock.block_size);
}
const streams = try gpa.alloc(MsfStream, stream_count);
errdefer gpa.free(streams);
for (streams, stream_sizes) |*stream, size| {
if (size == 0) {
stream.* = .empty;
continue;
}
const blocks = try gpa.alloc(u32, size);
errdefer gpa.free(blocks);
for (blocks) |*block| {
const block_id = try directory.interface.takeInt(u32, .little);
// Index 0 is reserved for the superblock.
// In theory, every page which is `n * block_size + 1` or `n * block_size + 2`
// is also reserved, for one of the FPMs. However, LLVM has been observed to map
// these into actual streams, so allow it for compatibility.
if (block_id == 0 or block_id >= superblock.num_blocks) return error.InvalidBlockIndex;
block.* = block_id;
}
const buffer = try gpa.alloc(u8, 64);
errdefer gpa.free(buffer);
stream.* = .init(superblock.block_size, file_reader, blocks, buffer);
}
const end = directory.logicalPos();
if (end - begin != superblock.num_directory_bytes)
return error.InvalidStreamDirectory;
return .{
.directory = directory,
.streams = streams,
};
}
fn deinit(self: *Msf, gpa: Allocator) void {
gpa.free(self.directory.blocks);
for (self.streams) |*stream| {
gpa.free(stream.interface.buffer);
gpa.free(stream.blocks);
}
gpa.free(self.streams);
}
}