Function load [src]
Prototype
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
Parameters
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
Possible Errors
In WASI, this error may occur when the file descriptor does not hold the required rights to open a new resource relative to it.
On Windows, antivirus software is enabled by default. It can be disabled, but Windows Update sometimes ignores the user's preference and re-enables it. When enabled, antivirus software on Windows intercepts file system operations and makes them significantly slower in addition to possibly failing with this error code.
On Windows, file paths cannot contain these characters: '/', '*', '?', '"', '<', '>', '|'
One of these three things:
- pathname refers to an executable image which is currently being executed and write access was requested.
- pathname refers to a file that is currently in use as a swap file, and the O_TRUNC flag was specified.
- pathname refers to a file that is currently being read by the kernel (e.g., for module/firmware loading), and write access was requested.
The underlying filesystem does not support file locks
Either:
- One of the path components does not exist.
- Cwd was used, but cwd has been deleted.
- The path associated with the open directory handle has been deleted.
- On macOS, multiple processes or threads raced to create the same file
with
O.EXCL
set tofalse
.
The file is too large to be opened. This error is unreachable for 64-bit targets, as well as when opening directories.
WASI-only; file paths must be valid UTF-8.
Windows-only; file paths provided by the user must be valid WTF-8. https://simonsapin.github.io/wtf-8/
The path refers to directory but the DIRECTORY
flag was not provided.
The path exceeded max_path_bytes
bytes.
On Windows, \\server
or \\server\share
was not found.
A new path cannot be created because the device has no room for the new file.
This error is only reachable when the CREAT
flag is provided.
A component used as a directory in the path was not, in fact, a directory, or
DIRECTORY
was specified and the path was not a directory.
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.
The path already exists and the CREAT
and EXCL
flags were provided.
Insufficient kernel memory was available, or the named file is a FIFO and per-user hard limit on memory allocation for pipes has been reached.
The Operating System returned an undocumented error code.
This error is in theory not possible, but it would be better to handle this error than to invoke undefined behavior.
When this error code is observed, it usually means the Zig Standard Library needs a small patch to add the error code to the error set for the respective function.
TODO: implement this and then remove this error code
Source
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,
};
}