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 @@...@@ -2,7 +2,7 @@
22
3pub const VirtualMachine = @import("Unwind/VirtualMachine.zig");3pub const VirtualMachine = @import("Unwind/VirtualMachine.zig");
44
5frame_section: ?struct {5frame_section: struct {
6 id: Section,6 id: Section,
7 /// The virtual address of the start of the section. "Virtual address" refers to the address in7 /// The virtual address of the start of the section. "Virtual address" refers to the address in
8 /// the binary (e.g. `sh_addr` in an ELF file); the equivalent runtime address may be relocated8 /// the binary (e.g. `sh_addr` in an ELF file); the equivalent runtime address may be relocated
...@@ -42,10 +42,41 @@ const SortedFdeEntry = struct {...@@ -42,10 +42,41 @@ const SortedFdeEntry = struct {
42const Section = enum { debug_frame, eh_frame };42const Section = enum { debug_frame, eh_frame };
4343
44// MLUGG TODO deinit?44// MLUGG TODO deinit?
45pub const init: Unwind = .{45
46 .frame_section = null,46/// Initialize with unwind information from the contents of a `.debug_frame` or `.eh_frame` section.
47 .lookup = null,47///
48};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
50/// This represents the decoded .eh_frame_hdr header81/// This represents the decoded .eh_frame_hdr header
51pub const EhFrameHeader = struct {82pub const EhFrameHeader = struct {
...@@ -371,51 +402,11 @@ pub const FrameDescriptionEntry = struct {...@@ -371,51 +402,11 @@ pub const FrameDescriptionEntry = struct {
371 }402 }
372};403};
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
415pub fn prepareLookup(unwind: *Unwind, gpa: Allocator, addr_size_bytes: u8, endian: Endian) !void {405pub fn prepareLookup(unwind: *Unwind, gpa: Allocator, addr_size_bytes: u8, endian: Endian) !void {
416 const section = unwind.frame_section.?;
417 if (unwind.lookup != null) return;406 if (unwind.lookup != null) return;
418407
408 const section = unwind.frame_section;
409
419 var r: Reader = .fixed(section.bytes);410 var r: Reader = .fixed(section.bytes);
420 var fde_list: std.ArrayList(SortedFdeEntry) = .empty;411 var fde_list: std.ArrayList(SortedFdeEntry) = .empty;
421 defer fde_list.deinit(gpa);412 defer fde_list.deinit(gpa);
...@@ -477,7 +468,7 @@ pub fn lookupPc(unwind: *const Unwind, pc: u64, addr_size_bytes: u8, endian: End...@@ -477,7 +468,7 @@ pub fn lookupPc(unwind: *const Unwind, pc: u64, addr_size_bytes: u8, endian: End
477 addr_size_bytes,468 addr_size_bytes,
478 endian,469 endian,
479 ) orelse return null;470 ) orelse return null;
480 return std.math.sub(u64, fde_vaddr, unwind.frame_section.?.vaddr) catch bad(); // convert vaddr to offset471 return std.math.sub(u64, fde_vaddr, unwind.frame_section.vaddr) catch bad(); // convert vaddr to offset
481 },472 },
482 .sorted_fdes => |sorted_fdes| sorted_fdes,473 .sorted_fdes => |sorted_fdes| sorted_fdes,
483 };474 };
...@@ -493,7 +484,7 @@ pub fn lookupPc(unwind: *const Unwind, pc: u64, addr_size_bytes: u8, endian: End...@@ -493,7 +484,7 @@ pub fn lookupPc(unwind: *const Unwind, pc: u64, addr_size_bytes: u8, endian: End
493}484}
494485
495pub fn getFde(unwind: *const Unwind, fde_offset: u64, addr_size_bytes: u8, endian: Endian) !struct { Format, CommonInformationEntry, FrameDescriptionEntry } {486pub 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
498 var fde_reader: Reader = .fixed(section.bytes[fde_offset..]);489 var fde_reader: Reader = .fixed(section.bytes[fde_offset..]);
499 const fde_info = switch (try EntryHeader.read(&fde_reader, fde_offset, section.id, endian)) {490 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 {...@@ -73,44 +73,26 @@ pub fn deinit(self: *SelfInfo) void {
7373
74pub fn unwindFrame(self: *SelfInfo, gpa: Allocator, context: *UnwindContext) !usize {74pub fn unwindFrame(self: *SelfInfo, gpa: Allocator, context: *UnwindContext) !usize {
75 comptime assert(target_supported);75 comptime assert(target_supported);
76 const module: Module = try .lookup(&self.lookup_cache, gpa, context.pc); // MLUGG TODO: don't take gpa76 const module: Module = try .lookup(&self.lookup_cache, gpa, context.pc);
77 const gop = try self.modules.getOrPut(gpa, module.load_offset);77 const gop = try self.modules.getOrPut(gpa, module.load_offset);
78 if (!gop.found_existing) gop.value_ptr.* = .init;78 if (!gop.found_existing) gop.value_ptr.* = .init;
79 if (!gop.value_ptr.loaded_unwind) {79 if (!gop.value_ptr.loaded_unwind) {
80 try module.loadUnwindInfo(gpa, &gop.value_ptr.di);80 try module.loadUnwindInfo(gpa, &gop.value_ptr.di);
81 gop.value_ptr.loaded_unwind = true;81 gop.value_ptr.loaded_unwind = true;
82 }82 }
83 // MLUGG TODO: the stuff below is impl!83 return module.unwindFrame(gpa, &gop.value_ptr.di, context);
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);
103}84}
10485
105pub fn getSymbolAtAddress(self: *SelfInfo, gpa: Allocator, address: usize) !std.debug.Symbol {86pub fn getSymbolAtAddress(self: *SelfInfo, gpa: Allocator, address: usize) !std.debug.Symbol {
106 comptime assert(target_supported);87 comptime assert(target_supported);
107 const module: Module = try .lookup(&self.lookup_cache, gpa, address); // MLUGG TODO: don't take gpa88 const module: Module = try .lookup(&self.lookup_cache, gpa, address);
108 const gop = try self.modules.getOrPut(gpa, module.key());89 const gop = try self.modules.getOrPut(gpa, module.key());
109 if (!gop.found_existing) gop.value_ptr.* = .init;90 if (!gop.found_existing) gop.value_ptr.* = .init;
110 if (!gop.value_ptr.loaded_debug) {91 if (!gop.value_ptr.loaded_debug) {
111 // MLUGG TODO: this overloads the name 'debug info' with including vs excluding unwind info92 // MLUGG TODO: this overloads the name 'debug info' with including vs excluding unwind info
112 // figure out a better name for one or the other (i think the inner one is maybe 'symbol info' or something idk)93 // figure out a better name for one or the other (i think the inner one is maybe 'symbol info' or something idk)
113 try module.loadDebugInfo(gpa, &gop.value_ptr.di);94 try module.loadDebugInfo(gpa, &gop.value_ptr.di);
95 gop.value_ptr.loaded_debug = true;
114 }96 }
115 return module.getSymbolAtAddress(gpa, &gop.value_ptr.di, address);97 return module.getSymbolAtAddress(gpa, &gop.value_ptr.di, address);
116}98}
...@@ -120,7 +102,7 @@ pub fn getSymbolAtAddress(self: *SelfInfo, gpa: Allocator, address: usize) !std....@@ -120,7 +102,7 @@ pub fn getSymbolAtAddress(self: *SelfInfo, gpa: Allocator, address: usize) !std.
120/// a path that doesn't rely on any side-effects of a prior successful module lookup.102/// a path that doesn't rely on any side-effects of a prior successful module lookup.
121pub fn getModuleNameForAddress(self: *SelfInfo, gpa: Allocator, address: usize) error{ Unexpected, OutOfMemory, MissingDebugInfo }![]const u8 {103pub fn getModuleNameForAddress(self: *SelfInfo, gpa: Allocator, address: usize) error{ Unexpected, OutOfMemory, MissingDebugInfo }![]const u8 {
122 comptime assert(target_supported);104 comptime assert(target_supported);
123 const module: Module = try .lookup(&self.lookup_cache, gpa, address); // MLUGG TODO: don't take gpa105 const module: Module = try .lookup(&self.lookup_cache, gpa, address);
124 return module.name;106 return module.name;
125}107}
126108
...@@ -251,6 +233,18 @@ const Module = switch (native_os) {...@@ -251,6 +233,18 @@ const Module = switch (native_os) {
251 },233 },
252 };234 };
253 }235 }
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 }
254 const LookupCache = void;248 const LookupCache = void;
255 const DebugInfo = struct {249 const DebugInfo = struct {
256 mapped_memory: []align(std.heap.page_size_min) const u8,250 mapped_memory: []align(std.heap.page_size_min) const u8,
...@@ -499,12 +493,16 @@ const Module = switch (native_os) {...@@ -499,12 +493,16 @@ const Module = switch (native_os) {
499 const section_bytes = module.gnu_eh_frame orelse return error.MissingUnwindInfo; // MLUGG TODO: load from file493 const section_bytes = module.gnu_eh_frame orelse return error.MissingUnwindInfo; // MLUGG TODO: load from file
500 const section_vaddr: u64 = @intFromPtr(section_bytes.ptr) - module.load_offset;494 const section_vaddr: u64 = @intFromPtr(section_bytes.ptr) - module.load_offset;
501 const header: Dwarf.Unwind.EhFrameHeader = try .parse(section_vaddr, section_bytes, @sizeOf(usize), native_endian);495 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));
503 try di.unwind.prepareLookup(gpa, @sizeOf(usize), native_endian);497 try di.unwind.prepareLookup(gpa, @sizeOf(usize), native_endian);
504 }498 }
505 fn getSymbolAtAddress(module: *const Module, gpa: Allocator, di: *DebugInfo, address: usize) !std.debug.Symbol {499 fn getSymbolAtAddress(module: *const Module, gpa: Allocator, di: *DebugInfo, address: usize) !std.debug.Symbol {
506 return di.em.getSymbolAtAddress(gpa, native_endian, module.load_offset, address);500 return di.em.getSymbolAtAddress(gpa, native_endian, module.load_offset, address);
507 }501 }
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 }
508 },506 },
509 .uefi, .windows => struct {507 .uefi, .windows => struct {
510 base_address: usize,508 base_address: usize,
...@@ -1507,9 +1505,12 @@ fn unwindFrameMachO(...@@ -1507,9 +1505,12 @@ fn unwindFrameMachO(
1507 .DWARF => {1505 .DWARF => {
1508 const eh_frame = opt_eh_frame orelse return error.MissingEhFrame;1506 const eh_frame = opt_eh_frame orelse return error.MissingEhFrame;
1509 const eh_frame_vaddr = @intFromPtr(eh_frame.ptr) - load_offset;1507 const eh_frame_vaddr = @intFromPtr(eh_frame.ptr) - load_offset;
1510 var dwarf_unwind: Dwarf.Unwind = .init;1508 return unwindFrameDwarf(
1511 dwarf_unwind.loadFromSection(.eh_frame, eh_frame_vaddr, eh_frame);1509 &.initSection(.eh_frame, eh_frame_vaddr, eh_frame),
1512 return unwindFrameDwarf(&dwarf_unwind, load_offset, context, @intCast(encoding.value.x86_64.dwarf));1510 load_offset,
1511 context,
1512 @intCast(encoding.value.x86_64.dwarf),
1513 );
1513 },1514 },
1514 },1515 },
1515 .aarch64, .aarch64_be => switch (encoding.mode.arm64) {1516 .aarch64, .aarch64_be => switch (encoding.mode.arm64) {
...@@ -1524,9 +1525,12 @@ fn unwindFrameMachO(...@@ -1524,9 +1525,12 @@ fn unwindFrameMachO(
1524 .DWARF => {1525 .DWARF => {
1525 const eh_frame = opt_eh_frame orelse return error.MissingEhFrame;1526 const eh_frame = opt_eh_frame orelse return error.MissingEhFrame;
1526 const eh_frame_vaddr = @intFromPtr(eh_frame.ptr) - load_offset;1527 const eh_frame_vaddr = @intFromPtr(eh_frame.ptr) - load_offset;
1527 var dwarf_unwind: Dwarf.Unwind = .init;1528 return unwindFrameDwarf(
1528 dwarf_unwind.loadFromSection(.eh_frame, eh_frame_vaddr, eh_frame);1529 &.initSection(.eh_frame, eh_frame_vaddr, eh_frame),
1529 return unwindFrameDwarf(dwarf_unwind, load_offset, context, @intCast(encoding.value.arm64.dwarf));1530 load_offset,
1531 context,
1532 @intCast(encoding.value.x86_64.dwarf),
1533 );
1530 },1534 },
1531 .FRAME => ip: {1535 .FRAME => ip: {
1532 const frame = encoding.value.arm64.frame;1536 const frame = encoding.value.arm64.frame;