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 {
2626 symbols: []u8,
2727 subsect_info: []u8,
2828 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
3034 pub fn deinit(self: *Module, allocator: Allocator) void {
3135 allocator.free(self.module_name);
......@@ -33,6 +37,7 @@ pub const Module = struct {
3337 if (self.populated) {
3438 allocator.free(self.symbols);
3539 allocator.free(self.subsect_info);
40 allocator.free(self.inlinee_source_lines);
3641 }
3742 }
3843};
......@@ -117,6 +122,7 @@ pub fn parseDbiStream(self: *Pdb) !void {
117122 .symbols = undefined,
118123 .subsect_info = undefined,
119124 .checksum_offset = null,
125 .inlinee_source_lines = undefined,
120126 });
121127
122128 mod_info_offset += this_record_len;
......@@ -657,40 +663,58 @@ pub fn getSymbolName(self: *Pdb, proc_sym: *align(1) const pdb.ProcSym) []const
657663pub const InlineeSourceLine = struct {
658664 signature: pdb.InlineeSourceLineSignature,
659665 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 }
660674};
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(
663681 self: *Pdb,
664682 mod: *Module,
665683 inlinee: u32,
666) ?InlineeSourceLine {
684) []const InlineeSourceLine {
667685 _ = 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| {
680 if (has_extra_files) {
681 const file_count = subsect.takeInt(u32, .little) catch return null;
682 const file_bytes = std.math.mul(usize, file_count, @sizeOf(u32)) catch return null;
683 subsect.discardAll(file_bytes) catch return null;
684 }
685
686 if (inlinee_src_line.inlinee == inlinee) return .{
687 .signature = signature,
688 .info = inlinee_src_line,
689 };
690 }
687 // Binary search to an arbitrary match, if there are other matches they will be adjacent
688 const any = std.sort.binarySearch(
689 InlineeSourceLine,
690 mod.inlinee_source_lines,
691 inlinee,
692 InlineeSourceLine.compare,
693 ) orelse return &.{};
694
695 // Linearly scan to the first match
696 const begin = b: {
697 var begin = any;
698 while (begin > 0) {
699 const prev = begin - 1;
700 if (mod.inlinee_source_lines[prev].info.inlinee != inlinee) break;
701 begin = prev;
691702 }
692 }
693 return null;
703 break :b begin;
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];
694718}
695719
696720pub fn getLineNumberInfo(self: *Pdb, module: *Module, address: u64) !std.debug.SourceLocation {
......@@ -810,7 +834,45 @@ pub fn getModule(self: *Pdb, index: usize) !?*Module {
810834 const gpa = self.allocator;
811835
812836 mod.symbols = try reader.readAlloc(gpa, mod.mod_info.sym_byte_size - 4);
837 errdefer gpa.free(mod.symbols);
813838 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
815877 var sect_offset: usize = 0;
816878 var skip_len: usize = undefined;
lib/std/debug/SelfInfo/Windows.zig+43-36
......@@ -286,43 +286,48 @@ const Module = struct {
286286 var last_inlinee: ?u32 = null;
287287 var iter = pdb.getInlinees(module, proc);
288288 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
303289 // Filter out duplicate inline sites. Tools like llvm-addr2line output
304290 // duplicate sites in the same cases as us if we elide this check,
305 // implying that they exist in the underlying data and are not
306 // indicative of a parser bug. No useful information is lost here since an
307 // inline site can't actually reference itself.
291 // implying that they exist in the underlying data and are not indicative
292 // of a parser bug. No useful information is lost here since an inline site
293 // can't actually reference itself.
308294 if (inline_site.inlinee == last_inlinee) continue;
309 last_inlinee = inline_site.inlinee;
310
311 // If we're appending this symbol, resolve the name. If we're replacing the
312 // last symbol, clear the previous symbols and wait to resolve the name
313 // until we've reached the last symbol to avoid doing work and then
314 // throwing it out.
315 const name = b: {
316 if (resolve_inline_callers) break :b pdb.findInlineeName(inline_site.inlinee);
317 symbols.items.len = 0;
318 break :b null;
319 };
320
321 try symbols.append(gpa, .{
322 .name = name,
323 .compile_unit_name = compile_unit_name,
324 .source_location = loc,
325 });
295
296 // If our address points into this site, get the source location(s) it
297 // points at
298 for (pdb.getInlineeSourceLines(
299 module,
300 inline_site.inlinee,
301 )) |inlinee_src_line| {
302 const maybe_loc = pdb.getInlineSiteSourceLocation(
303 module,
304 inline_site,
305 inlinee_src_line.info,
306 offset_in_func,
307 ) catch continue;
308 const loc = maybe_loc orelse continue;
309
310 // If we aren't trying to resolve inline callers, and we've matched a
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 }
326331 }
327332
328333 if (resolve_inline_callers) {
......@@ -332,8 +337,10 @@ const Module = struct {
332337 // complexity.
333338 std.mem.reverse(std.debug.Symbol, symbols.items);
334339 } else if (last_inlinee) |inlinee| {
335 // If we haven't resolved the name yet, resolve it now
336 symbols.items[symbols.items.len - 1].name = pdb.findInlineeName(inlinee);
340 // If we aren't resolving inline callers, then all results will have the
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;
337344 }
338345 }
339346