Function load [src]

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.

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: Allocatormapped_mem: []align(std.heap.page_size_min) const u8build_id: ?[]const u8expected_crc: ?u32parent_sections: *Dwarf.SectionArrayparent_mapped_mem: ?[]align(std.heap.page_size_min) const u8elf_filename: ?[]const u8

Possible Errors

AccessDenied OpenError

In WASI, this error may occur when the file descriptor does not hold the required rights to open a new resource relative to it.

AntivirusInterference OpenError

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.

BadPathName OpenError

On Windows, file paths cannot contain these characters: '/', '*', '?', '"', '<', '>', '|'

DeviceBusy OpenError
EndOfBuffer Error
FileBusy OpenError

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.
FileLocksNotSupported OpenError

The underlying filesystem does not support file locks

FileNotFound OpenError

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 to false.
FileTooBig OpenError

The file is too large to be opened. This error is unreachable for 64-bit targets, as well as when opening directories.

InvalidBuffer Error
InvalidDebugInfo
InvalidElfEndian
InvalidElfMagic
InvalidElfVersion
InvalidUtf8 OpenError

WASI-only; file paths must be valid UTF-8.

InvalidWtf8 OpenError

Windows-only; file paths provided by the user must be valid WTF-8. https://simonsapin.github.io/wtf-8/

IsDir OpenError

The path refers to directory but the DIRECTORY flag was not provided.

LockedMemoryLimitExceeded
MemoryMappingNotSupported
MissingDebugInfo
NameTooLong OpenError

The path exceeded max_path_bytes bytes.

NetworkNotFound OpenError

On Windows, \\server or \\server\share was not found.

NoDevice OpenError
NoSpaceLeft OpenError

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.

NotDir OpenError

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.

OutOfMemory Error
Overflow

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.

PathAlreadyExists OpenError

The path already exists and the CREAT and EXCL flags were provided.

PermissionDenied
PipeBusy OpenError
ProcessFdQuotaExceeded OpenError
SharingViolation OpenError
SymLinkLoop OpenError
SystemFdQuotaExceeded OpenError
SystemResources OpenError

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.

Unexpected UnexpectedError

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.

UnimplementedDwarfForeignEndian

TODO: implement this and then remove this error code

WouldBlock OpenError

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, }; }