authorgravatar for mason@gamesbymason.comMason Remaley <mason@gamesbymason.com> 2026-04-11 22:59:52-07:00
committergravatar for mason@gamesbymason.comMason Remaley <mason@gamesbymason.com> 2026-04-12 04:01:30-07:00
log312ef9558b68898b5402796a94f4bc97a05b308d
tree7ebe015be691a6e5343b732b88ceb63e5165b34c
parent541bd6c3693cc57850dc5d78257584dd82f00ec6

Mitigation for bug that results in reuse of inlinee IDs when functions share names


2 files changed, 131 insertions(+), 62 deletions(-)

lib/std/debug/Pdb.zig+88-26
...@@ -26,6 +26,10 @@ pub const Module = struct {...@@ -26,6 +26,10 @@ pub const Module = struct {
26 symbols: []u8,26 symbols: []u8,
27 subsect_info: []u8,27 subsect_info: []u8,
28 checksum_offset: ?usize,28 checksum_offset: ?usize,
29 /// The inlinee source lines, sorted by inlinee. This saves us from repeatedly doing linear
30 /// searches over all inlinees. We prefer binary search over a hashmap as LLVM somtimes outputs
31 /// multiple entries for a single inlinee ID, see `getInlineeSourceLines` for more info.
32 inlinee_source_lines: []InlineeSourceLine,
2933
30 pub fn deinit(self: *Module, allocator: Allocator) void {34 pub fn deinit(self: *Module, allocator: Allocator) void {
31 allocator.free(self.module_name);35 allocator.free(self.module_name);
...@@ -33,6 +37,7 @@ pub const Module = struct {...@@ -33,6 +37,7 @@ pub const Module = struct {
33 if (self.populated) {37 if (self.populated) {
34 allocator.free(self.symbols);38 allocator.free(self.symbols);
35 allocator.free(self.subsect_info);39 allocator.free(self.subsect_info);
40 allocator.free(self.inlinee_source_lines);
36 }41 }
37 }42 }
38};43};
...@@ -117,6 +122,7 @@ pub fn parseDbiStream(self: *Pdb) !void {...@@ -117,6 +122,7 @@ pub fn parseDbiStream(self: *Pdb) !void {
117 .symbols = undefined,122 .symbols = undefined,
118 .subsect_info = undefined,123 .subsect_info = undefined,
119 .checksum_offset = null,124 .checksum_offset = null,
125 .inlinee_source_lines = undefined,
120 });126 });
121127
122 mod_info_offset += this_record_len;128 mod_info_offset += this_record_len;
...@@ -657,40 +663,58 @@ pub fn getSymbolName(self: *Pdb, proc_sym: *align(1) const pdb.ProcSym) []const...@@ -657,40 +663,58 @@ pub fn getSymbolName(self: *Pdb, proc_sym: *align(1) const pdb.ProcSym) []const
657pub const InlineeSourceLine = struct {663pub const InlineeSourceLine = struct {
658 signature: pdb.InlineeSourceLineSignature,664 signature: pdb.InlineeSourceLineSignature,
659 info: *align(1) const pdb.InlineeSourceLine, 665 info: *align(1) const pdb.InlineeSourceLine,
666
667 fn lessThan(_: void, lhs: InlineeSourceLine, rhs: InlineeSourceLine) bool {
668 return lhs.info.inlinee < rhs.info.inlinee;
669 }
670
671 fn compare(inlinee: u32, self: InlineeSourceLine) std.math.Order {
672 return std.math.order(inlinee, self.info.inlinee);
673 }
660};674};
661675
662pub fn getInlineeSourceLine(676/// Returns all `InlineeSourceLine`s for a given module with the given inlinee. Ideally there would
677/// only be one entry per inlinee, but LLVM appears to assign all functions that share a name the
678/// same inlinee ID. This appears to be a bug, so the best the caller can do right now is print all
679/// the results.
680pub fn getInlineeSourceLines(
663 self: *Pdb,681 self: *Pdb,
664 mod: *Module,682 mod: *Module,
665 inlinee: u32,683 inlinee: u32,
666) ?InlineeSourceLine {684) []const InlineeSourceLine {
667 _ = self;685 _ = self;
668 var subsects: Io.Reader = .fixed(mod.subsect_info);
669 while (subsects.takeStructPointer(pdb.DebugSubsectionHeader) catch null) |subsect_hdr| {
670 var subsect: Io.Reader = .fixed(subsects.take(subsect_hdr.length) catch return null);
671 if (subsect_hdr.kind == .inlinee_lines) {
672 const signature = subsect.takeEnum(pdb.InlineeSourceLineSignature, .little) catch return null;
673 const has_extra_files = switch (signature) {
674 .normal => false,
675 .ex => true,
676 else => continue,
677 };
678686
679 while (subsect.takeStructPointer(pdb.InlineeSourceLine) catch null) |inlinee_src_line| {687 // Binary search to an arbitrary match, if there are other matches they will be adjacent
680 if (has_extra_files) {688 const any = std.sort.binarySearch(
681 const file_count = subsect.takeInt(u32, .little) catch return null;689 InlineeSourceLine,
682 const file_bytes = std.math.mul(usize, file_count, @sizeOf(u32)) catch return null;690 mod.inlinee_source_lines,
683 subsect.discardAll(file_bytes) catch return null;691 inlinee,
684 }692 InlineeSourceLine.compare,
685693 ) orelse return &.{};
686 if (inlinee_src_line.inlinee == inlinee) return .{694
687 .signature = signature,695 // Linearly scan to the first match
688 .info = inlinee_src_line,696 const begin = b: {
689 };697 var begin = any;
690 }698 while (begin > 0) {
699 const prev = begin - 1;
700 if (mod.inlinee_source_lines[prev].info.inlinee != inlinee) break;
701 begin = prev;
691 }702 }
692 }703 break :b begin;
693 return null;704 };
705
706 // Linearly scan to the last match
707 const end = b: {
708 var end = any + 1;
709 while (
710 end < mod.inlinee_source_lines.len and
711 mod.inlinee_source_lines[end].info.inlinee == inlinee
712 ) : (end += 1) {}
713 break :b end;
714 };
715
716 // Return a slice of all the matches
717 return mod.inlinee_source_lines[begin..end];
694}718}
695719
696pub fn getLineNumberInfo(self: *Pdb, module: *Module, address: u64) !std.debug.SourceLocation {720pub fn getLineNumberInfo(self: *Pdb, module: *Module, address: u64) !std.debug.SourceLocation {
...@@ -810,7 +834,45 @@ pub fn getModule(self: *Pdb, index: usize) !?*Module {...@@ -810,7 +834,45 @@ pub fn getModule(self: *Pdb, index: usize) !?*Module {
810 const gpa = self.allocator;834 const gpa = self.allocator;
811835
812 mod.symbols = try reader.readAlloc(gpa, mod.mod_info.sym_byte_size - 4);836 mod.symbols = try reader.readAlloc(gpa, mod.mod_info.sym_byte_size - 4);
837 errdefer gpa.free(mod.symbols);
813 mod.subsect_info = try reader.readAlloc(gpa, mod.mod_info.c13_byte_size);838 mod.subsect_info = try reader.readAlloc(gpa, mod.mod_info.c13_byte_size);
839 errdefer gpa.free(mod.subsect_info);
840 mod.inlinee_source_lines = b: {
841 var inlinee_source_lines: std.ArrayList(InlineeSourceLine) = .empty;
842 defer inlinee_source_lines.deinit(gpa);
843 var subsects: Io.Reader = .fixed(mod.subsect_info);
844 while (subsects.takeStructPointer(pdb.DebugSubsectionHeader) catch null) |subsect_hdr| {
845 var subsect: Io.Reader = .fixed(subsects.take(subsect_hdr.length) catch return null);
846 if (subsect_hdr.kind == .inlinee_lines) {
847 const inlinee_source_line_signature = subsect.takeEnum(pdb.InlineeSourceLineSignature, .little)
848 catch return error.InvalidDebugInfo;
849 const has_extra_files = switch (inlinee_source_line_signature) {
850 .normal => false,
851 .ex => true,
852 else => continue,
853 };
854 while (subsect.takeStructPointer(pdb.InlineeSourceLine) catch null) |info| {
855 if (has_extra_files) {
856 const file_count = subsect.takeInt(u32, .little) catch
857 return error.InvalidDebugInfo;
858 const file_bytes = std.math.mul(usize, file_count, @sizeOf(u32))
859 catch return error.InvalidDebugInfo;
860 subsect.discardAll(file_bytes) catch
861 return error.InvalidDebugInfo;
862 }
863
864 try inlinee_source_lines.append(gpa, .{
865 .signature = inlinee_source_line_signature,
866 .info = info,
867 });
868 }
869 }
870 }
871
872 std.mem.sort(InlineeSourceLine, inlinee_source_lines.items, {}, InlineeSourceLine.lessThan);
873 break :b try inlinee_source_lines.toOwnedSlice(gpa);
874 };
875 errdefer gpa.free(mod.inlinee_source_lines);
814876
815 var sect_offset: usize = 0;877 var sect_offset: usize = 0;
816 var skip_len: usize = undefined;878 var skip_len: usize = undefined;
lib/std/debug/SelfInfo/Windows.zig+43-36
...@@ -286,43 +286,48 @@ const Module = struct {...@@ -286,43 +286,48 @@ const Module = struct {
286 var last_inlinee: ?u32 = null;286 var last_inlinee: ?u32 = null;
287 var iter = pdb.getInlinees(module, proc);287 var iter = pdb.getInlinees(module, proc);
288 while (iter.next(module)) |inline_site| {288 while (iter.next(module)) |inline_site| {
289 // If our address points into this site, get the source location it
290 // points at
291 const inlinee_src_line = pdb.getInlineeSourceLine(
292 module,
293 inline_site.inlinee,
294 ) orelse continue;
295 const maybe_loc = pdb.getInlineSiteSourceLocation(
296 module,
297 inline_site,
298 inlinee_src_line.info,
299 offset_in_func,
300 ) catch continue;
301 const loc = maybe_loc orelse continue;
302
303 // Filter out duplicate inline sites. Tools like llvm-addr2line output289 // Filter out duplicate inline sites. Tools like llvm-addr2line output
304 // duplicate sites in the same cases as us if we elide this check,290 // duplicate sites in the same cases as us if we elide this check,
305 // implying that they exist in the underlying data and are not291 // implying that they exist in the underlying data and are not indicative
306 // indicative of a parser bug. No useful information is lost here since an292 // of a parser bug. No useful information is lost here since an inline site
307 // inline site can't actually reference itself.293 // can't actually reference itself.
308 if (inline_site.inlinee == last_inlinee) continue;294 if (inline_site.inlinee == last_inlinee) continue;
309 last_inlinee = inline_site.inlinee;295
310296 // If our address points into this site, get the source location(s) it
311 // If we're appending this symbol, resolve the name. If we're replacing the297 // points at
312 // last symbol, clear the previous symbols and wait to resolve the name298 for (pdb.getInlineeSourceLines(
313 // until we've reached the last symbol to avoid doing work and then299 module,
314 // throwing it out.300 inline_site.inlinee,
315 const name = b: {301 )) |inlinee_src_line| {
316 if (resolve_inline_callers) break :b pdb.findInlineeName(inline_site.inlinee);302 const maybe_loc = pdb.getInlineSiteSourceLocation(
317 symbols.items.len = 0;303 module,
318 break :b null;304 inline_site,
319 };305 inlinee_src_line.info,
320306 offset_in_func,
321 try symbols.append(gpa, .{307 ) catch continue;
322 .name = name,308 const loc = maybe_loc orelse continue;
323 .compile_unit_name = compile_unit_name,309
324 .source_location = loc,310 // If we aren't trying to resolve inline callers, and we've matched a
325 });311 // new inline site, we want to overwrite the previous results.
312 if (!resolve_inline_callers and inline_site.inlinee != last_inlinee) {
313 symbols.items.len = 0;
314 }
315
316 // Only resolve the name if we're resolving inline callers, otherwise
317 // wait until we're done to avoid duplicated work.
318 const name = if (resolve_inline_callers)
319 pdb.findInlineeName(inline_site.inlinee)
320 else
321 null;
322
323 try symbols.append(gpa, .{
324 .name = name,
325 .compile_unit_name = compile_unit_name,
326 .source_location = loc,
327 });
328
329 last_inlinee = inline_site.inlinee;
330 }
326 }331 }
327332
328 if (resolve_inline_callers) {333 if (resolve_inline_callers) {
...@@ -332,8 +337,10 @@ const Module = struct {...@@ -332,8 +337,10 @@ const Module = struct {
332 // complexity.337 // complexity.
333 std.mem.reverse(std.debug.Symbol, symbols.items);338 std.mem.reverse(std.debug.Symbol, symbols.items);
334 } else if (last_inlinee) |inlinee| {339 } else if (last_inlinee) |inlinee| {
335 // If we haven't resolved the name yet, resolve it now340 // If we aren't resolving inline callers, then all results will have the
336 symbols.items[symbols.items.len - 1].name = pdb.findInlineeName(inlinee);341 // same inline site, and we resolve its name once at the end.
342 const name = pdb.findInlineeName(inlinee);
343 for (symbols.items) |*symbol| symbol.name = name;
337 }344 }
338 }345 }
339346