authorgravatar for mason@gamesbymason.comMason Remaley <mason@gamesbymason.com> 2026-04-07 15:57:58-07:00
committergravatar for mason@gamesbymason.comMason Remaley <mason@gamesbymason.com> 2026-04-12 04:01:29-07:00
log7ec2f2b27d72c46b6d2e0b68f8f91aaf61c2263e
tree579f049b96c82bed04ec47ab9dfafda853c68331
parenta1a8dd1b40d2f42e9df6fee053c2cb737e0753f7

Cleans up, implements handling for the change file binary annotation


2 files changed, 90 insertions(+), 76 deletions(-)

lib/std/debug/Pdb.zig+62-58
...@@ -312,7 +312,7 @@ pub const BinaryAnnotation = union(enum) {...@@ -312,7 +312,7 @@ pub const BinaryAnnotation = union(enum) {
312312
313 const PartialRange = struct {313 const PartialRange = struct {
314 line_offset: u32,314 line_offset: u32,
315 file_offset: u32,315 file_id: ?u32,
316 code_offset: u32,316 code_offset: u32,
317 code_length: ?u32,317 code_length: ?u32,
318 };318 };
...@@ -322,7 +322,7 @@ pub const BinaryAnnotation = union(enum) {...@@ -322,7 +322,7 @@ pub const BinaryAnnotation = union(enum) {
322 .annotations = annotations,322 .annotations = annotations,
323 .curr = .{323 .curr = .{
324 .line_offset = 0,324 .line_offset = 0,
325 .file_offset = 0,325 .file_id = null,
326 .code_offset = 0,326 .code_offset = 0,
327 .code_length = null,327 .code_length = null,
328 },328 },
...@@ -332,7 +332,7 @@ pub const BinaryAnnotation = union(enum) {...@@ -332,7 +332,7 @@ pub const BinaryAnnotation = union(enum) {
332332
333 pub const Range = struct {333 pub const Range = struct {
334 line_offset: u32,334 line_offset: u32,
335 file_offset: u32,335 file_id: ?u32,
336 code_offset: u32,336 code_offset: u32,
337 code_length: u32,337 code_length: u32,
338338
...@@ -352,7 +352,11 @@ pub const BinaryAnnotation = union(enum) {...@@ -352,7 +352,11 @@ pub const BinaryAnnotation = union(enum) {
352 if (self.prev) |*prev| prev.code_length = prev.code_length orelse length;352 if (self.prev) |*prev| prev.code_length = prev.code_length orelse length;
353 self.curr.code_offset += length;353 self.curr.code_offset += length;
354 },354 },
355 .change_file => @panic("unimplemented"),355 // LLVM has code to emit these, but I wasn't able to figure out how trigger it
356 // so this logic is untested.
357 .change_file => |file_id| {
358 self.curr.file_id = file_id;
359 },
356 // LLVM never emits this opcode, but it's clear enough how to interpret it so we may as360 // LLVM never emits this opcode, but it's clear enough how to interpret it so we may as
357 // well in case they use it in the future361 // well in case they use it in the future
358 .change_code_length_and_code_offset => |info| {362 .change_code_length_and_code_offset => |info| {
...@@ -402,7 +406,7 @@ pub const BinaryAnnotation = union(enum) {...@@ -402,7 +406,7 @@ pub const BinaryAnnotation = union(enum) {
402 .code_offset = self.curr.code_offset,406 .code_offset = self.curr.code_offset,
403 .code_length = self.curr.code_length,407 .code_length = self.curr.code_length,
404 .line_offset = self.curr.line_offset,408 .line_offset = self.curr.line_offset,
405 .file_offset = self.curr.file_offset,409 .file_id = self.curr.file_id,
406 };410 };
407 const prev = self.prev orelse continue;411 const prev = self.prev orelse continue;
408 const prev_code_length = prev.code_length orelse continue;412 const prev_code_length = prev.code_length orelse continue;
...@@ -410,7 +414,7 @@ pub const BinaryAnnotation = union(enum) {...@@ -410,7 +414,7 @@ pub const BinaryAnnotation = union(enum) {
410 .code_offset = prev.code_offset,414 .code_offset = prev.code_offset,
411 .code_length = prev_code_length,415 .code_length = prev_code_length,
412 .line_offset = prev.line_offset,416 .line_offset = prev.line_offset,
413 .file_offset = prev.file_offset,417 .file_id = prev.file_id,
414 };418 };
415 }419 }
416420
...@@ -421,7 +425,7 @@ pub const BinaryAnnotation = union(enum) {...@@ -421,7 +425,7 @@ pub const BinaryAnnotation = union(enum) {
421 .code_offset = prev.code_offset,425 .code_offset = prev.code_offset,
422 .code_length = prev_code_length,426 .code_length = prev_code_length,
423 .line_offset = prev.line_offset,427 .line_offset = prev.line_offset,
424 .file_offset = prev.file_offset,428 .file_id = prev.file_id,
425 };429 };
426 }430 }
427 };431 };
...@@ -588,30 +592,62 @@ pub fn getBinaryAnnotations(self: *Pdb, site: *align(1) const pdb.InlineSiteSym)...@@ -588,30 +592,62 @@ pub fn getBinaryAnnotations(self: *Pdb, site: *align(1) const pdb.InlineSiteSym)
588 return .{ .reader = Io.Reader.fixed(slice) };592 return .{ .reader = Io.Reader.fixed(slice) };
589}593}
590594
591pub fn calculateOffset(595pub fn getInlineSiteSourceLocation(
592 self: *Pdb,596 self: *Pdb,
597 mod: *Module,
593 site: *align(1) const pdb.InlineSiteSym,598 site: *align(1) const pdb.InlineSiteSym,
594 loc: std.debug.SourceLocation,599 inlinee_src_line: *align(1) const pdb.InlineeSourceLine,
595 offset_in_func: usize,600 offset_in_func: usize,
596) error{InvalidDebugInfo, MissingDebugInfo, ReadFailed}!?std.debug.SourceLocation {601) !?std.debug.SourceLocation {
597 var ranges: BinaryAnnotation.RangeIterator = .init(self.getBinaryAnnotations(site));602 var ranges: BinaryAnnotation.RangeIterator = .init(self.getBinaryAnnotations(site));
598 while (try ranges.next()) |range| {603 while (try ranges.next()) |range| {
599 if (range.contains(offset_in_func)) {604 if (!range.contains(offset_in_func)) continue;
600 var result: std.debug.SourceLocation = loc;605
601 result.line += range.line_offset;606 const file_id = range.file_id orelse inlinee_src_line.file_id;
602 return result;607 const file_name = try self.getFileName(mod, file_id);
603 }608 errdefer self.allocator.free(file_name);
609
610 return .{
611 .line = inlinee_src_line.source_line_num + range.line_offset,
612 // LLVM doesn't currently emit column information for inlined calls in PDBs.
613 .column = 0,
614 .file_name = file_name,
615 };
604 }616 }
605 return null;617 return null;
606}618}
607619
620pub fn getFileName(self: *Pdb, mod: *Module, file_id: u32) ![]const u8 {
621 const checksum_offset = mod.checksum_offset orelse return error.MissingDebugInfo;
622 const subsect_index = checksum_offset + file_id;
623 const chksum_hdr: *align(1) pdb.FileChecksumEntryHeader = @ptrCast(&mod.subsect_info[subsect_index]);
624 const strtab_offset = @sizeOf(pdb.StringTableHeader) + chksum_hdr.file_name_offset;
625 self.string_table.?.seekTo(strtab_offset) catch return error.InvalidDebugInfo;
626 const string_reader = &self.string_table.?.interface;
627 var source_file_name: Io.Writer.Allocating = .init(self.allocator);
628 defer source_file_name.deinit();
629 _ = try string_reader.streamDelimiterLimit(&source_file_name.writer, 0, .limited(1024));
630 assert(string_reader.buffered()[0] == 0); // TODO change streamDelimiterLimit API
631 string_reader.toss(1);
632 return try source_file_name.toOwnedSlice();
633}
634
608pub fn getSymbolName(self: *Pdb, proc_sym: *align(1) const pdb.ProcSym) []const u8 {635pub fn getSymbolName(self: *Pdb, proc_sym: *align(1) const pdb.ProcSym) []const u8 {
609 _ = self;636 _ = self;
610 return std.mem.sliceTo(@as([*:0]const u8, @ptrCast(&proc_sym.name[0])), 0);637 return std.mem.sliceTo(@as([*:0]const u8, @ptrCast(&proc_sym.name[0])), 0);
611}638}
612639
613pub fn getInlineeInfo(self: *Pdb, mod: *Module, inlinee: u32) !std.debug.SourceLocation {640pub const InlineeSourceLine = struct {
614 const gpa = self.allocator;641 signature: pdb.InlineeSourceLineSignature,
642 info: *align(1) const pdb.InlineeSourceLine,
643};
644
645pub fn getInlineeSourceLine(
646 self: *Pdb,
647 mod: *Module,
648 inlinee: u32,
649) ?InlineeSourceLine {
650 _ = self;
615 var sect_offset: usize = 0;651 var sect_offset: usize = 0;
616 var skip_len: usize = undefined;652 var skip_len: usize = undefined;
617 while (sect_offset < mod.subsect_info.len) : (sect_offset += skip_len) {653 while (sect_offset < mod.subsect_info.len) : (sect_offset += skip_len) {
...@@ -631,7 +667,7 @@ pub fn getInlineeInfo(self: *Pdb, mod: *Module, inlinee: u32) !std.debug.SourceL...@@ -631,7 +667,7 @@ pub fn getInlineeInfo(self: *Pdb, mod: *Module, inlinee: u32) !std.debug.SourceL
631 };667 };
632668
633 while (offset < sect_offset + subsect_hdr.length) {669 while (offset < sect_offset + subsect_hdr.length) {
634 const entry: *const align(1) pdb.InlineeSourceLine = @ptrCast(&mod.subsect_info[offset]);670 const inlinee_src_line: *const align(1) pdb.InlineeSourceLine = @ptrCast(&mod.subsect_info[offset]);
635 offset += @sizeOf(pdb.InlineeSourceLine);671 offset += @sizeOf(pdb.InlineeSourceLine);
636672
637 if (has_extra_files) {673 if (has_extra_files) {
...@@ -640,33 +676,14 @@ pub fn getInlineeInfo(self: *Pdb, mod: *Module, inlinee: u32) !std.debug.SourceL...@@ -640,33 +676,14 @@ pub fn getInlineeInfo(self: *Pdb, mod: *Module, inlinee: u32) !std.debug.SourceL
640 offset += file_count.* * @sizeOf(u32);676 offset += file_count.* * @sizeOf(u32);
641 }677 }
642678
643 if (entry.inlinee == inlinee) {679 if (inlinee_src_line.inlinee == inlinee) return .{
644 const source_file_name = s: {680 .signature = signature.*,
645 const checksum_offset = mod.checksum_offset orelse return error.MissingDebugInfo;681 .info = inlinee_src_line,
646 const subsect_index = checksum_offset + entry.file_id;682 };
647 const chksum_hdr: *align(1) pdb.FileChecksumEntryHeader = @ptrCast(&mod.subsect_info[subsect_index]);
648 const strtab_offset = @sizeOf(pdb.StringTableHeader) + chksum_hdr.file_name_offset;
649 try self.string_table.?.seekTo(strtab_offset);
650 const string_reader = &self.string_table.?.interface;
651 var source_file_name: Io.Writer.Allocating = .init(gpa);
652 defer source_file_name.deinit();
653 _ = try string_reader.streamDelimiterLimit(&source_file_name.writer, 0, .limited(1024));
654 assert(string_reader.buffered()[0] == 0); // TODO change streamDelimiterLimit API
655 string_reader.toss(1);
656 break :s try source_file_name.toOwnedSlice();
657 };
658 errdefer gpa.free(source_file_name);
659
660 return .{
661 .line = entry.source_line_num,
662 .column = 0,
663 .file_name = source_file_name,
664 };
665 }
666 }683 }
667 }684 }
668 }685 }
669 return error.MissingDebugInfo;686 return null;
670}687}
671688
672pub fn getLineNumberInfo(self: *Pdb, module: *Module, address: u64) !std.debug.SourceLocation {689pub fn getLineNumberInfo(self: *Pdb, module: *Module, address: u64) !std.debug.SourceLocation {
...@@ -676,7 +693,6 @@ pub fn getLineNumberInfo(self: *Pdb, module: *Module, address: u64) !std.debug.S...@@ -676,7 +693,6 @@ pub fn getLineNumberInfo(self: *Pdb, module: *Module, address: u64) !std.debug.S
676693
677 var sect_offset: usize = 0;694 var sect_offset: usize = 0;
678 var skip_len: usize = undefined;695 var skip_len: usize = undefined;
679 const checksum_offset = module.checksum_offset orelse return error.MissingDebugInfo;
680 while (sect_offset != subsect_info.len) : (sect_offset += skip_len) {696 while (sect_offset != subsect_info.len) : (sect_offset += skip_len) {
681 const subsect_hdr: *align(1) pdb.DebugSubsectionHeader = @ptrCast(&subsect_info[sect_offset]);697 const subsect_hdr: *align(1) pdb.DebugSubsectionHeader = @ptrCast(&subsect_info[sect_offset]);
682 skip_len = subsect_hdr.length;698 skip_len = subsect_hdr.length;
...@@ -723,20 +739,8 @@ pub fn getLineNumberInfo(self: *Pdb, module: *Module, address: u64) !std.debug.S...@@ -723,20 +739,8 @@ pub fn getLineNumberInfo(self: *Pdb, module: *Module, address: u64) !std.debug.S
723739
724 // line_i == 0 would mean that no matching pdb.LineNumberEntry was found.740 // line_i == 0 would mean that no matching pdb.LineNumberEntry was found.
725 if (line_i > 0) {741 if (line_i > 0) {
726 const subsect_index = checksum_offset + block_hdr.name_index;742 const file_name = try self.getFileName(module, block_hdr.name_index);
727 const chksum_hdr: *align(1) pdb.FileChecksumEntryHeader = @ptrCast(&module.subsect_info[subsect_index]);743 errdefer gpa.free(file_name);
728 const strtab_offset = @sizeOf(pdb.StringTableHeader) + chksum_hdr.file_name_offset;
729 try self.string_table.?.seekTo(strtab_offset);
730 const source_file_name = s: {
731 const string_reader = &self.string_table.?.interface;
732 var source_file_name: Io.Writer.Allocating = .init(gpa);
733 defer source_file_name.deinit();
734 _ = try string_reader.streamDelimiterLimit(&source_file_name.writer, 0, .limited(1024));
735 assert(string_reader.buffered()[0] == 0); // TODO change streamDelimiterLimit API
736 string_reader.toss(1);
737 break :s try source_file_name.toOwnedSlice();
738 };
739 errdefer gpa.free(source_file_name);
740744
741 const line_entry_idx = line_i - 1;745 const line_entry_idx = line_i - 1;
742746
...@@ -751,7 +755,7 @@ pub fn getLineNumberInfo(self: *Pdb, module: *Module, address: u64) !std.debug.S...@@ -751,7 +755,7 @@ pub fn getLineNumberInfo(self: *Pdb, module: *Module, address: u64) !std.debug.S
751 const line_num_entry: *align(1) pdb.LineNumberEntry = @ptrCast(&subsect_info[found_line_index]);755 const line_num_entry: *align(1) pdb.LineNumberEntry = @ptrCast(&subsect_info[found_line_index]);
752756
753 return .{757 return .{
754 .file_name = source_file_name,758 .file_name = file_name,
755 .line = line_num_entry.flags.start,759 .line = line_num_entry.flags.start,
756 .column = column,760 .column = column,
757 };761 };
lib/std/debug/SelfInfo/Windows.zig+28-18
...@@ -64,26 +64,36 @@ pub const SymbolIterator = struct {...@@ -64,26 +64,36 @@ pub const SymbolIterator = struct {
6464
65 // Get the next inlinee if it exists65 // Get the next inlinee if it exists
66 if (info.proc) |proc| {66 if (info.proc) |proc| {
67 const offset_in_func = info.addr - proc.code_offset;
67 while (info.inline_sites.pop()) |site| {68 while (info.inline_sites.pop()) |site| {
68 if (pdb.getInlineeInfo(info.module, site.inlinee) catch null) |loc| {69 // If our address points into this site, get the source location it points
69 const offset_in_func = info.addr - proc.code_offset;70 // at
70 if (try pdb.calculateOffset(site, loc, offset_in_func)) |offset| {71 const inlinee_src_line = pdb.getInlineeSourceLine(
71 // If we've found a match, filter out any duplicate sites that72 info.module,
72 // follow. Tools like llvm-addr2line output duplicate sites in the73 site.inlinee,
73 // same cases as us, implying that they exist in the underlying74 ) orelse continue;
74 // data and are not indicative of a parser bug.75 const maybe_loc = pdb.getInlineSiteSourceLocation(
75 while (info.inline_sites.getLastOrNull()) |top| {76 info.module,
76 if (top.inlinee != site.inlinee) break;77 site,
77 _ = info.inline_sites.pop();78 inlinee_src_line.info,
78 }79 offset_in_func,
7980 ) catch continue;
80 return .{81 const loc = maybe_loc orelse continue;
81 .name = pdb.findInlineeName(site.inlinee),82
82 .compile_unit_name = fs.path.basename(info.module.obj_file_name),83 // If we've found a match, filter out any duplicates that might follow.
83 .source_location = offset,84 // Tools like llvm-addr2line output duplicate sites in the same cases as us,
84 };85 // implying that they exist in the underlying data and are not indicative of
85 }86 // a parser bug.
87 while (info.inline_sites.getLastOrNull()) |top| {
88 if (top.inlinee != site.inlinee) break;
89 _ = info.inline_sites.pop();
86 }90 }
91
92 return .{
93 .name = pdb.findInlineeName(site.inlinee),
94 .compile_unit_name = fs.path.basename(info.module.obj_file_name),
95 .source_location = loc,
96 };
87 }97 }
88 }98 }
8999