authorgravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2025-09-04 00:39:38+01:00
committergravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2025-09-30 13:44:50+01:00
log405075f7455492717ac6cba505603eb74e4f54b9
tree73a0c76f92a0e16518e4b7737772d72e087753fd
parentc895aa7a35b178576b89e600a20af9d16da36dea
signaturelock-open Commit is signed but in an unrecognized format.

SelfInfo: load eh_frame/debug_frame from ELF file if eh_frame_hdr omitted


2 files changed, 124 insertions(+), 37 deletions(-)

lib/std/debug/Dwarf/ElfModule.zig+69-19
......@@ -3,6 +3,13 @@
33
44dwarf: Dwarf,
55
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
613/// The memory-mapped ELF file, which is referenced by `dwarf`. This field is here only so that
714/// this memory can be unmapped by `ElfModule.deinit`.
815mapped_file: []align(std.heap.page_size_min) const u8,
......@@ -11,10 +18,18 @@ mapped_file: []align(std.heap.page_size_min) const u8,
1118/// be unmapped by `ElfModule.deinit`.
1219mapped_debug_file: ?[]align(std.heap.page_size_min) const u8,
1320
14pub fn deinit(em: *ElfModule, allocator: Allocator) void {
15 em.dwarf.deinit(allocator);
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);
1629 std.posix.munmap(em.mapped_file);
1730 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);
1833}
1934
2035pub const LoadError = error{
......@@ -98,7 +113,6 @@ pub fn load(
98113 )[0..hdr.e_shnum];
99114
100115 var sections: Dwarf.SectionArray = @splat(null);
101
102116 // Combine section list. This takes ownership over any owned sections from the parent scope.
103117 if (parent_sections) |ps| {
104118 for (ps, &sections) |*parent, *section_elem| {
......@@ -110,6 +124,12 @@ pub fn load(
110124 }
111125 errdefer for (sections) |opt_section| if (opt_section) |s| if (s.owned) gpa.free(s.data);
112126
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
113133 var separate_debug_filename: ?[]const u8 = null;
114134 var separate_debug_crc: ?u32 = null;
115135
......@@ -128,17 +148,35 @@ pub fn load(
128148 continue;
129149 }
130150
131 var section_index: ?usize = null;
132 inline for (@typeInfo(Dwarf.Section.Id).@"enum".fields, 0..) |sect, i| {
133 if (mem.eql(u8, "." ++ sect.name, name)) section_index = i;
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,
134170 }
135 if (section_index == null) continue;
136 if (sections[section_index.?] != null) continue;
137171
138172 if (mapped_mem.len < shdr.sh_offset + shdr.sh_size) return error.InvalidDebugInfo;
139 const section_bytes = mapped_mem[@intCast(shdr.sh_offset)..][0..@intCast(shdr.sh_size)];
140 sections[section_index.?] = if ((shdr.sh_flags & elf.SHF_COMPRESSED) > 0) blk: {
141 var section_reader: Reader = .fixed(section_bytes);
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);
142180 const chdr = section_reader.takeStruct(elf.Chdr, endian) catch continue;
143181 if (chdr.ch_type != .ZLIB) continue;
144182
......@@ -153,14 +191,24 @@ pub fn load(
153191 Dwarf.invalidDebugInfoDetected();
154192 continue;
155193 }
156 break :blk .{
157 .data = try decompressed_section.toOwnedSlice(gpa),
158 .owned = true,
159 };
160 } else .{
161 .data = section_bytes,
162 .owned = false,
194 break :section .{ try decompressed_section.toOwnedSlice(gpa), true };
163195 };
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 }
164212 }
165213
166214 const missing_debug_info =
......@@ -305,9 +353,11 @@ pub fn load(
305353 var dwarf: Dwarf = .{ .sections = sections };
306354 try dwarf.open(gpa, endian);
307355 return .{
356 .dwarf = dwarf,
357 .eh_frame = eh_frame_section,
358 .debug_frame = debug_frame_section,
308359 .mapped_file = parent_mapped_mem orelse mapped_mem,
309360 .mapped_debug_file = if (parent_mapped_mem != null) mapped_mem else null,
310 .dwarf = dwarf,
311361 };
312362}
313363
lib/std/debug/SelfInfo/ElfModule.zig+55-18
......@@ -8,10 +8,10 @@ pub const LookupCache = void;
88
99pub const DebugInfo = struct {
1010 loaded_elf: ?Dwarf.ElfModule,
11 unwind: ?Dwarf.Unwind,
11 unwind: [2]?Dwarf.Unwind,
1212 pub const init: DebugInfo = .{
1313 .loaded_elf = null,
14 .unwind = null,
14 .unwind = @splat(null),
1515 };
1616 pub fn deinit(di: *DebugInfo, gpa: Allocator) void {
1717 if (di.loaded_elf) |*loaded_elf| loaded_elf.deinit(gpa);
......@@ -143,30 +143,67 @@ pub fn getSymbolAtAddress(module: *const ElfModule, gpa: Allocator, di: *DebugIn
143143 => return error.InvalidDebugInfo,
144144 };
145145}
146fn loadUnwindInfo(module: *const ElfModule, gpa: Allocator, di: *DebugInfo) Error!void {
147 const section_bytes = module.gnu_eh_frame orelse return error.MissingDebugInfo; // MLUGG TODO: load from file
148
149 const section_vaddr: u64 = @intFromPtr(section_bytes.ptr) - module.load_offset;
150 const header = Dwarf.Unwind.EhFrameHeader.parse(section_vaddr, section_bytes, @sizeOf(usize), native_endian) catch |err| switch (err) {
151 error.ReadFailed => unreachable, // it's all fixed buffers
152 error.InvalidDebugInfo => |e| return e,
153 error.EndOfStream, error.Overflow => return error.InvalidDebugInfo,
154 error.UnsupportedAddrSize => return error.UnsupportedDebugInfo,
155 };
156
157 var unwind: Dwarf.Unwind = .initEhFrameHdr(header, section_vaddr, @ptrFromInt(module.load_offset + header.eh_frame_vaddr));
146fn prepareUnwindLookup(unwind: *Dwarf.Unwind, gpa: Allocator) Error!void {
158147 unwind.prepareLookup(gpa, @sizeOf(usize), native_endian) catch |err| switch (err) {
159148 error.ReadFailed => unreachable, // it's all fixed buffers
160149 error.InvalidDebugInfo, error.MissingDebugInfo, error.OutOfMemory => |e| return e,
161150 error.EndOfStream, error.Overflow, error.StreamTooLong => return error.InvalidDebugInfo,
162151 error.UnsupportedAddrSize, error.UnsupportedDwarfVersion => return error.UnsupportedDebugInfo,
163152 };
164
165 di.unwind = unwind;
153}
154fn loadUnwindInfo(module: *const ElfModule, gpa: Allocator, di: *DebugInfo) Error!void {
155 var buf: [2]Dwarf.Unwind = undefined;
156 const unwinds: []Dwarf.Unwind = if (module.gnu_eh_frame) |section_bytes| unwinds: {
157 const section_vaddr: u64 = @intFromPtr(section_bytes.ptr) - module.load_offset;
158 const header = Dwarf.Unwind.EhFrameHeader.parse(section_vaddr, section_bytes, @sizeOf(usize), native_endian) catch |err| switch (err) {
159 error.ReadFailed => unreachable, // it's all fixed buffers
160 error.InvalidDebugInfo => |e| return e,
161 error.EndOfStream, error.Overflow => return error.InvalidDebugInfo,
162 error.UnsupportedAddrSize => return error.UnsupportedDebugInfo,
163 };
164 buf[0] = .initEhFrameHdr(header, section_vaddr, @ptrFromInt(module.load_offset + header.eh_frame_vaddr));
165 break :unwinds buf[0..1];
166 } else unwinds: {
167 // There is no `.eh_frame_hdr` section. There may still be an `.eh_frame` or `.debug_frame`
168 // section, but we'll have to load the binary to get at it.
169 try module.loadDwarf(gpa, di);
170 const opt_debug_frame = &di.loaded_elf.?.debug_frame;
171 const opt_eh_frame = &di.loaded_elf.?.eh_frame;
172 // If both are present, we can't just pick one -- the info could be split between them.
173 // `.debug_frame` is likely to be the more complete section, so we'll prioritize that one.
174 if (opt_debug_frame.*) |*debug_frame| {
175 buf[0] = .initSection(.debug_frame, debug_frame.vaddr, debug_frame.bytes);
176 if (opt_eh_frame.*) |*eh_frame| {
177 buf[1] = .initSection(.eh_frame, eh_frame.vaddr, eh_frame.bytes);
178 break :unwinds buf[0..2];
179 }
180 break :unwinds buf[0..1];
181 } else if (opt_eh_frame.*) |eh_frame| {
182 buf[0] = .initSection(.eh_frame, eh_frame.vaddr, eh_frame.bytes);
183 break :unwinds buf[0..1];
184 }
185 return error.MissingDebugInfo;
186 };
187 errdefer for (unwinds) |*u| u.deinit(gpa);
188 for (unwinds) |*u| try prepareUnwindLookup(u, gpa);
189 switch (unwinds.len) {
190 0 => unreachable,
191 1 => di.unwind = .{ unwinds[0], null },
192 2 => di.unwind = .{ unwinds[0], unwinds[1] },
193 else => unreachable,
194 }
166195}
167196pub fn unwindFrame(module: *const ElfModule, gpa: Allocator, di: *DebugInfo, context: *UnwindContext) Error!usize {
168 if (di.unwind == null) try module.loadUnwindInfo(gpa, di);
169 return context.unwindFrameDwarf(&di.unwind.?, module.load_offset, null);
197 if (di.unwind[0] == null) try module.loadUnwindInfo(gpa, di);
198 std.debug.assert(di.unwind[0] != null);
199 for (&di.unwind) |*opt_unwind| {
200 const unwind = &(opt_unwind.* orelse break);
201 return context.unwindFrameDwarf(unwind, module.load_offset, null) catch |err| switch (err) {
202 error.MissingDebugInfo => continue, // try the next one
203 else => |e| return e,
204 };
205 }
206 return error.MissingDebugInfo;
170207}
171208
172209const ElfModule = @This();