| 1 | //! A helper type for loading an ELF file and collecting its DWARF debug information, unwind |
| 2 | //! information, and symbol table. |
| 3 | const ElfFile = @This(); |
| 4 | |
| 5 | const std = @import("std"); |
| 6 | const Io = std.Io; |
| 7 | const Endian = std.builtin.Endian; |
| 8 | const Dwarf = std.debug.Dwarf; |
| 9 | const Allocator = std.mem.Allocator; |
| 10 | const elf = std.elf; |
| 11 | |
| 12 | is_64: bool, |
| 13 | endian: Endian, |
| 14 | |
| 15 | /// This is `null` iff any of the required DWARF sections were missing. `ElfFile.load` does *not* |
| 16 | /// call `Dwarf.open`, `Dwarf.scanAllFunctions`, etc; that is the caller's responsibility. |
| 17 | dwarf: ?Dwarf, |
| 18 | |
| 19 | /// If non-`null`, describes the `.eh_frame` section, which can be used with `Dwarf.Unwind`. |
| 20 | eh_frame: ?UnwindSection, |
| 21 | /// If non-`null`, describes the `.debug_frame` section, which can be used with `Dwarf.Unwind`. |
| 22 | debug_frame: ?UnwindSection, |
| 23 | |
| 24 | /// If non-`null`, this is the contents of the `.strtab` section. |
| 25 | strtab: ?[]const u8, |
| 26 | /// If non-`null`, describes the `.symtab` section. |
| 27 | symtab: ?SymtabSection, |
| 28 | |
| 29 | /// Binary search table lazily populated by `searchSymtab`. |
| 30 | symbol_search_table: ?[]usize, |
| 31 | |
| 32 | /// The memory-mapped ELF file, which is referenced by `dwarf`. This field is here only so that |
| 33 | /// this memory can be unmapped by `ElfFile.deinit`. |
| 34 | mapped_file: []align(std.heap.page_size_min) const u8, |
| 35 | /// Sometimes, debug info is stored separately to the main ELF file. In that case, `mapped_file` |
| 36 | /// is the mapped ELF binary, and `mapped_debug_file` is the mapped debug info file. Both must |
| 37 | /// be unmapped by `ElfFile.deinit`. |
| 38 | mapped_debug_file: ?[]align(std.heap.page_size_min) const u8, |
| 39 | |
| 40 | arena: std.heap.ArenaAllocator.State, |
| 41 | |
| 42 | pub const UnwindSection = struct { |
| 43 | vaddr: u64, |
| 44 | bytes: []const u8, |
| 45 | }; |
| 46 | pub const SymtabSection = struct { |
| 47 | entry_size: u64, |
| 48 | bytes: []const u8, |
| 49 | }; |
| 50 | |
| 51 | pub const DebugInfoSearchPaths = struct { |
| 52 | /// The location of a debuginfod client directory, which acts as a search path for build IDs. If |
| 53 | /// given, we can load from this directory opportunistically, but make no effort to populate it. |
| 54 | /// To avoid allocation when building the search paths, this is given as two components which |
| 55 | /// will be concatenated. |
| 56 | debuginfod_client: ?[2][]const u8, |
| 57 | /// All "global debug directories" on the system. These are used as search paths for both debug |
| 58 | /// links and build IDs. On typical systems this is just "/usr/lib/debug". |
| 59 | global_debug: []const []const u8, |
| 60 | /// The path to the dirname of the ELF file, which acts as a search path for debug links. |
| 61 | exe_dir: ?[]const u8, |
| 62 | |
| 63 | pub const none: DebugInfoSearchPaths = .{ |
| 64 | .debuginfod_client = null, |
| 65 | .global_debug = &.{}, |
| 66 | .exe_dir = null, |
| 67 | }; |
| 68 | |
| 69 | pub fn native(exe_path: []const u8) DebugInfoSearchPaths { |
| 70 | if (std.Options.elf_debug_info_search_paths) |f| return f(exe_path); |
| 71 | if (std.Options.debug_threaded_io) |t| return .{ |
| 72 | .debuginfod_client = p: { |
| 73 | if (t.environString("DEBUGINFOD_CACHE_PATH")) |p| { |
| 74 | break :p .{ p, "" }; |
| 75 | } |
| 76 | if (t.environString("XDG_CACHE_HOME")) |cache_path| { |
| 77 | break :p .{ cache_path, "/debuginfod_client" }; |
| 78 | } |
| 79 | if (t.environString("HOME")) |home_path| { |
| 80 | break :p .{ home_path, "/.cache/debuginfod_client" }; |
| 81 | } |
| 82 | break :p null; |
| 83 | }, |
| 84 | .global_debug = &.{ |
| 85 | "/usr/lib/debug", |
| 86 | }, |
| 87 | .exe_dir = std.fs.path.dirname(exe_path) orelse ".", |
| 88 | }; |
| 89 | @compileError("std.Options.elf_debug_info_search_paths must be provided"); |
| 90 | } |
| 91 | }; |
| 92 | |
| 93 | pub fn deinit(ef: *ElfFile, gpa: Allocator) void { |
| 94 | if (ef.dwarf) |*dwarf| dwarf.deinit(gpa); |
| 95 | if (ef.symbol_search_table) |t| gpa.free(t); |
| 96 | var arena = ef.arena.promote(gpa); |
| 97 | arena.deinit(); |
| 98 | |
| 99 | std.posix.munmap(ef.mapped_file); |
| 100 | if (ef.mapped_debug_file) |m| std.posix.munmap(m); |
| 101 | |
| 102 | ef.* = undefined; |
| 103 | } |
| 104 | |
| 105 | pub const LoadError = error{ |
| 106 | OutOfMemory, |
| 107 | Overflow, |
| 108 | TruncatedElfFile, |
| 109 | InvalidCompressedSection, |
| 110 | InvalidElfMagic, |
| 111 | InvalidElfVersion, |
| 112 | InvalidElfClass, |
| 113 | InvalidElfEndian, |
| 114 | // The remaining errors all occur when attemping to stat or mmap a file. |
| 115 | SystemResources, |
| 116 | MemoryMappingNotSupported, |
| 117 | AccessDenied, |
| 118 | LockedMemoryLimitExceeded, |
| 119 | ProcessFdQuotaExceeded, |
| 120 | SystemFdQuotaExceeded, |
| 121 | Streaming, |
| 122 | Canceled, |
| 123 | Unexpected, |
| 124 | }; |
| 125 | |
| 126 | pub fn load( |
| 127 | gpa: Allocator, |
| 128 | io: Io, |
| 129 | elf_file: Io.File, |
| 130 | opt_build_id: ?[]const u8, |
| 131 | di_search_paths: *const DebugInfoSearchPaths, |
| 132 | ) LoadError!ElfFile { |
| 133 | var arena_instance: std.heap.ArenaAllocator = .init(gpa); |
| 134 | errdefer arena_instance.deinit(); |
| 135 | const arena = arena_instance.allocator(); |
| 136 | |
| 137 | var result = loadInner(arena, io, elf_file, null) catch |err| switch (err) { |
| 138 | error.CrcMismatch => unreachable, // we passed crc as null |
| 139 | else => |e| return e, |
| 140 | }; |
| 141 | errdefer std.posix.munmap(result.mapped_mem); |
| 142 | |
| 143 | // `loadInner` did most of the work, but we might need to load an external debug info file |
| 144 | |
| 145 | const di_mapped_mem: ?[]align(std.heap.page_size_min) const u8 = load_di: { |
| 146 | if (result.sections.get(.debug_info) != null and |
| 147 | result.sections.get(.debug_abbrev) != null and |
| 148 | result.sections.get(.debug_str) != null and |
| 149 | result.sections.get(.debug_line) != null) |
| 150 | { |
| 151 | // The info is already loaded from this file alone! |
| 152 | break :load_di null; |
| 153 | } |
| 154 | |
| 155 | // We're missing some debug info---let's try and load it from a separate file. |
| 156 | |
| 157 | build_id: { |
| 158 | const build_id = opt_build_id orelse break :build_id; |
| 159 | if (build_id.len < 3) break :build_id; |
| 160 | |
| 161 | for (di_search_paths.global_debug) |global_debug| { |
| 162 | if (try loadSeparateDebugFile(arena, io, &result, null, "{s}/.build-id/{x}/{x}.debug", .{ |
| 163 | global_debug, |
| 164 | build_id[0..1], |
| 165 | build_id[1..], |
| 166 | })) |mapped| break :load_di mapped; |
| 167 | } |
| 168 | |
| 169 | if (di_search_paths.debuginfod_client) |components| { |
| 170 | if (try loadSeparateDebugFile(arena, io, &result, null, "{s}{s}/{x}/debuginfo", .{ |
| 171 | components[0], |
| 172 | components[1], |
| 173 | build_id, |
| 174 | })) |mapped| break :load_di mapped; |
| 175 | } |
| 176 | } |
| 177 | |
| 178 | debug_link: { |
| 179 | const section = result.sections.get(.gnu_debuglink) orelse break :debug_link; |
| 180 | const debug_filename = std.mem.sliceTo(section.bytes, 0); |
| 181 | const crc_offset = std.mem.alignForward(usize, debug_filename.len + 1, 4); |
| 182 | if (section.bytes.len < crc_offset + 4) break :debug_link; |
| 183 | const debug_crc = std.mem.readInt(u32, section.bytes[crc_offset..][0..4], result.endian); |
| 184 | |
| 185 | const exe_dir = di_search_paths.exe_dir orelse break :debug_link; |
| 186 | |
| 187 | if (try loadSeparateDebugFile(arena, io, &result, debug_crc, "{s}/{s}", .{ |
| 188 | exe_dir, |
| 189 | debug_filename, |
| 190 | })) |mapped| break :load_di mapped; |
| 191 | if (try loadSeparateDebugFile(arena, io, &result, debug_crc, "{s}/.debug/{s}", .{ |
| 192 | exe_dir, |
| 193 | debug_filename, |
| 194 | })) |mapped| break :load_di mapped; |
| 195 | for (di_search_paths.global_debug) |global_debug| { |
| 196 | // This looks like a bug; it isn't. They really do embed the absolute path to the |
| 197 | // exe's dirname, *under* the global debug path. |
| 198 | if (try loadSeparateDebugFile(arena, io, &result, debug_crc, "{s}/{s}/{s}", .{ |
| 199 | global_debug, |
| 200 | exe_dir, |
| 201 | debug_filename, |
| 202 | })) |mapped| break :load_di mapped; |
| 203 | } |
| 204 | } |
| 205 | |
| 206 | break :load_di null; |
| 207 | }; |
| 208 | errdefer comptime unreachable; |
| 209 | |
| 210 | return .{ |
| 211 | .is_64 = result.is_64, |
| 212 | .endian = result.endian, |
| 213 | .dwarf = dwarf: { |
| 214 | if (result.sections.get(.debug_info) == null or |
| 215 | result.sections.get(.debug_abbrev) == null or |
| 216 | result.sections.get(.debug_str) == null or |
| 217 | result.sections.get(.debug_line) == null) |
| 218 | { |
| 219 | break :dwarf null; // debug info not present |
| 220 | } |
| 221 | var sections: Dwarf.SectionArray = @splat(null); |
| 222 | const info = @typeInfo(Dwarf.Section.Id).@"enum"; |
| 223 | inline for (info.field_names, info.field_values) |f_name, f_value| { |
| 224 | if (result.sections.get(@field(Section.Id, f_name))) |s| { |
| 225 | sections[f_value] = .{ .data = s.bytes, .owned = false }; |
| 226 | } |
| 227 | } |
| 228 | break :dwarf .{ .sections = sections }; |
| 229 | }, |
| 230 | .eh_frame = if (result.sections.get(.eh_frame)) |s| .{ |
| 231 | .vaddr = s.header.sh_addr, |
| 232 | .bytes = s.bytes, |
| 233 | } else null, |
| 234 | .debug_frame = if (result.sections.get(.debug_frame)) |s| .{ |
| 235 | .vaddr = s.header.sh_addr, |
| 236 | .bytes = s.bytes, |
| 237 | } else null, |
| 238 | .strtab = if (result.sections.get(.strtab)) |s| s.bytes else null, |
| 239 | .symtab = if (result.sections.get(.symtab)) |s| .{ |
| 240 | .entry_size = s.header.sh_entsize, |
| 241 | .bytes = s.bytes, |
| 242 | } else null, |
| 243 | .symbol_search_table = null, |
| 244 | .mapped_file = result.mapped_mem, |
| 245 | .mapped_debug_file = di_mapped_mem, |
| 246 | .arena = arena_instance.state, |
| 247 | }; |
| 248 | } |
| 249 | |
| 250 | pub fn searchSymtab(ef: *ElfFile, gpa: Allocator, vaddr: u64) error{ |
| 251 | NoSymtab, |
| 252 | NoStrtab, |
| 253 | BadSymtab, |
| 254 | OutOfMemory, |
| 255 | }!std.debug.Symbol { |
| 256 | const symtab = ef.symtab orelse return error.NoSymtab; |
| 257 | const strtab = ef.strtab orelse return error.NoStrtab; |
| 258 | |
| 259 | if (symtab.bytes.len % symtab.entry_size != 0) return error.BadSymtab; |
| 260 | |
| 261 | const swap_endian = ef.endian != @import("builtin").cpu.arch.endian(); |
| 262 | |
| 263 | switch (ef.is_64) { |
| 264 | inline true, false => |is_64| { |
| 265 | const Sym = if (is_64) elf.Elf64_Sym else elf.Elf32_Sym; |
| 266 | if (symtab.entry_size != @sizeOf(Sym)) return error.BadSymtab; |
| 267 | const symbols: []align(1) const Sym = @ptrCast(symtab.bytes); |
| 268 | if (ef.symbol_search_table == null) { |
| 269 | ef.symbol_search_table = try buildSymbolSearchTable(gpa, ef.endian, Sym, symbols); |
| 270 | } |
| 271 | const search_table = ef.symbol_search_table.?; |
| 272 | const SearchContext = struct { |
| 273 | swap_endian: bool, |
| 274 | target: u64, |
| 275 | symbols: []align(1) const Sym, |
| 276 | fn predicate(ctx: @This(), sym_index: usize) bool { |
| 277 | // We need to return `true` for the first N items, then `false` for the rest -- |
| 278 | // the index we'll get out is the first `false` one. So, we'll return `true` iff |
| 279 | // the target address is after the *end* of this symbol. This synchronizes with |
| 280 | // the logic in `buildSymbolSearchTable` which sorts by *end* address. |
| 281 | var sym = ctx.symbols[sym_index]; |
| 282 | if (ctx.swap_endian) std.mem.byteSwapAllFields(Sym, &sym); |
| 283 | const sym_end = sym.st_value + sym.st_size; |
| 284 | return ctx.target >= sym_end; |
| 285 | } |
| 286 | }; |
| 287 | const sym_index_index = std.sort.partitionPoint(usize, search_table, @as(SearchContext, .{ |
| 288 | .swap_endian = swap_endian, |
| 289 | .target = vaddr, |
| 290 | .symbols = symbols, |
| 291 | }), SearchContext.predicate); |
| 292 | if (sym_index_index == search_table.len) return .unknown; |
| 293 | var sym = symbols[search_table[sym_index_index]]; |
| 294 | if (swap_endian) std.mem.byteSwapAllFields(Sym, &sym); |
| 295 | if (vaddr < sym.st_value or vaddr >= sym.st_value + sym.st_size) return .unknown; |
| 296 | return .{ |
| 297 | .name = std.mem.sliceTo(strtab[sym.st_name..], 0), |
| 298 | .compile_unit_name = null, |
| 299 | .source_location = null, |
| 300 | }; |
| 301 | }, |
| 302 | } |
| 303 | } |
| 304 | |
| 305 | fn buildSymbolSearchTable(gpa: Allocator, endian: Endian, comptime Sym: type, symbols: []align(1) const Sym) error{ |
| 306 | OutOfMemory, |
| 307 | BadSymtab, |
| 308 | }![]usize { |
| 309 | var result: std.ArrayList(usize) = .empty; |
| 310 | defer result.deinit(gpa); |
| 311 | |
| 312 | const swap_endian = endian != @import("builtin").cpu.arch.endian(); |
| 313 | |
| 314 | for (symbols, 0..) |sym_orig, sym_index| { |
| 315 | var sym = sym_orig; |
| 316 | if (swap_endian) std.mem.byteSwapAllFields(Sym, &sym); |
| 317 | if (sym.st_name == 0) continue; |
| 318 | if (sym.st_shndx == elf.SHN_UNDEF) continue; |
| 319 | try result.append(gpa, sym_index); |
| 320 | } |
| 321 | |
| 322 | const SortContext = struct { |
| 323 | swap_endian: bool, |
| 324 | symbols: []align(1) const Sym, |
| 325 | fn lessThan(ctx: @This(), lhs_sym_index: usize, rhs_sym_index: usize) bool { |
| 326 | // We sort by *end* address, not start address. This matches up with logic in `searchSymtab`. |
| 327 | var lhs_sym = ctx.symbols[lhs_sym_index]; |
| 328 | var rhs_sym = ctx.symbols[rhs_sym_index]; |
| 329 | if (ctx.swap_endian) { |
| 330 | std.mem.byteSwapAllFields(Sym, &lhs_sym); |
| 331 | std.mem.byteSwapAllFields(Sym, &rhs_sym); |
| 332 | } |
| 333 | const lhs_val = lhs_sym.st_value + lhs_sym.st_size; |
| 334 | const rhs_val = rhs_sym.st_value + rhs_sym.st_size; |
| 335 | return lhs_val < rhs_val; |
| 336 | } |
| 337 | }; |
| 338 | std.mem.sort(usize, result.items, @as(SortContext, .{ |
| 339 | .swap_endian = swap_endian, |
| 340 | .symbols = symbols, |
| 341 | }), SortContext.lessThan); |
| 342 | |
| 343 | return result.toOwnedSlice(gpa); |
| 344 | } |
| 345 | |
| 346 | /// Only used locally, during `load`. |
| 347 | const Section = struct { |
| 348 | header: elf.Elf64_Shdr, |
| 349 | bytes: []const u8, |
| 350 | const Id = enum { |
| 351 | // DWARF sections: see `Dwarf.Section.Id`. |
| 352 | debug_info, |
| 353 | debug_abbrev, |
| 354 | debug_str, |
| 355 | debug_str_offsets, |
| 356 | debug_line, |
| 357 | debug_line_str, |
| 358 | debug_ranges, |
| 359 | debug_loclists, |
| 360 | debug_rnglists, |
| 361 | debug_addr, |
| 362 | debug_names, |
| 363 | // Then anything else we're interested in. |
| 364 | gnu_debuglink, |
| 365 | eh_frame, |
| 366 | debug_frame, |
| 367 | symtab, |
| 368 | strtab, |
| 369 | }; |
| 370 | const Array = std.enums.EnumArray(Section.Id, ?Section); |
| 371 | }; |
| 372 | |
| 373 | fn loadSeparateDebugFile( |
| 374 | arena: Allocator, |
| 375 | io: Io, |
| 376 | main_loaded: *LoadInnerResult, |
| 377 | opt_crc: ?u32, |
| 378 | comptime fmt: []const u8, |
| 379 | args: anytype, |
| 380 | ) Allocator.Error!?[]align(std.heap.page_size_min) const u8 { |
| 381 | const path = try std.fmt.allocPrint(arena, fmt, args); |
| 382 | const elf_file = Io.Dir.cwd().openFile(io, path, .{}) catch return null; |
| 383 | defer elf_file.close(io); |
| 384 | |
| 385 | const result = loadInner(arena, io, elf_file, opt_crc) catch |err| switch (err) { |
| 386 | error.OutOfMemory => |e| return e, |
| 387 | error.CrcMismatch => return null, |
| 388 | else => return null, |
| 389 | }; |
| 390 | errdefer comptime unreachable; |
| 391 | |
| 392 | const have_debug_sections = inline for (@as([]const []const u8, &.{ |
| 393 | "debug_info", |
| 394 | "debug_abbrev", |
| 395 | "debug_str", |
| 396 | "debug_line", |
| 397 | })) |name| { |
| 398 | const s = @field(Section.Id, name); |
| 399 | if (main_loaded.sections.get(s) == null and result.sections.get(s) == null) { |
| 400 | break false; |
| 401 | } |
| 402 | } else true; |
| 403 | |
| 404 | if (result.is_64 != main_loaded.is_64 or |
| 405 | result.endian != main_loaded.endian or |
| 406 | !have_debug_sections) |
| 407 | { |
| 408 | std.posix.munmap(result.mapped_mem); |
| 409 | return null; |
| 410 | } |
| 411 | |
| 412 | inline for (@typeInfo(Dwarf.Section.Id).@"enum".field_names) |f_name| { |
| 413 | const id = @field(Section.Id, f_name); |
| 414 | if (main_loaded.sections.get(id) == null) { |
| 415 | main_loaded.sections.set(id, result.sections.get(id)); |
| 416 | } |
| 417 | } |
| 418 | |
| 419 | return result.mapped_mem; |
| 420 | } |
| 421 | |
| 422 | const LoadInnerResult = struct { |
| 423 | is_64: bool, |
| 424 | endian: Endian, |
| 425 | sections: Section.Array, |
| 426 | mapped_mem: []align(std.heap.page_size_min) const u8, |
| 427 | }; |
| 428 | fn loadInner( |
| 429 | arena: Allocator, |
| 430 | io: Io, |
| 431 | elf_file: Io.File, |
| 432 | opt_crc: ?u32, |
| 433 | ) (LoadError || error{ CrcMismatch, Streaming, Canceled })!LoadInnerResult { |
| 434 | const mapped_mem: []align(std.heap.page_size_min) const u8 = mapped: { |
| 435 | const file_len = std.math.cast( |
| 436 | usize, |
| 437 | elf_file.length(io) catch |err| switch (err) { |
| 438 | error.PermissionDenied => unreachable, // not asking for PROT_EXEC |
| 439 | else => |e| return e, |
| 440 | }, |
| 441 | ) orelse return error.Overflow; |
| 442 | |
| 443 | break :mapped std.posix.mmap( |
| 444 | null, |
| 445 | file_len, |
| 446 | .{ .READ = true }, |
| 447 | .{ .TYPE = .SHARED }, |
| 448 | elf_file.handle, |
| 449 | 0, |
| 450 | ) catch |err| switch (err) { |
| 451 | error.MappingAlreadyExists => unreachable, // not using FIXED_NOREPLACE |
| 452 | error.PermissionDenied => unreachable, // not asking for PROT_EXEC |
| 453 | else => |e| return e, |
| 454 | }; |
| 455 | }; |
| 456 | |
| 457 | if (opt_crc) |crc| { |
| 458 | if (std.hash.Crc32.hash(mapped_mem) != crc) { |
| 459 | return error.CrcMismatch; |
| 460 | } |
| 461 | } |
| 462 | errdefer std.posix.munmap(mapped_mem); |
| 463 | |
| 464 | var fr: std.Io.Reader = .fixed(mapped_mem); |
| 465 | |
| 466 | const header = elf.Header.read(&fr) catch |err| switch (err) { |
| 467 | error.ReadFailed => unreachable, |
| 468 | error.EndOfStream => return error.TruncatedElfFile, |
| 469 | |
| 470 | error.InvalidElfMagic, |
| 471 | error.InvalidElfVersion, |
| 472 | error.InvalidElfClass, |
| 473 | error.InvalidElfEndian, |
| 474 | => |e| return e, |
| 475 | }; |
| 476 | const endian = header.endian; |
| 477 | |
| 478 | const shstrtab_shdr_off = try std.math.add( |
| 479 | u64, |
| 480 | header.shoff, |
| 481 | try std.math.mul(u64, header.shstrndx, header.shentsize), |
| 482 | ); |
| 483 | fr.seek = std.math.cast(usize, shstrtab_shdr_off) orelse return error.Overflow; |
| 484 | const shstrtab: []const u8 = if (header.is_64) shstrtab: { |
| 485 | const shdr = fr.takeStruct(elf.Elf64_Shdr, endian) catch return error.TruncatedElfFile; |
| 486 | if (shdr.sh_offset + shdr.sh_size > mapped_mem.len) return error.TruncatedElfFile; |
| 487 | break :shstrtab mapped_mem[@intCast(shdr.sh_offset)..][0..@intCast(shdr.sh_size)]; |
| 488 | } else shstrtab: { |
| 489 | const shdr = fr.takeStruct(elf.Elf32_Shdr, endian) catch return error.TruncatedElfFile; |
| 490 | if (shdr.sh_offset + shdr.sh_size > mapped_mem.len) return error.TruncatedElfFile; |
| 491 | break :shstrtab mapped_mem[@intCast(shdr.sh_offset)..][0..@intCast(shdr.sh_size)]; |
| 492 | }; |
| 493 | |
| 494 | var sections: Section.Array = .initFill(null); |
| 495 | |
| 496 | var it = header.iterateSectionHeadersBuffer(mapped_mem); |
| 497 | while (it.next() catch return error.TruncatedElfFile) |shdr| { |
| 498 | if (shdr.sh_type == elf.SHT_NULL or shdr.sh_type == elf.SHT_NOBITS) continue; |
| 499 | if (shdr.sh_name > shstrtab.len) return error.TruncatedElfFile; |
| 500 | const name = std.mem.sliceTo(shstrtab[@intCast(shdr.sh_name)..], 0); |
| 501 | |
| 502 | const section_id: Section.Id = inline for ( |
| 503 | @typeInfo(Section.Id).@"enum".field_names, |
| 504 | @typeInfo(Section.Id).@"enum".field_values, |
| 505 | ) |s_name, s_value| { |
| 506 | if (std.mem.eql(u8, "." ++ s_name, name)) { |
| 507 | break @fromBackingInt(@intCast(s_value)); |
| 508 | } |
| 509 | } else continue; |
| 510 | |
| 511 | if (sections.get(section_id) != null) continue; |
| 512 | |
| 513 | if (shdr.sh_offset + shdr.sh_size > mapped_mem.len) return error.TruncatedElfFile; |
| 514 | const raw_section_bytes = mapped_mem[@intCast(shdr.sh_offset)..][0..@intCast(shdr.sh_size)]; |
| 515 | const section_bytes: []const u8 = bytes: { |
| 516 | if ((shdr.sh_flags & elf.SHF_COMPRESSED) == 0) break :bytes raw_section_bytes; |
| 517 | |
| 518 | var section_reader: std.Io.Reader = .fixed(raw_section_bytes); |
| 519 | const ch_type: elf.COMPRESS, const ch_size: u64 = if (header.is_64) ch: { |
| 520 | const chdr = section_reader.takeStruct(elf.Elf64_Chdr, endian) catch return error.InvalidCompressedSection; |
| 521 | break :ch .{ chdr.ch_type, chdr.ch_size }; |
| 522 | } else ch: { |
| 523 | const chdr = section_reader.takeStruct(elf.Elf32_Chdr, endian) catch return error.InvalidCompressedSection; |
| 524 | break :ch .{ chdr.ch_type, chdr.ch_size }; |
| 525 | }; |
| 526 | if (ch_type != .ZLIB) { |
| 527 | // The compression algorithm is unsupported, but don't make that a hard error; the |
| 528 | // file might still be valid, and we might still be okay without this section. |
| 529 | continue; |
| 530 | } |
| 531 | |
| 532 | const buf = try arena.alloc(u8, std.math.cast(usize, ch_size) orelse return error.Overflow); |
| 533 | var fw: std.Io.Writer = .fixed(buf); |
| 534 | var decompress: std.compress.flate.Decompress = .init(&section_reader, .zlib, &.{}); |
| 535 | const n = decompress.reader.streamRemaining(&fw) catch |err| switch (err) { |
| 536 | // If a write failed, then `buf` filled up, so `ch_size` was incorrect |
| 537 | error.WriteFailed => return error.InvalidCompressedSection, |
| 538 | // If a read failed, flate expected the section to have more data |
| 539 | error.ReadFailed => return error.InvalidCompressedSection, |
| 540 | }; |
| 541 | // It's also an error if the data is shorter than expected. |
| 542 | if (n != buf.len) return error.InvalidCompressedSection; |
| 543 | break :bytes buf; |
| 544 | }; |
| 545 | sections.set(section_id, .{ .header = shdr, .bytes = section_bytes }); |
| 546 | } |
| 547 | |
| 548 | return .{ |
| 549 | .is_64 = header.is_64, |
| 550 | .endian = endian, |
| 551 | .sections = sections, |
| 552 | .mapped_mem = mapped_mem, |
| 553 | }; |
| 554 | } |