aboutsummaryrefslogtreecommitdiff
path: root/lib/std/debug/SelfInfo/ElfModule.zig
diff options
context:
space:
mode:
Diffstat (limited to 'lib/std/debug/SelfInfo/ElfModule.zig')
-rw-r--r--lib/std/debug/SelfInfo/ElfModule.zig349
1 files changed, 0 insertions, 349 deletions
diff --git a/lib/std/debug/SelfInfo/ElfModule.zig b/lib/std/debug/SelfInfo/ElfModule.zig
deleted file mode 100644
index 7ce24e2e2a..0000000000
--- a/lib/std/debug/SelfInfo/ElfModule.zig
+++ /dev/null
@@ -1,349 +0,0 @@
-load_offset: usize,
-name: []const u8,
-build_id: ?[]const u8,
-gnu_eh_frame: ?[]const u8,
-
-pub const LookupCache = struct {
- rwlock: std.Thread.RwLock,
- ranges: std.ArrayList(Range),
- const Range = struct {
- start: usize,
- len: usize,
- mod: ElfModule,
- };
- pub const init: LookupCache = .{
- .rwlock = .{},
- .ranges = .empty,
- };
- pub fn deinit(lc: *LookupCache, gpa: Allocator) void {
- lc.ranges.deinit(gpa);
- }
-};
-
-pub const DebugInfo = struct {
- /// Held while checking and/or populating `loaded_elf`/`scanned_dwarf`/`unwind`.
- /// Once data is populated and a pointer to the field has been gotten, the lock
- /// is released; i.e. it is not held while *using* the loaded debug info.
- mutex: std.Thread.Mutex,
-
- loaded_elf: ?ElfFile,
- scanned_dwarf: bool,
- unwind: if (supports_unwinding) [2]?Dwarf.Unwind else void,
- unwind_cache: if (supports_unwinding) *UnwindContext.Cache else void,
-
- pub const init: DebugInfo = .{
- .mutex = .{},
- .loaded_elf = null,
- .scanned_dwarf = false,
- .unwind = if (supports_unwinding) @splat(null),
- .unwind_cache = undefined,
- };
- pub fn deinit(di: *DebugInfo, gpa: Allocator) void {
- if (di.loaded_elf) |*loaded_elf| loaded_elf.deinit(gpa);
- if (supports_unwinding) {
- if (di.unwind[0] != null) gpa.destroy(di.unwind_cache);
- for (&di.unwind) |*opt_unwind| {
- const unwind = &(opt_unwind.* orelse continue);
- unwind.deinit(gpa);
- }
- }
- }
-};
-
-pub fn key(m: ElfModule) usize {
- return m.load_offset;
-}
-pub fn lookup(cache: *LookupCache, gpa: Allocator, address: usize) Error!ElfModule {
- if (lookupInCache(cache, address)) |m| return m;
-
- {
- // Check a new module hasn't been loaded
- cache.rwlock.lock();
- defer cache.rwlock.unlock();
- const DlIterContext = struct {
- ranges: *std.ArrayList(LookupCache.Range),
- gpa: Allocator,
-
- fn callback(info: *std.posix.dl_phdr_info, size: usize, context: *@This()) !void {
- _ = size;
-
- var mod: ElfModule = .{
- .load_offset = info.addr,
- // Android libc uses NULL instead of "" to mark the main program
- .name = mem.sliceTo(info.name, 0) orelse "",
- .build_id = null,
- .gnu_eh_frame = null,
- };
-
- // Populate `build_id` and `gnu_eh_frame`
- for (info.phdr[0..info.phnum]) |phdr| {
- switch (phdr.p_type) {
- elf.PT_NOTE => {
- // Look for .note.gnu.build-id
- const segment_ptr: [*]const u8 = @ptrFromInt(info.addr + phdr.p_vaddr);
- var r: std.Io.Reader = .fixed(segment_ptr[0..phdr.p_memsz]);
- const name_size = r.takeInt(u32, native_endian) catch continue;
- const desc_size = r.takeInt(u32, native_endian) catch continue;
- const note_type = r.takeInt(u32, native_endian) catch continue;
- const name = r.take(name_size) catch continue;
- if (note_type != elf.NT_GNU_BUILD_ID) continue;
- if (!mem.eql(u8, name, "GNU\x00")) continue;
- const desc = r.take(desc_size) catch continue;
- mod.build_id = desc;
- },
- elf.PT_GNU_EH_FRAME => {
- const segment_ptr: [*]const u8 = @ptrFromInt(info.addr + phdr.p_vaddr);
- mod.gnu_eh_frame = segment_ptr[0..phdr.p_memsz];
- },
- else => {},
- }
- }
-
- // Now that `mod` is populated, create the ranges
- for (info.phdr[0..info.phnum]) |phdr| {
- if (phdr.p_type != elf.PT_LOAD) continue;
- try context.ranges.append(context.gpa, .{
- // Overflowing addition handles VSDOs having p_vaddr = 0xffffffffff700000
- .start = info.addr +% phdr.p_vaddr,
- .len = phdr.p_memsz,
- .mod = mod,
- });
- }
- }
- };
- cache.ranges.clearRetainingCapacity();
- var ctx: DlIterContext = .{
- .ranges = &cache.ranges,
- .gpa = gpa,
- };
- try std.posix.dl_iterate_phdr(&ctx, error{OutOfMemory}, DlIterContext.callback);
- }
-
- if (lookupInCache(cache, address)) |m| return m;
- return error.MissingDebugInfo;
-}
-fn lookupInCache(cache: *LookupCache, address: usize) ?ElfModule {
- cache.rwlock.lockShared();
- defer cache.rwlock.unlockShared();
- for (cache.ranges.items) |*range| {
- if (address >= range.start and address < range.start + range.len) {
- return range.mod;
- }
- }
- return null;
-}
-fn loadElf(module: *const ElfModule, gpa: Allocator, di: *DebugInfo) Error!void {
- std.debug.assert(di.loaded_elf == null);
- std.debug.assert(!di.scanned_dwarf);
-
- const load_result = if (module.name.len > 0) res: {
- var file = std.fs.cwd().openFile(module.name, .{}) catch return error.MissingDebugInfo;
- defer file.close();
- break :res ElfFile.load(gpa, file, module.build_id, &.native(module.name));
- } else res: {
- const path = std.fs.selfExePathAlloc(gpa) catch |err| switch (err) {
- error.OutOfMemory => |e| return e,
- else => return error.ReadFailed,
- };
- defer gpa.free(path);
- var file = std.fs.cwd().openFile(path, .{}) catch return error.MissingDebugInfo;
- defer file.close();
- break :res ElfFile.load(gpa, file, module.build_id, &.native(path));
- };
- di.loaded_elf = load_result catch |err| switch (err) {
- error.OutOfMemory,
- error.Unexpected,
- => |e| return e,
-
- error.Overflow,
- error.TruncatedElfFile,
- error.InvalidCompressedSection,
- error.InvalidElfMagic,
- error.InvalidElfVersion,
- error.InvalidElfClass,
- error.InvalidElfEndian,
- => return error.InvalidDebugInfo,
-
- error.SystemResources,
- error.MemoryMappingNotSupported,
- error.AccessDenied,
- error.LockedMemoryLimitExceeded,
- error.ProcessFdQuotaExceeded,
- error.SystemFdQuotaExceeded,
- => return error.ReadFailed,
- };
-
- const matches_native =
- di.loaded_elf.?.endian == native_endian and
- di.loaded_elf.?.is_64 == (@sizeOf(usize) == 8);
-
- if (!matches_native) {
- di.loaded_elf.?.deinit(gpa);
- di.loaded_elf = null;
- return error.InvalidDebugInfo;
- }
-}
-pub fn getSymbolAtAddress(module: *const ElfModule, gpa: Allocator, di: *DebugInfo, address: usize) Error!std.debug.Symbol {
- const vaddr = address - module.load_offset;
- {
- di.mutex.lock();
- defer di.mutex.unlock();
- if (di.loaded_elf == null) try module.loadElf(gpa, di);
- const loaded_elf = &di.loaded_elf.?;
- // We need the lock if using DWARF, as we might scan the DWARF or build a line number table.
- if (loaded_elf.dwarf) |*dwarf| {
- if (!di.scanned_dwarf) {
- dwarf.open(gpa, native_endian) catch |err| switch (err) {
- error.InvalidDebugInfo,
- error.MissingDebugInfo,
- error.OutOfMemory,
- => |e| return e,
- error.EndOfStream,
- error.Overflow,
- error.ReadFailed,
- error.StreamTooLong,
- => return error.InvalidDebugInfo,
- };
- di.scanned_dwarf = true;
- }
- return dwarf.getSymbol(gpa, native_endian, vaddr) catch |err| switch (err) {
- error.InvalidDebugInfo,
- error.MissingDebugInfo,
- error.OutOfMemory,
- => |e| return e,
- error.ReadFailed,
- error.EndOfStream,
- error.Overflow,
- error.StreamTooLong,
- => return error.InvalidDebugInfo,
- };
- }
- // Otherwise, we're just going to scan the symtab, which we don't need the lock for; fall out of this block.
- }
- // When there's no DWARF available, fall back to searching the symtab.
- return di.loaded_elf.?.searchSymtab(gpa, vaddr) catch |err| switch (err) {
- error.NoSymtab, error.NoStrtab => return error.MissingDebugInfo,
- error.BadSymtab => return error.InvalidDebugInfo,
- error.OutOfMemory => |e| return e,
- };
-}
-fn prepareUnwindLookup(unwind: *Dwarf.Unwind, gpa: Allocator) Error!void {
- unwind.prepare(gpa, @sizeOf(usize), native_endian, true, false) catch |err| switch (err) {
- error.ReadFailed => unreachable, // it's all fixed buffers
- error.InvalidDebugInfo,
- error.MissingDebugInfo,
- error.OutOfMemory,
- => |e| return e,
- error.EndOfStream,
- error.Overflow,
- error.StreamTooLong,
- error.InvalidOperand,
- error.InvalidOpcode,
- error.InvalidOperation,
- => return error.InvalidDebugInfo,
- error.UnsupportedAddrSize,
- error.UnsupportedDwarfVersion,
- error.UnimplementedUserOpcode,
- => return error.UnsupportedDebugInfo,
- };
-}
-fn loadUnwindInfo(module: *const ElfModule, gpa: Allocator, di: *DebugInfo) Error!void {
- var buf: [2]Dwarf.Unwind = undefined;
- const unwinds: []Dwarf.Unwind = if (module.gnu_eh_frame) |section_bytes| unwinds: {
- const section_vaddr: u64 = @intFromPtr(section_bytes.ptr) - module.load_offset;
- const header = Dwarf.Unwind.EhFrameHeader.parse(section_vaddr, section_bytes, @sizeOf(usize), native_endian) catch |err| switch (err) {
- error.ReadFailed => unreachable, // it's all fixed buffers
- error.InvalidDebugInfo => |e| return e,
- error.EndOfStream, error.Overflow => return error.InvalidDebugInfo,
- error.UnsupportedAddrSize => return error.UnsupportedDebugInfo,
- };
- buf[0] = .initEhFrameHdr(header, section_vaddr, @ptrFromInt(@as(usize, @intCast(module.load_offset + header.eh_frame_vaddr))));
- break :unwinds buf[0..1];
- } else unwinds: {
- // There is no `.eh_frame_hdr` section. There may still be an `.eh_frame` or `.debug_frame`
- // section, but we'll have to load the binary to get at it.
- if (di.loaded_elf == null) try module.loadElf(gpa, di);
- const opt_debug_frame = &di.loaded_elf.?.debug_frame;
- const opt_eh_frame = &di.loaded_elf.?.eh_frame;
- var i: usize = 0;
- // If both are present, we can't just pick one -- the info could be split between them.
- // `.debug_frame` is likely to be the more complete section, so we'll prioritize that one.
- if (opt_debug_frame.*) |*debug_frame| {
- buf[i] = .initSection(.debug_frame, debug_frame.vaddr, debug_frame.bytes);
- i += 1;
- }
- if (opt_eh_frame.*) |*eh_frame| {
- buf[i] = .initSection(.eh_frame, eh_frame.vaddr, eh_frame.bytes);
- i += 1;
- }
- if (i == 0) return error.MissingDebugInfo;
- break :unwinds buf[0..i];
- };
- errdefer for (unwinds) |*u| u.deinit(gpa);
- for (unwinds) |*u| try prepareUnwindLookup(u, gpa);
-
- const unwind_cache = try gpa.create(UnwindContext.Cache);
- errdefer gpa.destroy(unwind_cache);
- unwind_cache.init();
-
- switch (unwinds.len) {
- 0 => unreachable,
- 1 => di.unwind = .{ unwinds[0], null },
- 2 => di.unwind = .{ unwinds[0], unwinds[1] },
- else => unreachable,
- }
- di.unwind_cache = unwind_cache;
-}
-pub fn unwindFrame(module: *const ElfModule, gpa: Allocator, di: *DebugInfo, context: *UnwindContext) Error!usize {
- const unwinds: *const [2]?Dwarf.Unwind = u: {
- di.mutex.lock();
- defer di.mutex.unlock();
- if (di.unwind[0] == null) try module.loadUnwindInfo(gpa, di);
- std.debug.assert(di.unwind[0] != null);
- break :u &di.unwind;
- };
- for (unwinds) |*opt_unwind| {
- const unwind = &(opt_unwind.* orelse break);
- return context.unwindFrame(di.unwind_cache, gpa, unwind, module.load_offset, null) catch |err| switch (err) {
- error.MissingDebugInfo => continue, // try the next one
- else => |e| return e,
- };
- }
- return error.MissingDebugInfo;
-}
-pub const UnwindContext = std.debug.SelfInfo.DwarfUnwindContext;
-pub const supports_unwinding: bool = s: {
- // Notably, we are yet to support unwinding on ARM. There, unwinding is not done through
- // `.eh_frame`, but instead with the `.ARM.exidx` section, which has a different format.
- const archs: []const std.Target.Cpu.Arch = switch (builtin.target.os.tag) {
- .linux => &.{ .x86, .x86_64, .aarch64, .aarch64_be },
- .netbsd => &.{ .x86, .x86_64, .aarch64, .aarch64_be },
- .freebsd => &.{ .x86_64, .aarch64, .aarch64_be },
- .openbsd => &.{.x86_64},
- .solaris => &.{ .x86, .x86_64 },
- .illumos => &.{ .x86, .x86_64 },
- else => unreachable,
- };
- for (archs) |a| {
- if (builtin.target.cpu.arch == a) break :s true;
- }
- break :s false;
-};
-comptime {
- if (supports_unwinding) {
- std.debug.assert(Dwarf.supportsUnwinding(&builtin.target));
- }
-}
-
-const ElfModule = @This();
-
-const std = @import("../../std.zig");
-const Allocator = std.mem.Allocator;
-const Dwarf = std.debug.Dwarf;
-const ElfFile = std.debug.ElfFile;
-const elf = std.elf;
-const mem = std.mem;
-const Error = std.debug.SelfInfo.Error;
-
-const builtin = @import("builtin");
-const native_endian = builtin.target.cpu.arch.endian();