struct ElfModule [src]
Fields
base_address: usize
dwarf: Dwarf
mapped_memory: []align(std.heap.page_size_min) const u8
external_mapped_memory: ?[]align(std.heap.page_size_min) const u8
Members
- deinit (Function)
- getDwarfInfoForAddress (Function)
- getSymbolAtAddress (Function)
- load (Function)
- LoadError (Error Set)
- loadPath (Function)
Source
pub const ElfModule = struct {
base_address: usize,
dwarf: Dwarf,
mapped_memory: []align(std.heap.page_size_min) const u8,
external_mapped_memory: ?[]align(std.heap.page_size_min) const u8,
pub fn deinit(self: *@This(), allocator: Allocator) void {
self.dwarf.deinit(allocator);
std.posix.munmap(self.mapped_memory);
if (self.external_mapped_memory) |m| std.posix.munmap(m);
}
pub fn getSymbolAtAddress(self: *@This(), allocator: Allocator, address: usize) !std.debug.Symbol {
// Translate the VA into an address into this object
const relocated_address = address - self.base_address;
return self.dwarf.getSymbol(allocator, relocated_address);
}
pub fn getDwarfInfoForAddress(self: *@This(), allocator: Allocator, address: usize) !?*Dwarf {
_ = allocator;
_ = address;
return &self.dwarf;
}
pub const LoadError = error{
InvalidDebugInfo,
MissingDebugInfo,
InvalidElfMagic,
InvalidElfVersion,
InvalidElfEndian,
/// TODO: implement this and then remove this error code
UnimplementedDwarfForeignEndian,
/// The debug info may be valid but this implementation uses memory
/// mapping which limits things to usize. If the target debug info is
/// 64-bit and host is 32-bit, there may be debug info that is not
/// supportable using this method.
Overflow,
PermissionDenied,
LockedMemoryLimitExceeded,
MemoryMappingNotSupported,
} || Allocator.Error || std.fs.File.OpenError || OpenError;
/// Reads debug info from an already mapped ELF file.
///
/// If the required sections aren't present but a reference to external debug
/// info is, then this this function will recurse to attempt to load the debug
/// sections from an external file.
pub fn load(
gpa: Allocator,
mapped_mem: []align(std.heap.page_size_min) const u8,
build_id: ?[]const u8,
expected_crc: ?u32,
parent_sections: *Dwarf.SectionArray,
parent_mapped_mem: ?[]align(std.heap.page_size_min) const u8,
elf_filename: ?[]const u8,
) LoadError!Dwarf.ElfModule {
if (expected_crc) |crc| if (crc != std.hash.crc.Crc32.hash(mapped_mem)) return error.InvalidDebugInfo;
const hdr: *const elf.Ehdr = @ptrCast(&mapped_mem[0]);
if (!mem.eql(u8, hdr.e_ident[0..4], elf.MAGIC)) return error.InvalidElfMagic;
if (hdr.e_ident[elf.EI_VERSION] != 1) return error.InvalidElfVersion;
const endian: std.builtin.Endian = switch (hdr.e_ident[elf.EI_DATA]) {
elf.ELFDATA2LSB => .little,
elf.ELFDATA2MSB => .big,
else => return error.InvalidElfEndian,
};
if (endian != native_endian) return error.UnimplementedDwarfForeignEndian;
const shoff = hdr.e_shoff;
const str_section_off = shoff + @as(u64, hdr.e_shentsize) * @as(u64, hdr.e_shstrndx);
const str_shdr: *const elf.Shdr = @ptrCast(@alignCast(&mapped_mem[cast(usize, str_section_off) orelse return error.Overflow]));
const header_strings = mapped_mem[str_shdr.sh_offset..][0..str_shdr.sh_size];
const shdrs = @as(
[*]const elf.Shdr,
@ptrCast(@alignCast(&mapped_mem[shoff])),
)[0..hdr.e_shnum];
var sections: Dwarf.SectionArray = Dwarf.null_section_array;
// Combine section list. This takes ownership over any owned sections from the parent scope.
for (parent_sections, §ions) |*parent, *section_elem| {
if (parent.*) |*p| {
section_elem.* = p.*;
p.owned = false;
}
}
errdefer for (sections) |opt_section| if (opt_section) |s| if (s.owned) gpa.free(s.data);
var separate_debug_filename: ?[]const u8 = null;
var separate_debug_crc: ?u32 = null;
for (shdrs) |*shdr| {
if (shdr.sh_type == elf.SHT_NULL or shdr.sh_type == elf.SHT_NOBITS) continue;
const name = mem.sliceTo(header_strings[shdr.sh_name..], 0);
if (mem.eql(u8, name, ".gnu_debuglink")) {
const gnu_debuglink = try chopSlice(mapped_mem, shdr.sh_offset, shdr.sh_size);
const debug_filename = mem.sliceTo(@as([*:0]const u8, @ptrCast(gnu_debuglink.ptr)), 0);
const crc_offset = mem.alignForward(usize, debug_filename.len + 1, 4);
const crc_bytes = gnu_debuglink[crc_offset..][0..4];
separate_debug_crc = mem.readInt(u32, crc_bytes, native_endian);
separate_debug_filename = debug_filename;
continue;
}
var section_index: ?usize = null;
inline for (@typeInfo(Dwarf.Section.Id).@"enum".fields, 0..) |sect, i| {
if (mem.eql(u8, "." ++ sect.name, name)) section_index = i;
}
if (section_index == null) continue;
if (sections[section_index.?] != null) continue;
const section_bytes = try chopSlice(mapped_mem, shdr.sh_offset, shdr.sh_size);
sections[section_index.?] = if ((shdr.sh_flags & elf.SHF_COMPRESSED) > 0) blk: {
var section_stream = std.io.fixedBufferStream(section_bytes);
const section_reader = section_stream.reader();
const chdr = section_reader.readStruct(elf.Chdr) catch continue;
if (chdr.ch_type != .ZLIB) continue;
var zlib_stream = std.compress.zlib.decompressor(section_reader);
const decompressed_section = try gpa.alloc(u8, chdr.ch_size);
errdefer gpa.free(decompressed_section);
const read = zlib_stream.reader().readAll(decompressed_section) catch continue;
assert(read == decompressed_section.len);
break :blk .{
.data = decompressed_section,
.virtual_address = shdr.sh_addr,
.owned = true,
};
} else .{
.data = section_bytes,
.virtual_address = shdr.sh_addr,
.owned = false,
};
}
const missing_debug_info =
sections[@intFromEnum(Dwarf.Section.Id.debug_info)] == null or
sections[@intFromEnum(Dwarf.Section.Id.debug_abbrev)] == null or
sections[@intFromEnum(Dwarf.Section.Id.debug_str)] == null or
sections[@intFromEnum(Dwarf.Section.Id.debug_line)] == null;
// Attempt to load debug info from an external file
// See: https://sourceware.org/gdb/onlinedocs/gdb/Separate-Debug-Files.html
if (missing_debug_info) {
// Only allow one level of debug info nesting
if (parent_mapped_mem) |_| {
return error.MissingDebugInfo;
}
// $XDG_CACHE_HOME/debuginfod_client//debuginfo
// This only opportunisticly tries to load from the debuginfod cache, but doesn't try to populate it.
// One can manually run `debuginfod-find debuginfo PATH` to download the symbols
if (build_id) |id| blk: {
var debuginfod_dir: std.fs.Dir = switch (builtin.os.tag) {
.wasi, .windows => break :blk,
else => dir: {
if (std.posix.getenv("DEBUGINFOD_CACHE_PATH")) |path| {
break :dir std.fs.openDirAbsolute(path, .{}) catch break :blk;
}
if (std.posix.getenv("XDG_CACHE_HOME")) |cache_path| {
if (cache_path.len > 0) {
const path = std.fs.path.join(gpa, &[_][]const u8{ cache_path, "debuginfod_client" }) catch break :blk;
defer gpa.free(path);
break :dir std.fs.openDirAbsolute(path, .{}) catch break :blk;
}
}
if (std.posix.getenv("HOME")) |home_path| {
const path = std.fs.path.join(gpa, &[_][]const u8{ home_path, ".cache", "debuginfod_client" }) catch break :blk;
defer gpa.free(path);
break :dir std.fs.openDirAbsolute(path, .{}) catch break :blk;
}
break :blk;
},
};
defer debuginfod_dir.close();
const filename = std.fmt.allocPrint(
gpa,
"{s}/debuginfo",
.{std.fmt.fmtSliceHexLower(id)},
) catch break :blk;
defer gpa.free(filename);
const path: Path = .{
.root_dir = .{ .path = null, .handle = debuginfod_dir },
.sub_path = filename,
};
return loadPath(gpa, path, null, separate_debug_crc, §ions, mapped_mem) catch break :blk;
}
const global_debug_directories = [_][]const u8{
"/usr/lib/debug",
};
// /.build-id/<2-character id prefix>/.debug
if (build_id) |id| blk: {
if (id.len < 3) break :blk;
// Either md5 (16 bytes) or sha1 (20 bytes) are used here in practice
const extension = ".debug";
var id_prefix_buf: [2]u8 = undefined;
var filename_buf: [38 + extension.len]u8 = undefined;
_ = std.fmt.bufPrint(&id_prefix_buf, "{s}", .{std.fmt.fmtSliceHexLower(id[0..1])}) catch unreachable;
const filename = std.fmt.bufPrint(
&filename_buf,
"{s}" ++ extension,
.{std.fmt.fmtSliceHexLower(id[1..])},
) catch break :blk;
for (global_debug_directories) |global_directory| {
const path: Path = .{
.root_dir = std.Build.Cache.Directory.cwd(),
.sub_path = try std.fs.path.join(gpa, &.{
global_directory, ".build-id", &id_prefix_buf, filename,
}),
};
defer gpa.free(path.sub_path);
return loadPath(gpa, path, null, separate_debug_crc, §ions, mapped_mem) catch continue;
}
}
// use the path from .gnu_debuglink, in the same search order as gdb
if (separate_debug_filename) |separate_filename| blk: {
if (elf_filename != null and mem.eql(u8, elf_filename.?, separate_filename))
return error.MissingDebugInfo;
exe_dir: {
var exe_dir_buf: [std.fs.max_path_bytes]u8 = undefined;
const exe_dir_path = std.fs.selfExeDirPath(&exe_dir_buf) catch break :exe_dir;
var exe_dir = std.fs.openDirAbsolute(exe_dir_path, .{}) catch break :exe_dir;
defer exe_dir.close();
// /
if (loadPath(
gpa,
.{
.root_dir = .{ .path = null, .handle = exe_dir },
.sub_path = separate_filename,
},
null,
separate_debug_crc,
§ions,
mapped_mem,
)) |debug_info| {
return debug_info;
} else |_| {}
// /.debug/
const path: Path = .{
.root_dir = .{ .path = null, .handle = exe_dir },
.sub_path = try std.fs.path.join(gpa, &.{ ".debug", separate_filename }),
};
defer gpa.free(path.sub_path);
if (loadPath(gpa, path, null, separate_debug_crc, §ions, mapped_mem)) |debug_info| return debug_info else |_| {}
}
var cwd_buf: [std.fs.max_path_bytes]u8 = undefined;
const cwd_path = std.posix.realpath(".", &cwd_buf) catch break :blk;
// //
for (global_debug_directories) |global_directory| {
const path: Path = .{
.root_dir = std.Build.Cache.Directory.cwd(),
.sub_path = try std.fs.path.join(gpa, &.{ global_directory, cwd_path, separate_filename }),
};
defer gpa.free(path.sub_path);
if (loadPath(gpa, path, null, separate_debug_crc, §ions, mapped_mem)) |debug_info| return debug_info else |_| {}
}
}
return error.MissingDebugInfo;
}
var di: Dwarf = .{
.endian = endian,
.sections = sections,
.is_macho = false,
};
try Dwarf.open(&di, gpa);
return .{
.base_address = 0,
.dwarf = di,
.mapped_memory = parent_mapped_mem orelse mapped_mem,
.external_mapped_memory = if (parent_mapped_mem != null) mapped_mem else null,
};
}
pub fn loadPath(
gpa: Allocator,
elf_file_path: Path,
build_id: ?[]const u8,
expected_crc: ?u32,
parent_sections: *Dwarf.SectionArray,
parent_mapped_mem: ?[]align(std.heap.page_size_min) const u8,
) LoadError!Dwarf.ElfModule {
const elf_file = elf_file_path.root_dir.handle.openFile(elf_file_path.sub_path, .{}) catch |err| switch (err) {
error.FileNotFound => return missing(),
else => return err,
};
defer elf_file.close();
const end_pos = elf_file.getEndPos() catch return bad();
const file_len = cast(usize, end_pos) orelse return error.Overflow;
const mapped_mem = std.posix.mmap(
null,
file_len,
std.posix.PROT.READ,
.{ .TYPE = .SHARED },
elf_file.handle,
0,
) catch |err| switch (err) {
error.MappingAlreadyExists => unreachable,
else => |e| return e,
};
errdefer std.posix.munmap(mapped_mem);
return load(
gpa,
mapped_mem,
build_id,
expected_crc,
parent_sections,
parent_mapped_mem,
elf_file_path.sub_path,
);
}
}