mirror of
https://codeberg.org/ziglang/zig.git
synced 2025-12-06 13:54:21 +00:00
...in that it isn't: it's currently very specialized to DWARF unwinding. Also, make a type unmanaged.
240 lines
9.9 KiB
Zig
240 lines
9.9 KiB
Zig
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 = void;
|
|
|
|
pub const DebugInfo = struct {
|
|
loaded_elf: ?Dwarf.ElfModule,
|
|
unwind: [2]?Dwarf.Unwind,
|
|
pub const init: DebugInfo = .{
|
|
.loaded_elf = null,
|
|
.unwind = @splat(null),
|
|
};
|
|
pub fn deinit(di: *DebugInfo, gpa: Allocator) void {
|
|
if (di.loaded_elf) |*loaded_elf| loaded_elf.deinit(gpa);
|
|
}
|
|
};
|
|
|
|
pub fn key(m: ElfModule) usize {
|
|
return m.load_offset;
|
|
}
|
|
pub fn lookup(cache: *LookupCache, gpa: Allocator, address: usize) Error!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 loadDwarf(module: *const ElfModule, gpa: Allocator, di: *DebugInfo) Error!void {
|
|
const load_result = if (module.name.len > 0) res: {
|
|
break :res Dwarf.ElfModule.load(gpa, .{
|
|
.root_dir = .cwd(),
|
|
.sub_path = module.name,
|
|
}, module.build_id, null, null, null);
|
|
} 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);
|
|
break :res Dwarf.ElfModule.load(gpa, .{
|
|
.root_dir = .cwd(),
|
|
.sub_path = path,
|
|
}, module.build_id, null, null, null);
|
|
};
|
|
di.loaded_elf = load_result catch |err| switch (err) {
|
|
error.FileNotFound => return error.MissingDebugInfo,
|
|
|
|
error.OutOfMemory,
|
|
error.InvalidDebugInfo,
|
|
error.MissingDebugInfo,
|
|
error.Unexpected,
|
|
=> |e| return e,
|
|
|
|
error.InvalidElfEndian,
|
|
error.InvalidElfMagic,
|
|
error.InvalidElfVersion,
|
|
error.InvalidUtf8,
|
|
error.InvalidWtf8,
|
|
error.EndOfStream,
|
|
error.Overflow,
|
|
error.UnimplementedDwarfForeignEndian, // this should be impossible as we're looking at the debug info for this process
|
|
=> return error.InvalidDebugInfo,
|
|
|
|
else => return error.ReadFailed,
|
|
};
|
|
}
|
|
pub fn getSymbolAtAddress(module: *const ElfModule, gpa: Allocator, di: *DebugInfo, address: usize) Error!std.debug.Symbol {
|
|
if (di.loaded_elf == null) try module.loadDwarf(gpa, di);
|
|
const vaddr = address - module.load_offset;
|
|
return di.loaded_elf.?.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,
|
|
};
|
|
}
|
|
fn prepareUnwindLookup(unwind: *Dwarf.Unwind, gpa: Allocator) Error!void {
|
|
unwind.prepareLookup(gpa, @sizeOf(usize), native_endian) 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 => return error.InvalidDebugInfo,
|
|
error.UnsupportedAddrSize, error.UnsupportedDwarfVersion => 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(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.
|
|
try module.loadDwarf(gpa, di);
|
|
const opt_debug_frame = &di.loaded_elf.?.debug_frame;
|
|
const opt_eh_frame = &di.loaded_elf.?.eh_frame;
|
|
// 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[0] = .initSection(.debug_frame, debug_frame.vaddr, debug_frame.bytes);
|
|
if (opt_eh_frame.*) |*eh_frame| {
|
|
buf[1] = .initSection(.eh_frame, eh_frame.vaddr, eh_frame.bytes);
|
|
break :unwinds buf[0..2];
|
|
}
|
|
break :unwinds buf[0..1];
|
|
} else if (opt_eh_frame.*) |*eh_frame| {
|
|
buf[0] = .initSection(.eh_frame, eh_frame.vaddr, eh_frame.bytes);
|
|
break :unwinds buf[0..1];
|
|
}
|
|
return error.MissingDebugInfo;
|
|
};
|
|
errdefer for (unwinds) |*u| u.deinit(gpa);
|
|
for (unwinds) |*u| try prepareUnwindLookup(u, gpa);
|
|
switch (unwinds.len) {
|
|
0 => unreachable,
|
|
1 => di.unwind = .{ unwinds[0], null },
|
|
2 => di.unwind = .{ unwinds[0], unwinds[1] },
|
|
else => unreachable,
|
|
}
|
|
}
|
|
pub fn unwindFrame(module: *const ElfModule, gpa: Allocator, di: *DebugInfo, context: *DwarfUnwindContext) Error!usize {
|
|
if (di.unwind[0] == null) try module.loadUnwindInfo(gpa, di);
|
|
std.debug.assert(di.unwind[0] != null);
|
|
for (&di.unwind) |*opt_unwind| {
|
|
const unwind = &(opt_unwind.* orelse break);
|
|
return context.unwindFrame(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 supports_unwinding: bool = s: {
|
|
const archs: []const std.Target.Cpu.Arch = switch (builtin.target.os.tag) {
|
|
.linux => &.{ .x86, .x86_64, .arm, .armeb, .thumb, .thumbeb, .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.abi.supportsUnwinding(&builtin.target));
|
|
}
|
|
}
|
|
|
|
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 DwarfUnwindContext = std.debug.SelfInfo.DwarfUnwindContext;
|
|
const Error = std.debug.SelfInfo.Error;
|
|
|
|
const builtin = @import("builtin");
|
|
const native_endian = builtin.target.cpu.arch.endian();
|