authorgravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2025-09-02 13:07:42+01:00
committergravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2025-09-30 13:44:49+01:00
log3f6a90766c7ababdaaca629cc5165c5ff5b5dcd7
treedd3bb62e489b59e9b4fdb624521e287a34d082cd
parent89d862180f1d032b36e8d4371a037ae018bc43c5
signaturelock-open Commit is signed but in an unrecognized format.

sky pirates! which are even better!


2 files changed, 74 insertions(+), 79 deletions(-)

lib/std/debug/Dwarf/Unwind.zig+40-49
......@@ -2,7 +2,7 @@
22
33pub const VirtualMachine = @import("Unwind/VirtualMachine.zig");
44
5frame_section: ?struct {
5frame_section: struct {
66 id: Section,
77 /// The virtual address of the start of the section. "Virtual address" refers to the address in
88 /// the binary (e.g. `sh_addr` in an ELF file); the equivalent runtime address may be relocated
......@@ -42,10 +42,41 @@ const SortedFdeEntry = struct {
4242const Section = enum { debug_frame, eh_frame };
4343
4444// MLUGG TODO deinit?
45pub const init: Unwind = .{
46 .frame_section = null,
47 .lookup = null,
48};
45
46/// Initialize with unwind information from the contents of a `.debug_frame` or `.eh_frame` section.
47///
48/// If the `.eh_frame_hdr` section is available, consider instead using `initEhFrameHdr`. This
49/// allows the implementation to use a search table embedded in that section if it is available.
50pub fn initSection(section: Section, section_vaddr: u64, section_bytes: []const u8) Unwind {
51 return .{
52 .frame_section = .{
53 .id = section,
54 .bytes = section_bytes,
55 .vaddr = section_vaddr,
56 },
57 .lookup = null,
58 };
59}
60
61/// Initialize with unwind information from a header loaded from an `.eh_frame_hdr` section, and a
62/// pointer to the contents of the `.eh_frame` section.
63///
64/// This differs from `loadFromSection` because `.eh_frame_hdr` may embed a binary search table, and
65/// if it does, this function will use that for address lookups instead of constructing our own
66/// search table.
67pub fn initEhFrameHdr(header: EhFrameHeader, section_vaddr: u64, section_bytes_ptr: [*]const u8) Unwind {
68 return .{
69 .frame_section = .{
70 .id = .eh_frame,
71 .bytes = maxSlice(section_bytes_ptr),
72 .vaddr = header.eh_frame_vaddr,
73 },
74 .lookup = if (header.search_table) |table| .{ .eh_frame_hdr = .{
75 .vaddr = section_vaddr,
76 .table = table,
77 } } else null,
78 };
79}
4980
5081/// This represents the decoded .eh_frame_hdr header
5182pub const EhFrameHeader = struct {
......@@ -371,51 +402,11 @@ pub const FrameDescriptionEntry = struct {
371402 }
372403};
373404
374/// Load unwind information from the contents of an `.eh_frame` or `.debug_frame` section.
375///
376/// If the `.eh_frame_hdr` section is available, consider instead using `loadFromEhFrameHdr`. This
377/// allows the implementation to use a search table embedded in that section if it is available.
378pub fn loadFromSection(unwind: *Unwind, section: Section, section_vaddr: u64, section_bytes: []const u8) void {
379 assert(unwind.frame_section == null);
380 assert(unwind.lookup == null);
381 unwind.frame_section = .{
382 .id = section,
383 .bytes = section_bytes,
384 .vaddr = section_vaddr,
385 };
386}
387
388/// Load unwind information from a header loaded from an `.eh_frame_hdr` section, and a pointer to
389/// the contents of the `.eh_frame` section.
390///
391/// This differs from `loadFromSection` because `.eh_frame_hdr` may embed a binary search table, and
392/// if it does, this function will use that for address lookups instead of constructing our own
393/// search table.
394pub fn loadFromEhFrameHdr(
395 unwind: *Unwind,
396 header: EhFrameHeader,
397 section_vaddr: u64,
398 section_bytes_ptr: [*]const u8,
399) !void {
400 assert(unwind.frame_section == null);
401 assert(unwind.lookup == null);
402 unwind.frame_section = .{
403 .id = .eh_frame,
404 .bytes = maxSlice(section_bytes_ptr),
405 .vaddr = header.eh_frame_vaddr,
406 };
407 if (header.search_table) |table| {
408 unwind.lookup = .{ .eh_frame_hdr = .{
409 .vaddr = section_vaddr,
410 .table = table,
411 } };
412 }
413}
414
415405pub fn prepareLookup(unwind: *Unwind, gpa: Allocator, addr_size_bytes: u8, endian: Endian) !void {
416 const section = unwind.frame_section.?;
417406 if (unwind.lookup != null) return;
418407
408 const section = unwind.frame_section;
409
419410 var r: Reader = .fixed(section.bytes);
420411 var fde_list: std.ArrayList(SortedFdeEntry) = .empty;
421412 defer fde_list.deinit(gpa);
......@@ -477,7 +468,7 @@ pub fn lookupPc(unwind: *const Unwind, pc: u64, addr_size_bytes: u8, endian: End
477468 addr_size_bytes,
478469 endian,
479470 ) orelse return null;
480 return std.math.sub(u64, fde_vaddr, unwind.frame_section.?.vaddr) catch bad(); // convert vaddr to offset
471 return std.math.sub(u64, fde_vaddr, unwind.frame_section.vaddr) catch bad(); // convert vaddr to offset
481472 },
482473 .sorted_fdes => |sorted_fdes| sorted_fdes,
483474 };
......@@ -493,7 +484,7 @@ pub fn lookupPc(unwind: *const Unwind, pc: u64, addr_size_bytes: u8, endian: End
493484}
494485
495486pub fn getFde(unwind: *const Unwind, fde_offset: u64, addr_size_bytes: u8, endian: Endian) !struct { Format, CommonInformationEntry, FrameDescriptionEntry } {
496 const section = unwind.frame_section.?;
487 const section = unwind.frame_section;
497488
498489 var fde_reader: Reader = .fixed(section.bytes[fde_offset..]);
499490 const fde_info = switch (try EntryHeader.read(&fde_reader, fde_offset, section.id, endian)) {
lib/std/debug/SelfInfo.zig+34-30
......@@ -73,44 +73,26 @@ pub fn deinit(self: *SelfInfo) void {
7373
7474pub fn unwindFrame(self: *SelfInfo, gpa: Allocator, context: *UnwindContext) !usize {
7575 comptime assert(target_supported);
76 const module: Module = try .lookup(&self.lookup_cache, gpa, context.pc); // MLUGG TODO: don't take gpa
76 const module: Module = try .lookup(&self.lookup_cache, gpa, context.pc);
7777 const gop = try self.modules.getOrPut(gpa, module.load_offset);
7878 if (!gop.found_existing) gop.value_ptr.* = .init;
7979 if (!gop.value_ptr.loaded_unwind) {
8080 try module.loadUnwindInfo(gpa, &gop.value_ptr.di);
8181 gop.value_ptr.loaded_unwind = true;
8282 }
83 // MLUGG TODO: the stuff below is impl!
84 if (native_os.isDarwin()) {
85 // __unwind_info is a requirement for unwinding on Darwin. It may fall back to DWARF, but unwinding
86 // via DWARF before attempting to use the compact unwind info will produce incorrect results.
87 if (gop.value_ptr.di.unwind_info) |unwind_info| {
88 if (unwindFrameMachO(
89 module.text_base,
90 module.load_offset,
91 context,
92 unwind_info,
93 gop.value_ptr.di.eh_frame,
94 )) |return_address| {
95 return return_address;
96 } else |err| {
97 if (err != error.RequiresDWARFUnwind) return err;
98 }
99 }
100 return error.MissingUnwindInfo;
101 }
102 return unwindFrameDwarf(&gop.value_ptr.di.unwind, module.load_offset, context, null);
83 return module.unwindFrame(gpa, &gop.value_ptr.di, context);
10384}
10485
10586pub fn getSymbolAtAddress(self: *SelfInfo, gpa: Allocator, address: usize) !std.debug.Symbol {
10687 comptime assert(target_supported);
107 const module: Module = try .lookup(&self.lookup_cache, gpa, address); // MLUGG TODO: don't take gpa
88 const module: Module = try .lookup(&self.lookup_cache, gpa, address);
10889 const gop = try self.modules.getOrPut(gpa, module.key());
10990 if (!gop.found_existing) gop.value_ptr.* = .init;
11091 if (!gop.value_ptr.loaded_debug) {
11192 // MLUGG TODO: this overloads the name 'debug info' with including vs excluding unwind info
11293 // figure out a better name for one or the other (i think the inner one is maybe 'symbol info' or something idk)
11394 try module.loadDebugInfo(gpa, &gop.value_ptr.di);
95 gop.value_ptr.loaded_debug = true;
11496 }
11597 return module.getSymbolAtAddress(gpa, &gop.value_ptr.di, address);
11698}
......@@ -120,7 +102,7 @@ pub fn getSymbolAtAddress(self: *SelfInfo, gpa: Allocator, address: usize) !std.
120102/// a path that doesn't rely on any side-effects of a prior successful module lookup.
121103pub fn getModuleNameForAddress(self: *SelfInfo, gpa: Allocator, address: usize) error{ Unexpected, OutOfMemory, MissingDebugInfo }![]const u8 {
122104 comptime assert(target_supported);
123 const module: Module = try .lookup(&self.lookup_cache, gpa, address); // MLUGG TODO: don't take gpa
105 const module: Module = try .lookup(&self.lookup_cache, gpa, address);
124106 return module.name;
125107}
126108
......@@ -251,6 +233,18 @@ const Module = switch (native_os) {
251233 },
252234 };
253235 }
236 fn unwindFrame(module: *const Module, gpa: Allocator, di: *DebugInfo, context: *UnwindContext) !usize {
237 _ = gpa;
238 const unwind_info = di.unwind_info orelse return error.MissingUnwindInfo;
239 // MLUGG TODO: inline
240 return unwindFrameMachO(
241 module.text_base,
242 module.load_offset,
243 context,
244 unwind_info,
245 di.eh_frame,
246 );
247 }
254248 const LookupCache = void;
255249 const DebugInfo = struct {
256250 mapped_memory: []align(std.heap.page_size_min) const u8,
......@@ -499,12 +493,16 @@ const Module = switch (native_os) {
499493 const section_bytes = module.gnu_eh_frame orelse return error.MissingUnwindInfo; // MLUGG TODO: load from file
500494 const section_vaddr: u64 = @intFromPtr(section_bytes.ptr) - module.load_offset;
501495 const header: Dwarf.Unwind.EhFrameHeader = try .parse(section_vaddr, section_bytes, @sizeOf(usize), native_endian);
502 try di.unwind.loadFromEhFrameHdr(header, section_vaddr, @ptrFromInt(module.load_offset + header.eh_frame_vaddr));
496 di.unwind = .initEhFrameHdr(header, section_vaddr, @ptrFromInt(module.load_offset + header.eh_frame_vaddr));
503497 try di.unwind.prepareLookup(gpa, @sizeOf(usize), native_endian);
504498 }
505499 fn getSymbolAtAddress(module: *const Module, gpa: Allocator, di: *DebugInfo, address: usize) !std.debug.Symbol {
506500 return di.em.getSymbolAtAddress(gpa, native_endian, module.load_offset, address);
507501 }
502 fn unwindFrame(module: *const Module, gpa: Allocator, di: *DebugInfo, context: *UnwindContext) !usize {
503 _ = gpa;
504 return unwindFrameDwarf(&di.unwind, module.load_offset, context, null);
505 }
508506 },
509507 .uefi, .windows => struct {
510508 base_address: usize,
......@@ -1507,9 +1505,12 @@ fn unwindFrameMachO(
15071505 .DWARF => {
15081506 const eh_frame = opt_eh_frame orelse return error.MissingEhFrame;
15091507 const eh_frame_vaddr = @intFromPtr(eh_frame.ptr) - load_offset;
1510 var dwarf_unwind: Dwarf.Unwind = .init;
1511 dwarf_unwind.loadFromSection(.eh_frame, eh_frame_vaddr, eh_frame);
1512 return unwindFrameDwarf(&dwarf_unwind, load_offset, context, @intCast(encoding.value.x86_64.dwarf));
1508 return unwindFrameDwarf(
1509 &.initSection(.eh_frame, eh_frame_vaddr, eh_frame),
1510 load_offset,
1511 context,
1512 @intCast(encoding.value.x86_64.dwarf),
1513 );
15131514 },
15141515 },
15151516 .aarch64, .aarch64_be => switch (encoding.mode.arm64) {
......@@ -1524,9 +1525,12 @@ fn unwindFrameMachO(
15241525 .DWARF => {
15251526 const eh_frame = opt_eh_frame orelse return error.MissingEhFrame;
15261527 const eh_frame_vaddr = @intFromPtr(eh_frame.ptr) - load_offset;
1527 var dwarf_unwind: Dwarf.Unwind = .init;
1528 dwarf_unwind.loadFromSection(.eh_frame, eh_frame_vaddr, eh_frame);
1529 return unwindFrameDwarf(dwarf_unwind, load_offset, context, @intCast(encoding.value.arm64.dwarf));
1528 return unwindFrameDwarf(
1529 &.initSection(.eh_frame, eh_frame_vaddr, eh_frame),
1530 load_offset,
1531 context,
1532 @intCast(encoding.value.x86_64.dwarf),
1533 );
15301534 },
15311535 .FRAME => ip: {
15321536 const frame = encoding.value.arm64.frame;