diff options
| author | mlugg <mlugg@mlugg.co.uk> | 2025-09-02 18:36:05 +0100 |
|---|---|---|
| committer | mlugg <mlugg@mlugg.co.uk> | 2025-09-30 13:44:50 +0100 |
| commit | ba3f38959a31ace9af1816f16cda6c0717518b7f (patch) | |
| tree | 3dc9419d74c55a563b69554c781a9faaa61f5ff3 /lib/std/debug/SelfInfo/ElfModule.zig | |
| parent | 1397b95143a799d836f549448485d87a70de391c (diff) | |
| download | zig-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.zig | 144 |
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(); |
