authorgravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2025-09-17 23:03:45+01:00
committergravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2025-09-30 13:44:54+01:00
logdd8d59686a069fdb72d9f0753e3482ff99cce98c
tree0a90991d41626bb9400a1d6cf956d10b99ed3a6c
parenta18fd41064493e742eacebc88e2afeadd54ff6f0
signaturelock-open Commit is signed but in an unrecognized format.

std.debug: miscellaneous fixes

Mostly on macOS, since Loris showed me a not-great stack trace, and I spent 8 hours trying to make it better. The dyld shared cache is designed in a way which makes this really hard to do right, and documentation is non-existent, but this *seems* to work pretty well. I'll leave the ruling on whether I did a good job to CI and our users.

8 files changed, 276 insertions(+), 132 deletions(-)

lib/std/c.zig+3
...@@ -10994,6 +10994,9 @@ pub extern "c" fn dlclose(handle: *anyopaque) c_int;...@@ -10994,6 +10994,9 @@ pub extern "c" fn dlclose(handle: *anyopaque) c_int;
10994pub extern "c" fn dlsym(handle: ?*anyopaque, symbol: [*:0]const u8) ?*anyopaque;10994pub extern "c" fn dlsym(handle: ?*anyopaque, symbol: [*:0]const u8) ?*anyopaque;
10995pub extern "c" fn dlerror() ?[*:0]u8;10995pub extern "c" fn dlerror() ?[*:0]u8;
1099610996
10997pub const dladdr = if (native_os.isDarwin()) darwin.dladdr else {};
10998pub const dl_info = if (native_os.isDarwin()) darwin.dl_info else {};
10999
10997pub extern "c" fn sync() void;11000pub extern "c" fn sync() void;
10998pub extern "c" fn syncfs(fd: c_int) c_int;11001pub extern "c" fn syncfs(fd: c_int) c_int;
10999pub extern "c" fn fsync(fd: c_int) c_int;11002pub extern "c" fn fsync(fd: c_int) c_int;
lib/std/c/darwin.zig+8
...@@ -354,6 +354,14 @@ pub extern "c" fn _dyld_image_count() u32;...@@ -354,6 +354,14 @@ pub extern "c" fn _dyld_image_count() u32;
354pub extern "c" fn _dyld_get_image_header(image_index: u32) ?*mach_header;354pub extern "c" fn _dyld_get_image_header(image_index: u32) ?*mach_header;
355pub extern "c" fn _dyld_get_image_vmaddr_slide(image_index: u32) usize;355pub extern "c" fn _dyld_get_image_vmaddr_slide(image_index: u32) usize;
356pub extern "c" fn _dyld_get_image_name(image_index: u32) [*:0]const u8;356pub extern "c" fn _dyld_get_image_name(image_index: u32) [*:0]const u8;
357pub extern "c" fn dladdr(addr: *const anyopaque, info: *dl_info) c_int;
358
359pub const dl_info = extern struct {
360 fname: [*:0]const u8,
361 fbase: *anyopaque,
362 sname: ?[*:0]const u8,
363 saddr: ?*anyopaque,
364};
357365
358pub const COPYFILE = packed struct(u32) {366pub const COPYFILE = packed struct(u32) {
359 ACL: bool = false,367 ACL: bool = false,
lib/std/debug.zig+55-20
...@@ -585,12 +585,14 @@ pub fn captureCurrentStackTrace(options: StackUnwindOptions, addr_buf: []usize)...@@ -585,12 +585,14 @@ pub fn captureCurrentStackTrace(options: StackUnwindOptions, addr_buf: []usize)
585 while (true) switch (it.next()) {585 while (true) switch (it.next()) {
586 .switch_to_fp => if (!it.stratOk(options.allow_unsafe_unwind)) break,586 .switch_to_fp => if (!it.stratOk(options.allow_unsafe_unwind)) break,
587 .end => break,587 .end => break,
588 .frame => |return_address| {588 .frame => |pc_addr| {
589 if (wait_for) |target| {589 if (wait_for) |target| {
590 if (return_address != target) continue;590 // Possible off-by-one error: `pc_addr` might be one less than the return address (so
591 // that it falls *inside* the function call), while `target` *is* a return address.
592 if (pc_addr != target and pc_addr + 1 != target) continue;
591 wait_for = null;593 wait_for = null;
592 }594 }
593 if (frame_idx < addr_buf.len) addr_buf[frame_idx] = return_address;595 if (frame_idx < addr_buf.len) addr_buf[frame_idx] = pc_addr;
594 frame_idx += 1;596 frame_idx += 1;
595 },597 },
596 };598 };
...@@ -631,6 +633,7 @@ pub fn writeCurrentStackTrace(options: StackUnwindOptions, writer: *Writer, tty_...@@ -631,6 +633,7 @@ pub fn writeCurrentStackTrace(options: StackUnwindOptions, writer: *Writer, tty_
631 var printed_any_frame = false;633 var printed_any_frame = false;
632 while (true) switch (it.next()) {634 while (true) switch (it.next()) {
633 .switch_to_fp => |unwind_error| {635 .switch_to_fp => |unwind_error| {
636 if (StackIterator.fp_unwind_is_safe) continue; // no need to even warn
634 const module_name = di.getModuleNameForAddress(di_gpa, unwind_error.address) catch "???";637 const module_name = di.getModuleNameForAddress(di_gpa, unwind_error.address) catch "???";
635 const caption: []const u8 = switch (unwind_error.err) {638 const caption: []const u8 = switch (unwind_error.err) {
636 error.MissingDebugInfo => "unwind info unavailable",639 error.MissingDebugInfo => "unwind info unavailable",
...@@ -658,12 +661,14 @@ pub fn writeCurrentStackTrace(options: StackUnwindOptions, writer: *Writer, tty_...@@ -658,12 +661,14 @@ pub fn writeCurrentStackTrace(options: StackUnwindOptions, writer: *Writer, tty_
658 }661 }
659 },662 },
660 .end => break,663 .end => break,
661 .frame => |return_address| {664 .frame => |pc_addr| {
662 if (wait_for) |target| {665 if (wait_for) |target| {
663 if (return_address != target) continue;666 // Possible off-by-one error: `pc_addr` might be one less than the return address (so
667 // that it falls *inside* the function call), while `target` *is* a return address.
668 if (pc_addr != target and pc_addr + 1 != target) continue;
664 wait_for = null;669 wait_for = null;
665 }670 }
666 try printSourceAtAddress(di_gpa, di, writer, return_address -| 1, tty_config);671 try printSourceAtAddress(di_gpa, di, writer, pc_addr, tty_config);
667 printed_any_frame = true;672 printed_any_frame = true;
668 },673 },
669 };674 };
...@@ -703,8 +708,8 @@ pub fn writeStackTrace(st: *const std.builtin.StackTrace, writer: *Writer, tty_c...@@ -703,8 +708,8 @@ pub fn writeStackTrace(st: *const std.builtin.StackTrace, writer: *Writer, tty_c
703 },708 },
704 };709 };
705 const captured_frames = @min(n_frames, st.instruction_addresses.len);710 const captured_frames = @min(n_frames, st.instruction_addresses.len);
706 for (st.instruction_addresses[0..captured_frames]) |return_address| {711 for (st.instruction_addresses[0..captured_frames]) |pc_addr| {
707 try printSourceAtAddress(di_gpa, di, writer, return_address -| 1, tty_config);712 try printSourceAtAddress(di_gpa, di, writer, pc_addr, tty_config);
708 }713 }
709 if (n_frames > captured_frames) {714 if (n_frames > captured_frames) {
710 tty_config.setColor(writer, .bold) catch {};715 tty_config.setColor(writer, .bold) catch {};
...@@ -725,6 +730,8 @@ pub fn dumpStackTrace(st: *const std.builtin.StackTrace) void {...@@ -725,6 +730,8 @@ pub fn dumpStackTrace(st: *const std.builtin.StackTrace) void {
725const StackIterator = union(enum) {730const StackIterator = union(enum) {
726 /// Unwinding using debug info (e.g. DWARF CFI).731 /// Unwinding using debug info (e.g. DWARF CFI).
727 di: if (SelfInfo.supports_unwinding) SelfInfo.UnwindContext else noreturn,732 di: if (SelfInfo.supports_unwinding) SelfInfo.UnwindContext else noreturn,
733 /// We will first report the *current* PC of this `UnwindContext`, then we will switch to `di`.
734 di_first: if (SelfInfo.supports_unwinding) SelfInfo.UnwindContext else noreturn,
728 /// Naive frame-pointer-based unwinding. Very simple, but typically unreliable.735 /// Naive frame-pointer-based unwinding. Very simple, but typically unreliable.
729 fp: usize,736 fp: usize,
730737
...@@ -742,9 +749,12 @@ const StackIterator = union(enum) {...@@ -742,9 +749,12 @@ const StackIterator = union(enum) {
742 }749 }
743 if (opt_context_ptr) |context_ptr| {750 if (opt_context_ptr) |context_ptr| {
744 if (!SelfInfo.supports_unwinding) return error.CannotUnwindFromContext;751 if (!SelfInfo.supports_unwinding) return error.CannotUnwindFromContext;
745 return .{ .di = .init(context_ptr) };752 // Use `di_first` here so we report the PC in the context before unwinding any further.
753 return .{ .di_first = .init(context_ptr) };
746 }754 }
747 if (SelfInfo.supports_unwinding and cpu_context.Native != noreturn) {755 if (SelfInfo.supports_unwinding and cpu_context.Native != noreturn) {
756 // We don't need `di_first` here, because our PC is in `std.debug`; we're only interested
757 // in our caller's frame and above.
748 return .{ .di = .init(&.current()) };758 return .{ .di = .init(&.current()) };
749 }759 }
750 return .{ .fp = @frameAddress() };760 return .{ .fp = @frameAddress() };
...@@ -752,7 +762,7 @@ const StackIterator = union(enum) {...@@ -752,7 +762,7 @@ const StackIterator = union(enum) {
752 fn deinit(si: *StackIterator) void {762 fn deinit(si: *StackIterator) void {
753 switch (si.*) {763 switch (si.*) {
754 .fp => {},764 .fp => {},
755 .di => |*unwind_context| unwind_context.deinit(getDebugInfoAllocator()),765 .di, .di_first => |*unwind_context| unwind_context.deinit(getDebugInfoAllocator()),
756 }766 }
757 }767 }
758768
...@@ -763,7 +773,7 @@ const StackIterator = union(enum) {...@@ -763,7 +773,7 @@ const StackIterator = union(enum) {
763 /// Whether the current unwind strategy is allowed given `allow_unsafe`.773 /// Whether the current unwind strategy is allowed given `allow_unsafe`.
764 fn stratOk(it: *const StackIterator, allow_unsafe: bool) bool {774 fn stratOk(it: *const StackIterator, allow_unsafe: bool) bool {
765 return switch (it.*) {775 return switch (it.*) {
766 .di => true,776 .di, .di_first => true,
767 // If we omitted frame pointers from *this* compilation, FP unwinding would crash777 // If we omitted frame pointers from *this* compilation, FP unwinding would crash
768 // immediately regardless of anything. But FPs could also be omitted from a different778 // immediately regardless of anything. But FPs could also be omitted from a different
769 // linked object, so it's not guaranteed to be safe, unless the target specifically779 // linked object, so it's not guaranteed to be safe, unless the target specifically
...@@ -773,11 +783,11 @@ const StackIterator = union(enum) {...@@ -773,11 +783,11 @@ const StackIterator = union(enum) {
773 }783 }
774784
775 const Result = union(enum) {785 const Result = union(enum) {
776 /// A stack frame has been found; this is the corresponding return address.786 /// A stack frame has been found; this is the corresponding program counter address.
777 frame: usize,787 frame: usize,
778 /// The end of the stack has been reached.788 /// The end of the stack has been reached.
779 end,789 end,
780 /// We were using the `.di` strategy, but are now switching to `.fp` due to this error.790 /// We were using `SelfInfo.UnwindInfo`, but are now switching to FP unwinding due to this error.
781 switch_to_fp: struct {791 switch_to_fp: struct {
782 address: usize,792 address: usize,
783 err: SelfInfo.Error,793 err: SelfInfo.Error,
...@@ -785,20 +795,25 @@ const StackIterator = union(enum) {...@@ -785,20 +795,25 @@ const StackIterator = union(enum) {
785 };795 };
786 fn next(it: *StackIterator) Result {796 fn next(it: *StackIterator) Result {
787 switch (it.*) {797 switch (it.*) {
798 .di_first => |unwind_context| {
799 const first_pc = unwind_context.pc;
800 if (first_pc == 0) return .end;
801 it.* = .{ .di = unwind_context };
802 return .{ .frame = first_pc };
803 },
788 .di => |*unwind_context| {804 .di => |*unwind_context| {
789 const di = getSelfDebugInfo() catch unreachable;805 const di = getSelfDebugInfo() catch unreachable;
790 const di_gpa = getDebugInfoAllocator();806 const di_gpa = getDebugInfoAllocator();
791 if (di.unwindFrame(di_gpa, unwind_context)) |ra| {807 di.unwindFrame(di_gpa, unwind_context) catch |err| {
792 if (ra <= 1) return .end;
793 return .{ .frame = ra };
794 } else |err| {
795 const pc = unwind_context.pc;808 const pc = unwind_context.pc;
796 it.* = .{ .fp = unwind_context.getFp() };809 it.* = .{ .fp = unwind_context.getFp() };
797 return .{ .switch_to_fp = .{810 return .{ .switch_to_fp = .{
798 .address = pc,811 .address = pc,
799 .err = err,812 .err = err,
800 } };813 } };
801 }814 };
815 const pc = unwind_context.pc;
816 return if (pc == 0) .end else .{ .frame = pc };
802 },817 },
803 .fp => |fp| {818 .fp => |fp| {
804 if (fp == 0) return .end; // we reached the "sentinel" base pointer819 if (fp == 0) return .end; // we reached the "sentinel" base pointer
...@@ -824,9 +839,9 @@ const StackIterator = union(enum) {...@@ -824,9 +839,9 @@ const StackIterator = union(enum) {
824 if (bp != 0 and bp <= fp) return .end;839 if (bp != 0 and bp <= fp) return .end;
825840
826 it.fp = bp;841 it.fp = bp;
827 const ra = ra_ptr.*;842 const ra = stripInstructionPtrAuthCode(ra_ptr.*);
828 if (ra <= 1) return .end;843 if (ra <= 1) return .end;
829 return .{ .frame = ra };844 return .{ .frame = ra - 1 };
830 },845 },
831 }846 }
832 }847 }
...@@ -860,6 +875,26 @@ const StackIterator = union(enum) {...@@ -860,6 +875,26 @@ const StackIterator = union(enum) {
860 }875 }
861};876};
862877
878/// Some platforms use pointer authentication: the upper bits of instruction pointers contain a
879/// signature. This function clears those signature bits to make the pointer directly usable.
880pub inline fn stripInstructionPtrAuthCode(ptr: usize) usize {
881 if (native_arch.isAARCH64()) {
882 // `hint 0x07` maps to `xpaclri` (or `nop` if the hardware doesn't support it)
883 // The save / restore is because `xpaclri` operates on x30 (LR)
884 return asm (
885 \\mov x16, x30
886 \\mov x30, x15
887 \\hint 0x07
888 \\mov x15, x30
889 \\mov x30, x16
890 : [ret] "={x15}" (-> usize),
891 : [ptr] "{x15}" (ptr),
892 : .{ .x16 = true });
893 }
894
895 return ptr;
896}
897
863fn printSourceAtAddress(gpa: Allocator, debug_info: *SelfInfo, writer: *Writer, address: usize, tty_config: tty.Config) Writer.Error!void {898fn printSourceAtAddress(gpa: Allocator, debug_info: *SelfInfo, writer: *Writer, address: usize, tty_config: tty.Config) Writer.Error!void {
864 const symbol: Symbol = debug_info.getSymbolAtAddress(gpa, address) catch |err| switch (err) {899 const symbol: Symbol = debug_info.getSymbolAtAddress(gpa, address) catch |err| switch (err) {
865 error.MissingDebugInfo,900 error.MissingDebugInfo,
lib/std/debug/SelfInfo.zig+18-36
...@@ -2,7 +2,6 @@...@@ -2,7 +2,6 @@
2//! goal of minimal code bloat and compilation speed penalty.2//! goal of minimal code bloat and compilation speed penalty.
33
4const builtin = @import("builtin");4const builtin = @import("builtin");
5const native_os = builtin.os.tag;
6const native_endian = native_arch.endian();5const native_endian = native_arch.endian();
7const native_arch = builtin.cpu.arch;6const native_arch = builtin.cpu.arch;
87
...@@ -13,6 +12,8 @@ const assert = std.debug.assert;...@@ -13,6 +12,8 @@ const assert = std.debug.assert;
13const Dwarf = std.debug.Dwarf;12const Dwarf = std.debug.Dwarf;
14const CpuContext = std.debug.cpu_context.Native;13const CpuContext = std.debug.cpu_context.Native;
1514
15const stripInstructionPtrAuthCode = std.debug.stripInstructionPtrAuthCode;
16
16const root = @import("root");17const root = @import("root");
1718
18const SelfInfo = @This();19const SelfInfo = @This();
...@@ -52,7 +53,7 @@ pub fn deinit(self: *SelfInfo, gpa: Allocator) void {...@@ -52,7 +53,7 @@ pub fn deinit(self: *SelfInfo, gpa: Allocator) void {
52 if (Module.LookupCache != void) self.lookup_cache.deinit(gpa);53 if (Module.LookupCache != void) self.lookup_cache.deinit(gpa);
53}54}
5455
55pub fn unwindFrame(self: *SelfInfo, gpa: Allocator, context: *UnwindContext) Error!usize {56pub fn unwindFrame(self: *SelfInfo, gpa: Allocator, context: *UnwindContext) Error!void {
56 comptime assert(supports_unwinding);57 comptime assert(supports_unwinding);
57 const module: Module = try .lookup(&self.lookup_cache, gpa, context.pc);58 const module: Module = try .lookup(&self.lookup_cache, gpa, context.pc);
58 const gop = try self.modules.getOrPut(gpa, module.key());59 const gop = try self.modules.getOrPut(gpa, module.key());
...@@ -115,7 +116,7 @@ pub fn getModuleNameForAddress(self: *SelfInfo, gpa: Allocator, address: usize)...@@ -115,7 +116,7 @@ pub fn getModuleNameForAddress(self: *SelfInfo, gpa: Allocator, address: usize)
115/// pub const supports_unwinding: bool;116/// pub const supports_unwinding: bool;
116/// /// Only required if `supports_unwinding == true`.117/// /// Only required if `supports_unwinding == true`.
117/// pub const UnwindContext = struct {118/// pub const UnwindContext = struct {
118/// /// A PC value inside the function of the last unwound frame.119/// /// A PC value representing the location in the last frame.
119/// pc: usize,120/// pc: usize,
120/// pub fn init(ctx: *std.debug.cpu_context.Native, gpa: Allocator) Allocator.Error!UnwindContext;121/// pub fn init(ctx: *std.debug.cpu_context.Native, gpa: Allocator) Allocator.Error!UnwindContext;
121/// pub fn deinit(uc: *UnwindContext, gpa: Allocator) void;122/// pub fn deinit(uc: *UnwindContext, gpa: Allocator) void;
...@@ -123,21 +124,22 @@ pub fn getModuleNameForAddress(self: *SelfInfo, gpa: Allocator, address: usize)...@@ -123,21 +124,22 @@ pub fn getModuleNameForAddress(self: *SelfInfo, gpa: Allocator, address: usize)
123/// /// pointer is unknown, 0 may be returned instead.124/// /// pointer is unknown, 0 may be returned instead.
124/// pub fn getFp(uc: *UnwindContext) usize;125/// pub fn getFp(uc: *UnwindContext) usize;
125/// };126/// };
126/// /// Only required if `supports_unwinding == true`. Unwinds a single stack frame and returns127/// /// Only required if `supports_unwinding == true`. Unwinds a single stack frame.
127/// /// the next return address (which may be 0 indicating end of stack).128/// /// The caller will read the new instruction poiter from the `pc` field.
129/// /// `pc = 0` indicates end of stack / no more frames.
128/// pub fn unwindFrame(130/// pub fn unwindFrame(
129/// mod: *const Module,131/// mod: *const Module,
130/// gpa: Allocator,132/// gpa: Allocator,
131/// di: *DebugInfo,133/// di: *DebugInfo,
132/// ctx: *UnwindContext,134/// ctx: *UnwindContext,
133/// ) SelfInfo.Error!usize;135/// ) SelfInfo.Error!void;
134/// ```136/// ```
135const Module: type = Module: {137const Module: type = Module: {
136 // Allow overriding the target-specific `SelfInfo` implementation by exposing `root.debug.Module`.138 // Allow overriding the target-specific `SelfInfo` implementation by exposing `root.debug.Module`.
137 if (@hasDecl(root, "debug") and @hasDecl(root.debug, "Module")) {139 if (@hasDecl(root, "debug") and @hasDecl(root.debug, "Module")) {
138 break :Module root.debug.Module;140 break :Module root.debug.Module;
139 }141 }
140 break :Module switch (native_os) {142 break :Module switch (builtin.os.tag) {
141 .linux,143 .linux,
142 .netbsd,144 .netbsd,
143 .freebsd,145 .freebsd,
...@@ -222,7 +224,7 @@ pub const DwarfUnwindContext = struct {...@@ -222,7 +224,7 @@ pub const DwarfUnwindContext = struct {
222 const register = col.register orelse return error.InvalidRegister;224 const register = col.register orelse return error.InvalidRegister;
223 // The default type is usually undefined, but can be overriden by ABI authors.225 // The default type is usually undefined, but can be overriden by ABI authors.
224 // See the doc comment on `Dwarf.Unwind.VirtualMachine.RegisterRule.default`.226 // See the doc comment on `Dwarf.Unwind.VirtualMachine.RegisterRule.default`.
225 if (builtin.cpu.arch.isAARCH64() and register >= 19 and register <= 18) {227 if (builtin.cpu.arch.isAARCH64() and register >= 19 and register <= 28) {
226 // Callee-saved registers are initialized as if they had the .same_value rule228 // Callee-saved registers are initialized as if they had the .same_value rule
227 const src = try context.cpu_context.dwarfRegisterBytes(register);229 const src = try context.cpu_context.dwarfRegisterBytes(register);
228 if (src.len != out.len) return error.RegisterSizeMismatch;230 if (src.len != out.len) return error.RegisterSizeMismatch;
...@@ -310,7 +312,7 @@ pub const DwarfUnwindContext = struct {...@@ -310,7 +312,7 @@ pub const DwarfUnwindContext = struct {
310 unwind: *const Dwarf.Unwind,312 unwind: *const Dwarf.Unwind,
311 load_offset: usize,313 load_offset: usize,
312 explicit_fde_offset: ?usize,314 explicit_fde_offset: ?usize,
313 ) Error!usize {315 ) Error!void {
314 return unwindFrameInner(context, gpa, unwind, load_offset, explicit_fde_offset) catch |err| switch (err) {316 return unwindFrameInner(context, gpa, unwind, load_offset, explicit_fde_offset) catch |err| switch (err) {
315 error.InvalidDebugInfo, error.MissingDebugInfo, error.OutOfMemory => |e| return e,317 error.InvalidDebugInfo, error.MissingDebugInfo, error.OutOfMemory => |e| return e,
316318
...@@ -358,9 +360,10 @@ pub const DwarfUnwindContext = struct {...@@ -358,9 +360,10 @@ pub const DwarfUnwindContext = struct {
358 unwind: *const Dwarf.Unwind,360 unwind: *const Dwarf.Unwind,
359 load_offset: usize,361 load_offset: usize,
360 explicit_fde_offset: ?usize,362 explicit_fde_offset: ?usize,
361 ) !usize {363 ) !void {
362 if (!supports_unwinding) return error.UnsupportedCpuArchitecture;364 comptime assert(supports_unwinding);
363 if (context.pc == 0) return 0;365
366 if (context.pc == 0) return;
364367
365 const pc_vaddr = context.pc - load_offset;368 const pc_vaddr = context.pc - load_offset;
366369
...@@ -430,12 +433,12 @@ pub const DwarfUnwindContext = struct {...@@ -430,12 +433,12 @@ pub const DwarfUnwindContext = struct {
430 }433 }
431 }434 }
432435
433 const return_address: u64 = if (has_return_address) pc: {436 const return_address: usize = if (has_return_address) pc: {
434 const raw_ptr = try regNative(&new_cpu_context, cie.return_address_register);437 const raw_ptr = try regNative(&new_cpu_context, cie.return_address_register);
435 break :pc stripInstructionPtrAuthCode(raw_ptr.*);438 break :pc stripInstructionPtrAuthCode(raw_ptr.*);
436 } else 0;439 } else 0;
437440
438 (try regNative(new_cpu_context, ip_reg_num)).* = return_address;441 (try regNative(&new_cpu_context, ip_reg_num)).* = return_address;
439442
440 // The new CPU context is complete; flush changes.443 // The new CPU context is complete; flush changes.
441 context.cpu_context = new_cpu_context;444 context.cpu_context = new_cpu_context;
...@@ -444,11 +447,9 @@ pub const DwarfUnwindContext = struct {...@@ -444,11 +447,9 @@ pub const DwarfUnwindContext = struct {
444 // *after* the call, it could (in the case of noreturn functions) actually point outside of447 // *after* the call, it could (in the case of noreturn functions) actually point outside of
445 // the caller's address range, meaning an FDE lookup would fail. We can handle this by448 // the caller's address range, meaning an FDE lookup would fail. We can handle this by
446 // subtracting 1 from `return_address` so that the next lookup is guaranteed to land inside449 // subtracting 1 from `return_address` so that the next lookup is guaranteed to land inside
447 // the `call` instruction`. The exception to this rule is signal frames, where the return450 // the `call` instruction. The exception to this rule is signal frames, where the return
448 // address is the same instruction that triggered the handler.451 // address is the same instruction that triggered the handler.
449 context.pc = if (cie.is_signal_frame) return_address else return_address -| 1;452 context.pc = if (cie.is_signal_frame) return_address else return_address -| 1;
450
451 return return_address;
452 }453 }
453 /// Since register rules are applied (usually) during a panic,454 /// Since register rules are applied (usually) during a panic,
454 /// checked addition / subtraction is used so that we can return455 /// checked addition / subtraction is used so that we can return
...@@ -459,25 +460,6 @@ pub const DwarfUnwindContext = struct {...@@ -459,25 +460,6 @@ pub const DwarfUnwindContext = struct {
459 else460 else
460 try std.math.sub(usize, base, @as(usize, @intCast(-offset)));461 try std.math.sub(usize, base, @as(usize, @intCast(-offset)));
461 }462 }
462 /// Some platforms use pointer authentication - the upper bits of instruction pointers contain a signature.
463 /// This function clears these signature bits to make the pointer usable.
464 pub inline fn stripInstructionPtrAuthCode(ptr: usize) usize {
465 if (native_arch.isAARCH64()) {
466 // `hint 0x07` maps to `xpaclri` (or `nop` if the hardware doesn't support it)
467 // The save / restore is because `xpaclri` operates on x30 (LR)
468 return asm (
469 \\mov x16, x30
470 \\mov x30, x15
471 \\hint 0x07
472 \\mov x15, x30
473 \\mov x30, x16
474 : [ret] "={x15}" (-> usize),
475 : [ptr] "{x15}" (ptr),
476 : .{ .x16 = true });
477 }
478
479 return ptr;
480 }
481463
482 pub fn regNative(ctx: *CpuContext, num: u16) error{464 pub fn regNative(ctx: *CpuContext, num: u16) error{
483 InvalidRegister,465 InvalidRegister,
lib/std/debug/SelfInfo/DarwinModule.zig+153-71
...@@ -1,6 +1,5 @@...@@ -1,6 +1,5 @@
1/// The runtime address where __TEXT is loaded.1/// The runtime address where __TEXT is loaded.
2text_base: usize,2text_base: usize,
3load_offset: usize,
4name: []const u8,3name: []const u8,
54
6pub fn key(m: *const DarwinModule) usize {5pub fn key(m: *const DarwinModule) usize {
...@@ -12,38 +11,14 @@ pub const LookupCache = void;...@@ -12,38 +11,14 @@ pub const LookupCache = void;
12pub fn lookup(cache: *LookupCache, gpa: Allocator, address: usize) Error!DarwinModule {11pub fn lookup(cache: *LookupCache, gpa: Allocator, address: usize) Error!DarwinModule {
13 _ = cache;12 _ = cache;
14 _ = gpa;13 _ = gpa;
15 const image_count = std.c._dyld_image_count();14 var info: std.c.dl_info = undefined;
16 for (0..image_count) |image_idx| {15 switch (std.c.dladdr(@ptrFromInt(address), &info)) {
17 const header = std.c._dyld_get_image_header(@intCast(image_idx)) orelse continue;16 0 => return error.MissingDebugInfo,
18 const text_base = @intFromPtr(header);17 else => return .{
19 if (address < text_base) continue;18 .name = std.mem.span(info.fname),
20 const load_offset = std.c._dyld_get_image_vmaddr_slide(@intCast(image_idx));19 .text_base = @intFromPtr(info.fbase),
2120 },
22 // Find the __TEXT segment
23 var it: macho.LoadCommandIterator = .{
24 .ncmds = header.ncmds,
25 .buffer = @as([*]u8, @ptrCast(header))[@sizeOf(macho.mach_header_64)..][0..header.sizeofcmds],
26 };
27 const text_segment_cmd = while (it.next()) |load_cmd| {
28 if (load_cmd.cmd() != .SEGMENT_64) continue;
29 const segment_cmd = load_cmd.cast(macho.segment_command_64).?;
30 if (!mem.eql(u8, segment_cmd.segName(), "__TEXT")) continue;
31 break segment_cmd;
32 } else continue;
33
34 const seg_start = load_offset + text_segment_cmd.vmaddr;
35 assert(seg_start == text_base);
36 const seg_end = seg_start + text_segment_cmd.vmsize;
37 if (address < seg_start or address >= seg_end) continue;
38
39 // We've found the matching __TEXT segment. This is the image we need.
40 return .{
41 .text_base = text_base,
42 .load_offset = load_offset,
43 .name = mem.span(std.c._dyld_get_image_name(@intCast(image_idx))),
44 };
45 }21 }
46 return error.MissingDebugInfo;
47}22}
48fn loadUnwindInfo(module: *const DarwinModule) DebugInfo.Unwind {23fn loadUnwindInfo(module: *const DarwinModule) DebugInfo.Unwind {
49 const header: *std.macho.mach_header = @ptrFromInt(module.text_base);24 const header: *std.macho.mach_header = @ptrFromInt(module.text_base);
...@@ -52,56 +27,115 @@ fn loadUnwindInfo(module: *const DarwinModule) DebugInfo.Unwind {...@@ -52,56 +27,115 @@ fn loadUnwindInfo(module: *const DarwinModule) DebugInfo.Unwind {
52 .ncmds = header.ncmds,27 .ncmds = header.ncmds,
53 .buffer = @as([*]u8, @ptrCast(header))[@sizeOf(macho.mach_header_64)..][0..header.sizeofcmds],28 .buffer = @as([*]u8, @ptrCast(header))[@sizeOf(macho.mach_header_64)..][0..header.sizeofcmds],
54 };29 };
55 const sections = while (it.next()) |load_cmd| {30 const sections, const text_vmaddr = while (it.next()) |load_cmd| {
56 if (load_cmd.cmd() != .SEGMENT_64) continue;31 if (load_cmd.cmd() != .SEGMENT_64) continue;
57 const segment_cmd = load_cmd.cast(macho.segment_command_64).?;32 const segment_cmd = load_cmd.cast(macho.segment_command_64).?;
58 if (!mem.eql(u8, segment_cmd.segName(), "__TEXT")) continue;33 if (!mem.eql(u8, segment_cmd.segName(), "__TEXT")) continue;
59 break load_cmd.getSections();34 break .{ load_cmd.getSections(), segment_cmd.vmaddr };
60 } else unreachable;35 } else unreachable;
6136
37 const vmaddr_slide = module.text_base - text_vmaddr;
38
62 var unwind_info: ?[]const u8 = null;39 var unwind_info: ?[]const u8 = null;
63 var eh_frame: ?[]const u8 = null;40 var eh_frame: ?[]const u8 = null;
64 for (sections) |sect| {41 for (sections) |sect| {
65 if (mem.eql(u8, sect.sectName(), "__unwind_info")) {42 if (mem.eql(u8, sect.sectName(), "__unwind_info")) {
66 const sect_ptr: [*]u8 = @ptrFromInt(@as(usize, @intCast(module.load_offset + sect.addr)));43 const sect_ptr: [*]u8 = @ptrFromInt(@as(usize, @intCast(vmaddr_slide + sect.addr)));
67 unwind_info = sect_ptr[0..@intCast(sect.size)];44 unwind_info = sect_ptr[0..@intCast(sect.size)];
68 } else if (mem.eql(u8, sect.sectName(), "__eh_frame")) {45 } else if (mem.eql(u8, sect.sectName(), "__eh_frame")) {
69 const sect_ptr: [*]u8 = @ptrFromInt(@as(usize, @intCast(module.load_offset + sect.addr)));46 const sect_ptr: [*]u8 = @ptrFromInt(@as(usize, @intCast(vmaddr_slide + sect.addr)));
70 eh_frame = sect_ptr[0..@intCast(sect.size)];47 eh_frame = sect_ptr[0..@intCast(sect.size)];
71 }48 }
72 }49 }
73 return .{50 return .{
51 .vmaddr_slide = vmaddr_slide,
74 .unwind_info = unwind_info,52 .unwind_info = unwind_info,
75 .eh_frame = eh_frame,53 .eh_frame = eh_frame,
76 };54 };
77}55}
78fn loadMachO(module: *const DarwinModule, gpa: Allocator) !DebugInfo.LoadedMachO {56fn loadMachO(module: *const DarwinModule, gpa: Allocator) !DebugInfo.LoadedMachO {
79 const mapped_mem = try mapDebugInfoFile(module.name);57 const all_mapped_memory = try mapDebugInfoFile(module.name);
80 errdefer posix.munmap(mapped_mem);58 errdefer posix.munmap(all_mapped_memory);
59
60 // In most cases, the file we just mapped is a Mach-O binary. However, it could be a "universal
61 // binary": a simple file format which contains Mach-O binaries for multiple targets. For
62 // instance, `/usr/lib/dyld` is currently distributed as a universal binary containing images
63 // for both ARM64 Macs and x86_64 Macs.
64 if (all_mapped_memory.len < 4) return error.InvalidDebugInfo;
65 const magic = @as(*const u32, @ptrCast(all_mapped_memory.ptr)).*;
66 // The contents of a Mach-O file, which may or may not be the whole of `all_mapped_memory`.
67 const mapped_macho = switch (magic) {
68 macho.MH_MAGIC_64 => all_mapped_memory,
69
70 macho.FAT_CIGAM => mapped_macho: {
71 // This is the universal binary format (aka a "fat binary"). Annoyingly, the whole thing
72 // is big-endian, so we'll be swapping some bytes.
73 if (all_mapped_memory.len < @sizeOf(macho.fat_header)) return error.InvalidDebugInfo;
74 const hdr: *const macho.fat_header = @ptrCast(all_mapped_memory.ptr);
75 const archs_ptr: [*]const macho.fat_arch = @ptrCast(all_mapped_memory.ptr + @sizeOf(macho.fat_header));
76 const archs: []const macho.fat_arch = archs_ptr[0..@byteSwap(hdr.nfat_arch)];
77 const native_cpu_type = switch (builtin.cpu.arch) {
78 .x86_64 => macho.CPU_TYPE_X86_64,
79 .aarch64 => macho.CPU_TYPE_ARM64,
80 else => comptime unreachable,
81 };
82 for (archs) |*arch| {
83 if (@byteSwap(arch.cputype) != native_cpu_type) continue;
84 const offset = @byteSwap(arch.offset);
85 const size = @byteSwap(arch.size);
86 break :mapped_macho all_mapped_memory[offset..][0..size];
87 }
88 // Our native architecture was not present in the fat binary.
89 return error.MissingDebugInfo;
90 },
91
92 // Even on modern 64-bit targets, this format doesn't seem to be too extensively used. It
93 // will be fairly easy to add support here if necessary; it's very similar to above.
94 macho.FAT_CIGAM_64 => return error.UnsupportedDebugInfo,
8195
82 const hdr: *const macho.mach_header_64 = @ptrCast(@alignCast(mapped_mem.ptr));96 else => return error.InvalidDebugInfo,
97 };
98
99 const hdr: *const macho.mach_header_64 = @ptrCast(@alignCast(mapped_macho.ptr));
83 if (hdr.magic != macho.MH_MAGIC_64)100 if (hdr.magic != macho.MH_MAGIC_64)
84 return error.InvalidDebugInfo;101 return error.InvalidDebugInfo;
85102
86 const symtab: macho.symtab_command = symtab: {103 const symtab: macho.symtab_command, const text_vmaddr: u64 = lc_iter: {
87 var it: macho.LoadCommandIterator = .{104 var it: macho.LoadCommandIterator = .{
88 .ncmds = hdr.ncmds,105 .ncmds = hdr.ncmds,
89 .buffer = mapped_mem[@sizeOf(macho.mach_header_64)..][0..hdr.sizeofcmds],106 .buffer = mapped_macho[@sizeOf(macho.mach_header_64)..][0..hdr.sizeofcmds],
90 };107 };
108 var symtab: ?macho.symtab_command = null;
109 var text_vmaddr: ?u64 = null;
91 while (it.next()) |cmd| switch (cmd.cmd()) {110 while (it.next()) |cmd| switch (cmd.cmd()) {
92 .SYMTAB => break :symtab cmd.cast(macho.symtab_command) orelse return error.InvalidDebugInfo,111 .SYMTAB => symtab = cmd.cast(macho.symtab_command) orelse return error.InvalidDebugInfo,
112 .SEGMENT_64 => if (cmd.cast(macho.segment_command_64)) |seg_cmd| {
113 if (!mem.eql(u8, seg_cmd.segName(), "__TEXT")) continue;
114 text_vmaddr = seg_cmd.vmaddr;
115 },
93 else => {},116 else => {},
94 };117 };
95 return error.MissingDebugInfo;118 break :lc_iter .{
119 symtab orelse return error.MissingDebugInfo,
120 text_vmaddr orelse return error.MissingDebugInfo,
121 };
96 };122 };
97123
98 const syms_ptr: [*]align(1) const macho.nlist_64 = @ptrCast(mapped_mem[symtab.symoff..]);124 const syms_ptr: [*]align(1) const macho.nlist_64 = @ptrCast(mapped_macho[symtab.symoff..]);
99 const syms = syms_ptr[0..symtab.nsyms];125 const syms = syms_ptr[0..symtab.nsyms];
100 const strings = mapped_mem[symtab.stroff..][0 .. symtab.strsize - 1 :0];126 const strings = mapped_macho[symtab.stroff..][0 .. symtab.strsize - 1];
101127
102 var symbols: std.ArrayList(MachoSymbol) = try .initCapacity(gpa, syms.len);128 var symbols: std.ArrayList(MachoSymbol) = try .initCapacity(gpa, syms.len);
103 defer symbols.deinit(gpa);129 defer symbols.deinit(gpa);
104130
131 // This map is temporary; it is used only to detect duplicates here. This is
132 // necessary because we prefer to use STAB ("symbolic debugging table") symbols,
133 // but they might not be present, so we track normal symbols too.
134 // Indices match 1-1 with those of `symbols`.
135 var symbol_names: std.StringArrayHashMapUnmanaged(void) = .empty;
136 defer symbol_names.deinit(gpa);
137 try symbol_names.ensureUnusedCapacity(gpa, syms.len);
138
105 var ofile: u32 = undefined;139 var ofile: u32 = undefined;
106 var last_sym: MachoSymbol = undefined;140 var last_sym: MachoSymbol = undefined;
107 var state: enum {141 var state: enum {
...@@ -115,7 +149,25 @@ fn loadMachO(module: *const DarwinModule, gpa: Allocator) !DebugInfo.LoadedMachO...@@ -115,7 +149,25 @@ fn loadMachO(module: *const DarwinModule, gpa: Allocator) !DebugInfo.LoadedMachO
115 } = .init;149 } = .init;
116150
117 for (syms) |*sym| {151 for (syms) |*sym| {
118 if (sym.n_type.bits.is_stab == 0) continue;152 if (sym.n_type.bits.is_stab == 0) {
153 if (sym.n_strx == 0) continue;
154 switch (sym.n_type.bits.type) {
155 .undf, .pbud, .indr, .abs, _ => continue,
156 .sect => {
157 const name = std.mem.sliceTo(strings[sym.n_strx..], 0);
158 const gop = symbol_names.getOrPutAssumeCapacity(name);
159 if (!gop.found_existing) {
160 assert(gop.index == symbols.items.len);
161 symbols.appendAssumeCapacity(.{
162 .strx = sym.n_strx,
163 .addr = sym.n_value,
164 .ofile = MachoSymbol.unknown_ofile,
165 });
166 }
167 },
168 }
169 continue;
170 }
119171
120 // TODO handle globals N_GSYM, and statics N_STSYM172 // TODO handle globals N_GSYM, and statics N_STSYM
121 switch (sym.n_type.stab) {173 switch (sym.n_type.stab) {
...@@ -132,7 +184,6 @@ fn loadMachO(module: *const DarwinModule, gpa: Allocator) !DebugInfo.LoadedMachO...@@ -132,7 +184,6 @@ fn loadMachO(module: *const DarwinModule, gpa: Allocator) !DebugInfo.LoadedMachO
132 last_sym = .{184 last_sym = .{
133 .strx = 0,185 .strx = 0,
134 .addr = sym.n_value,186 .addr = sym.n_value,
135 .size = 0,
136 .ofile = ofile,187 .ofile = ofile,
137 };188 };
138 },189 },
...@@ -145,14 +196,22 @@ fn loadMachO(module: *const DarwinModule, gpa: Allocator) !DebugInfo.LoadedMachO...@@ -145,14 +196,22 @@ fn loadMachO(module: *const DarwinModule, gpa: Allocator) !DebugInfo.LoadedMachO
145 },196 },
146 .fun_strx => {197 .fun_strx => {
147 state = .fun_size;198 state = .fun_size;
148 last_sym.size = @intCast(sym.n_value);
149 },199 },
150 else => return error.InvalidDebugInfo,200 else => return error.InvalidDebugInfo,
151 },201 },
152 .ensym => switch (state) {202 .ensym => switch (state) {
153 .fun_size => {203 .fun_size => {
154 state = .ensym;204 state = .ensym;
155 symbols.appendAssumeCapacity(last_sym);205 if (last_sym.strx != 0) {
206 const name = std.mem.sliceTo(strings[sym.n_strx..], 0);
207 const gop = symbol_names.getOrPutAssumeCapacity(name);
208 if (!gop.found_existing) {
209 assert(gop.index == symbols.items.len);
210 symbols.appendAssumeCapacity(last_sym);
211 } else {
212 symbols.items[gop.index] = last_sym;
213 }
214 }
156 },215 },
157 else => return error.InvalidDebugInfo,216 else => return error.InvalidDebugInfo,
158 },217 },
...@@ -168,9 +227,12 @@ fn loadMachO(module: *const DarwinModule, gpa: Allocator) !DebugInfo.LoadedMachO...@@ -168,9 +227,12 @@ fn loadMachO(module: *const DarwinModule, gpa: Allocator) !DebugInfo.LoadedMachO
168 }227 }
169228
170 switch (state) {229 switch (state) {
171 .init => return error.MissingDebugInfo,230 .init => {
231 // Missing STAB symtab entries is still okay, unless there were also no normal symbols.
232 if (symbols.items.len == 0) return error.MissingDebugInfo;
233 },
172 .oso_close => {},234 .oso_close => {},
173 else => return error.InvalidDebugInfo,235 else => return error.InvalidDebugInfo, // corrupted STAB entries in symtab
174 }236 }
175237
176 const symbols_slice = try symbols.toOwnedSlice(gpa);238 const symbols_slice = try symbols.toOwnedSlice(gpa);
...@@ -182,10 +244,11 @@ fn loadMachO(module: *const DarwinModule, gpa: Allocator) !DebugInfo.LoadedMachO...@@ -182,10 +244,11 @@ fn loadMachO(module: *const DarwinModule, gpa: Allocator) !DebugInfo.LoadedMachO
182 mem.sort(MachoSymbol, symbols_slice, {}, MachoSymbol.addressLessThan);244 mem.sort(MachoSymbol, symbols_slice, {}, MachoSymbol.addressLessThan);
183245
184 return .{246 return .{
185 .mapped_memory = mapped_mem,247 .mapped_memory = all_mapped_memory,
186 .symbols = symbols_slice,248 .symbols = symbols_slice,
187 .strings = strings,249 .strings = strings,
188 .ofiles = .empty,250 .ofiles = .empty,
251 .vaddr_offset = module.text_base - text_vmaddr,
189 };252 };
190}253}
191pub fn getSymbolAtAddress(module: *const DarwinModule, gpa: Allocator, di: *DebugInfo, address: usize) Error!std.debug.Symbol {254pub fn getSymbolAtAddress(module: *const DarwinModule, gpa: Allocator, di: *DebugInfo, address: usize) Error!std.debug.Symbol {
...@@ -195,7 +258,7 @@ pub fn getSymbolAtAddress(module: *const DarwinModule, gpa: Allocator, di: *Debu...@@ -195,7 +258,7 @@ pub fn getSymbolAtAddress(module: *const DarwinModule, gpa: Allocator, di: *Debu
195 };258 };
196 const loaded_macho = &di.loaded_macho.?;259 const loaded_macho = &di.loaded_macho.?;
197260
198 const vaddr = address - module.load_offset;261 const vaddr = address - loaded_macho.vaddr_offset;
199 const symbol = MachoSymbol.find(loaded_macho.symbols, vaddr) orelse return .unknown;262 const symbol = MachoSymbol.find(loaded_macho.symbols, vaddr) orelse return .unknown;
200263
201 // offset of `address` from start of `symbol`264 // offset of `address` from start of `symbol`
...@@ -212,6 +275,11 @@ pub fn getSymbolAtAddress(module: *const DarwinModule, gpa: Allocator, di: *Debu...@@ -212,6 +275,11 @@ pub fn getSymbolAtAddress(module: *const DarwinModule, gpa: Allocator, di: *Debu
212 .source_location = null,275 .source_location = null,
213 };276 };
214277
278 if (symbol.ofile == MachoSymbol.unknown_ofile) {
279 // We don't have STAB info, so can't track down the object file; all we can do is the symbol name.
280 return sym_only_result;
281 }
282
215 const o_file: *DebugInfo.OFile = of: {283 const o_file: *DebugInfo.OFile = of: {
216 const gop = try loaded_macho.ofiles.getOrPut(gpa, symbol.ofile);284 const gop = try loaded_macho.ofiles.getOrPut(gpa, symbol.ofile);
217 if (!gop.found_existing) {285 if (!gop.found_existing) {
...@@ -233,7 +301,7 @@ pub fn getSymbolAtAddress(module: *const DarwinModule, gpa: Allocator, di: *Debu...@@ -233,7 +301,7 @@ pub fn getSymbolAtAddress(module: *const DarwinModule, gpa: Allocator, di: *Debu
233 const compile_unit = o_file.dwarf.findCompileUnit(native_endian, symbol_ofile_vaddr) catch return sym_only_result;301 const compile_unit = o_file.dwarf.findCompileUnit(native_endian, symbol_ofile_vaddr) catch return sym_only_result;
234302
235 return .{303 return .{
236 .name = o_file.dwarf.getSymbolName(symbol_ofile_vaddr) orelse stab_symbol,304 .name = o_file.dwarf.getSymbolName(symbol_ofile_vaddr + address_symbol_offset) orelse stab_symbol,
237 .compile_unit_name = compile_unit.die.getAttrString(305 .compile_unit_name = compile_unit.die.getAttrString(
238 &o_file.dwarf,306 &o_file.dwarf,
239 native_endian,307 native_endian,
...@@ -256,7 +324,7 @@ pub const UnwindContext = std.debug.SelfInfo.DwarfUnwindContext;...@@ -256,7 +324,7 @@ pub const UnwindContext = std.debug.SelfInfo.DwarfUnwindContext;
256/// Unwind a frame using MachO compact unwind info (from __unwind_info).324/// Unwind a frame using MachO compact unwind info (from __unwind_info).
257/// If the compact encoding can't encode a way to unwind a frame, it will325/// If the compact encoding can't encode a way to unwind a frame, it will
258/// defer unwinding to DWARF, in which case `.eh_frame` will be used if available.326/// defer unwinding to DWARF, in which case `.eh_frame` will be used if available.
259pub fn unwindFrame(module: *const DarwinModule, gpa: Allocator, di: *DebugInfo, context: *UnwindContext) Error!usize {327pub fn unwindFrame(module: *const DarwinModule, gpa: Allocator, di: *DebugInfo, context: *UnwindContext) Error!void {
260 return unwindFrameInner(module, gpa, di, context) catch |err| switch (err) {328 return unwindFrameInner(module, gpa, di, context) catch |err| switch (err) {
261 error.InvalidDebugInfo,329 error.InvalidDebugInfo,
262 error.MissingDebugInfo,330 error.MissingDebugInfo,
...@@ -272,7 +340,7 @@ pub fn unwindFrame(module: *const DarwinModule, gpa: Allocator, di: *DebugInfo,...@@ -272,7 +340,7 @@ pub fn unwindFrame(module: *const DarwinModule, gpa: Allocator, di: *DebugInfo,
272 => return error.InvalidDebugInfo,340 => return error.InvalidDebugInfo,
273 };341 };
274}342}
275fn unwindFrameInner(module: *const DarwinModule, gpa: Allocator, di: *DebugInfo, context: *UnwindContext) !usize {343fn unwindFrameInner(module: *const DarwinModule, gpa: Allocator, di: *DebugInfo, context: *UnwindContext) !void {
276 if (di.unwind == null) di.unwind = module.loadUnwindInfo();344 if (di.unwind == null) di.unwind = module.loadUnwindInfo();
277 const unwind = &di.unwind.?;345 const unwind = &di.unwind.?;
278346
...@@ -500,11 +568,11 @@ fn unwindFrameInner(module: *const DarwinModule, gpa: Allocator, di: *DebugInfo,...@@ -500,11 +568,11 @@ fn unwindFrameInner(module: *const DarwinModule, gpa: Allocator, di: *DebugInfo,
500 },568 },
501 .DWARF => {569 .DWARF => {
502 const eh_frame = unwind.eh_frame orelse return error.MissingDebugInfo;570 const eh_frame = unwind.eh_frame orelse return error.MissingDebugInfo;
503 const eh_frame_vaddr = @intFromPtr(eh_frame.ptr) - module.load_offset;571 const eh_frame_vaddr = @intFromPtr(eh_frame.ptr) - unwind.vmaddr_slide;
504 return context.unwindFrame(572 return context.unwindFrame(
505 gpa,573 gpa,
506 &.initSection(.eh_frame, eh_frame_vaddr, eh_frame),574 &.initSection(.eh_frame, eh_frame_vaddr, eh_frame),
507 module.load_offset,575 unwind.vmaddr_slide,
508 @intCast(encoding.value.x86_64.dwarf),576 @intCast(encoding.value.x86_64.dwarf),
509 );577 );
510 },578 },
...@@ -520,11 +588,11 @@ fn unwindFrameInner(module: *const DarwinModule, gpa: Allocator, di: *DebugInfo,...@@ -520,11 +588,11 @@ fn unwindFrameInner(module: *const DarwinModule, gpa: Allocator, di: *DebugInfo,
520 },588 },
521 .DWARF => {589 .DWARF => {
522 const eh_frame = unwind.eh_frame orelse return error.MissingDebugInfo;590 const eh_frame = unwind.eh_frame orelse return error.MissingDebugInfo;
523 const eh_frame_vaddr = @intFromPtr(eh_frame.ptr) - module.load_offset;591 const eh_frame_vaddr = @intFromPtr(eh_frame.ptr) - unwind.vmaddr_slide;
524 return context.unwindFrame(592 return context.unwindFrame(
525 gpa,593 gpa,
526 &.initSection(.eh_frame, eh_frame_vaddr, eh_frame),594 &.initSection(.eh_frame, eh_frame_vaddr, eh_frame),
527 module.load_offset,595 unwind.vmaddr_slide,
528 @intCast(encoding.value.x86_64.dwarf),596 @intCast(encoding.value.x86_64.dwarf),
529 );597 );
530 },598 },
...@@ -572,9 +640,7 @@ fn unwindFrameInner(module: *const DarwinModule, gpa: Allocator, di: *DebugInfo,...@@ -572,9 +640,7 @@ fn unwindFrameInner(module: *const DarwinModule, gpa: Allocator, di: *DebugInfo,
572 else => comptime unreachable, // unimplemented640 else => comptime unreachable, // unimplemented
573 };641 };
574642
575 context.pc = UnwindContext.stripInstructionPtrAuthCode(new_ip);643 context.pc = std.debug.stripInstructionPtrAuthCode(new_ip) -| 1;
576 if (context.pc > 0) context.pc -= 1;
577 return new_ip;
578}644}
579pub const DebugInfo = struct {645pub const DebugInfo = struct {
580 unwind: ?Unwind,646 unwind: ?Unwind,
...@@ -590,6 +656,7 @@ pub const DebugInfo = struct {...@@ -590,6 +656,7 @@ pub const DebugInfo = struct {
590 for (loaded_macho.ofiles.values()) |*ofile| {656 for (loaded_macho.ofiles.values()) |*ofile| {
591 ofile.dwarf.deinit(gpa);657 ofile.dwarf.deinit(gpa);
592 ofile.symbols_by_name.deinit(gpa);658 ofile.symbols_by_name.deinit(gpa);
659 posix.munmap(ofile.mapped_memory);
593 }660 }
594 loaded_macho.ofiles.deinit(gpa);661 loaded_macho.ofiles.deinit(gpa);
595 gpa.free(loaded_macho.symbols);662 gpa.free(loaded_macho.symbols);
...@@ -598,6 +665,9 @@ pub const DebugInfo = struct {...@@ -598,6 +665,9 @@ pub const DebugInfo = struct {
598 }665 }
599666
600 const Unwind = struct {667 const Unwind = struct {
668 /// The slide applied to the following sections. So, `unwind_info.ptr` is this many bytes
669 /// higher than the vmaddr of `__unwind_info`, and likewise for `__eh_frame`.
670 vmaddr_slide: u64,
601 // Backed by the in-memory sections mapped by the loader671 // Backed by the in-memory sections mapped by the loader
602 unwind_info: ?[]const u8,672 unwind_info: ?[]const u8,
603 eh_frame: ?[]const u8,673 eh_frame: ?[]const u8,
...@@ -606,21 +676,31 @@ pub const DebugInfo = struct {...@@ -606,21 +676,31 @@ pub const DebugInfo = struct {
606 const LoadedMachO = struct {676 const LoadedMachO = struct {
607 mapped_memory: []align(std.heap.page_size_min) const u8,677 mapped_memory: []align(std.heap.page_size_min) const u8,
608 symbols: []const MachoSymbol,678 symbols: []const MachoSymbol,
609 strings: [:0]const u8,679 strings: []const u8,
610 /// Key is index into `strings` of the file path.680 /// Key is index into `strings` of the file path.
611 ofiles: std.AutoArrayHashMapUnmanaged(u32, OFile),681 ofiles: std.AutoArrayHashMapUnmanaged(u32, OFile),
682 /// This is not necessarily the same as the vmaddr_slide that dyld would report. This is
683 /// because the segments in the file on disk might differ from the ones in memory. Normally
684 /// we wouldn't necessarily expect that to work, but /usr/lib/dyld is incredibly annoying:
685 /// it exists on disk (necessarily, because the kernel needs to load it!), but is also in
686 /// the dyld cache (dyld actually restart itself from cache after loading it), and the two
687 /// versions have (very) different segment base addresses. It's sort of like a large slide
688 /// has been applied to all addresses in memory. For an optimal experience, we consider the
689 /// on-disk vmaddr instead of the in-memory one.
690 vaddr_offset: usize,
612 };691 };
613692
614 const OFile = struct {693 const OFile = struct {
694 mapped_memory: []align(std.heap.page_size_min) const u8,
615 dwarf: Dwarf,695 dwarf: Dwarf,
616 strtab: [:0]const u8,696 strtab: []const u8,
617 symtab: []align(1) const macho.nlist_64,697 symtab: []align(1) const macho.nlist_64,
618 /// All named symbols in `symtab`. Stored `u32` key is the index into `symtab`. Accessed698 /// All named symbols in `symtab`. Stored `u32` key is the index into `symtab`. Accessed
619 /// through `SymbolAdapter`, so that the symbol name is used as the logical key.699 /// through `SymbolAdapter`, so that the symbol name is used as the logical key.
620 symbols_by_name: std.ArrayHashMapUnmanaged(u32, void, void, true),700 symbols_by_name: std.ArrayHashMapUnmanaged(u32, void, void, true),
621701
622 const SymbolAdapter = struct {702 const SymbolAdapter = struct {
623 strtab: [:0]const u8,703 strtab: []const u8,
624 symtab: []align(1) const macho.nlist_64,704 symtab: []align(1) const macho.nlist_64,
625 pub fn hash(ctx: SymbolAdapter, sym_name: []const u8) u32 {705 pub fn hash(ctx: SymbolAdapter, sym_name: []const u8) u32 {
626 _ = ctx;706 _ = ctx;
...@@ -663,7 +743,7 @@ pub const DebugInfo = struct {...@@ -663,7 +743,7 @@ pub const DebugInfo = struct {
663743
664 if (mapped_mem.len < symtab_cmd.stroff + symtab_cmd.strsize) return error.InvalidDebugInfo;744 if (mapped_mem.len < symtab_cmd.stroff + symtab_cmd.strsize) return error.InvalidDebugInfo;
665 if (mapped_mem[symtab_cmd.stroff + symtab_cmd.strsize - 1] != 0) return error.InvalidDebugInfo;745 if (mapped_mem[symtab_cmd.stroff + symtab_cmd.strsize - 1] != 0) return error.InvalidDebugInfo;
666 const strtab = mapped_mem[symtab_cmd.stroff..][0 .. symtab_cmd.strsize - 1 :0];746 const strtab = mapped_mem[symtab_cmd.stroff..][0 .. symtab_cmd.strsize - 1];
667747
668 const n_sym_bytes = symtab_cmd.nsyms * @sizeOf(macho.nlist_64);748 const n_sym_bytes = symtab_cmd.nsyms * @sizeOf(macho.nlist_64);
669 if (mapped_mem.len < symtab_cmd.symoff + n_sym_bytes) return error.InvalidDebugInfo;749 if (mapped_mem.len < symtab_cmd.symoff + n_sym_bytes) return error.InvalidDebugInfo;
...@@ -717,6 +797,7 @@ pub const DebugInfo = struct {...@@ -717,6 +797,7 @@ pub const DebugInfo = struct {
717 try dwarf.open(gpa, native_endian);797 try dwarf.open(gpa, native_endian);
718798
719 return .{799 return .{
800 .mapped_memory = mapped_mem,
720 .dwarf = dwarf,801 .dwarf = dwarf,
721 .strtab = strtab,802 .strtab = strtab,
722 .symtab = symtab,803 .symtab = symtab,
...@@ -728,8 +809,9 @@ pub const DebugInfo = struct {...@@ -728,8 +809,9 @@ pub const DebugInfo = struct {
728const MachoSymbol = struct {809const MachoSymbol = struct {
729 strx: u32,810 strx: u32,
730 addr: u64,811 addr: u64,
731 size: u32,812 /// Value may be `unknown_ofile`.
732 ofile: u32,813 ofile: u32,
814 const unknown_ofile = std.math.maxInt(u32);
733 fn addressLessThan(context: void, lhs: MachoSymbol, rhs: MachoSymbol) bool {815 fn addressLessThan(context: void, lhs: MachoSymbol, rhs: MachoSymbol) bool {
734 _ = context;816 _ = context;
735 return lhs.addr < rhs.addr;817 return lhs.addr < rhs.addr;
...@@ -754,9 +836,9 @@ const MachoSymbol = struct {...@@ -754,9 +836,9 @@ const MachoSymbol = struct {
754836
755 test find {837 test find {
756 const symbols: []const MachoSymbol = &.{838 const symbols: []const MachoSymbol = &.{
757 .{ .addr = 100, .strx = undefined, .size = undefined, .ofile = undefined },839 .{ .addr = 100, .strx = undefined, .ofile = undefined },
758 .{ .addr = 200, .strx = undefined, .size = undefined, .ofile = undefined },840 .{ .addr = 200, .strx = undefined, .ofile = undefined },
759 .{ .addr = 300, .strx = undefined, .size = undefined, .ofile = undefined },841 .{ .addr = 300, .strx = undefined, .ofile = undefined },
760 };842 };
761843
762 try testing.expectEqual(null, find(symbols, 0));844 try testing.expectEqual(null, find(symbols, 0));
lib/std/debug/SelfInfo/ElfModule.zig+1-1
...@@ -230,7 +230,7 @@ fn loadUnwindInfo(module: *const ElfModule, gpa: Allocator, di: *DebugInfo) Erro...@@ -230,7 +230,7 @@ fn loadUnwindInfo(module: *const ElfModule, gpa: Allocator, di: *DebugInfo) Erro
230 else => unreachable,230 else => unreachable,
231 }231 }
232}232}
233pub fn unwindFrame(module: *const ElfModule, gpa: Allocator, di: *DebugInfo, context: *UnwindContext) Error!usize {233pub fn unwindFrame(module: *const ElfModule, gpa: Allocator, di: *DebugInfo, context: *UnwindContext) Error!void {
234 if (di.unwind[0] == null) try module.loadUnwindInfo(gpa, di);234 if (di.unwind[0] == null) try module.loadUnwindInfo(gpa, di);
235 std.debug.assert(di.unwind[0] != null);235 std.debug.assert(di.unwind[0] != null);
236 for (&di.unwind) |*opt_unwind| {236 for (&di.unwind) |*opt_unwind| {
lib/std/debug/SelfInfo/WindowsModule.zig+32-4
...@@ -332,6 +332,34 @@ pub const UnwindContext = struct {...@@ -332,6 +332,34 @@ pub const UnwindContext = struct {
332 .Wcr = @splat(0),332 .Wcr = @splat(0),
333 .Wvr = @splat(0),333 .Wvr = @splat(0),
334 },334 },
335 .thumb => .{
336 .ContextFlags = 0,
337 .R0 = ctx.r[0],
338 .R1 = ctx.r[1],
339 .R2 = ctx.r[2],
340 .R3 = ctx.r[3],
341 .R4 = ctx.r[4],
342 .R5 = ctx.r[5],
343 .R6 = ctx.r[6],
344 .R7 = ctx.r[7],
345 .R8 = ctx.r[8],
346 .R9 = ctx.r[9],
347 .R10 = ctx.r[10],
348 .R11 = ctx.r[11],
349 .R12 = ctx.r[12],
350 .Sp = ctx.r[13],
351 .Lr = ctx.r[14],
352 .Pc = ctx.r[15],
353 .Cpsr = 0,
354 .Fpcsr = 0,
355 .Padding = 0,
356 .DUMMYUNIONNAME = .{ .S = @splat(0) },
357 .Bvr = @splat(0),
358 .Bcr = @splat(0),
359 .Wvr = @splat(0),
360 .Wcr = @splat(0),
361 .Padding2 = @splat(0),
362 },
335 else => comptime unreachable,363 else => comptime unreachable,
336 },364 },
337 .history_table = std.mem.zeroes(windows.UNWIND_HISTORY_TABLE),365 .history_table = std.mem.zeroes(windows.UNWIND_HISTORY_TABLE),
...@@ -345,7 +373,7 @@ pub const UnwindContext = struct {...@@ -345,7 +373,7 @@ pub const UnwindContext = struct {
345 return ctx.cur.getRegs().bp;373 return ctx.cur.getRegs().bp;
346 }374 }
347};375};
348pub fn unwindFrame(module: *const WindowsModule, gpa: Allocator, di: *DebugInfo, context: *UnwindContext) !usize {376pub fn unwindFrame(module: *const WindowsModule, gpa: Allocator, di: *DebugInfo, context: *UnwindContext) !void {
349 _ = module;377 _ = module;
350 _ = gpa;378 _ = gpa;
351 _ = di;379 _ = di;
...@@ -374,10 +402,10 @@ pub fn unwindFrame(module: *const WindowsModule, gpa: Allocator, di: *DebugInfo,...@@ -374,10 +402,10 @@ pub fn unwindFrame(module: *const WindowsModule, gpa: Allocator, di: *DebugInfo,
374 const next_regs = context.cur.getRegs();402 const next_regs = context.cur.getRegs();
375 const tib = &windows.teb().NtTib;403 const tib = &windows.teb().NtTib;
376 if (next_regs.sp < @intFromPtr(tib.StackLimit) or next_regs.sp > @intFromPtr(tib.StackBase)) {404 if (next_regs.sp < @intFromPtr(tib.StackLimit) or next_regs.sp > @intFromPtr(tib.StackBase)) {
377 return 0;405 context.pc = 0;
406 } else {
407 context.pc = next_regs.ip -| 1;
378 }408 }
379 context.pc = next_regs.ip -| 1;
380 return next_regs.ip;
381}409}
382410
383const WindowsModule = @This();411const WindowsModule = @This();
lib/std/debug/cpu_context.zig+6
...@@ -214,6 +214,12 @@ pub fn fromWindowsContext(ctx: *const std.os.windows.CONTEXT) Native {...@@ -214,6 +214,12 @@ pub fn fromWindowsContext(ctx: *const std.os.windows.CONTEXT) Native {
214 .sp = ctx.Sp,214 .sp = ctx.Sp,
215 .pc = ctx.Pc,215 .pc = ctx.Pc,
216 },216 },
217 .thumb => .{ .r = .{
218 ctx.R0, ctx.R1, ctx.R2, ctx.R3,
219 ctx.R4, ctx.R5, ctx.R6, ctx.R7,
220 ctx.R8, ctx.R9, ctx.R10, ctx.R11,
221 ctx.R12, ctx.Sp, ctx.Lr, ctx.Pc,
222 } },
217 else => comptime unreachable,223 else => comptime unreachable,
218 };224 };
219}225}