std.debug.Pdb: migrate more towards new Reader API

There was some bug in this branch, and rather than diagnosing it, I
fully finished porting over to new Reader API. Did it fix the bug?
This commit is contained in:
Andrew Kelley 2025-08-28 21:32:53 -07:00
parent 7da9e4b35e
commit 43fbc37a49
3 changed files with 188 additions and 169 deletions

View File

@ -143,8 +143,8 @@ pub const failing: Reader = .{
/// This is generally safe to `@constCast` because it has an empty buffer, so
/// there is not really a way to accidentally attempt mutation of these fields.
const ending_state: Reader = .fixed(&.{});
pub const ending: *Reader = @constCast(&ending_state);
pub const ending_instance: Reader = .fixed(&.{});
pub const ending: *Reader = @constCast(&ending_instance);
pub fn limited(r: *Reader, limit: Limit, buffer: []u8) Limited {
return .init(r, limit, buffer);

View File

@ -6,7 +6,7 @@ const assert = std.debug.assert;
const Pdb = @This();
in_file: File,
file_reader: *File.Reader,
msf: Msf,
allocator: Allocator,
string_table: ?*MsfStream,
@ -36,31 +36,28 @@ pub const Module = struct {
}
};
pub fn init(allocator: Allocator, path: []const u8) !Pdb {
const file = try std.fs.cwd().openFile(path, .{});
errdefer file.close();
pub fn init(gpa: Allocator, file_reader: *File.Reader) !Pdb {
return .{
.in_file = file,
.allocator = allocator,
.file_reader = file_reader,
.allocator = gpa,
.string_table = null,
.dbi = null,
.msf = try Msf.init(allocator, file),
.modules = &[_]Module{},
.sect_contribs = &[_]pdb.SectionContribEntry{},
.msf = try Msf.init(gpa, file_reader),
.modules = &.{},
.sect_contribs = &.{},
.guid = undefined,
.age = undefined,
};
}
pub fn deinit(self: *Pdb) void {
self.in_file.close();
self.msf.deinit(self.allocator);
const gpa = self.allocator;
self.msf.deinit(gpa);
for (self.modules) |*module| {
module.deinit(self.allocator);
module.deinit(gpa);
}
self.allocator.free(self.modules);
self.allocator.free(self.sect_contribs);
gpa.free(self.modules);
gpa.free(self.sect_contribs);
}
pub fn parseDbiStream(self: *Pdb) !void {
@ -68,11 +65,7 @@ pub fn parseDbiStream(self: *Pdb) !void {
return error.InvalidDebugInfo;
const gpa = self.allocator;
const deprecated_reader = stream.reader();
var adapted_buffer: [1024]u8 = undefined;
var adapted_reader = deprecated_reader.adaptToNewApi(&adapted_buffer);
const reader = &adapted_reader.new_interface;
const reader = &stream.interface;
const header = try reader.takeStruct(std.pdb.DbiStreamHeader, .little);
if (header.version_header != 19990903) // V70, only value observed by LLVM team
@ -113,7 +106,7 @@ pub fn parseDbiStream(self: *Pdb) !void {
this_record_len += march_forward_bytes;
}
try modules.append(Module{
try modules.append(.{
.mod_info = mod_info,
.module_name = try module_name.toOwnedSlice(),
.obj_file_name = try obj_file_name.toOwnedSlice(),
@ -156,29 +149,28 @@ pub fn parseDbiStream(self: *Pdb) !void {
}
pub fn parseInfoStream(self: *Pdb) !void {
var stream = self.getStream(pdb.StreamType.pdb) orelse
return error.InvalidDebugInfo;
const reader = stream.reader();
var stream = self.getStream(pdb.StreamType.pdb) orelse return error.InvalidDebugInfo;
const reader = &stream.interface;
// Parse the InfoStreamHeader.
const version = try reader.readInt(u32, .little);
const signature = try reader.readInt(u32, .little);
const version = try reader.takeInt(u32, .little);
const signature = try reader.takeInt(u32, .little);
_ = signature;
const age = try reader.readInt(u32, .little);
const guid = try reader.readBytesNoEof(16);
const age = try reader.takeInt(u32, .little);
const guid = try reader.takeArray(16);
if (version != 20000404) // VC70, only value observed by LLVM team
return error.UnknownPDBVersion;
self.guid = guid;
self.guid = guid.*;
self.age = age;
const gpa = self.allocator;
// Find the string table.
const string_table_index = str_tab_index: {
const name_bytes_len = try reader.readInt(u32, .little);
const name_bytes = try self.allocator.alloc(u8, name_bytes_len);
defer self.allocator.free(name_bytes);
try reader.readNoEof(name_bytes);
const name_bytes_len = try reader.takeInt(u32, .little);
const name_bytes = try reader.readAlloc(gpa, name_bytes_len);
const HashTableHeader = extern struct {
size: u32,
@ -188,23 +180,23 @@ pub fn parseInfoStream(self: *Pdb) !void {
return cap * 2 / 3 + 1;
}
};
const hash_tbl_hdr = try reader.readStruct(HashTableHeader);
const hash_tbl_hdr = try reader.takeStruct(HashTableHeader, .little);
if (hash_tbl_hdr.capacity == 0)
return error.InvalidDebugInfo;
if (hash_tbl_hdr.size > HashTableHeader.maxLoad(hash_tbl_hdr.capacity))
return error.InvalidDebugInfo;
const present = try readSparseBitVector(&reader, self.allocator);
defer self.allocator.free(present);
const present = try readSparseBitVector(reader, gpa);
defer gpa.free(present);
if (present.len != hash_tbl_hdr.size)
return error.InvalidDebugInfo;
const deleted = try readSparseBitVector(&reader, self.allocator);
defer self.allocator.free(deleted);
const deleted = try readSparseBitVector(reader, gpa);
defer gpa.free(deleted);
for (present) |_| {
const name_offset = try reader.readInt(u32, .little);
const name_index = try reader.readInt(u32, .little);
const name_offset = try reader.takeInt(u32, .little);
const name_index = try reader.takeInt(u32, .little);
if (name_offset > name_bytes.len)
return error.InvalidDebugInfo;
const name = std.mem.sliceTo(name_bytes[name_offset..], 0);
@ -302,14 +294,12 @@ pub fn getLineNumberInfo(self: *Pdb, module: *Module, address: u64) !std.debug.S
const strtab_offset = @sizeOf(pdb.StringTableHeader) + chksum_hdr.file_name_offset;
try self.string_table.?.seekTo(strtab_offset);
const source_file_name = s: {
const deprecated_reader = self.string_table.?.reader();
var adapted_buffer: [1024]u8 = undefined;
var adapted_reader = deprecated_reader.adaptToNewApi(&adapted_buffer);
const string_reader = &self.string_table.?.interface;
var source_file_name: std.Io.Writer.Allocating = .init(gpa);
defer source_file_name.deinit();
_ = try adapted_reader.new_interface.streamDelimiterLimit(&source_file_name.writer, 0, .limited(1024));
assert(adapted_reader.new_interface.buffered()[0] == 0); // TODO change streamDelimiterLimit API
adapted_reader.new_interface.toss(1);
_ = try string_reader.streamDelimiterLimit(&source_file_name.writer, 0, .limited(1024));
assert(string_reader.buffered()[0] == 0); // TODO change streamDelimiterLimit API
string_reader.toss(1);
break :s try source_file_name.toOwnedSlice();
};
errdefer gpa.free(source_file_name);
@ -366,19 +356,16 @@ pub fn getModule(self: *Pdb, index: usize) !?*Module {
const stream = self.getStreamById(mod.mod_info.module_sym_stream) orelse
return error.MissingDebugInfo;
const reader = stream.reader();
const reader = &stream.interface;
const signature = try reader.readInt(u32, .little);
const signature = try reader.takeInt(u32, .little);
if (signature != 4)
return error.InvalidDebugInfo;
mod.symbols = try self.allocator.alloc(u8, mod.mod_info.sym_byte_size - 4);
errdefer self.allocator.free(mod.symbols);
try reader.readNoEof(mod.symbols);
const gpa = self.allocator;
mod.subsect_info = try self.allocator.alloc(u8, mod.mod_info.c13_byte_size);
errdefer self.allocator.free(mod.subsect_info);
try reader.readNoEof(mod.subsect_info);
mod.symbols = try reader.readAlloc(gpa, mod.mod_info.sym_byte_size - 4);
mod.subsect_info = try reader.readAlloc(gpa, mod.mod_info.c13_byte_size);
var sect_offset: usize = 0;
var skip_len: usize = undefined;
@ -404,8 +391,7 @@ pub fn getModule(self: *Pdb, index: usize) !?*Module {
}
pub fn getStreamById(self: *Pdb, id: u32) ?*MsfStream {
if (id >= self.msf.streams.len)
return null;
if (id >= self.msf.streams.len) return null;
return &self.msf.streams[id];
}
@ -419,17 +405,14 @@ const Msf = struct {
directory: MsfStream,
streams: []MsfStream,
fn init(allocator: Allocator, file: File) !Msf {
const in = file.deprecatedReader();
fn init(gpa: Allocator, file_reader: *File.Reader) !Msf {
const superblock = try file_reader.interface.takeStruct(pdb.SuperBlock, .little);
const superblock = try in.readStruct(pdb.SuperBlock);
// Sanity checks
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;
const file_len = try file.getEndPos();
const file_len = try file_reader.getSize();
if (superblock.num_blocks * superblock.block_size != file_len)
return error.InvalidDebugInfo;
switch (superblock.block_size) {
@ -442,163 +425,182 @@ const Msf = struct {
if (dir_block_count > superblock.block_size / @sizeOf(u32))
return error.UnhandledBigDirectoryStream; // cf. BlockMapAddr comment.
try file.seekTo(superblock.block_size * superblock.block_map_addr);
const dir_blocks = try allocator.alloc(u32, dir_block_count);
try file_reader.seekTo(superblock.block_size * superblock.block_map_addr);
const dir_blocks = try gpa.alloc(u32, dir_block_count);
for (dir_blocks) |*b| {
b.* = try in.readInt(u32, .little);
b.* = try file_reader.interface.takeInt(u32, .little);
}
var directory = MsfStream.init(
superblock.block_size,
file,
dir_blocks,
);
var directory_buffer: [64]u8 = undefined;
var directory = MsfStream.init(superblock.block_size, file_reader, dir_blocks, &directory_buffer);
const begin = directory.pos;
const stream_count = try directory.reader().readInt(u32, .little);
const stream_sizes = try allocator.alloc(u32, stream_count);
defer allocator.free(stream_sizes);
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 = 0xFFFFFFFF;
const nil_size = 0xFFFFFFFF;
for (stream_sizes) |*s| {
const size = try directory.reader().readInt(u32, .little);
s.* = if (size == Nil) 0 else blockCountFromSize(size, superblock.block_size);
const size = try directory.interface.takeInt(u32, .little);
s.* = if (size == nil_size) 0 else blockCountFromSize(size, superblock.block_size);
}
const streams = try allocator.alloc(MsfStream, stream_count);
const streams = try gpa.alloc(MsfStream, stream_count);
errdefer gpa.free(streams);
for (streams, 0..) |*stream, i| {
const size = stream_sizes[i];
if (size == 0) {
stream.* = MsfStream{
.blocks = &[_]u32{},
};
stream.* = .empty;
} else {
var blocks = try allocator.alloc(u32, size);
var j: u32 = 0;
while (j < size) : (j += 1) {
const block_id = try directory.reader().readInt(u32, .little);
const blocks = try gpa.alloc(u32, size);
errdefer gpa.free(blocks);
for (blocks) |*block| {
const block_id = try directory.interface.takeInt(u32, .little);
const n = (block_id % superblock.block_size);
// 0 is for pdb.SuperBlock, 1 and 2 for FPMs.
if (block_id == 0 or n == 1 or n == 2 or block_id * superblock.block_size > file_len)
return error.InvalidBlockIndex;
blocks[j] = block_id;
block.* = block_id;
}
stream.* = MsfStream.init(
superblock.block_size,
file,
blocks,
);
const buffer = try gpa.alloc(u8, 64);
errdefer gpa.free(buffer);
stream.* = .init(superblock.block_size, file_reader, blocks, buffer);
}
}
const end = directory.pos;
const end = directory.logicalPos();
if (end - begin != superblock.num_directory_bytes)
return error.InvalidStreamDirectory;
return Msf{
return .{
.directory = directory,
.streams = streams,
};
}
fn deinit(self: *Msf, allocator: Allocator) void {
allocator.free(self.directory.blocks);
fn deinit(self: *Msf, gpa: Allocator) void {
gpa.free(self.directory.blocks);
for (self.streams) |*stream| {
allocator.free(stream.blocks);
gpa.free(stream.interface.buffer);
gpa.free(stream.blocks);
}
allocator.free(self.streams);
gpa.free(self.streams);
}
};
const MsfStream = struct {
in_file: File = undefined,
pos: u64 = undefined,
blocks: []u32 = undefined,
block_size: u32 = undefined,
file_reader: *File.Reader,
next_read_pos: u64,
blocks: []u32,
block_size: u32,
interface: std.Io.Reader,
err: ?Error,
pub const Error = @typeInfo(@typeInfo(@TypeOf(read)).@"fn".return_type.?).error_union.error_set;
const Error = File.Reader.SeekError;
fn init(block_size: u32, file: File, blocks: []u32) MsfStream {
const stream = MsfStream{
.in_file = file,
.pos = 0,
const empty: MsfStream = .{
.file_reader = undefined,
.next_read_pos = 0,
.blocks = &.{},
.block_size = undefined,
.interface = .ending_instance,
.err = null,
};
fn init(block_size: u32, file_reader: *File.Reader, blocks: []u32, buffer: []u8) MsfStream {
return .{
.file_reader = file_reader,
.next_read_pos = 0,
.blocks = blocks,
.block_size = block_size,
.interface = .{
.vtable = &.{ .stream = stream },
.buffer = buffer,
.seek = 0,
.end = 0,
},
.err = null,
};
return stream;
}
fn read(self: *MsfStream, buffer: []u8) !usize {
var block_id = @as(usize, @intCast(self.pos / self.block_size));
if (block_id >= self.blocks.len) return 0; // End of Stream
var block = self.blocks[block_id];
var offset = self.pos % self.block_size;
fn stream(r: *std.Io.Reader, w: *std.Io.Writer, limit: std.Io.Limit) std.Io.Reader.StreamError!usize {
const ms: *MsfStream = @alignCast(@fieldParentPtr("interface", r));
try self.in_file.seekTo(block * self.block_size + offset);
const in = self.in_file.deprecatedReader();
var block_id: usize = @intCast(ms.next_read_pos / ms.block_size);
if (block_id >= ms.blocks.len) return error.EndOfStream;
var block = ms.blocks[block_id];
var offset = ms.next_read_pos % ms.block_size;
var size: usize = 0;
var rem_buffer = buffer;
while (size < buffer.len) {
const size_to_read = @min(self.block_size - offset, rem_buffer.len);
size += try in.read(rem_buffer[0..size_to_read]);
rem_buffer = buffer[size..];
offset += size_to_read;
ms.file_reader.seekTo(block * ms.block_size + offset) catch |err| {
ms.err = err;
return error.ReadFailed;
};
var remaining = @intFromEnum(limit);
while (remaining != 0) {
const stream_len: usize = @min(remaining, ms.block_size - offset);
const n = try ms.file_reader.interface.stream(w, .limited(stream_len));
remaining -= n;
offset += n;
// If we're at the end of a block, go to the next one.
if (offset == self.block_size) {
if (offset == ms.block_size) {
offset = 0;
block_id += 1;
if (block_id >= self.blocks.len) break; // End of Stream
block = self.blocks[block_id];
try self.in_file.seekTo(block * self.block_size);
if (block_id >= ms.blocks.len) break; // End of Stream
block = ms.blocks[block_id];
ms.file_reader.seekTo(block * ms.block_size) catch |err| {
ms.err = err;
return error.ReadFailed;
};
}
}
self.pos += buffer.len;
return buffer.len;
const total = @intFromEnum(limit) - remaining;
ms.next_read_pos += total;
return total;
}
pub fn seekBy(self: *MsfStream, len: i64) !void {
self.pos = @as(u64, @intCast(@as(i64, @intCast(self.pos)) + len));
if (self.pos >= self.blocks.len * self.block_size)
return error.EOF;
pub fn logicalPos(ms: *const MsfStream) u64 {
return ms.next_read_pos - ms.interface.bufferedLen();
}
pub fn seekTo(self: *MsfStream, len: u64) !void {
self.pos = len;
if (self.pos >= self.blocks.len * self.block_size)
return error.EOF;
pub fn seekBy(ms: *MsfStream, len: i64) !void {
ms.next_read_pos = @as(u64, @intCast(@as(i64, @intCast(ms.logicalPos())) + len));
if (ms.next_read_pos >= ms.blocks.len * ms.block_size) return error.EOF;
ms.interface.tossBuffered();
}
fn getSize(self: *const MsfStream) u64 {
return self.blocks.len * self.block_size;
pub fn seekTo(ms: *MsfStream, len: u64) !void {
ms.next_read_pos = len;
if (ms.next_read_pos >= ms.blocks.len * ms.block_size) return error.EOF;
ms.interface.tossBuffered();
}
fn getFilePos(self: MsfStream) u64 {
const block_id = self.pos / self.block_size;
const block = self.blocks[block_id];
const offset = self.pos % self.block_size;
return block * self.block_size + offset;
fn getSize(ms: *const MsfStream) u64 {
return ms.blocks.len * ms.block_size;
}
pub fn reader(self: *MsfStream) std.io.GenericReader(*MsfStream, Error, read) {
return .{ .context = self };
fn getFilePos(ms: *const MsfStream) u64 {
const pos = ms.logicalPos();
const block_id = pos / ms.block_size;
const block = ms.blocks[block_id];
const offset = pos % ms.block_size;
return block * ms.block_size + offset;
}
};
fn readSparseBitVector(stream: anytype, allocator: Allocator) ![]u32 {
const num_words = try stream.readInt(u32, .little);
fn readSparseBitVector(reader: *std.Io.Reader, allocator: Allocator) ![]u32 {
const num_words = try reader.takeInt(u32, .little);
var list = std.array_list.Managed(u32).init(allocator);
errdefer list.deinit();
var word_i: u32 = 0;
while (word_i != num_words) : (word_i += 1) {
const word = try stream.readInt(u32, .little);
const word = try reader.takeInt(u32, .little);
var bit_i: u5 = 0;
while (true) : (bit_i += 1) {
if (word & (@as(u32, 1) << bit_i) != 0) {

View File

@ -713,22 +713,26 @@ pub const Module = switch (native_os) {
},
.uefi, .windows => struct {
base_address: usize,
pdb: ?Pdb = null,
dwarf: ?Dwarf = null,
pdb: ?Pdb,
dwarf: ?Dwarf,
coff_image_base: u64,
/// Only used if pdb is non-null
coff_section_headers: []coff.SectionHeader,
pub fn deinit(self: *@This(), allocator: Allocator) void {
pub fn deinit(self: *@This(), gpa: Allocator) void {
if (self.dwarf) |*dwarf| {
dwarf.deinit(allocator);
dwarf.deinit(gpa);
}
if (self.pdb) |*p| {
gpa.free(p.file_reader.interface.buffer);
gpa.destroy(p.file_reader);
p.deinit();
allocator.free(self.coff_section_headers);
gpa.free(self.coff_section_headers);
}
self.* = undefined;
}
fn getSymbolFromPdb(self: *@This(), relocated_address: usize) !?std.debug.Symbol {
@ -970,23 +974,25 @@ fn readMachODebugInfo(allocator: Allocator, macho_file: File) !Module {
};
}
fn readCoffDebugInfo(allocator: Allocator, coff_obj: *coff.Coff) !Module {
fn readCoffDebugInfo(gpa: Allocator, coff_obj: *coff.Coff) !Module {
nosuspend {
var di: Module = .{
.base_address = undefined,
.coff_image_base = coff_obj.getImageBase(),
.coff_section_headers = undefined,
.pdb = null,
.dwarf = null,
};
if (coff_obj.getSectionByName(".debug_info")) |_| {
// This coff file has embedded DWARF debug info
var sections: Dwarf.SectionArray = Dwarf.null_section_array;
errdefer for (sections) |section| if (section) |s| if (s.owned) allocator.free(s.data);
errdefer for (sections) |section| if (section) |s| if (s.owned) gpa.free(s.data);
inline for (@typeInfo(Dwarf.Section.Id).@"enum".fields, 0..) |section, i| {
sections[i] = if (coff_obj.getSectionByName("." ++ section.name)) |section_header| blk: {
break :blk .{
.data = try coff_obj.getSectionDataAlloc(section_header, allocator),
.data = try coff_obj.getSectionDataAlloc(section_header, gpa),
.virtual_address = section_header.virtual_address,
.owned = true,
};
@ -999,7 +1005,7 @@ fn readCoffDebugInfo(allocator: Allocator, coff_obj: *coff.Coff) !Module {
.is_macho = false,
};
try Dwarf.open(&dwarf, allocator);
try Dwarf.open(&dwarf, gpa);
di.dwarf = dwarf;
}
@ -1008,20 +1014,31 @@ fn readCoffDebugInfo(allocator: Allocator, coff_obj: *coff.Coff) !Module {
if (fs.path.isAbsolute(raw_path)) {
break :blk raw_path;
} else {
const self_dir = try fs.selfExeDirPathAlloc(allocator);
defer allocator.free(self_dir);
break :blk try fs.path.join(allocator, &.{ self_dir, raw_path });
const self_dir = try fs.selfExeDirPathAlloc(gpa);
defer gpa.free(self_dir);
break :blk try fs.path.join(gpa, &.{ self_dir, raw_path });
}
};
defer if (path.ptr != raw_path.ptr) allocator.free(path);
defer if (path.ptr != raw_path.ptr) gpa.free(path);
di.pdb = Pdb.init(allocator, path) catch |err| switch (err) {
const pdb_file = std.fs.cwd().openFile(path, .{}) catch |err| switch (err) {
error.FileNotFound, error.IsDir => {
if (di.dwarf == null) return error.MissingDebugInfo;
return di;
},
else => return err,
else => |e| return e,
};
errdefer pdb_file.close();
const pdb_file_reader_buffer = try gpa.alloc(u8, 4096);
errdefer gpa.free(pdb_file_reader_buffer);
const pdb_file_reader = try gpa.create(File.Reader);
errdefer gpa.destroy(pdb_file_reader);
pdb_file_reader.* = pdb_file.reader(pdb_file_reader_buffer);
di.pdb = try Pdb.init(gpa, pdb_file_reader);
try di.pdb.?.parseInfoStream();
try di.pdb.?.parseDbiStream();
@ -1029,8 +1046,8 @@ fn readCoffDebugInfo(allocator: Allocator, coff_obj: *coff.Coff) !Module {
return error.InvalidDebugInfo;
// Only used by the pdb path
di.coff_section_headers = try coff_obj.getSectionHeadersAlloc(allocator);
errdefer allocator.free(di.coff_section_headers);
di.coff_section_headers = try coff_obj.getSectionHeadersAlloc(gpa);
errdefer gpa.free(di.coff_section_headers);
return di;
}