authorgravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2025-09-09 14:20:49+01:00
committergravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2025-09-30 13:44:52+01:00
logc1a30bd0d876330ce7a241fc297c66577ae7e6aa
treefbc6e50c11746e259fb5366caf57fce40c4d6964
parentf7980487395b660d5c568ba57891ab371a27102d
signaturelock-open Commit is signed but in an unrecognized format.

std: replace debug.Dwarf.ElfModule with debug.ElfFile

This abstraction isn't really tied to DWARF at all! Really, we're just loading some information from an ELF file which is useful for debugging. That *includes* DWARF, but it also includes other information. For instance, the other change here: Now, if DWARF information is missing, `debug.SelfInfo.ElfModule` will name symbols by finding a matching symtab entry. We actually already do this on Mach-O, so it makes obvious sense to do the same on ELF! This change is what motivated the restructuring to begin with. The symtab work is derived from #22077. Co-authored-by: geemili <opensource@geemili.xyz>

7 files changed, 627 insertions(+), 421 deletions(-)

lib/std/debug.zig+1
......@@ -18,6 +18,7 @@ const root = @import("root");
1818
1919pub const Dwarf = @import("debug/Dwarf.zig");
2020pub const Pdb = @import("debug/Pdb.zig");
21pub const ElfFile = @import("debug/ElfFile.zig");
2122pub const SelfInfo = @import("debug/SelfInfo.zig");
2223pub const Info = @import("debug/Info.zig");
2324pub const Coverage = @import("debug/Coverage.zig");
lib/std/debug/Dwarf.zig-1
......@@ -30,7 +30,6 @@ pub const expression = @import("Dwarf/expression.zig");
3030pub const abi = @import("Dwarf/abi.zig");
3131pub const call_frame = @import("Dwarf/call_frame.zig");
3232pub const Unwind = @import("Dwarf/Unwind.zig");
33pub const ElfModule = @import("Dwarf/ElfModule.zig");
3433
3534/// Useful to temporarily enable while working on this file.
3635const debug_debug_mode = false;
lib/std/debug/Dwarf/ElfModule.zig deleted-376
......@@ -1,376 +0,0 @@
1//! A thin wrapper around `Dwarf` which handles loading debug information from an ELF file. Load the
2//! info with `load`, then directly access the `dwarf` field before finally `deinit`ing.
3
4dwarf: Dwarf,
5
6/// If we encounter a `.eh_frame` section while loading the ELF module, it is stored here and may be
7/// used with `Dwarf.Unwind` for call stack unwinding.
8eh_frame: ?UnwindSection,
9/// If we encounter a `.debug_frame` section while loading the ELF module, it is stored here and may
10/// be used with `Dwarf.Unwind` for call stack unwinding.
11debug_frame: ?UnwindSection,
12
13/// The memory-mapped ELF file, which is referenced by `dwarf`. This field is here only so that
14/// this memory can be unmapped by `ElfModule.deinit`.
15mapped_file: []align(std.heap.page_size_min) const u8,
16/// Sometimes, debug info is stored separately to the main ELF file. In that case, `mapped_file`
17/// is the mapped ELF binary, and `mapped_debug_file` is the mapped debug info file. Both must
18/// be unmapped by `ElfModule.deinit`.
19mapped_debug_file: ?[]align(std.heap.page_size_min) const u8,
20
21pub const UnwindSection = struct {
22 vaddr: u64,
23 bytes: []const u8,
24 owned: bool,
25};
26
27pub fn deinit(em: *ElfModule, gpa: Allocator) void {
28 em.dwarf.deinit(gpa);
29 std.posix.munmap(em.mapped_file);
30 if (em.mapped_debug_file) |m| std.posix.munmap(m);
31 if (em.eh_frame) |s| if (s.owned) gpa.free(s.bytes);
32 if (em.debug_frame) |s| if (s.owned) gpa.free(s.bytes);
33}
34
35pub const LoadError = error{
36 InvalidDebugInfo,
37 MissingDebugInfo,
38 InvalidElfMagic,
39 InvalidElfVersion,
40 InvalidElfEndian,
41 /// TODO: implement this and then remove this error code
42 UnimplementedDwarfForeignEndian,
43 /// The debug info may be valid but this implementation uses memory
44 /// mapping which limits things to usize. If the target debug info is
45 /// 64-bit and host is 32-bit, there may be debug info that is not
46 /// supportable using this method.
47 Overflow,
48
49 PermissionDenied,
50 LockedMemoryLimitExceeded,
51 MemoryMappingNotSupported,
52} || Allocator.Error || std.fs.File.OpenError || Dwarf.OpenError;
53
54/// Reads debug info from an ELF file given its path.
55///
56/// If the required sections aren't present but a reference to external debug
57/// info is, then this this function will recurse to attempt to load the debug
58/// sections from an external file.
59pub fn load(
60 gpa: Allocator,
61 elf_file_path: Path,
62 build_id: ?[]const u8,
63 expected_crc: ?u32,
64 parent_sections: ?*Dwarf.SectionArray,
65 parent_mapped_mem: ?[]align(std.heap.page_size_min) const u8,
66) LoadError!ElfModule {
67 const mapped_mem: []align(std.heap.page_size_min) const u8 = mapped: {
68 const elf_file = try elf_file_path.root_dir.handle.openFile(elf_file_path.sub_path, .{});
69 defer elf_file.close();
70
71 const file_len = std.math.cast(
72 usize,
73 elf_file.getEndPos() catch return Dwarf.bad(),
74 ) orelse return error.Overflow;
75
76 break :mapped std.posix.mmap(
77 null,
78 file_len,
79 std.posix.PROT.READ,
80 .{ .TYPE = .SHARED },
81 elf_file.handle,
82 0,
83 ) catch |err| switch (err) {
84 error.MappingAlreadyExists => unreachable,
85 else => |e| return e,
86 };
87 };
88 errdefer std.posix.munmap(mapped_mem);
89
90 if (expected_crc) |crc| if (crc != std.hash.crc.Crc32.hash(mapped_mem)) return error.InvalidDebugInfo;
91
92 const hdr: *const elf.Ehdr = @ptrCast(&mapped_mem[0]);
93 if (!mem.eql(u8, hdr.e_ident[0..4], elf.MAGIC)) return error.InvalidElfMagic;
94 if (hdr.e_ident[elf.EI_VERSION] != 1) return error.InvalidElfVersion;
95
96 const endian: std.builtin.Endian = switch (hdr.e_ident[elf.EI_DATA]) {
97 elf.ELFDATA2LSB => .little,
98 elf.ELFDATA2MSB => .big,
99 else => return error.InvalidElfEndian,
100 };
101 if (endian != native_endian) return error.UnimplementedDwarfForeignEndian;
102
103 const shoff = hdr.e_shoff;
104 const str_section_off = std.math.cast(
105 usize,
106 shoff + @as(u64, hdr.e_shentsize) * @as(u64, hdr.e_shstrndx),
107 ) orelse return error.Overflow;
108 const str_shdr: *const elf.Shdr = @ptrCast(@alignCast(mapped_mem[str_section_off..]));
109 const header_strings = mapped_mem[str_shdr.sh_offset..][0..str_shdr.sh_size];
110 const shdrs = @as(
111 [*]const elf.Shdr,
112 @ptrCast(@alignCast(&mapped_mem[shoff])),
113 )[0..hdr.e_shnum];
114
115 var sections: Dwarf.SectionArray = @splat(null);
116 // Combine section list. This takes ownership over any owned sections from the parent scope.
117 if (parent_sections) |ps| {
118 for (ps, &sections) |*parent, *section_elem| {
119 if (parent.*) |*p| {
120 section_elem.* = p.*;
121 p.owned = false;
122 }
123 }
124 }
125 errdefer for (sections) |opt_section| if (opt_section) |s| if (s.owned) gpa.free(s.data);
126
127 var eh_frame_section: ?UnwindSection = null;
128 errdefer if (eh_frame_section) |s| if (s.owned) gpa.free(s.bytes);
129
130 var debug_frame_section: ?UnwindSection = null;
131 errdefer if (debug_frame_section) |s| if (s.owned) gpa.free(s.bytes);
132
133 var separate_debug_filename: ?[]const u8 = null;
134 var separate_debug_crc: ?u32 = null;
135
136 for (shdrs) |*shdr| {
137 if (shdr.sh_type == elf.SHT_NULL or shdr.sh_type == elf.SHT_NOBITS) continue;
138 const name = mem.sliceTo(header_strings[shdr.sh_name..], 0);
139
140 if (mem.eql(u8, name, ".gnu_debuglink")) {
141 if (mapped_mem.len < shdr.sh_offset + shdr.sh_size) return error.InvalidDebugInfo;
142 const gnu_debuglink = mapped_mem[@intCast(shdr.sh_offset)..][0..@intCast(shdr.sh_size)];
143 const debug_filename = mem.sliceTo(@as([*:0]const u8, @ptrCast(gnu_debuglink.ptr)), 0);
144 const crc_offset = mem.alignForward(usize, debug_filename.len + 1, 4);
145 const crc_bytes = gnu_debuglink[crc_offset..][0..4];
146 separate_debug_crc = mem.readInt(u32, crc_bytes, endian);
147 separate_debug_filename = debug_filename;
148 continue;
149 }
150
151 const section_id: union(enum) {
152 dwarf: Dwarf.Section.Id,
153 eh_frame,
154 debug_frame,
155 } = s: {
156 inline for (@typeInfo(Dwarf.Section.Id).@"enum".fields) |s| {
157 if (mem.eql(u8, "." ++ s.name, name)) {
158 break :s .{ .dwarf = @enumFromInt(s.value) };
159 }
160 }
161 if (mem.eql(u8, ".eh_frame", name)) break :s .eh_frame;
162 if (mem.eql(u8, ".debug_frame", name)) break :s .debug_frame;
163 continue;
164 };
165
166 switch (section_id) {
167 .dwarf => |i| if (sections[@intFromEnum(i)] != null) continue,
168 .eh_frame => if (eh_frame_section != null) continue,
169 .debug_frame => if (debug_frame_section != null) continue,
170 }
171
172 if (mapped_mem.len < shdr.sh_offset + shdr.sh_size) return error.InvalidDebugInfo;
173 const raw_section_bytes = mapped_mem[@intCast(shdr.sh_offset)..][0..@intCast(shdr.sh_size)];
174
175 const section_bytes: []const u8, const section_owned: bool = section: {
176 if ((shdr.sh_flags & elf.SHF_COMPRESSED) == 0) {
177 break :section .{ raw_section_bytes, false };
178 }
179 var section_reader: Reader = .fixed(raw_section_bytes);
180 const chdr = section_reader.takeStruct(elf.Chdr, endian) catch continue;
181 if (chdr.ch_type != .ZLIB) continue;
182
183 var decompress: std.compress.flate.Decompress = .init(&section_reader, .zlib, &.{});
184 var decompressed_section: ArrayList(u8) = .empty;
185 defer decompressed_section.deinit(gpa);
186 decompress.reader.appendRemainingUnlimited(gpa, &decompressed_section) catch {
187 Dwarf.invalidDebugInfoDetected();
188 continue;
189 };
190 if (chdr.ch_size != decompressed_section.items.len) {
191 Dwarf.invalidDebugInfoDetected();
192 continue;
193 }
194 break :section .{ try decompressed_section.toOwnedSlice(gpa), true };
195 };
196 switch (section_id) {
197 .dwarf => |id| sections[@intFromEnum(id)] = .{
198 .data = section_bytes,
199 .owned = section_owned,
200 },
201 .eh_frame => eh_frame_section = .{
202 .vaddr = shdr.sh_addr,
203 .bytes = section_bytes,
204 .owned = section_owned,
205 },
206 .debug_frame => debug_frame_section = .{
207 .vaddr = shdr.sh_addr,
208 .bytes = section_bytes,
209 .owned = section_owned,
210 },
211 }
212 }
213
214 const missing_debug_info =
215 sections[@intFromEnum(Dwarf.Section.Id.debug_info)] == null or
216 sections[@intFromEnum(Dwarf.Section.Id.debug_abbrev)] == null or
217 sections[@intFromEnum(Dwarf.Section.Id.debug_str)] == null or
218 sections[@intFromEnum(Dwarf.Section.Id.debug_line)] == null;
219
220 // Attempt to load debug info from an external file
221 // See: https://sourceware.org/gdb/onlinedocs/gdb/Separate-Debug-Files.html
222 if (missing_debug_info) {
223 // Only allow one level of debug info nesting
224 if (parent_mapped_mem) |_| {
225 return error.MissingDebugInfo;
226 }
227
228 // $XDG_CACHE_HOME/debuginfod_client/<buildid>/debuginfo
229 // This only opportunisticly tries to load from the debuginfod cache, but doesn't try to populate it.
230 // One can manually run `debuginfod-find debuginfo PATH` to download the symbols
231 debuginfod: {
232 const id = build_id orelse break :debuginfod;
233 switch (builtin.os.tag) {
234 .wasi, .windows => break :debuginfod,
235 else => {},
236 }
237 const id_dir_path: []u8 = p: {
238 if (std.posix.getenv("DEBUGINFOD_CACHE_PATH")) |path| {
239 break :p try std.fmt.allocPrint(gpa, "{s}/{x}", .{ path, id });
240 }
241 if (std.posix.getenv("XDG_CACHE_HOME")) |cache_path| {
242 if (cache_path.len > 0) {
243 break :p try std.fmt.allocPrint(gpa, "{s}/debuginfod_client/{x}", .{ cache_path, id });
244 }
245 }
246 if (std.posix.getenv("HOME")) |home_path| {
247 break :p try std.fmt.allocPrint(gpa, "{s}/.cache/debuginfod_client/{x}", .{ home_path, id });
248 }
249 break :debuginfod;
250 };
251 defer gpa.free(id_dir_path);
252 if (!std.fs.path.isAbsolute(id_dir_path)) break :debuginfod;
253
254 var id_dir = std.fs.openDirAbsolute(id_dir_path, .{}) catch break :debuginfod;
255 defer id_dir.close();
256
257 return load(gpa, .{
258 .root_dir = .{ .path = id_dir_path, .handle = id_dir },
259 .sub_path = "debuginfo",
260 }, null, separate_debug_crc, &sections, mapped_mem) catch break :debuginfod;
261 }
262
263 const global_debug_directories = [_][]const u8{
264 "/usr/lib/debug",
265 };
266
267 // <global debug directory>/.build-id/<2-character id prefix>/<id remainder>.debug
268 if (build_id) |id| blk: {
269 if (id.len < 3) break :blk;
270
271 // Either md5 (16 bytes) or sha1 (20 bytes) are used here in practice
272 const extension = ".debug";
273 var id_prefix_buf: [2]u8 = undefined;
274 var filename_buf: [38 + extension.len]u8 = undefined;
275
276 _ = std.fmt.bufPrint(&id_prefix_buf, "{x}", .{id[0..1]}) catch unreachable;
277 const filename = std.fmt.bufPrint(&filename_buf, "{x}" ++ extension, .{id[1..]}) catch break :blk;
278
279 for (global_debug_directories) |global_directory| {
280 const path: Path = .{
281 .root_dir = .cwd(),
282 .sub_path = try std.fs.path.join(gpa, &.{
283 global_directory, ".build-id", &id_prefix_buf, filename,
284 }),
285 };
286 defer gpa.free(path.sub_path);
287
288 return load(gpa, path, null, separate_debug_crc, &sections, mapped_mem) catch continue;
289 }
290 }
291
292 // use the path from .gnu_debuglink, in the same search order as gdb
293 separate: {
294 const separate_filename = separate_debug_filename orelse break :separate;
295 if (mem.eql(u8, std.fs.path.basename(elf_file_path.sub_path), separate_filename))
296 return error.MissingDebugInfo;
297
298 exe_dir: {
299 const exe_dir_path = try std.fs.path.resolve(gpa, &.{
300 elf_file_path.root_dir.path orelse ".",
301 std.fs.path.dirname(elf_file_path.sub_path) orelse ".",
302 });
303 defer gpa.free(exe_dir_path);
304 var exe_dir = std.fs.openDirAbsolute(exe_dir_path, .{}) catch break :exe_dir;
305 defer exe_dir.close();
306
307 // <exe_dir>/<gnu_debuglink>
308 if (load(
309 gpa,
310 .{
311 .root_dir = .{ .path = exe_dir_path, .handle = exe_dir },
312 .sub_path = separate_filename,
313 },
314 null,
315 separate_debug_crc,
316 &sections,
317 mapped_mem,
318 )) |em| {
319 return em;
320 } else |_| {}
321
322 // <exe_dir>/.debug/<gnu_debuglink>
323 const path: Path = .{
324 .root_dir = .{ .path = exe_dir_path, .handle = exe_dir },
325 .sub_path = try std.fs.path.join(gpa, &.{ ".debug", separate_filename }),
326 };
327 defer gpa.free(path.sub_path);
328
329 if (load(gpa, path, null, separate_debug_crc, &sections, mapped_mem)) |em| {
330 return em;
331 } else |_| {}
332 }
333
334 var cwd_buf: [std.fs.max_path_bytes]u8 = undefined;
335 const cwd_path = std.posix.realpath(".", &cwd_buf) catch break :separate;
336
337 // <global debug directory>/<absolute folder of current binary>/<gnu_debuglink>
338 for (global_debug_directories) |global_directory| {
339 const path: Path = .{
340 .root_dir = .cwd(),
341 .sub_path = try std.fs.path.join(gpa, &.{ global_directory, cwd_path, separate_filename }),
342 };
343 defer gpa.free(path.sub_path);
344 if (load(gpa, path, null, separate_debug_crc, &sections, mapped_mem)) |em| {
345 return em;
346 } else |_| {}
347 }
348 }
349
350 return error.MissingDebugInfo;
351 }
352
353 var dwarf: Dwarf = .{ .sections = sections };
354 try dwarf.open(gpa, endian);
355 return .{
356 .dwarf = dwarf,
357 .eh_frame = eh_frame_section,
358 .debug_frame = debug_frame_section,
359 .mapped_file = parent_mapped_mem orelse mapped_mem,
360 .mapped_debug_file = if (parent_mapped_mem != null) mapped_mem else null,
361 };
362}
363
364const std = @import("../../std.zig");
365const Allocator = std.mem.Allocator;
366const ArrayList = std.ArrayList;
367const Dwarf = std.debug.Dwarf;
368const Path = std.Build.Cache.Path;
369const Reader = std.Io.Reader;
370const mem = std.mem;
371const elf = std.elf;
372
373const builtin = @import("builtin");
374const native_endian = builtin.cpu.arch.endian();
375
376const ElfModule = @This();
lib/std/debug/ElfFile.zig created+536
......@@ -0,0 +1,536 @@
1//! A helper type for loading an ELF file and collecting its DWARF debug information, unwind
2//! information, and symbol table.
3
4is_64: bool,
5endian: Endian,
6
7/// This is `null` iff any of the required DWARF sections were missing. `ElfFile.load` does *not*
8/// call `Dwarf.open`, `Dwarf.scanAllFunctions`, etc; that is the caller's responsibility.
9dwarf: ?Dwarf,
10
11/// If non-`null`, describes the `.eh_frame` section, which can be used with `Dwarf.Unwind`.
12eh_frame: ?UnwindSection,
13/// If non-`null`, describes the `.debug_frame` section, which can be used with `Dwarf.Unwind`.
14debug_frame: ?UnwindSection,
15
16/// If non-`null`, this is the contents of the `.strtab` section.
17strtab: ?[]const u8,
18/// If non-`null`, describes the `.symtab` section.
19symtab: ?SymtabSection,
20
21/// Binary search table lazily populated by `searchSymtab`.
22symbol_search_table: ?[]u64,
23
24/// The memory-mapped ELF file, which is referenced by `dwarf`. This field is here only so that
25/// this memory can be unmapped by `ElfFile.deinit`.
26mapped_file: []align(std.heap.page_size_min) const u8,
27/// Sometimes, debug info is stored separately to the main ELF file. In that case, `mapped_file`
28/// is the mapped ELF binary, and `mapped_debug_file` is the mapped debug info file. Both must
29/// be unmapped by `ElfFile.deinit`.
30mapped_debug_file: ?[]align(std.heap.page_size_min) const u8,
31
32arena: std.heap.ArenaAllocator.State,
33
34pub const UnwindSection = struct {
35 vaddr: u64,
36 bytes: []const u8,
37};
38pub const SymtabSection = struct {
39 entry_size: u64,
40 bytes: []const u8,
41};
42
43pub const DebugInfoSearchPaths = struct {
44 /// The location of a debuginfod client directory, which acts as a search path for build IDs. If
45 /// given, we can load from this directory opportunistically, but make no effort to populate it.
46 /// To avoid allocation when building the search paths, this is given as two components which
47 /// will be concatenated.
48 debuginfod_client: ?[2][]const u8,
49 /// All "global debug directories" on the system. These are used as search paths for both debug
50 /// links and build IDs. On typical systems this is just "/usr/lib/debug".
51 global_debug: []const []const u8,
52 /// The path to the dirname of the ELF file, which acts as a search path for debug links.
53 exe_dir: ?[]const u8,
54
55 pub const none: DebugInfoSearchPaths = .{
56 .debuginfod_client = null,
57 .global_debug = &.{},
58 .exe_dir = null,
59 };
60
61 pub fn native(exe_path: []const u8) DebugInfoSearchPaths {
62 return .{
63 .debuginfod_client = p: {
64 if (std.posix.getenv("DEBUGINFOD_CACHE_PATH")) |p| {
65 break :p .{ p, "" };
66 }
67 if (std.posix.getenv("XDG_CACHE_HOME")) |cache_path| {
68 break :p .{ cache_path, "/debuginfod_client" };
69 }
70 if (std.posix.getenv("HOME")) |home_path| {
71 break :p .{ home_path, "/.cache/debuginfod_client" };
72 }
73 break :p null;
74 },
75 .global_debug = &.{
76 "/usr/lib/debug",
77 },
78 .exe_dir = std.fs.path.dirname(exe_path) orelse ".",
79 };
80 }
81};
82
83pub fn deinit(ef: *ElfFile, gpa: Allocator) void {
84 if (ef.dwarf) |*dwarf| dwarf.deinit(gpa);
85 if (ef.symbol_search_table) |t| gpa.free(t);
86 var arena = ef.arena.promote(gpa);
87 arena.deinit();
88
89 std.posix.munmap(ef.mapped_file);
90 if (ef.mapped_debug_file) |m| std.posix.munmap(m);
91
92 ef.* = undefined;
93}
94
95pub const LoadError = error{
96 OutOfMemory,
97 Overflow,
98 TruncatedElfFile,
99 InvalidCompressedSection,
100 InvalidElfMagic,
101 InvalidElfVersion,
102 InvalidElfClass,
103 InvalidElfEndian,
104 // The remaining errors all occur when attemping to stat or mmap a file.
105 SystemResources,
106 MemoryMappingNotSupported,
107 AccessDenied,
108 LockedMemoryLimitExceeded,
109 ProcessFdQuotaExceeded,
110 SystemFdQuotaExceeded,
111 Unexpected,
112};
113
114pub fn load(
115 gpa: Allocator,
116 elf_file: std.fs.File,
117 opt_build_id: ?[]const u8,
118 di_search_paths: *const DebugInfoSearchPaths,
119) LoadError!ElfFile {
120 var arena_instance: std.heap.ArenaAllocator = .init(gpa);
121 errdefer arena_instance.deinit();
122 const arena = arena_instance.allocator();
123
124 var result = loadInner(arena, elf_file, null) catch |err| switch (err) {
125 error.CrcMismatch => unreachable, // we passed crc as null
126 else => |e| return e,
127 };
128 errdefer std.posix.munmap(result.mapped_mem);
129
130 // `loadInner` did most of the work, but we might need to load an external debug info file
131
132 const di_mapped_mem: ?[]align(std.heap.page_size_min) const u8 = load_di: {
133 if (result.sections.get(.debug_info) != null and
134 result.sections.get(.debug_abbrev) != null and
135 result.sections.get(.debug_str) != null and
136 result.sections.get(.debug_line) != null)
137 {
138 // The info is already loaded from this file alone!
139 break :load_di null;
140 }
141
142 // We're missing some debug info---let's try and load it from a separate file.
143
144 build_id: {
145 const build_id = opt_build_id orelse break :build_id;
146 if (build_id.len < 3) break :build_id;
147
148 for (di_search_paths.global_debug) |global_debug| {
149 if (try loadSeparateDebugFile(arena, &result, null, "{s}/.build-id/{x}/{x}.debug", .{
150 global_debug,
151 build_id[0..1],
152 build_id[1..],
153 })) |mapped| break :load_di mapped;
154 }
155
156 if (di_search_paths.debuginfod_client) |components| {
157 if (try loadSeparateDebugFile(arena, &result, null, "{s}{s}/{x}/debuginfo", .{
158 components[0],
159 components[1],
160 build_id,
161 })) |mapped| break :load_di mapped;
162 }
163 }
164
165 debug_link: {
166 const section = result.sections.get(.gnu_debuglink) orelse break :debug_link;
167 const debug_filename = std.mem.sliceTo(section.bytes, 0);
168 const crc_offset = std.mem.alignForward(usize, debug_filename.len + 1, 4);
169 if (section.bytes.len < crc_offset + 4) break :debug_link;
170 const debug_crc = std.mem.readInt(u32, section.bytes[crc_offset..][0..4], result.endian);
171
172 const exe_dir = di_search_paths.exe_dir orelse break :debug_link;
173
174 if (try loadSeparateDebugFile(arena, &result, debug_crc, "{s}/{s}", .{
175 exe_dir,
176 debug_filename,
177 })) |mapped| break :load_di mapped;
178 if (try loadSeparateDebugFile(arena, &result, debug_crc, "{s}/.debug/{s}", .{
179 exe_dir,
180 debug_filename,
181 })) |mapped| break :load_di mapped;
182 for (di_search_paths.global_debug) |global_debug| {
183 // This looks like a bug; it isn't. They really do embed the absolute path to the
184 // exe's dirname, *under* the global debug path.
185 if (try loadSeparateDebugFile(arena, &result, debug_crc, "{s}/{s}/{s}", .{
186 global_debug,
187 exe_dir,
188 debug_filename,
189 })) |mapped| break :load_di mapped;
190 }
191 }
192
193 break :load_di null;
194 };
195 errdefer comptime unreachable;
196
197 return .{
198 .is_64 = result.is_64,
199 .endian = result.endian,
200 .dwarf = dwarf: {
201 if (result.sections.get(.debug_info) == null or
202 result.sections.get(.debug_abbrev) == null or
203 result.sections.get(.debug_str) == null or
204 result.sections.get(.debug_line) == null)
205 {
206 break :dwarf null; // debug info not present
207 }
208 var sections: Dwarf.SectionArray = @splat(null);
209 inline for (@typeInfo(Dwarf.Section.Id).@"enum".fields) |f| {
210 if (result.sections.get(@field(Section.Id, f.name))) |s| {
211 sections[f.value] = .{ .data = s.bytes, .owned = false };
212 }
213 }
214 break :dwarf .{ .sections = sections };
215 },
216 .eh_frame = if (result.sections.get(.eh_frame)) |s| .{
217 .vaddr = s.header.sh_addr,
218 .bytes = s.bytes,
219 } else null,
220 .debug_frame = if (result.sections.get(.debug_frame)) |s| .{
221 .vaddr = s.header.sh_addr,
222 .bytes = s.bytes,
223 } else null,
224 .strtab = if (result.sections.get(.strtab)) |s| s.bytes else null,
225 .symtab = if (result.sections.get(.symtab)) |s| .{
226 .entry_size = s.header.sh_entsize,
227 .bytes = s.bytes,
228 } else null,
229 .symbol_search_table = null,
230 .mapped_file = result.mapped_mem,
231 .mapped_debug_file = di_mapped_mem,
232 .arena = arena_instance.state,
233 };
234}
235
236pub fn searchSymtab(ef: *ElfFile, gpa: Allocator, vaddr: u64) error{
237 NoSymtab,
238 NoStrtab,
239 BadSymtab,
240 OutOfMemory,
241}!std.debug.Symbol {
242 const symtab = ef.symtab orelse return error.NoSymtab;
243 const strtab = ef.strtab orelse return error.NoStrtab;
244
245 if (symtab.bytes.len % symtab.entry_size != 0) return error.BadSymtab;
246
247 const swap_endian = ef.endian != @import("builtin").cpu.arch.endian();
248
249 switch (ef.is_64) {
250 inline true, false => |is_64| {
251 const Sym = if (is_64) elf.Elf64_Sym else elf.Elf32_Sym;
252 if (symtab.entry_size != @sizeOf(Sym)) return error.BadSymtab;
253 const symbols: []align(1) const Sym = @ptrCast(symtab.bytes);
254 if (ef.symbol_search_table == null) {
255 ef.symbol_search_table = try buildSymbolSearchTable(gpa, ef.endian, Sym, symbols);
256 }
257 const search_table = ef.symbol_search_table.?;
258 const SearchContext = struct {
259 swap_endian: bool,
260 target: u64,
261 symbols: []align(1) const Sym,
262 fn predicate(ctx: @This(), sym_index: u64) bool {
263 // We need to return `true` for the first N items, then `false` for the rest --
264 // the index we'll get out is the first `false` one. So, we'll return `true` iff
265 // the target address is after the *end* of this symbol. This synchronizes with
266 // the logic in `buildSymbolSearchTable` which sorts by *end* address.
267 var sym = ctx.symbols[sym_index];
268 if (ctx.swap_endian) std.mem.byteSwapAllFields(Sym, &sym);
269 const sym_end = sym.st_value + sym.st_size;
270 return ctx.target >= sym_end;
271 }
272 };
273 const sym_index_index = std.sort.partitionPoint(u64, search_table, @as(SearchContext, .{
274 .swap_endian = swap_endian,
275 .target = vaddr,
276 .symbols = symbols,
277 }), SearchContext.predicate);
278 if (sym_index_index == search_table.len) return .unknown;
279 var sym = symbols[search_table[sym_index_index]];
280 if (swap_endian) std.mem.byteSwapAllFields(Sym, &sym);
281 if (vaddr < sym.st_value or vaddr >= sym.st_value + sym.st_size) return .unknown;
282 return .{
283 .name = std.mem.sliceTo(strtab[sym.st_name..], 0),
284 .compile_unit_name = null,
285 .source_location = null,
286 };
287 },
288 }
289}
290
291fn buildSymbolSearchTable(gpa: Allocator, endian: Endian, comptime Sym: type, symbols: []align(1) const Sym) error{
292 OutOfMemory,
293 BadSymtab,
294}![]u64 {
295 var result: std.ArrayList(u64) = .empty;
296 defer result.deinit(gpa);
297
298 const swap_endian = endian != @import("builtin").cpu.arch.endian();
299
300 for (symbols, 0..) |sym_orig, sym_index| {
301 var sym = sym_orig;
302 if (swap_endian) std.mem.byteSwapAllFields(Sym, &sym);
303 if (sym.st_name == 0) continue;
304 if (sym.st_shndx == elf.SHN_UNDEF) continue;
305 try result.append(gpa, sym_index);
306 }
307
308 const SortContext = struct {
309 swap_endian: bool,
310 symbols: []align(1) const Sym,
311 fn lessThan(ctx: @This(), lhs_sym_index: u64, rhs_sym_index: u64) bool {
312 // We sort by *end* address, not start address. This matches up with logic in `searchSymtab`.
313 var lhs_sym = ctx.symbols[lhs_sym_index];
314 var rhs_sym = ctx.symbols[rhs_sym_index];
315 if (ctx.swap_endian) {
316 std.mem.byteSwapAllFields(Sym, &lhs_sym);
317 std.mem.byteSwapAllFields(Sym, &rhs_sym);
318 }
319 const lhs_val = lhs_sym.st_value + lhs_sym.st_size;
320 const rhs_val = rhs_sym.st_value + rhs_sym.st_size;
321 return lhs_val < rhs_val;
322 }
323 };
324 std.mem.sort(u64, result.items, @as(SortContext, .{
325 .swap_endian = swap_endian,
326 .symbols = symbols,
327 }), SortContext.lessThan);
328
329 return result.toOwnedSlice(gpa);
330}
331
332/// Only used locally, during `load`.
333const Section = struct {
334 header: elf.Elf64_Shdr,
335 bytes: []const u8,
336 const Id = enum {
337 // DWARF sections: see `Dwarf.Section.Id`.
338 debug_info,
339 debug_abbrev,
340 debug_str,
341 debug_str_offsets,
342 debug_line,
343 debug_line_str,
344 debug_ranges,
345 debug_loclists,
346 debug_rnglists,
347 debug_addr,
348 debug_names,
349 // Then anything else we're interested in.
350 gnu_debuglink,
351 eh_frame,
352 debug_frame,
353 symtab,
354 strtab,
355 };
356 const Array = std.enums.EnumArray(Section.Id, ?Section);
357};
358
359fn loadSeparateDebugFile(arena: Allocator, main_loaded: *LoadInnerResult, opt_crc: ?u32, comptime fmt: []const u8, args: anytype) Allocator.Error!?[]align(std.heap.page_size_min) const u8 {
360 const path = try std.fmt.allocPrint(arena, fmt, args);
361 const elf_file = std.fs.cwd().openFile(path, .{}) catch return null;
362 defer elf_file.close();
363
364 const result = loadInner(arena, elf_file, opt_crc) catch |err| switch (err) {
365 error.OutOfMemory => |e| return e,
366 error.CrcMismatch => return null,
367 else => return null,
368 };
369 errdefer comptime unreachable;
370
371 const have_debug_sections = inline for (@as([]const []const u8, &.{
372 "debug_info",
373 "debug_abbrev",
374 "debug_str",
375 "debug_line",
376 })) |name| {
377 const s = @field(Section.Id, name);
378 if (main_loaded.sections.get(s) == null and result.sections.get(s) != null) {
379 break false;
380 }
381 } else true;
382
383 if (result.is_64 != main_loaded.is_64 or
384 result.endian != main_loaded.endian or
385 !have_debug_sections)
386 {
387 std.posix.munmap(result.mapped_mem);
388 return null;
389 }
390
391 inline for (@typeInfo(Dwarf.Section.Id).@"enum".fields) |f| {
392 const id = @field(Section.Id, f.name);
393 if (main_loaded.sections.get(id) == null) {
394 main_loaded.sections.set(id, result.sections.get(id));
395 }
396 }
397
398 return result.mapped_mem;
399}
400
401const LoadInnerResult = struct {
402 is_64: bool,
403 endian: Endian,
404 sections: Section.Array,
405 mapped_mem: []align(std.heap.page_size_min) const u8,
406};
407fn loadInner(
408 arena: Allocator,
409 elf_file: std.fs.File,
410 opt_crc: ?u32,
411) (LoadError || error{CrcMismatch})!LoadInnerResult {
412 const mapped_mem: []align(std.heap.page_size_min) const u8 = mapped: {
413 const file_len = std.math.cast(
414 usize,
415 elf_file.getEndPos() catch |err| switch (err) {
416 error.PermissionDenied => unreachable, // not asking for PROT_EXEC
417 else => |e| return e,
418 },
419 ) orelse return error.Overflow;
420
421 break :mapped std.posix.mmap(
422 null,
423 file_len,
424 std.posix.PROT.READ,
425 .{ .TYPE = .SHARED },
426 elf_file.handle,
427 0,
428 ) catch |err| switch (err) {
429 error.MappingAlreadyExists => unreachable, // not using FIXED_NOREPLACE
430 error.PermissionDenied => unreachable, // not asking for PROT_EXEC
431 else => |e| return e,
432 };
433 };
434
435 if (opt_crc) |crc| {
436 if (std.hash.crc.Crc32.hash(mapped_mem) != crc) {
437 return error.CrcMismatch;
438 }
439 }
440 errdefer std.posix.munmap(mapped_mem);
441
442 var fr: std.Io.Reader = .fixed(mapped_mem);
443
444 const header = elf.Header.read(&fr) catch |err| switch (err) {
445 error.ReadFailed => unreachable,
446 error.EndOfStream => return error.TruncatedElfFile,
447
448 error.InvalidElfMagic,
449 error.InvalidElfVersion,
450 error.InvalidElfClass,
451 error.InvalidElfEndian,
452 => |e| return e,
453 };
454 const endian = header.endian;
455
456 const shstrtab_shdr_off = try std.math.add(
457 u64,
458 header.shoff,
459 try std.math.mul(u64, header.shstrndx, header.shentsize),
460 );
461 fr.seek = std.math.cast(usize, shstrtab_shdr_off) orelse return error.Overflow;
462 const shstrtab: []const u8 = if (header.is_64) shstrtab: {
463 const shdr = fr.takeStruct(elf.Elf64_Shdr, endian) catch return error.TruncatedElfFile;
464 if (shdr.sh_offset + shdr.sh_size > mapped_mem.len) return error.TruncatedElfFile;
465 break :shstrtab mapped_mem[@intCast(shdr.sh_offset)..][0..@intCast(shdr.sh_size)];
466 } else shstrtab: {
467 const shdr = fr.takeStruct(elf.Elf32_Shdr, endian) catch return error.TruncatedElfFile;
468 if (shdr.sh_offset + shdr.sh_size > mapped_mem.len) return error.TruncatedElfFile;
469 break :shstrtab mapped_mem[@intCast(shdr.sh_offset)..][0..@intCast(shdr.sh_size)];
470 };
471
472 var sections: Section.Array = .initFill(null);
473
474 var it = header.iterateSectionHeadersBuffer(mapped_mem);
475 while (it.next() catch return error.TruncatedElfFile) |shdr| {
476 if (shdr.sh_type == elf.SHT_NULL or shdr.sh_type == elf.SHT_NOBITS) continue;
477 if (shdr.sh_name > shstrtab.len) return error.TruncatedElfFile;
478 const name = std.mem.sliceTo(shstrtab[@intCast(shdr.sh_name)..], 0);
479
480 const section_id: Section.Id = inline for (@typeInfo(Section.Id).@"enum".fields) |s| {
481 if (std.mem.eql(u8, "." ++ s.name, name)) {
482 break @enumFromInt(s.value);
483 }
484 } else continue;
485
486 if (sections.get(section_id) != null) continue;
487
488 if (shdr.sh_offset + shdr.sh_size > mapped_mem.len) return error.TruncatedElfFile;
489 const raw_section_bytes = mapped_mem[@intCast(shdr.sh_offset)..][0..@intCast(shdr.sh_size)];
490 const section_bytes: []const u8 = bytes: {
491 if ((shdr.sh_flags & elf.SHF_COMPRESSED) == 0) break :bytes raw_section_bytes;
492
493 var section_reader: std.Io.Reader = .fixed(raw_section_bytes);
494 const ch_type: elf.COMPRESS, const ch_size: u64 = if (header.is_64) ch: {
495 const chdr = section_reader.takeStruct(elf.Elf64_Chdr, endian) catch return error.InvalidCompressedSection;
496 break :ch .{ chdr.ch_type, chdr.ch_size };
497 } else ch: {
498 const chdr = section_reader.takeStruct(elf.Elf32_Chdr, endian) catch return error.InvalidCompressedSection;
499 break :ch .{ chdr.ch_type, chdr.ch_size };
500 };
501 if (ch_type != .ZLIB) {
502 // The compression algorithm is unsupported, but don't make that a hard error; the
503 // file might still be valid, and we might still be okay without this section.
504 continue;
505 }
506
507 const buf = try arena.alloc(u8, ch_size);
508 var fw: std.Io.Writer = .fixed(buf);
509 var decompress: std.compress.flate.Decompress = .init(&section_reader, .zlib, &.{});
510 const n = decompress.reader.streamRemaining(&fw) catch |err| switch (err) {
511 // If a write failed, then `buf` filled up, so `ch_size` was incorrect
512 error.WriteFailed => return error.InvalidCompressedSection,
513 // If a read failed, flate expected the section to have more data
514 error.ReadFailed => return error.InvalidCompressedSection,
515 };
516 // It's also an error if the data is shorter than expected.
517 if (n != buf.len) return error.InvalidCompressedSection;
518 break :bytes buf;
519 };
520 sections.set(section_id, .{ .header = shdr, .bytes = section_bytes });
521 }
522
523 return .{
524 .is_64 = header.is_64,
525 .endian = endian,
526 .sections = sections,
527 .mapped_mem = mapped_mem,
528 };
529}
530
531const std = @import("std");
532const Endian = std.builtin.Endian;
533const Dwarf = std.debug.Dwarf;
534const ElfFile = @This();
535const Allocator = std.mem.Allocator;
536const elf = std.elf;
lib/std/debug/Info.zig+19-14
......@@ -9,7 +9,7 @@
99const std = @import("../std.zig");
1010const Allocator = std.mem.Allocator;
1111const Path = std.Build.Cache.Path;
12const Dwarf = std.debug.Dwarf;
12const ElfFile = std.debug.ElfFile;
1313const assert = std.debug.assert;
1414const Coverage = std.debug.Coverage;
1515const SourceLocation = std.debug.Coverage.SourceLocation;
......@@ -17,28 +17,35 @@ const SourceLocation = std.debug.Coverage.SourceLocation;
1717const Info = @This();
1818
1919/// Sorted by key, ascending.
20address_map: std.AutoArrayHashMapUnmanaged(u64, Dwarf.ElfModule),
20address_map: std.AutoArrayHashMapUnmanaged(u64, ElfFile),
2121/// Externally managed, outlives this `Info` instance.
2222coverage: *Coverage,
2323
24pub const LoadError = Dwarf.ElfModule.LoadError;
24pub const LoadError = std.fs.File.OpenError || ElfFile.LoadError || std.debug.Dwarf.ScanError || error{MissingDebugInfo};
2525
2626pub fn load(gpa: Allocator, path: Path, coverage: *Coverage) LoadError!Info {
27 var elf_module = try Dwarf.ElfModule.load(gpa, path, null, null, null, null);
28 // This is correct because `Dwarf.ElfModule` currently only supports native-endian ELF files.
29 const endian = @import("builtin").target.cpu.arch.endian();
30 try elf_module.dwarf.populateRanges(gpa, endian);
27 var file = try path.root_dir.handle.openFile(path.sub_path, .{});
28 defer file.close();
29
30 var elf_file: ElfFile = try .load(gpa, file, null, &.none);
31 errdefer elf_file.deinit(gpa);
32
33 if (elf_file.dwarf == null) return error.MissingDebugInfo;
34 try elf_file.dwarf.?.open(gpa, elf_file.endian);
35 try elf_file.dwarf.?.populateRanges(gpa, elf_file.endian);
36
3137 var info: Info = .{
3238 .address_map = .{},
3339 .coverage = coverage,
3440 };
35 try info.address_map.put(gpa, 0, elf_module);
41 try info.address_map.put(gpa, 0, elf_file);
42 errdefer comptime unreachable; // elf_file is owned by the map now
3643 return info;
3744}
3845
3946pub fn deinit(info: *Info, gpa: Allocator) void {
40 for (info.address_map.values()) |*elf_module| {
41 elf_module.dwarf.deinit(gpa);
47 for (info.address_map.values()) |*elf_file| {
48 elf_file.dwarf.?.deinit(gpa);
4249 }
4350 info.address_map.deinit(gpa);
4451 info.* = undefined;
......@@ -58,8 +65,6 @@ pub fn resolveAddresses(
5865) ResolveAddressesError!void {
5966 assert(sorted_pc_addrs.len == output.len);
6067 if (info.address_map.entries.len != 1) @panic("TODO");
61 const elf_module = &info.address_map.values()[0];
62 // This is correct because `Dwarf.ElfModule` currently only supports native-endian ELF files.
63 const endian = @import("builtin").target.cpu.arch.endian();
64 return info.coverage.resolveAddressesDwarf(gpa, endian, sorted_pc_addrs, output, &elf_module.dwarf);
68 const elf_file = &info.address_map.values()[0];
69 return info.coverage.resolveAddressesDwarf(gpa, elf_file.endian, sorted_pc_addrs, output, &elf_file.dwarf.?);
6570}
lib/std/debug/SelfInfo.zig+1
......@@ -78,6 +78,7 @@ pub fn getSymbolAtAddress(self: *SelfInfo, gpa: Allocator, address: usize) Error
7878pub fn getModuleNameForAddress(self: *SelfInfo, gpa: Allocator, address: usize) Error![]const u8 {
7979 comptime assert(target_supported);
8080 const module: Module = try .lookup(&self.lookup_cache, gpa, address);
81 if (module.name.len == 0) return error.MissingDebugInfo;
8182 return module.name;
8283}
8384
lib/std/debug/SelfInfo/ElfModule.zig+70-30
......@@ -7,10 +7,12 @@ gnu_eh_frame: ?[]const u8,
77pub const LookupCache = void;
88
99pub const DebugInfo = struct {
10 loaded_elf: ?Dwarf.ElfModule,
10 loaded_elf: ?ElfFile,
11 scanned_dwarf: bool,
1112 unwind: [2]?Dwarf.Unwind,
1213 pub const init: DebugInfo = .{
1314 .loaded_elf = null,
15 .scanned_dwarf = false,
1416 .unwind = @splat(null),
1517 };
1618 pub fn deinit(di: *DebugInfo, gpa: Allocator) void {
......@@ -92,55 +94,92 @@ pub fn lookup(cache: *LookupCache, gpa: Allocator, address: usize) Error!ElfModu
9294 };
9395 return error.MissingDebugInfo;
9496}
95fn loadDwarf(module: *const ElfModule, gpa: Allocator, di: *DebugInfo) Error!void {
97fn loadElf(module: *const ElfModule, gpa: Allocator, di: *DebugInfo) Error!void {
98 std.debug.assert(di.loaded_elf == null);
99 std.debug.assert(!di.scanned_dwarf);
100
96101 const load_result = if (module.name.len > 0) res: {
97 break :res Dwarf.ElfModule.load(gpa, .{
98 .root_dir = .cwd(),
99 .sub_path = module.name,
100 }, module.build_id, null, null, null);
102 var file = std.fs.cwd().openFile(module.name, .{}) catch return error.MissingDebugInfo;
103 defer file.close();
104 break :res ElfFile.load(gpa, file, module.build_id, &.native(module.name));
101105 } else res: {
102106 const path = std.fs.selfExePathAlloc(gpa) catch |err| switch (err) {
103107 error.OutOfMemory => |e| return e,
104108 else => return error.ReadFailed,
105109 };
106110 defer gpa.free(path);
107 break :res Dwarf.ElfModule.load(gpa, .{
108 .root_dir = .cwd(),
109 .sub_path = path,
110 }, module.build_id, null, null, null);
111 var file = std.fs.cwd().openFile(path, .{}) catch return error.MissingDebugInfo;
112 defer file.close();
113 break :res ElfFile.load(gpa, file, module.build_id, &.native(path));
111114 };
112115 di.loaded_elf = load_result catch |err| switch (err) {
113 error.FileNotFound => return error.MissingDebugInfo,
114
115116 error.OutOfMemory,
116 error.InvalidDebugInfo,
117 error.MissingDebugInfo,
118117 error.Unexpected,
119118 => |e| return e,
120119
121 error.InvalidElfEndian,
120 error.Overflow,
121 error.TruncatedElfFile,
122 error.InvalidCompressedSection,
122123 error.InvalidElfMagic,
123124 error.InvalidElfVersion,
124 error.InvalidUtf8,
125 error.InvalidWtf8,
126 error.EndOfStream,
127 error.Overflow,
128 error.UnimplementedDwarfForeignEndian, // this should be impossible as we're looking at the debug info for this process
125 error.InvalidElfClass,
126 error.InvalidElfEndian,
129127 => return error.InvalidDebugInfo,
130128
131 else => return error.ReadFailed,
129 error.SystemResources,
130 error.MemoryMappingNotSupported,
131 error.AccessDenied,
132 error.LockedMemoryLimitExceeded,
133 error.ProcessFdQuotaExceeded,
134 error.SystemFdQuotaExceeded,
135 => return error.ReadFailed,
132136 };
137
138 const matches_native =
139 di.loaded_elf.?.endian == native_endian and
140 di.loaded_elf.?.is_64 == (@sizeOf(usize) == 8);
141
142 if (!matches_native) {
143 di.loaded_elf.?.deinit(gpa);
144 di.loaded_elf = null;
145 return error.InvalidDebugInfo;
146 }
133147}
134148pub fn getSymbolAtAddress(module: *const ElfModule, gpa: Allocator, di: *DebugInfo, address: usize) Error!std.debug.Symbol {
135 if (di.loaded_elf == null) try module.loadDwarf(gpa, di);
149 if (di.loaded_elf == null) try module.loadElf(gpa, di);
136150 const vaddr = address - module.load_offset;
137 return di.loaded_elf.?.dwarf.getSymbol(gpa, native_endian, vaddr) catch |err| switch (err) {
138 error.InvalidDebugInfo, error.MissingDebugInfo, error.OutOfMemory => |e| return e,
139 error.ReadFailed,
140 error.EndOfStream,
141 error.Overflow,
142 error.StreamTooLong,
143 => return error.InvalidDebugInfo,
151 if (di.loaded_elf.?.dwarf) |*dwarf| {
152 if (!di.scanned_dwarf) {
153 dwarf.open(gpa, native_endian) catch |err| switch (err) {
154 error.InvalidDebugInfo,
155 error.MissingDebugInfo,
156 error.OutOfMemory,
157 => |e| return e,
158 error.EndOfStream,
159 error.Overflow,
160 error.ReadFailed,
161 error.StreamTooLong,
162 => return error.InvalidDebugInfo,
163 };
164 di.scanned_dwarf = true;
165 }
166 return dwarf.getSymbol(gpa, native_endian, vaddr) catch |err| switch (err) {
167 error.InvalidDebugInfo,
168 error.MissingDebugInfo,
169 error.OutOfMemory,
170 => |e| return e,
171 error.ReadFailed,
172 error.EndOfStream,
173 error.Overflow,
174 error.StreamTooLong,
175 => return error.InvalidDebugInfo,
176 };
177 }
178 // When there's no DWARF available, fall back to searching the symtab.
179 return di.loaded_elf.?.searchSymtab(gpa, vaddr) catch |err| switch (err) {
180 error.NoSymtab, error.NoStrtab => return error.MissingDebugInfo,
181 error.BadSymtab => return error.InvalidDebugInfo,
182 error.OutOfMemory => |e| return e,
144183 };
145184}
146185fn prepareUnwindLookup(unwind: *Dwarf.Unwind, gpa: Allocator) Error!void {
......@@ -166,7 +205,7 @@ fn loadUnwindInfo(module: *const ElfModule, gpa: Allocator, di: *DebugInfo) Erro
166205 } else unwinds: {
167206 // There is no `.eh_frame_hdr` section. There may still be an `.eh_frame` or `.debug_frame`
168207 // section, but we'll have to load the binary to get at it.
169 try module.loadDwarf(gpa, di);
208 try module.loadElf(gpa, di);
170209 const opt_debug_frame = &di.loaded_elf.?.debug_frame;
171210 const opt_eh_frame = &di.loaded_elf.?.eh_frame;
172211 // If both are present, we can't just pick one -- the info could be split between them.
......@@ -232,6 +271,7 @@ const ElfModule = @This();
232271const std = @import("../../std.zig");
233272const Allocator = std.mem.Allocator;
234273const Dwarf = std.debug.Dwarf;
274const ElfFile = std.debug.ElfFile;
235275const elf = std.elf;
236276const mem = std.mem;
237277const Error = std.debug.SelfInfo.Error;