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 @@...@@ -3,6 +3,13 @@
33
4dwarf: Dwarf,4dwarf: 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
6/// The memory-mapped ELF file, which is referenced by `dwarf`. This field is here only so that13/// The memory-mapped ELF file, which is referenced by `dwarf`. This field is here only so that
7/// this memory can be unmapped by `ElfModule.deinit`.14/// this memory can be unmapped by `ElfModule.deinit`.
8mapped_file: []align(std.heap.page_size_min) const u8,15mapped_file: []align(std.heap.page_size_min) const u8,
...@@ -11,10 +18,18 @@ mapped_file: []align(std.heap.page_size_min) const u8,...@@ -11,10 +18,18 @@ mapped_file: []align(std.heap.page_size_min) const u8,
11/// be unmapped by `ElfModule.deinit`.18/// be unmapped by `ElfModule.deinit`.
12mapped_debug_file: ?[]align(std.heap.page_size_min) const u8,19mapped_debug_file: ?[]align(std.heap.page_size_min) const u8,
1320
14pub fn deinit(em: *ElfModule, allocator: Allocator) void {21pub const UnwindSection = struct {
15 em.dwarf.deinit(allocator);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);
16 std.posix.munmap(em.mapped_file);29 std.posix.munmap(em.mapped_file);
17 if (em.mapped_debug_file) |m| std.posix.munmap(m);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);
18}33}
1934
20pub const LoadError = error{35pub const LoadError = error{
...@@ -98,7 +113,6 @@ pub fn load(...@@ -98,7 +113,6 @@ pub fn load(
98 )[0..hdr.e_shnum];113 )[0..hdr.e_shnum];
99114
100 var sections: Dwarf.SectionArray = @splat(null);115 var sections: Dwarf.SectionArray = @splat(null);
101
102 // Combine section list. This takes ownership over any owned sections from the parent scope.116 // Combine section list. This takes ownership over any owned sections from the parent scope.
103 if (parent_sections) |ps| {117 if (parent_sections) |ps| {
104 for (ps, &sections) |*parent, *section_elem| {118 for (ps, &sections) |*parent, *section_elem| {
...@@ -110,6 +124,12 @@ pub fn load(...@@ -110,6 +124,12 @@ pub fn load(
110 }124 }
111 errdefer for (sections) |opt_section| if (opt_section) |s| if (s.owned) gpa.free(s.data);125 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
113 var separate_debug_filename: ?[]const u8 = null;133 var separate_debug_filename: ?[]const u8 = null;
114 var separate_debug_crc: ?u32 = null;134 var separate_debug_crc: ?u32 = null;
115135
...@@ -128,17 +148,35 @@ pub fn load(...@@ -128,17 +148,35 @@ pub fn load(
128 continue;148 continue;
129 }149 }
130150
131 var section_index: ?usize = null;151 const section_id: union(enum) {
132 inline for (@typeInfo(Dwarf.Section.Id).@"enum".fields, 0..) |sect, i| {152 dwarf: Dwarf.Section.Id,
133 if (mem.eql(u8, "." ++ sect.name, name)) section_index = i;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,
134 }170 }
135 if (section_index == null) continue;
136 if (sections[section_index.?] != null) continue;
137171
138 if (mapped_mem.len < shdr.sh_offset + shdr.sh_size) return error.InvalidDebugInfo;172 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)];173 const raw_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: {174
141 var section_reader: Reader = .fixed(section_bytes);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);
142 const chdr = section_reader.takeStruct(elf.Chdr, endian) catch continue;180 const chdr = section_reader.takeStruct(elf.Chdr, endian) catch continue;
143 if (chdr.ch_type != .ZLIB) continue;181 if (chdr.ch_type != .ZLIB) continue;
144182
...@@ -153,14 +191,24 @@ pub fn load(...@@ -153,14 +191,24 @@ pub fn load(
153 Dwarf.invalidDebugInfoDetected();191 Dwarf.invalidDebugInfoDetected();
154 continue;192 continue;
155 }193 }
156 break :blk .{194 break :section .{ try decompressed_section.toOwnedSlice(gpa), true };
157 .data = try decompressed_section.toOwnedSlice(gpa),
158 .owned = true,
159 };
160 } else .{
161 .data = section_bytes,
162 .owned = false,
163 };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 }
164 }212 }
165213
166 const missing_debug_info =214 const missing_debug_info =
...@@ -305,9 +353,11 @@ pub fn load(...@@ -305,9 +353,11 @@ pub fn load(
305 var dwarf: Dwarf = .{ .sections = sections };353 var dwarf: Dwarf = .{ .sections = sections };
306 try dwarf.open(gpa, endian);354 try dwarf.open(gpa, endian);
307 return .{355 return .{
356 .dwarf = dwarf,
357 .eh_frame = eh_frame_section,
358 .debug_frame = debug_frame_section,
308 .mapped_file = parent_mapped_mem orelse mapped_mem,359 .mapped_file = parent_mapped_mem orelse mapped_mem,
309 .mapped_debug_file = if (parent_mapped_mem != null) mapped_mem else null,360 .mapped_debug_file = if (parent_mapped_mem != null) mapped_mem else null,
310 .dwarf = dwarf,
311 };361 };
312}362}
313363
lib/std/debug/SelfInfo/ElfModule.zig+55-18
...@@ -8,10 +8,10 @@ pub const LookupCache = void;...@@ -8,10 +8,10 @@ pub const LookupCache = void;
88
9pub const DebugInfo = struct {9pub const DebugInfo = struct {
10 loaded_elf: ?Dwarf.ElfModule,10 loaded_elf: ?Dwarf.ElfModule,
11 unwind: ?Dwarf.Unwind,11 unwind: [2]?Dwarf.Unwind,
12 pub const init: DebugInfo = .{12 pub const init: DebugInfo = .{
13 .loaded_elf = null,13 .loaded_elf = null,
14 .unwind = null,14 .unwind = @splat(null),
15 };15 };
16 pub fn deinit(di: *DebugInfo, gpa: Allocator) void {16 pub fn deinit(di: *DebugInfo, gpa: Allocator) void {
17 if (di.loaded_elf) |*loaded_elf| loaded_elf.deinit(gpa);17 if (di.loaded_elf) |*loaded_elf| loaded_elf.deinit(gpa);
...@@ -143,30 +143,67 @@ pub fn getSymbolAtAddress(module: *const ElfModule, gpa: Allocator, di: *DebugIn...@@ -143,30 +143,67 @@ pub fn getSymbolAtAddress(module: *const ElfModule, gpa: Allocator, di: *DebugIn
143 => return error.InvalidDebugInfo,143 => return error.InvalidDebugInfo,
144 };144 };
145}145}
146fn loadUnwindInfo(module: *const ElfModule, gpa: Allocator, di: *DebugInfo) Error!void {146fn prepareUnwindLookup(unwind: *Dwarf.Unwind, gpa: Allocator) 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));
158 unwind.prepareLookup(gpa, @sizeOf(usize), native_endian) catch |err| switch (err) {147 unwind.prepareLookup(gpa, @sizeOf(usize), native_endian) catch |err| switch (err) {
159 error.ReadFailed => unreachable, // it's all fixed buffers148 error.ReadFailed => unreachable, // it's all fixed buffers
160 error.InvalidDebugInfo, error.MissingDebugInfo, error.OutOfMemory => |e| return e,149 error.InvalidDebugInfo, error.MissingDebugInfo, error.OutOfMemory => |e| return e,
161 error.EndOfStream, error.Overflow, error.StreamTooLong => return error.InvalidDebugInfo,150 error.EndOfStream, error.Overflow, error.StreamTooLong => return error.InvalidDebugInfo,
162 error.UnsupportedAddrSize, error.UnsupportedDwarfVersion => return error.UnsupportedDebugInfo,151 error.UnsupportedAddrSize, error.UnsupportedDwarfVersion => return error.UnsupportedDebugInfo,
163 };152 };
164153}
165 di.unwind = unwind;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 }
166}195}
167pub fn unwindFrame(module: *const ElfModule, gpa: Allocator, di: *DebugInfo, context: *UnwindContext) Error!usize {196pub fn unwindFrame(module: *const ElfModule, gpa: Allocator, di: *DebugInfo, context: *UnwindContext) Error!usize {
168 if (di.unwind == null) try module.loadUnwindInfo(gpa, di);197 if (di.unwind[0] == null) try module.loadUnwindInfo(gpa, di);
169 return context.unwindFrameDwarf(&di.unwind.?, module.load_offset, null);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;
170}207}
171208
172const ElfModule = @This();209const ElfModule = @This();