aboutsummaryrefslogtreecommitdiff
path: root/lib/std/debug/SelfInfo/ElfModule.zig
diff options
context:
space:
mode:
authormlugg <mlugg@mlugg.co.uk>2025-09-02 18:36:05 +0100
committermlugg <mlugg@mlugg.co.uk>2025-09-30 13:44:50 +0100
commitba3f38959a31ace9af1816f16cda6c0717518b7f (patch)
tree3dc9419d74c55a563b69554c781a9faaa61f5ff3 /lib/std/debug/SelfInfo/ElfModule.zig
parent1397b95143a799d836f549448485d87a70de391c (diff)
downloadzig-ba3f38959a31ace9af1816f16cda6c0717518b7f.tar.gz
zig-ba3f38959a31ace9af1816f16cda6c0717518b7f.zip
split SelfInfo into a file per impl
Diffstat (limited to 'lib/std/debug/SelfInfo/ElfModule.zig')
-rw-r--r--lib/std/debug/SelfInfo/ElfModule.zig144
1 files changed, 144 insertions, 0 deletions
diff --git a/lib/std/debug/SelfInfo/ElfModule.zig b/lib/std/debug/SelfInfo/ElfModule.zig
new file mode 100644
index 0000000000..41ea722962
--- /dev/null
+++ b/lib/std/debug/SelfInfo/ElfModule.zig
@@ -0,0 +1,144 @@
+load_offset: usize,
+name: []const u8,
+build_id: ?[]const u8,
+gnu_eh_frame: ?[]const u8,
+
+/// No cache needed, because `dl_iterate_phdr` is already fast.
+pub const LookupCache = struct {
+ pub const init: LookupCache = .{};
+};
+
+pub const DebugInfo = struct {
+ loaded_elf: ?Dwarf.ElfModule,
+ unwind: ?Dwarf.Unwind,
+ pub const init: DebugInfo = .{
+ .loaded_elf = null,
+ .unwind = null,
+ };
+};
+
+pub fn key(m: ElfModule) usize {
+ return m.load_offset;
+}
+pub fn lookup(cache: *LookupCache, gpa: Allocator, address: usize) !ElfModule {
+ _ = cache;
+ _ = gpa;
+ if (builtin.target.os.tag == .haiku) @panic("TODO implement lookup module for Haiku");
+ const DlIterContext = struct {
+ /// input
+ address: usize,
+ /// output
+ module: ElfModule,
+
+ fn callback(info: *std.posix.dl_phdr_info, size: usize, context: *@This()) !void {
+ _ = size;
+ // The base address is too high
+ if (context.address < info.addr)
+ return;
+
+ const phdrs = info.phdr[0..info.phnum];
+ for (phdrs) |*phdr| {
+ if (phdr.p_type != elf.PT_LOAD) continue;
+
+ // Overflowing addition is used to handle the case of VSDOs having a p_vaddr = 0xffffffffff700000
+ const seg_start = info.addr +% phdr.p_vaddr;
+ const seg_end = seg_start + phdr.p_memsz;
+ if (context.address >= seg_start and context.address < seg_end) {
+ context.module = .{
+ .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,
+ };
+ break;
+ }
+ } else return;
+
+ 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;
+ context.module.build_id = desc;
+ },
+ elf.PT_GNU_EH_FRAME => {
+ const segment_ptr: [*]const u8 = @ptrFromInt(info.addr + phdr.p_vaddr);
+ context.module.gnu_eh_frame = segment_ptr[0..phdr.p_memsz];
+ },
+ else => {},
+ }
+ }
+
+ // Stop the iteration
+ return error.Found;
+ }
+ };
+ var ctx: DlIterContext = .{
+ .address = address,
+ .module = undefined,
+ };
+ std.posix.dl_iterate_phdr(&ctx, error{Found}, DlIterContext.callback) catch |err| switch (err) {
+ error.Found => return ctx.module,
+ };
+ return error.MissingDebugInfo;
+}
+fn loadLocationInfo(module: *const ElfModule, gpa: Allocator, di: *DebugInfo) !void {
+ if (module.name.len > 0) {
+ di.loaded_elf = Dwarf.ElfModule.load(gpa, .{
+ .root_dir = .cwd(),
+ .sub_path = module.name,
+ }, module.build_id, null, null, null) catch |err| switch (err) {
+ error.FileNotFound => return error.MissingDebugInfo,
+ error.Overflow => return error.InvalidDebugInfo,
+ else => |e| return e,
+ };
+ } else {
+ const path = try std.fs.selfExePathAlloc(gpa);
+ defer gpa.free(path);
+ di.loaded_elf = Dwarf.ElfModule.load(gpa, .{
+ .root_dir = .cwd(),
+ .sub_path = path,
+ }, module.build_id, null, null, null) catch |err| switch (err) {
+ error.FileNotFound => return error.MissingDebugInfo,
+ error.Overflow => return error.InvalidDebugInfo,
+ else => |e| return e,
+ };
+ }
+}
+pub fn getSymbolAtAddress(module: *const ElfModule, gpa: Allocator, di: *DebugInfo, address: usize) !std.debug.Symbol {
+ if (di.loaded_elf == null) try module.loadLocationInfo(gpa, di);
+ const vaddr = address - module.load_offset;
+ return di.loaded_elf.?.dwarf.getSymbol(gpa, native_endian, vaddr);
+}
+fn loadUnwindInfo(module: *const ElfModule, gpa: Allocator, di: *DebugInfo) !void {
+ const section_bytes = module.gnu_eh_frame orelse return error.MissingUnwindInfo; // MLUGG TODO: load from file
+ const section_vaddr: u64 = @intFromPtr(section_bytes.ptr) - module.load_offset;
+ const header: Dwarf.Unwind.EhFrameHeader = try .parse(section_vaddr, section_bytes, @sizeOf(usize), native_endian);
+ di.unwind = .initEhFrameHdr(header, section_vaddr, @ptrFromInt(module.load_offset + header.eh_frame_vaddr));
+ try di.unwind.?.prepareLookup(gpa, @sizeOf(usize), native_endian);
+}
+pub fn unwindFrame(module: *const ElfModule, gpa: Allocator, di: *DebugInfo, context: *UnwindContext) !usize {
+ if (di.unwind == null) try module.loadUnwindInfo(gpa, di);
+ return context.unwindFrameDwarf(&di.unwind.?, module.load_offset, null);
+}
+
+const ElfModule = @This();
+
+const std = @import("../../std.zig");
+const Allocator = std.mem.Allocator;
+const Dwarf = std.debug.Dwarf;
+const elf = std.elf;
+const mem = std.mem;
+const UnwindContext = std.debug.SelfInfo.UnwindContext;
+
+const builtin = @import("builtin");
+const native_endian = builtin.target.cpu.arch.endian();