load
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.
Function parameters
Parameters
Type definitions in this namespace
Types
- ExceptionFrameHeader
- This represents the decoded .eh_frame_hdr header
Initialize DWARF info.
Functions
- open
- Initialize DWARF info.
- findCompileUnit
- TODO: change this to binary searching the sorted compile unit list
- scanAllUnwindInfo
- If `.eh_frame_hdr` is present, then only the header needs to be parsed.
- scanCieFdeInfo
- Scan `.eh_frame` and `.debug_frame` and build a sorted list of FDEs for binary searching during
- compactUnwindToDwarfRegNumber
- Returns the DWARF register number for an x86_64 register number found in compact unwind info
- bad
- This function is to make it handy to comment out the return and make it
Error sets in this namespace
Error Sets
= [_]?Section{null} ** num_sections
Values
- null_section_array
- = [_]?Section{null} ** num_sections
Source
Implementation
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_reader: std.Io.Reader = .fixed(section_bytes);
const chdr = section_reader.takeStruct(elf.Chdr, endian) catch continue;
if (chdr.ch_type != .ZLIB) continue;
var decompress: std.compress.flate.Decompress = .init(§ion_reader, .zlib, &.{});
var decompressed_section: ArrayList(u8) = .empty;
defer decompressed_section.deinit(gpa);
decompress.reader.appendRemainingUnlimited(gpa, &decompressed_section) catch {
invalidDebugInfoDetected();
continue;
};
if (chdr.ch_size != decompressed_section.items.len) {
invalidDebugInfoDetected();
continue;
}
break :blk .{
.data = try decompressed_section.toOwnedSlice(gpa),
.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/<buildid>/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, "{x}/debuginfo", .{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",
};
// <global debug directory>/.build-id/<2-character id prefix>/<id remainder>.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, "{x}", .{id[0..1]}) catch unreachable;
const filename = std.fmt.bufPrint(&filename_buf, "{x}" ++ extension, .{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();
// <exe_dir>/<gnu_debuglink>
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 |_| {}
// <exe_dir>/.debug/<gnu_debuglink>
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;
// <global debug directory>/<absolute folder of current binary>/<gnu_debuglink>
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,
};
}