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

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