diff --git a/lib/std/debug.zig b/lib/std/debug.zig index 7297352f0a..9ff9601478 100644 --- a/lib/std/debug.zig +++ b/lib/std/debug.zig @@ -38,8 +38,12 @@ pub const cpu_context = @import("debug/cpu_context.zig"); /// pub const init: SelfInfo; /// pub fn deinit(si: *SelfInfo, io: Io) void; /// -/// /// Returns an iterator over the symbols and source locations of the instruction at `address`. -/// pub fn getSymbols(si: *SelfInfo, io: Io, address: usize) SelfInfo.SymbolIterator; +/// /// Returns the the symbols and source locations of the instruction at `address`. Often this +/// /// will return a single result, but in the case of inlines it may return multiple. When +/// /// multiple results are returned, they are sorted from innermost to outermost. +/// pub fn getSymbols(si: *SelfInfo, io: Io, address: usize) SelfInfoError![]const Symbol; +/// /// Frees symbols returned from `getSymbols`. +/// pub fn freeSymbols(si: *SelfInfo, symbols: []const Symbol) void; /// /// Returns a name for the "module" (e.g. shared library or executable image) containing `address`. /// pub fn getModuleName(si: *SelfInfo, io: Io, address: usize) SelfInfoError![]const u8; /// pub fn getModuleSlide(si: *SelfInfo, io: Io, address: usize) SelfInfoError!usize; @@ -60,11 +64,6 @@ pub const cpu_context = @import("debug/cpu_context.zig"); /// /// Only required if `can_unwind == true`. Unwinds a single stack frame, returning the frame's /// /// return address, or 0 if the end of the stack has been reached. /// pub fn unwindFrame(si: *SelfInfo, io: Io, context: *UnwindContext) SelfInfoError!usize; -/// /// Iterates symbols found at an address. -/// pub const SymbolIterator = struct { -/// pub fn deinit(Self: *SymbolIterator, io: Io) void; -/// pub fn next(self: *SymbolIterator) ?SelfInfoError!Symbol; -/// }; /// ``` pub const SelfInfo = if (@hasDecl(root, "debug") and @hasDecl(root.debug, "SelfInfo")) root.debug.SelfInfo @@ -1193,35 +1192,35 @@ fn printSourceAtAddress( t: Io.Terminal, options: PrintSourceAddressOptions, ) Writer.Error!void { - var symbols: SelfInfo.SymbolIterator = debug_info.getSymbols(io, options.address); - defer symbols.deinit(io); - while (symbols.next()) |curr| { - const symbol: Symbol = curr catch |err| switch (err) { + const symbols: []const Symbol = debug_info.getSymbols(io, options.address) catch |err| { + t.setColor(.dim) catch {}; + defer t.setColor(.reset) catch {}; + switch (err) { error.MissingDebugInfo, error.UnsupportedDebugInfo, error.InvalidDebugInfo, - => .unknown, - error.ReadFailed, error.Unexpected, error.Canceled => s: { - t.setColor(.dim) catch {}; + => {}, + error.ReadFailed, error.Unexpected, error.Canceled => { try t.writer.print("Failed to read debug info from filesystem, trace may be incomplete\n\n", .{}); - t.setColor(.reset) catch {}; - break :s .unknown; }, - error.OutOfMemory => s: { + error.OutOfMemory => { t.setColor(.dim) catch {}; try t.writer.print("Ran out of memory loading debug info, trace may be incomplete\n\n", .{}); t.setColor(.reset) catch {}; - break :s .unknown; }, - }; - defer if (symbol.source_location) |sl| getDebugInfoAllocator().free(sl.file_name); + } + return printLineInfo(io, t, debug_info, null, options.address, null, null); + }; + defer debug_info.freeSymbols(symbols); + for (symbols) |symbol| { try printLineInfo( io, t, + debug_info, symbol.source_location, options.address, - symbol.name orelse "???", - symbol.compile_unit_name orelse debug_info.getModuleName(io, options.address) catch "???", + symbol.name, + symbol.compile_unit_name, ); if (!options.resolve_inline_callers) break; } @@ -1229,10 +1228,11 @@ fn printSourceAtAddress( fn printLineInfo( io: Io, t: Io.Terminal, + debug_info: *SelfInfo, source_location: ?SourceLocation, address: usize, - symbol_name: []const u8, - compile_unit_name: []const u8, + symbol_name: ?[]const u8, + compile_unit_name: ?[]const u8, ) Writer.Error!void { const writer = t.writer; t.setColor(.bold) catch {}; @@ -1250,7 +1250,11 @@ fn printLineInfo( t.setColor(.reset) catch {}; try writer.writeAll(": "); t.setColor(.dim) catch {}; - try writer.print("0x{x} in {s} ({s})", .{ address, symbol_name, compile_unit_name }); + try writer.print("0x{x} in {s} ({s})", .{ + address, + symbol_name orelse "???", + compile_unit_name orelse debug_info.getModuleName(io, address) catch "???", + }); t.setColor(.reset) catch {}; try writer.writeAll("\n"); diff --git a/lib/std/debug/Dwarf.zig b/lib/std/debug/Dwarf.zig index 0cd2a608e9..24288a515e 100644 --- a/lib/std/debug/Dwarf.zig +++ b/lib/std/debug/Dwarf.zig @@ -1545,26 +1545,17 @@ fn getStringGeneric(opt_str: ?[]const u8, offset: u64) ![:0]const u8 { return str[casted_offset..last :0]; } -pub const SymbolIterator = struct { - curr: ?std.debug.SelfInfoError!std.debug.Symbol, - - pub fn deinit(self: *SymbolIterator, _: Io) void { - self.* = undefined; - } - - pub fn next(self: *SymbolIterator) ?Error!std.debug.Symbol { - const result = self.curr; - self.curr = null; - return result; - } -}; - -pub fn getSymbols(di: *Dwarf, gpa: Allocator, endian: Endian, address: u64) SymbolIterator { +pub fn getSymbols(di: *Dwarf, gpa: Allocator, endian: Endian, address: u64) std.debug.SelfInfoError![]const std.debug.Symbol { + const symbol = try gpa.create(std.debug.Symbol); + errdefer gpa.destroy(symbol); const compile_unit = di.findCompileUnit(endian, address) catch |err| switch (err) { - error.EndOfStream, error.Overflow => return .{ .curr = error.InvalidDebugInfo }, - else => |e| return .{ .curr = e }, + error.EndOfStream, error.Overflow => { + symbol.* = .unknown; + return symbol[0..1]; + }, + else => |e| return e, }; - return .{ .curr = .{ + symbol.* = .{ .name = di.getSymbolName(address), .compile_unit_name = compile_unit.die.getAttrString(di, endian, std.dwarf.AT.name, di.section(.debug_str), compile_unit) catch |err| switch (err) { error.MissingDebugInfo, error.InvalidDebugInfo => null, @@ -1575,10 +1566,11 @@ pub fn getSymbols(di: *Dwarf, gpa: Allocator, endian: Endian, address: u64) Symb error.EndOfStream, error.Overflow, error.StreamTooLong, - => return .{ .curr = error.InvalidDebugInfo }, - else => |e| return .{ .curr = e }, + => return error.InvalidDebugInfo, + else => |e| return e, }, - } }; + }; + return symbol[0..1]; } /// DWARF5 7.4: "In the 32-bit DWARF format, all values that represent lengths of DWARF sections and diff --git a/lib/std/debug/SelfInfo/Elf.zig b/lib/std/debug/SelfInfo/Elf.zig index 8a0535c487..71f172200f 100644 --- a/lib/std/debug/SelfInfo/Elf.zig +++ b/lib/std/debug/SelfInfo/Elf.zig @@ -30,41 +30,50 @@ pub fn deinit(si: *SelfInfo, io: Io) void { if (si.unwind_cache) |cache| gpa.free(cache); } -pub const SymbolIterator = std.debug.Dwarf.SymbolIterator; - -pub fn getSymbols(si: *SelfInfo, io: Io, address: usize) SymbolIterator { +pub fn getSymbols(si: *SelfInfo, io: Io, address: usize) Error![]const std.debug.Symbol { const gpa = std.debug.getDebugInfoAllocator(); - const module = si.findModule(gpa, io, address, .exclusive) catch |err| return .{ .curr = err }; + const module = try si.findModule(gpa, io, address, .exclusive); defer si.rwlock.unlock(io); const vaddr = address - module.load_offset; - const loaded_elf = module.getLoadedElf(gpa, io) catch |err| return .{ .curr = err }; + const loaded_elf = try module.getLoadedElf(gpa, io); if (loaded_elf.file.dwarf) |*dwarf| { if (!loaded_elf.scanned_dwarf) { dwarf.open(gpa, native_endian) catch |err| switch (err) { error.InvalidDebugInfo, error.MissingDebugInfo, error.OutOfMemory, - => |e| return .{ .curr = e }, + => |e| return e, error.EndOfStream, error.Overflow, error.ReadFailed, error.StreamTooLong, - => return .{ .curr = error.InvalidDebugInfo }, + => return error.InvalidDebugInfo, }; loaded_elf.scanned_dwarf = true; } return dwarf.getSymbols(gpa, native_endian, vaddr); } // When DWARF is unavailable, fall back to searching the symtab. - const symbol = loaded_elf.file.searchSymtab(gpa, vaddr) catch |err| switch (err) { - error.NoSymtab, error.NoStrtab => return .{ .curr = error.MissingDebugInfo }, - error.BadSymtab => return .{ .curr = error.InvalidDebugInfo }, - error.OutOfMemory => |e| return .{ .curr = e }, + const symbol = try gpa.create(std.debug.Symbol); + errdefer gpa.destroy(symbol); + symbol.* = loaded_elf.file.searchSymtab(gpa, vaddr) catch |err| switch (err) { + error.NoSymtab, error.NoStrtab => return error.MissingDebugInfo, + error.BadSymtab => return error.InvalidDebugInfo, + error.OutOfMemory => |e| return e, }; - - return .{ .curr = symbol }; + return symbol[0..1]; +} +pub fn freeSymbols(si: *SelfInfo, symbols: []const std.debug.Symbol) void { + _ = si; + const gpa = std.debug.getDebugInfoAllocator(); + for (symbols) |symbol| { + if (symbol.source_location) |source_location| { + gpa.free(source_location.file_name); + } + } + gpa.free(symbols); } pub fn getModuleName(si: *SelfInfo, io: Io, address: usize) Error![]const u8 { const gpa = std.debug.getDebugInfoAllocator(); diff --git a/lib/std/debug/SelfInfo/MachO.zig b/lib/std/debug/SelfInfo/MachO.zig index d6523d924d..d8f73976d0 100644 --- a/lib/std/debug/SelfInfo/MachO.zig +++ b/lib/std/debug/SelfInfo/MachO.zig @@ -36,12 +36,15 @@ pub const SymbolIterator = struct { } }; -pub fn getSymbols(si: *SelfInfo, io: Io, address: usize) SymbolIterator { +pub fn getSymbols(si: *SelfInfo, io: Io, address: usize) Error![]const std.debug.Symbol { const gpa = std.debug.getDebugInfoAllocator(); - const module = si.findModule(gpa, io, address) catch |err| return .{ .curr = err }; + const module = try si.findModule(gpa, io, address); defer si.mutex.unlock(io); - const file = module.getFile(gpa, io) catch |err| return .{ .curr = err }; + const file = try module.getFile(gpa, io); + + const symbol = try gpa.create(std.debug.Symbol); + errdefer gpa.destroy(symbol); // This is not necessarily the same as the vmaddr_slide that dyld would report. This is // because the segments in the file on disk might differ from the ones in memory. Normally @@ -57,26 +60,27 @@ pub fn getSymbols(si: *SelfInfo, io: Io, address: usize) SymbolIterator { const ofile_dwarf, const ofile_vaddr = file.getDwarfForAddress(gpa, io, vaddr) catch { // Return at least the symbol name if available. - return .{ .curr = .{ - .name = file.lookupSymbolName(vaddr) catch |err| return .{ .curr = err }, + symbol.* = .{ + .name = try file.lookupSymbolName(vaddr), .compile_unit_name = null, .source_location = null, - } }; + }; + return symbol[0..1]; }; const compile_unit = ofile_dwarf.findCompileUnit(native_endian, ofile_vaddr) catch { // Return at least the symbol name if available. - return .{ .curr = .{ - .name = file.lookupSymbolName(vaddr) catch |err| return .{ .curr = err }, + symbol.* = .{ + .name = try file.lookupSymbolName(vaddr), .compile_unit_name = null, .source_location = null, - } }; + }; + return symbol[0..1]; }; - return .{ .curr = .{ + symbol.* = .{ .name = ofile_dwarf.getSymbolName(ofile_vaddr) orelse - file.lookupSymbolName(vaddr) catch |err| - return .{ .curr = err }, + try file.lookupSymbolName(vaddr), .compile_unit_name = compile_unit.die.getAttrString( ofile_dwarf, native_endian, @@ -92,7 +96,18 @@ pub fn getSymbols(si: *SelfInfo, io: Io, address: usize) SymbolIterator { compile_unit, ofile_vaddr, ) catch null, - } }; + }; + return symbol[0..1]; +} +pub fn freeSymbols(si: *SelfInfo, symbols: []const std.debug.Symbol) void { + _ = si; + const gpa = std.debug.getDebugInfoAllocator(); + for (symbols) |symbol| { + if (symbol.source_location) |source_location| { + gpa.free(source_location.file_name); + } + } + gpa.free(symbols); } pub fn getModuleName(si: *SelfInfo, io: Io, address: usize) Error![]const u8 { _ = si; diff --git a/lib/std/debug/SelfInfo/Windows.zig b/lib/std/debug/SelfInfo/Windows.zig index c3d72d4558..d2a28803b1 100644 --- a/lib/std/debug/SelfInfo/Windows.zig +++ b/lib/std/debug/SelfInfo/Windows.zig @@ -25,107 +25,26 @@ pub fn deinit(si: *SelfInfo, io: Io) void { si.modules.deinit(gpa); } -pub const SymbolIterator = struct { - err: Error!void = {}, - lock: ?*Io.RwLock, - module: *Module, - symbols: Module.DebugInfo.Symbols, +pub fn getSymbols(si: *SelfInfo, io: Io, address: usize) Error![]const std.debug.Symbol { + const gpa = std.debug.getDebugInfoAllocator(); + try si.lock.lockShared(io); + defer si.lock.unlockShared(io); + const module = try si.findModule(gpa, address); + const di = try module.getDebugInfo(gpa, io); + return di.getSymbols(gpa, address - @intFromPtr(module.entry.DllBase)); +} - pub fn deinit(self: *SymbolIterator, io: Io) void { - if (self.lock) |lock| lock.unlockShared(io); - self.symbols.deinit(io); - self.* = undefined; - } - - fn failing(err: Error) SymbolIterator { - return .{ - .err = err, - .lock = null, - .module = undefined, - .symbols = .none, - }; - } - - pub fn next(self: *SymbolIterator) ?Error!std.debug.Symbol { - // Check for errors - self.err catch |err| { - self.err = {}; - self.symbols = .none; - return err; - }; - - // Return the next symbol for the debug info type - switch (self.symbols) { - .pdb => |*info| { - // The failure cases are unreachable because we only set the pdb field if these are - // set - const di = if (self.module.di.?) |*di| di else |_| unreachable; - const pdb = if (di.pdb) |*pdb| pdb else unreachable; - - // Get the next inlinee if it exists - if (info.proc) |proc| { - const offset_in_func = info.addr - proc.code_offset; - while (info.inline_sites.pop()) |site| { - // If our address points into this site, get the source location it points - // at - const inlinee_src_line = pdb.getInlineeSourceLine( - info.module, - site.inlinee, - ) orelse continue; - const maybe_loc = pdb.getInlineSiteSourceLocation( - info.module, - site, - inlinee_src_line.info, - offset_in_func, - ) catch continue; - const loc = maybe_loc orelse continue; - - // If we've found a match, filter out any duplicates that might follow. - // Tools like llvm-addr2line output duplicate sites in the same cases as us, - // implying that they exist in the underlying data and are not indicative of - // a parser bug. - while (info.inline_sites.getLastOrNull()) |top| { - if (top.inlinee != site.inlinee) break; - _ = info.inline_sites.pop(); - } - - return .{ - .name = pdb.findInlineeName(site.inlinee), - .compile_unit_name = fs.path.basename(info.module.obj_file_name), - .source_location = loc, - }; - } - } - - // Return the main symbol and end the iterator - defer self.symbols = .none; - return .{ - .name = if (info.proc) |proc| pdb.getSymbolName(proc) else null, - .compile_unit_name = fs.path.basename(info.module.obj_file_name), - .source_location = pdb.getLineNumberInfo(info.module, info.addr) catch null, - }; - }, - .dwarf => |*info| return info.next(), - .none => return null, +pub fn freeSymbols(si: *SelfInfo, symbols: []const std.debug.Symbol) void { + _ = si; + const gpa = std.debug.getDebugInfoAllocator(); + for (symbols) |symbol| { + if (symbol.source_location) |source_location| { + gpa.free(source_location.file_name); } } -}; - -pub fn getSymbols(si: *SelfInfo, io: Io, address: usize) SymbolIterator { - const gpa = std.debug.getDebugInfoAllocator(); - si.lock.lockShared(io) catch |err| return .failing(err); - errdefer si.lock.unlockShared(io); - const module = si.findModule(gpa, address) catch |err| return .failing(err); - const di = module.getDebugInfo(gpa, io) catch |err| return .failing(err); - const symbols = Module.DebugInfo.Symbols.init(di, address - @intFromPtr(module.entry.DllBase)) - catch |err| return .failing(err); - errdefer comptime unreachable; - return .{ - .lock = &si.lock, - .module = module, - .symbols = symbols, - }; + gpa.free(symbols); } + pub fn getModuleName(si: *SelfInfo, io: Io, address: usize) Error![]const u8 { const gpa = std.debug.getDebugInfoAllocator(); try si.lock.lockShared(io); @@ -333,94 +252,100 @@ const Module = struct { arena.deinit(); } - pub const Symbols = union(enum) { - pdb: struct { - module: *Pdb.Module, - proc: ?*align(1) const std.pdb.ProcSym, - addr: usize, - /// Inline sites are stored in the pdb in reverse order, so we build up a list of up - /// front so that our iterator can return them in the correct order without doing an - /// n^2 search. We don't try to filter inline sites based on address until the user - /// calls `next` as this requires parsing binary annotations, and this is work we - /// may be able to elide if the caller chooses to early out before finishing - /// iteration, e.g. because they only wanted the topmost call. - inline_sites: std.ArrayList(*align(1) const std.pdb.InlineSiteSym), - }, - dwarf: std.debug.Dwarf.SymbolIterator, - none: void, + fn getSymbols(di: *DebugInfo, gpa: Allocator, vaddr: usize) Error![]const std.debug.Symbol { + pdb: { + const pdb = &(di.pdb orelse break :pdb); + var coff_section: *align(1) const coff.SectionHeader = undefined; + const mod_index = for (pdb.sect_contribs) |sect_contrib| { + if (sect_contrib.section > di.coff_section_headers.len) continue; + // Remember that SectionContribEntry.Section is 1-based. + coff_section = &di.coff_section_headers[sect_contrib.section - 1]; - fn init(di: *DebugInfo, vaddr: usize) Error!Symbols { - const gpa = std.debug.getDebugInfoAllocator(); + const vaddr_start = coff_section.virtual_address + sect_contrib.offset; + const vaddr_end = vaddr_start + sect_contrib.size; + if (vaddr >= vaddr_start and vaddr < vaddr_end) { + break sect_contrib.module_index; + } + } else { + // we have no information to add to the address + break :pdb; + }; + const module = pdb.getModule(mod_index) catch |err| switch (err) { + error.InvalidDebugInfo, + error.MissingDebugInfo, + error.OutOfMemory, + => |e| return e, - pdb: { - const pdb = &(di.pdb orelse break :pdb); - var coff_section: *align(1) const coff.SectionHeader = undefined; - const mod_index = for (pdb.sect_contribs) |sect_contrib| { - if (sect_contrib.section > di.coff_section_headers.len) continue; - // Remember that SectionContribEntry.Section is 1-based. - coff_section = &di.coff_section_headers[sect_contrib.section - 1]; + error.ReadFailed, + error.EndOfStream, + => return error.InvalidDebugInfo, + } orelse { + return error.InvalidDebugInfo; // bad module index + }; - const vaddr_start = coff_section.virtual_address + sect_contrib.offset; - const vaddr_end = vaddr_start + sect_contrib.size; - if (vaddr >= vaddr_start and vaddr < vaddr_end) { - break sect_contrib.module_index; - } - } else { - // we have no information to add to the address - break :pdb; - }; - const module = pdb.getModule(mod_index) catch |err| switch (err) { - error.InvalidDebugInfo, - error.MissingDebugInfo, - error.OutOfMemory, - => |e| return e, + const addr = vaddr - coff_section.virtual_address; + const maybe_proc = pdb.getProcSym(module, addr); + var symbols: std.ArrayList(std.debug.Symbol) = .empty; + errdefer symbols.deinit(gpa); - error.ReadFailed, - error.EndOfStream, - => return error.InvalidDebugInfo, - } orelse { - return error.InvalidDebugInfo; // bad module index - }; + if (maybe_proc) |proc| { + const offset_in_func = addr - proc.code_offset; + var last_inlinee: ?u32 = null; + var iter = pdb.getInlinees(module, proc); + while (iter.next(module)) |inline_site| { + // If our address points into this site, get the source location it + // points at + const inlinee_src_line = pdb.getInlineeSourceLine( + module, + inline_site.inlinee, + ) orelse continue; + const maybe_loc = pdb.getInlineSiteSourceLocation( + module, + inline_site, + inlinee_src_line.info, + offset_in_func, + ) catch continue; + const loc = maybe_loc orelse continue; - const addr = vaddr - coff_section.virtual_address; - const maybe_proc = pdb.getProcSym(module, addr); + // Filter out duplicate inline sites. Tools like llvm-addr2line output + // duplicate sites in the same cases as us if we elide this check, + // implying that they exist in the underlying data and are not + // indicative of a parser bug. No useful information is lost here since an + // inline site can't actually reference itself. + if (inline_site.inlinee == last_inlinee) continue; + last_inlinee = inline_site.inlinee; - var inline_sites: std.ArrayList(*align(1) const std.pdb.InlineSiteSym) = .empty; - if (maybe_proc) |proc| { - var iter = pdb.getInlinees(module, proc); - while (iter.next(module)) |inline_site| { - try inline_sites.append(gpa, inline_site); - } + try symbols.append(gpa, .{ + .name = pdb.findInlineeName(inline_site.inlinee), + .compile_unit_name = fs.path.basename(module.obj_file_name), + .source_location = loc, + }); } - return .{ .pdb = .{ - .module = module, - .proc = maybe_proc, - .addr = addr, - .inline_sites = inline_sites, - } }; + // Inline sites are stored in the pdb in reverse order, so we reverse the + // matching sites here. We could alternatively use the parent fields to + // determine the order, but this would introduce seemingly unecessary + // complexity. + std.mem.reverse(std.debug.Symbol, symbols.items); } - // Dwarf - dwarf: { - const dwarf = &(di.dwarf orelse break :dwarf); - const addr = vaddr + di.coff_image_base; - return .{ .dwarf = dwarf.getSymbols(gpa, native_endian, addr) }; - } + try symbols.append(gpa, .{ + .name = if (maybe_proc) |proc| pdb.getSymbolName(proc) else null, + .compile_unit_name = fs.path.basename(module.obj_file_name), + .source_location = pdb.getLineNumberInfo(module, addr) catch null, + }); - return error.MissingDebugInfo; + return symbols.toOwnedSlice(gpa); } - fn deinit(self: *Symbols, io: Io) void { - const gpa = std.debug.getDebugInfoAllocator(); - switch (self.*) { - .pdb => |*info| info.inline_sites.deinit(gpa), - .dwarf => |*info| info.deinit(io), - .none => {}, - } + dwarf: { + const dwarf = &(di.dwarf orelse break :dwarf); + const addr = vaddr + di.coff_image_base; + return dwarf.getSymbols(gpa, native_endian, addr); } - }; + return error.MissingDebugInfo; + } }; fn deinit(module: *Module, gpa: Allocator, io: Io) void {