| author | |
| committer | |
| log | 1120546f72405ac263dce7414eb71ca4e6c96fc8 |
| tree | 4a6f90029d8feff983889a133326fbe2a4e3465d |
| parent | 12ceb896faebf25195d8b360e4972dd2bf23ede1 |
| signature |
There were only a few dozen lines of common logic, and they frankly
introduced more complexity than they eliminated. Instead, let's accept
that the implementations of `SelfInfo` are all pretty different and want
to track different state. This probably fixes some synchronization and
memory bugs by simplifying a bunch of stuff. It also improves the DWARF
unwind cache, making it around twice as fast in a debug build with the
self-hosted x86_64 backend, because we no longer have to redundantly go
through the hashmap lookup logic to find the module. Unwinding on
Windows will also see a slight performance boost from this change,
because `RtlVirtualUnwind` does not need to know the module whatsoever,
so the old `SelfInfo` implementation was doing redundant work. Lastly,
this makes it even easier to implement `SelfInfo` on freestanding
targets; there is no longer a need to emulate a real module system,
since the user controls the whole implementation!
There are various other small refactors here in the `SelfInfo`
implementations as well as in the DWARF unwinding logic. This change
turned out to make a lot of stuff simpler!13 files changed, 2415 insertions(+), 2320 deletions(-)
lib/std/debug.zig+86-11| ... | ... | @@ -19,11 +19,85 @@ const root = @import("root"); |
| 19 | 19 | pub const Dwarf = @import("debug/Dwarf.zig"); |
| 20 | 20 | pub const Pdb = @import("debug/Pdb.zig"); |
| 21 | 21 | pub const ElfFile = @import("debug/ElfFile.zig"); |
| 22 | pub const SelfInfo = @import("debug/SelfInfo.zig"); | |
| 23 | 22 | pub const Info = @import("debug/Info.zig"); |
| 24 | 23 | pub const Coverage = @import("debug/Coverage.zig"); |
| 25 | 24 | pub const cpu_context = @import("debug/cpu_context.zig"); |
| 26 | 25 | |
| 26 | /// This type abstracts the target-specific implementation of accessing this process' own debug | |
| 27 | /// information behind a generic interface which supports looking up source locations associated | |
| 28 | /// with addresses, as well as unwinding the stack where a safe mechanism to do so exists. | |
| 29 | /// | |
| 30 | /// The Zig Standard Library provides default implementations of `SelfInfo` for common targets, but | |
| 31 | /// the implementation can be overriden by exposing `root.debug.SelfInfo`. Setting `SelfInfo` to | |
| 32 | /// `void` indicates that the `SelfInfo` API is not supported. | |
| 33 | /// | |
| 34 | /// This type must expose the following declarations: | |
| 35 | /// | |
| 36 | /// ``` | |
| 37 | /// pub const init: SelfInfo; | |
| 38 | /// pub fn deinit(si: *SelfInfo, gpa: Allocator) void; | |
| 39 | /// | |
| 40 | /// /// Returns the symbol and source location of the instruction at `address`. | |
| 41 | /// pub fn getSymbol(si: *SelfInfo, gpa: Allocator, address: usize) SelfInfoError!Symbol; | |
| 42 | /// /// Returns a name for the "module" (e.g. shared library or executable image) containing `address`. | |
| 43 | /// pub fn getModuleName(si: *SelfInfo, gpa: Allocator, address: usize) SelfInfoError![]const u8; | |
| 44 | /// | |
| 45 | /// /// Whether a reliable stack unwinding strategy, such as DWARF unwinding, is available. | |
| 46 | /// pub const can_unwind: bool; | |
| 47 | /// /// Only required if `can_unwind == true`. | |
| 48 | /// pub const UnwindContext = struct { | |
| 49 | /// /// An address representing the instruction pointer in the last frame. | |
| 50 | /// pc: usize, | |
| 51 | /// | |
| 52 | /// pub fn init(ctx: *cpu_context.Native, gpa: Allocator) Allocator.Error!UnwindContext; | |
| 53 | /// pub fn deinit(ctx: *UnwindContext, gpa: Allocator) void; | |
| 54 | /// /// Returns the frame pointer associated with the last unwound stack frame. | |
| 55 | /// /// If the frame pointer is unknown, 0 may be returned instead. | |
| 56 | /// pub fn getFp(uc: *UnwindContext) usize; | |
| 57 | /// }; | |
| 58 | /// /// Only required if `can_unwind == true`. Unwinds a single stack frame, returning the frame's | |
| 59 | /// /// return address, or 0 if the end of the stack has been reached. | |
| 60 | /// pub fn unwindFrame(si: *SelfInfo, gpa: Allocator, context: *UnwindContext) SelfInfoError!usize; | |
| 61 | /// ``` | |
| 62 | pub const SelfInfo = if (@hasDecl(root, "debug") and @hasDecl(root.debug, "SelfInfo")) | |
| 63 | root.debug.SelfInfo | |
| 64 | else switch (native_os) { | |
| 65 | .linux, | |
| 66 | .netbsd, | |
| 67 | .freebsd, | |
| 68 | .dragonfly, | |
| 69 | .openbsd, | |
| 70 | .solaris, | |
| 71 | .illumos, | |
| 72 | => @import("debug/SelfInfo/Elf.zig"), | |
| 73 | ||
| 74 | .macos, | |
| 75 | .ios, | |
| 76 | .watchos, | |
| 77 | .tvos, | |
| 78 | .visionos, | |
| 79 | => @import("debug/SelfInfo/Darwin.zig"), | |
| 80 | ||
| 81 | .uefi, | |
| 82 | .windows, | |
| 83 | => @import("debug/SelfInfo/Windows.zig"), | |
| 84 | ||
| 85 | else => void, | |
| 86 | }; | |
| 87 | ||
| 88 | pub const SelfInfoError = error{ | |
| 89 | /// The required debug info is invalid or corrupted. | |
| 90 | InvalidDebugInfo, | |
| 91 | /// The required debug info could not be found. | |
| 92 | MissingDebugInfo, | |
| 93 | /// The required debug info was found, and may be valid, but is not supported by this implementation. | |
| 94 | UnsupportedDebugInfo, | |
| 95 | /// The required debug info could not be read from disk due to some IO error. | |
| 96 | ReadFailed, | |
| 97 | OutOfMemory, | |
| 98 | Unexpected, | |
| 99 | }; | |
| 100 | ||
| 27 | 101 | pub const simple_panic = @import("debug/simple_panic.zig"); |
| 28 | 102 | pub const no_panic = @import("debug/no_panic.zig"); |
| 29 | 103 | |
| ... | ... | @@ -240,7 +314,7 @@ pub fn print(comptime fmt: []const u8, args: anytype) void { |
| 240 | 314 | |
| 241 | 315 | /// Marked `inline` to propagate a comptime-known error to callers. |
| 242 | 316 | pub inline fn getSelfDebugInfo() !*SelfInfo { |
| 243 | if (!SelfInfo.target_supported) return error.UnsupportedTarget; | |
| 317 | if (SelfInfo == void) return error.UnsupportedTarget; | |
| 244 | 318 | const S = struct { |
| 245 | 319 | var self_info: SelfInfo = .init; |
| 246 | 320 | }; |
| ... | ... | @@ -640,7 +714,7 @@ pub fn writeCurrentStackTrace(options: StackUnwindOptions, writer: *Writer, tty_ |
| 640 | 714 | while (true) switch (it.next()) { |
| 641 | 715 | .switch_to_fp => |unwind_error| { |
| 642 | 716 | if (StackIterator.fp_unwind_is_safe) continue; // no need to even warn |
| 643 | const module_name = di.getModuleNameForAddress(di_gpa, unwind_error.address) catch "???"; | |
| 717 | const module_name = di.getModuleName(di_gpa, unwind_error.address) catch "???"; | |
| 644 | 718 | const caption: []const u8 = switch (unwind_error.err) { |
| 645 | 719 | error.MissingDebugInfo => "unwind info unavailable", |
| 646 | 720 | error.InvalidDebugInfo => "unwind info invalid", |
| ... | ... | @@ -753,9 +827,9 @@ pub fn dumpStackTrace(st: *const std.builtin.StackTrace) void { |
| 753 | 827 | |
| 754 | 828 | const StackIterator = union(enum) { |
| 755 | 829 | /// Unwinding using debug info (e.g. DWARF CFI). |
| 756 | di: if (SelfInfo.supports_unwinding) SelfInfo.UnwindContext else noreturn, | |
| 830 | di: if (SelfInfo != void and SelfInfo.can_unwind) SelfInfo.UnwindContext else noreturn, | |
| 757 | 831 | /// We will first report the *current* PC of this `UnwindContext`, then we will switch to `di`. |
| 758 | di_first: if (SelfInfo.supports_unwinding) SelfInfo.UnwindContext else noreturn, | |
| 832 | di_first: if (SelfInfo != void and SelfInfo.can_unwind) SelfInfo.UnwindContext else noreturn, | |
| 759 | 833 | /// Naive frame-pointer-based unwinding. Very simple, but typically unreliable. |
| 760 | 834 | fp: usize, |
| 761 | 835 | |
| ... | ... | @@ -772,7 +846,7 @@ const StackIterator = union(enum) { |
| 772 | 846 | } |
| 773 | 847 | } |
| 774 | 848 | if (opt_context_ptr) |context_ptr| { |
| 775 | if (!SelfInfo.supports_unwinding) return error.CannotUnwindFromContext; | |
| 849 | if (SelfInfo == void or !SelfInfo.can_unwind) return error.CannotUnwindFromContext; | |
| 776 | 850 | // Use `di_first` here so we report the PC in the context before unwinding any further. |
| 777 | 851 | return .{ .di_first = .init(context_ptr) }; |
| 778 | 852 | } |
| ... | ... | @@ -780,7 +854,8 @@ const StackIterator = union(enum) { |
| 780 | 854 | // call to `current`. This effectively constrains stack trace collection and dumping to FP |
| 781 | 855 | // unwinding when building with CBE for MSVC. |
| 782 | 856 | if (!(builtin.zig_backend == .stage2_c and builtin.target.abi == .msvc) and |
| 783 | SelfInfo.supports_unwinding and | |
| 857 | SelfInfo != void and | |
| 858 | SelfInfo.can_unwind and | |
| 784 | 859 | cpu_context.Native != noreturn) |
| 785 | 860 | { |
| 786 | 861 | // We don't need `di_first` here, because our PC is in `std.debug`; we're only interested |
| ... | ... | @@ -820,7 +895,7 @@ const StackIterator = union(enum) { |
| 820 | 895 | /// We were using `SelfInfo.UnwindInfo`, but are now switching to FP unwinding due to this error. |
| 821 | 896 | switch_to_fp: struct { |
| 822 | 897 | address: usize, |
| 823 | err: SelfInfo.Error, | |
| 898 | err: SelfInfoError, | |
| 824 | 899 | }, |
| 825 | 900 | }; |
| 826 | 901 | |
| ... | ... | @@ -929,7 +1004,7 @@ pub inline fn stripInstructionPtrAuthCode(ptr: usize) usize { |
| 929 | 1004 | } |
| 930 | 1005 | |
| 931 | 1006 | fn printSourceAtAddress(gpa: Allocator, debug_info: *SelfInfo, writer: *Writer, address: usize, tty_config: tty.Config) Writer.Error!void { |
| 932 | const symbol: Symbol = debug_info.getSymbolAtAddress(gpa, address) catch |err| switch (err) { | |
| 1007 | const symbol: Symbol = debug_info.getSymbol(gpa, address) catch |err| switch (err) { | |
| 933 | 1008 | error.MissingDebugInfo, |
| 934 | 1009 | error.UnsupportedDebugInfo, |
| 935 | 1010 | error.InvalidDebugInfo, |
| ... | ... | @@ -953,7 +1028,7 @@ fn printSourceAtAddress(gpa: Allocator, debug_info: *SelfInfo, writer: *Writer, |
| 953 | 1028 | symbol.source_location, |
| 954 | 1029 | address, |
| 955 | 1030 | symbol.name orelse "???", |
| 956 | symbol.compile_unit_name orelse debug_info.getModuleNameForAddress(gpa, address) catch "???", | |
| 1031 | symbol.compile_unit_name orelse debug_info.getModuleName(gpa, address) catch "???", | |
| 957 | 1032 | tty_config, |
| 958 | 1033 | ); |
| 959 | 1034 | } |
| ... | ... | @@ -1386,7 +1461,7 @@ pub fn dumpStackPointerAddr(prefix: []const u8) void { |
| 1386 | 1461 | } |
| 1387 | 1462 | |
| 1388 | 1463 | test "manage resources correctly" { |
| 1389 | if (!SelfInfo.target_supported) return error.SkipZigTest; | |
| 1464 | if (SelfInfo == void) return error.SkipZigTest; | |
| 1390 | 1465 | const S = struct { |
| 1391 | 1466 | noinline fn showMyTrace() usize { |
| 1392 | 1467 | return @returnAddress(); |
lib/std/debug/Dwarf.zig+3-2| ... | ... | @@ -28,6 +28,7 @@ const Dwarf = @This(); |
| 28 | 28 | |
| 29 | 29 | pub const expression = @import("Dwarf/expression.zig"); |
| 30 | 30 | pub const Unwind = @import("Dwarf/Unwind.zig"); |
| 31 | pub const SelfUnwinder = @import("Dwarf/SelfUnwinder.zig"); | |
| 31 | 32 | |
| 32 | 33 | /// Useful to temporarily enable while working on this file. |
| 33 | 34 | const debug_debug_mode = false; |
| ... | ... | @@ -1458,8 +1459,8 @@ pub fn spRegNum(arch: std.Target.Cpu.Arch) u16 { |
| 1458 | 1459 | |
| 1459 | 1460 | /// Tells whether unwinding for this target is supported by the Dwarf standard. |
| 1460 | 1461 | /// |
| 1461 | /// See also `std.debug.SelfInfo.supports_unwinding` which tells whether the Zig | |
| 1462 | /// standard library has a working implementation of unwinding for this target. | |
| 1462 | /// See also `std.debug.SelfInfo.can_unwind` which tells whether the Zig standard | |
| 1463 | /// library has a working implementation of unwinding for the current target. | |
| 1463 | 1464 | pub fn supportsUnwinding(target: *const std.Target) bool { |
| 1464 | 1465 | return switch (target.cpu.arch) { |
| 1465 | 1466 | .amdgcn, |
lib/std/debug/Dwarf/SelfUnwinder.zig created+334| ... | ... | @@ -0,0 +1,334 @@ |
| 1 | //! Implements stack unwinding based on `Dwarf.Unwind`. The caller is responsible for providing the | |
| 2 | //! initialized `Dwarf.Unwind` from the `.debug_frame` (or equivalent) section; this type handles | |
| 3 | //! computing and applying the CFI register rules to evolve a `std.debug.cpu_context.Native` through | |
| 4 | //! stack frames, hence performing the virtual unwind. | |
| 5 | //! | |
| 6 | //! Notably, this type is a valid implementation of `std.debug.SelfInfo.UnwindContext`. | |
| 7 | ||
| 8 | /// The state of the CPU in the current stack frame. | |
| 9 | cpu_state: std.debug.cpu_context.Native, | |
| 10 | /// The value of the Program Counter in this frame. This is almost the same as the value of the IP | |
| 11 | /// register in `cpu_state`, but may be off by one because the IP is typically a *return* address. | |
| 12 | pc: usize, | |
| 13 | ||
| 14 | cfi_vm: Dwarf.Unwind.VirtualMachine, | |
| 15 | expr_vm: Dwarf.expression.StackMachine(.{ .call_frame_context = true }), | |
| 16 | ||
| 17 | pub const CacheEntry = struct { | |
| 18 | const max_regs = 32; | |
| 19 | ||
| 20 | pc: usize, | |
| 21 | cie: *const Dwarf.Unwind.CommonInformationEntry, | |
| 22 | cfa_rule: Dwarf.Unwind.VirtualMachine.CfaRule, | |
| 23 | num_rules: u8, | |
| 24 | rules_regs: [max_regs]u16, | |
| 25 | rules: [max_regs]Dwarf.Unwind.VirtualMachine.RegisterRule, | |
| 26 | ||
| 27 | pub fn find(entries: []const CacheEntry, pc: usize) ?*const CacheEntry { | |
| 28 | assert(pc != 0); | |
| 29 | const idx = std.hash.int(pc) % entries.len; | |
| 30 | const entry = &entries[idx]; | |
| 31 | return if (entry.pc == pc) entry else null; | |
| 32 | } | |
| 33 | ||
| 34 | pub fn populate(entry: *const CacheEntry, entries: []CacheEntry) void { | |
| 35 | const idx = std.hash.int(entry.pc) % entries.len; | |
| 36 | entries[idx] = entry.*; | |
| 37 | } | |
| 38 | ||
| 39 | pub const empty: CacheEntry = .{ | |
| 40 | .pc = 0, | |
| 41 | .cie = undefined, | |
| 42 | .cfa_rule = undefined, | |
| 43 | .num_rules = undefined, | |
| 44 | .rules_regs = undefined, | |
| 45 | .rules = undefined, | |
| 46 | }; | |
| 47 | }; | |
| 48 | ||
| 49 | pub fn init(cpu_context: *const std.debug.cpu_context.Native) SelfUnwinder { | |
| 50 | // `@constCast` is safe because we aren't going to store to the resulting pointer. | |
| 51 | const raw_pc_ptr = regNative(@constCast(cpu_context), ip_reg_num) catch |err| switch (err) { | |
| 52 | error.InvalidRegister => unreachable, // `ip_reg_num` is definitely valid | |
| 53 | error.UnsupportedRegister => unreachable, // the implementation needs to support ip | |
| 54 | error.IncompatibleRegisterSize => unreachable, // ip is definitely `usize`-sized | |
| 55 | }; | |
| 56 | const pc = stripInstructionPtrAuthCode(raw_pc_ptr.*); | |
| 57 | return .{ | |
| 58 | .cpu_state = cpu_context.*, | |
| 59 | .pc = pc, | |
| 60 | .cfi_vm = .{}, | |
| 61 | .expr_vm = .{}, | |
| 62 | }; | |
| 63 | } | |
| 64 | ||
| 65 | pub fn deinit(unwinder: *SelfUnwinder, gpa: Allocator) void { | |
| 66 | unwinder.cfi_vm.deinit(gpa); | |
| 67 | unwinder.expr_vm.deinit(gpa); | |
| 68 | unwinder.* = undefined; | |
| 69 | } | |
| 70 | ||
| 71 | pub fn getFp(unwinder: *const SelfUnwinder) usize { | |
| 72 | // `@constCast` is safe because we aren't going to store to the resulting pointer. | |
| 73 | const ptr = regNative(@constCast(&unwinder.cpu_state), fp_reg_num) catch |err| switch (err) { | |
| 74 | error.InvalidRegister => unreachable, // `fp_reg_num` is definitely valid | |
| 75 | error.UnsupportedRegister => unreachable, // the implementation needs to support fp | |
| 76 | error.IncompatibleRegisterSize => unreachable, // fp is a pointer so is `usize`-sized | |
| 77 | }; | |
| 78 | return ptr.*; | |
| 79 | } | |
| 80 | ||
| 81 | /// Compute the rule set for the address `unwinder.pc` from the information in `unwind`. The caller | |
| 82 | /// may store the returned rule set in a simple fixed-size cache keyed on the `pc` field to avoid | |
| 83 | /// frequently recomputing register rules when unwinding many times. | |
| 84 | /// | |
| 85 | /// To actually apply the computed rules, see `next`. | |
| 86 | pub fn computeRules( | |
| 87 | unwinder: *SelfUnwinder, | |
| 88 | gpa: Allocator, | |
| 89 | unwind: *const Dwarf.Unwind, | |
| 90 | load_offset: usize, | |
| 91 | explicit_fde_offset: ?usize, | |
| 92 | ) !CacheEntry { | |
| 93 | assert(unwinder.pc != 0); | |
| 94 | ||
| 95 | const pc_vaddr = unwinder.pc - load_offset; | |
| 96 | ||
| 97 | const fde_offset = explicit_fde_offset orelse try unwind.lookupPc( | |
| 98 | pc_vaddr, | |
| 99 | @sizeOf(usize), | |
| 100 | native_endian, | |
| 101 | ) orelse return error.MissingDebugInfo; | |
| 102 | const cie, const fde = try unwind.getFde(fde_offset, native_endian); | |
| 103 | ||
| 104 | // `lookupPc` can return false positives, so check if the FDE *actually* includes the pc | |
| 105 | if (pc_vaddr < fde.pc_begin or pc_vaddr >= fde.pc_begin + fde.pc_range) { | |
| 106 | return error.MissingDebugInfo; | |
| 107 | } | |
| 108 | ||
| 109 | unwinder.cfi_vm.reset(); | |
| 110 | const row = try unwinder.cfi_vm.runTo(gpa, pc_vaddr, cie, &fde, @sizeOf(usize), native_endian); | |
| 111 | const cols = unwinder.cfi_vm.rowColumns(&row); | |
| 112 | ||
| 113 | if (cols.len > CacheEntry.max_regs) return error.UnsupportedDebugInfo; | |
| 114 | ||
| 115 | var entry: CacheEntry = .{ | |
| 116 | .pc = unwinder.pc, | |
| 117 | .cie = cie, | |
| 118 | .cfa_rule = row.cfa, | |
| 119 | .num_rules = @intCast(cols.len), | |
| 120 | .rules_regs = undefined, | |
| 121 | .rules = undefined, | |
| 122 | }; | |
| 123 | for (cols, 0..) |col, i| { | |
| 124 | entry.rules_regs[i] = col.register; | |
| 125 | entry.rules[i] = col.rule; | |
| 126 | } | |
| 127 | return entry; | |
| 128 | } | |
| 129 | ||
| 130 | /// Applies the register rules given in `cache_entry` to the current state of `unwinder`. The caller | |
| 131 | /// is responsible for ensuring that `cache_entry` contains the correct rule set for `unwinder.pc`. | |
| 132 | /// | |
| 133 | /// `unwinder.cpu_state` and `unwinder.pc` are updated to refer to the next frame, and this frame's | |
| 134 | /// return address is returned as a `usize`. | |
| 135 | pub fn next(unwinder: *SelfUnwinder, gpa: Allocator, cache_entry: *const CacheEntry) std.debug.SelfInfoError!usize { | |
| 136 | return unwinder.nextInner(gpa, cache_entry) catch |err| switch (err) { | |
| 137 | error.OutOfMemory, | |
| 138 | error.InvalidDebugInfo, | |
| 139 | => |e| return e, | |
| 140 | ||
| 141 | error.UnsupportedRegister, | |
| 142 | error.UnimplementedExpressionCall, | |
| 143 | error.UnimplementedOpcode, | |
| 144 | error.UnimplementedUserOpcode, | |
| 145 | error.UnimplementedTypedComparison, | |
| 146 | error.UnimplementedTypeConversion, | |
| 147 | error.UnknownExpressionOpcode, | |
| 148 | => return error.UnsupportedDebugInfo, | |
| 149 | ||
| 150 | error.ReadFailed, | |
| 151 | error.EndOfStream, | |
| 152 | error.Overflow, | |
| 153 | error.IncompatibleRegisterSize, | |
| 154 | error.InvalidRegister, | |
| 155 | error.IncompleteExpressionContext, | |
| 156 | error.InvalidCFAOpcode, | |
| 157 | error.InvalidExpression, | |
| 158 | error.InvalidFrameBase, | |
| 159 | error.InvalidIntegralTypeSize, | |
| 160 | error.InvalidSubExpression, | |
| 161 | error.InvalidTypeLength, | |
| 162 | error.TruncatedIntegralType, | |
| 163 | error.DivisionByZero, | |
| 164 | => return error.InvalidDebugInfo, | |
| 165 | }; | |
| 166 | } | |
| 167 | ||
| 168 | fn nextInner(unwinder: *SelfUnwinder, gpa: Allocator, cache_entry: *const CacheEntry) !usize { | |
| 169 | const format = cache_entry.cie.format; | |
| 170 | const return_address_register = cache_entry.cie.return_address_register; | |
| 171 | ||
| 172 | const cfa = switch (cache_entry.cfa_rule) { | |
| 173 | .none => return error.InvalidDebugInfo, | |
| 174 | .reg_off => |ro| cfa: { | |
| 175 | const ptr = try regNative(&unwinder.cpu_state, ro.register); | |
| 176 | break :cfa try applyOffset(ptr.*, ro.offset); | |
| 177 | }, | |
| 178 | .expression => |expr| cfa: { | |
| 179 | // On all implemented architectures, the CFA is defined to be the previous frame's SP | |
| 180 | const prev_cfa_val = (try regNative(&unwinder.cpu_state, sp_reg_num)).*; | |
| 181 | unwinder.expr_vm.reset(); | |
| 182 | const value = try unwinder.expr_vm.run(expr, gpa, .{ | |
| 183 | .format = format, | |
| 184 | .cpu_context = &unwinder.cpu_state, | |
| 185 | }, prev_cfa_val) orelse return error.InvalidDebugInfo; | |
| 186 | switch (value) { | |
| 187 | .generic => |g| break :cfa g, | |
| 188 | else => return error.InvalidDebugInfo, | |
| 189 | } | |
| 190 | }, | |
| 191 | }; | |
| 192 | ||
| 193 | // If unspecified, we'll use the default rule for the return address register, which is | |
| 194 | // typically equivalent to `.undefined` (meaning there is no return address), but may be | |
| 195 | // overriden by ABIs. | |
| 196 | var has_return_address: bool = builtin.cpu.arch.isAARCH64() and | |
| 197 | return_address_register >= 19 and | |
| 198 | return_address_register <= 28; | |
| 199 | ||
| 200 | // Create a copy of the CPU state, to which we will apply the new rules. | |
| 201 | var new_cpu_state = unwinder.cpu_state; | |
| 202 | ||
| 203 | // On all implemented architectures, the CFA is defined to be the previous frame's SP | |
| 204 | (try regNative(&new_cpu_state, sp_reg_num)).* = cfa; | |
| 205 | ||
| 206 | const rules_len = cache_entry.num_rules; | |
| 207 | for (cache_entry.rules_regs[0..rules_len], cache_entry.rules[0..rules_len]) |register, rule| { | |
| 208 | const new_val: union(enum) { | |
| 209 | same, | |
| 210 | undefined, | |
| 211 | val: usize, | |
| 212 | bytes: []const u8, | |
| 213 | } = switch (rule) { | |
| 214 | .default => val: { | |
| 215 | // The default rule is typically equivalent to `.undefined`, but ABIs may override it. | |
| 216 | if (builtin.cpu.arch.isAARCH64() and register >= 19 and register <= 28) { | |
| 217 | break :val .same; | |
| 218 | } | |
| 219 | break :val .undefined; | |
| 220 | }, | |
| 221 | .undefined => .undefined, | |
| 222 | .same_value => .same, | |
| 223 | .offset => |offset| val: { | |
| 224 | const ptr: *const usize = @ptrFromInt(try applyOffset(cfa, offset)); | |
| 225 | break :val .{ .val = ptr.* }; | |
| 226 | }, | |
| 227 | .val_offset => |offset| .{ .val = try applyOffset(cfa, offset) }, | |
| 228 | .register => |r| .{ .bytes = try unwinder.cpu_state.dwarfRegisterBytes(r) }, | |
| 229 | .expression => |expr| val: { | |
| 230 | unwinder.expr_vm.reset(); | |
| 231 | const value = try unwinder.expr_vm.run(expr, gpa, .{ | |
| 232 | .format = format, | |
| 233 | .cpu_context = &unwinder.cpu_state, | |
| 234 | }, cfa) orelse return error.InvalidDebugInfo; | |
| 235 | const ptr: *const usize = switch (value) { | |
| 236 | .generic => |addr| @ptrFromInt(addr), | |
| 237 | else => return error.InvalidDebugInfo, | |
| 238 | }; | |
| 239 | break :val .{ .val = ptr.* }; | |
| 240 | }, | |
| 241 | .val_expression => |expr| val: { | |
| 242 | unwinder.expr_vm.reset(); | |
| 243 | const value = try unwinder.expr_vm.run(expr, gpa, .{ | |
| 244 | .format = format, | |
| 245 | .cpu_context = &unwinder.cpu_state, | |
| 246 | }, cfa) orelse return error.InvalidDebugInfo; | |
| 247 | switch (value) { | |
| 248 | .generic => |val| break :val .{ .val = val }, | |
| 249 | else => return error.InvalidDebugInfo, | |
| 250 | } | |
| 251 | }, | |
| 252 | }; | |
| 253 | switch (new_val) { | |
| 254 | .same => {}, | |
| 255 | .undefined => { | |
| 256 | const dest = try new_cpu_state.dwarfRegisterBytes(@intCast(register)); | |
| 257 | @memset(dest, undefined); | |
| 258 | }, | |
| 259 | .val => |val| { | |
| 260 | const dest = try new_cpu_state.dwarfRegisterBytes(@intCast(register)); | |
| 261 | if (dest.len != @sizeOf(usize)) return error.InvalidDebugInfo; | |
| 262 | const dest_ptr: *align(1) usize = @ptrCast(dest); | |
| 263 | dest_ptr.* = val; | |
| 264 | }, | |
| 265 | .bytes => |src| { | |
| 266 | const dest = try new_cpu_state.dwarfRegisterBytes(@intCast(register)); | |
| 267 | if (dest.len != src.len) return error.InvalidDebugInfo; | |
| 268 | @memcpy(dest, src); | |
| 269 | }, | |
| 270 | } | |
| 271 | if (register == return_address_register) { | |
| 272 | has_return_address = new_val != .undefined; | |
| 273 | } | |
| 274 | } | |
| 275 | ||
| 276 | const return_address: usize = if (has_return_address) pc: { | |
| 277 | const raw_ptr = try regNative(&new_cpu_state, return_address_register); | |
| 278 | break :pc stripInstructionPtrAuthCode(raw_ptr.*); | |
| 279 | } else 0; | |
| 280 | ||
| 281 | (try regNative(&new_cpu_state, ip_reg_num)).* = return_address; | |
| 282 | ||
| 283 | // The new CPU state is complete; flush changes. | |
| 284 | unwinder.cpu_state = new_cpu_state; | |
| 285 | ||
| 286 | // The caller will subtract 1 from the return address to get an address corresponding to the | |
| 287 | // function call. However, if this is a signal frame, that's actually incorrect, because the | |
| 288 | // "return address" we have is the instruction which triggered the signal (if the signal | |
| 289 | // handler returned, the instruction would be re-run). Compensate for this by incrementing | |
| 290 | // the address in that case. | |
| 291 | const adjusted_ret_addr = if (cache_entry.cie.is_signal_frame) return_address +| 1 else return_address; | |
| 292 | ||
| 293 | // We also want to do that same subtraction here to get the PC for the next frame's FDE. | |
| 294 | // This is because if the callee was noreturn, then the function call might be the caller's | |
| 295 | // last instruction, so `return_address` might actually point outside of it! | |
| 296 | unwinder.pc = adjusted_ret_addr -| 1; | |
| 297 | ||
| 298 | return adjusted_ret_addr; | |
| 299 | } | |
| 300 | ||
| 301 | pub fn regNative(ctx: *std.debug.cpu_context.Native, num: u16) error{ | |
| 302 | InvalidRegister, | |
| 303 | UnsupportedRegister, | |
| 304 | IncompatibleRegisterSize, | |
| 305 | }!*align(1) usize { | |
| 306 | const bytes = try ctx.dwarfRegisterBytes(num); | |
| 307 | if (bytes.len != @sizeOf(usize)) return error.IncompatibleRegisterSize; | |
| 308 | return @ptrCast(bytes); | |
| 309 | } | |
| 310 | ||
| 311 | /// Since register rules are applied (usually) during a panic, | |
| 312 | /// checked addition / subtraction is used so that we can return | |
| 313 | /// an error and fall back to FP-based unwinding. | |
| 314 | fn applyOffset(base: usize, offset: i64) !usize { | |
| 315 | return if (offset >= 0) | |
| 316 | try std.math.add(usize, base, @as(usize, @intCast(offset))) | |
| 317 | else | |
| 318 | try std.math.sub(usize, base, @as(usize, @intCast(-offset))); | |
| 319 | } | |
| 320 | ||
| 321 | const ip_reg_num = Dwarf.ipRegNum(builtin.target.cpu.arch).?; | |
| 322 | const fp_reg_num = Dwarf.fpRegNum(builtin.target.cpu.arch); | |
| 323 | const sp_reg_num = Dwarf.spRegNum(builtin.target.cpu.arch); | |
| 324 | ||
| 325 | const std = @import("std"); | |
| 326 | const Allocator = std.mem.Allocator; | |
| 327 | const Dwarf = std.debug.Dwarf; | |
| 328 | const assert = std.debug.assert; | |
| 329 | const stripInstructionPtrAuthCode = std.debug.stripInstructionPtrAuthCode; | |
| 330 | ||
| 331 | const builtin = @import("builtin"); | |
| 332 | const native_endian = builtin.target.cpu.arch.endian(); | |
| 333 | ||
| 334 | const SelfUnwinder = @This(); |
lib/std/debug/Dwarf/Unwind.zig+11-9| ... | ... | @@ -530,16 +530,18 @@ pub fn prepare( |
| 530 | 530 | }; |
| 531 | 531 | if (saw_terminator != expect_terminator) return bad(); |
| 532 | 532 | |
| 533 | std.mem.sortUnstable(SortedFdeEntry, fde_list.items, {}, struct { | |
| 534 | fn lessThan(ctx: void, a: SortedFdeEntry, b: SortedFdeEntry) bool { | |
| 535 | ctx; | |
| 536 | return a.pc_begin < b.pc_begin; | |
| 537 | } | |
| 538 | }.lessThan); | |
| 533 | if (need_lookup) { | |
| 534 | std.mem.sortUnstable(SortedFdeEntry, fde_list.items, {}, struct { | |
| 535 | fn lessThan(ctx: void, a: SortedFdeEntry, b: SortedFdeEntry) bool { | |
| 536 | ctx; | |
| 537 | return a.pc_begin < b.pc_begin; | |
| 538 | } | |
| 539 | }.lessThan); | |
| 539 | 540 | |
| 540 | // This temporary is necessary to avoid an RLS footgun where `lookup` ends up non-null `undefined` on OOM. | |
| 541 | const final_fdes = try fde_list.toOwnedSlice(gpa); | |
| 542 | unwind.lookup = .{ .sorted_fdes = final_fdes }; | |
| 541 | // This temporary is necessary to avoid an RLS footgun where `lookup` ends up non-null `undefined` on OOM. | |
| 542 | const final_fdes = try fde_list.toOwnedSlice(gpa); | |
| 543 | unwind.lookup = .{ .sorted_fdes = final_fdes }; | |
| 544 | } | |
| 543 | 545 | } |
| 544 | 546 | |
| 545 | 547 | fn findCie(unwind: *const Unwind, offset: u64) ?*const CommonInformationEntry { |
lib/std/debug/Dwarf/expression.zig+1-1| ... | ... | @@ -10,7 +10,7 @@ const assert = std.debug.assert; |
| 10 | 10 | const testing = std.testing; |
| 11 | 11 | const Writer = std.Io.Writer; |
| 12 | 12 | |
| 13 | const regNative = std.debug.SelfInfo.DwarfUnwindContext.regNative; | |
| 13 | const regNative = std.debug.Dwarf.SelfUnwinder.regNative; | |
| 14 | 14 | |
| 15 | 15 | const ip_reg_num = std.debug.Dwarf.ipRegNum(native_arch).?; |
| 16 | 16 | const fp_reg_num = std.debug.Dwarf.fpRegNum(native_arch); |
lib/std/debug/SelfInfo.zig deleted-551| ... | ... | @@ -1,551 +0,0 @@ |
| 1 | //! Cross-platform abstraction for this binary's own debug information, with a | |
| 2 | //! goal of minimal code bloat and compilation speed penalty. | |
| 3 | ||
| 4 | const builtin = @import("builtin"); | |
| 5 | const native_endian = native_arch.endian(); | |
| 6 | const native_arch = builtin.cpu.arch; | |
| 7 | ||
| 8 | const std = @import("../std.zig"); | |
| 9 | const mem = std.mem; | |
| 10 | const Allocator = std.mem.Allocator; | |
| 11 | const assert = std.debug.assert; | |
| 12 | const Dwarf = std.debug.Dwarf; | |
| 13 | const CpuContext = std.debug.cpu_context.Native; | |
| 14 | ||
| 15 | const stripInstructionPtrAuthCode = std.debug.stripInstructionPtrAuthCode; | |
| 16 | ||
| 17 | const root = @import("root"); | |
| 18 | ||
| 19 | const SelfInfo = @This(); | |
| 20 | ||
| 21 | /// Locks access to `modules`. However, does *not* lock the `Module.DebugInfo`, nor `lookup_cache` | |
| 22 | /// the implementation is responsible for locking as needed in its exposed methods. | |
| 23 | /// | |
| 24 | /// TODO: to allow `SelfInfo` to work on freestanding, we currently just don't use this mutex there. | |
| 25 | /// That's a bad solution, but a better one depends on the standard library's general support for | |
| 26 | /// "bring your own OS" being improved. | |
| 27 | modules_mutex: switch (builtin.os.tag) { | |
| 28 | else => std.Thread.Mutex, | |
| 29 | .freestanding, .other => struct { | |
| 30 | fn lock(_: @This()) void {} | |
| 31 | fn unlock(_: @This()) void {} | |
| 32 | }, | |
| 33 | }, | |
| 34 | /// Value is allocated into gpa to give it a stable pointer. | |
| 35 | modules: if (target_supported) std.AutoArrayHashMapUnmanaged(usize, *Module.DebugInfo) else void, | |
| 36 | lookup_cache: if (target_supported) Module.LookupCache else void, | |
| 37 | ||
| 38 | pub const Error = error{ | |
| 39 | /// The required debug info is invalid or corrupted. | |
| 40 | InvalidDebugInfo, | |
| 41 | /// The required debug info could not be found. | |
| 42 | MissingDebugInfo, | |
| 43 | /// The required debug info was found, and may be valid, but is not supported by this implementation. | |
| 44 | UnsupportedDebugInfo, | |
| 45 | /// The required debug info could not be read from disk due to some IO error. | |
| 46 | ReadFailed, | |
| 47 | OutOfMemory, | |
| 48 | Unexpected, | |
| 49 | }; | |
| 50 | ||
| 51 | /// Indicates whether the `SelfInfo` implementation has support for this target. | |
| 52 | pub const target_supported: bool = Module != void; | |
| 53 | ||
| 54 | /// Indicates whether the `SelfInfo` implementation has support for unwinding on this target. | |
| 55 | pub const supports_unwinding: bool = target_supported and Module.supports_unwinding; | |
| 56 | ||
| 57 | pub const UnwindContext = if (supports_unwinding) Module.UnwindContext; | |
| 58 | ||
| 59 | pub const init: SelfInfo = .{ | |
| 60 | .modules_mutex = .{}, | |
| 61 | .modules = .empty, | |
| 62 | .lookup_cache = if (Module.LookupCache != void) .init, | |
| 63 | }; | |
| 64 | ||
| 65 | pub fn deinit(self: *SelfInfo, gpa: Allocator) void { | |
| 66 | for (self.modules.values()) |di| { | |
| 67 | di.deinit(gpa); | |
| 68 | gpa.destroy(di); | |
| 69 | } | |
| 70 | self.modules.deinit(gpa); | |
| 71 | if (Module.LookupCache != void) self.lookup_cache.deinit(gpa); | |
| 72 | } | |
| 73 | ||
| 74 | pub fn unwindFrame(self: *SelfInfo, gpa: Allocator, context: *UnwindContext) Error!usize { | |
| 75 | comptime assert(supports_unwinding); | |
| 76 | const module: Module = try .lookup(&self.lookup_cache, gpa, context.pc); | |
| 77 | const di: *Module.DebugInfo = di: { | |
| 78 | self.modules_mutex.lock(); | |
| 79 | defer self.modules_mutex.unlock(); | |
| 80 | const gop = try self.modules.getOrPut(gpa, module.key()); | |
| 81 | if (gop.found_existing) break :di gop.value_ptr.*; | |
| 82 | errdefer _ = self.modules.pop().?; | |
| 83 | const di = try gpa.create(Module.DebugInfo); | |
| 84 | di.* = .init; | |
| 85 | gop.value_ptr.* = di; | |
| 86 | break :di di; | |
| 87 | }; | |
| 88 | return module.unwindFrame(gpa, di, context); | |
| 89 | } | |
| 90 | ||
| 91 | pub fn getSymbolAtAddress(self: *SelfInfo, gpa: Allocator, address: usize) Error!std.debug.Symbol { | |
| 92 | comptime assert(target_supported); | |
| 93 | const module: Module = try .lookup(&self.lookup_cache, gpa, address); | |
| 94 | const di: *Module.DebugInfo = di: { | |
| 95 | self.modules_mutex.lock(); | |
| 96 | defer self.modules_mutex.unlock(); | |
| 97 | const gop = try self.modules.getOrPut(gpa, module.key()); | |
| 98 | if (gop.found_existing) break :di gop.value_ptr.*; | |
| 99 | errdefer _ = self.modules.pop().?; | |
| 100 | const di = try gpa.create(Module.DebugInfo); | |
| 101 | di.* = .init; | |
| 102 | gop.value_ptr.* = di; | |
| 103 | break :di di; | |
| 104 | }; | |
| 105 | return module.getSymbolAtAddress(gpa, di, address); | |
| 106 | } | |
| 107 | ||
| 108 | pub fn getModuleNameForAddress(self: *SelfInfo, gpa: Allocator, address: usize) Error![]const u8 { | |
| 109 | comptime assert(target_supported); | |
| 110 | const module: Module = try .lookup(&self.lookup_cache, gpa, address); | |
| 111 | if (module.name.len == 0) return error.MissingDebugInfo; | |
| 112 | return module.name; | |
| 113 | } | |
| 114 | ||
| 115 | /// `void` indicates that `SelfInfo` is not supported for this target. | |
| 116 | /// | |
| 117 | /// This type contains the target-specific implementation. Logically, a `Module` represents a subset | |
| 118 | /// of the executable with its own debug information. This typically corresponds to what ELF calls a | |
| 119 | /// module, i.e. a shared library or executable image, but could be anything. For instance, it would | |
| 120 | /// be valid to consider the entire application one module, or on the other hand to consider each | |
| 121 | /// object file a module. | |
| 122 | /// | |
| 123 | /// Because different threads can collect stack traces concurrently, the implementation must be able | |
| 124 | /// to tolerate concurrent calls to any method it implements. | |
| 125 | /// | |
| 126 | /// This type must must expose the following declarations: | |
| 127 | /// | |
| 128 | /// ``` | |
| 129 | /// /// Holds state cached by the implementation between calls to `lookup`. | |
| 130 | /// /// This may be `void`, in which case the inner declarations can be omitted. | |
| 131 | /// pub const LookupCache = struct { | |
| 132 | /// pub const init: LookupCache; | |
| 133 | /// pub fn deinit(lc: *LookupCache, gpa: Allocator) void; | |
| 134 | /// }; | |
| 135 | /// /// Holds debug information associated with a particular `Module`. | |
| 136 | /// pub const DebugInfo = struct { | |
| 137 | /// pub const init: DebugInfo; | |
| 138 | /// }; | |
| 139 | /// /// Finds the `Module` corresponding to `address`. | |
| 140 | /// pub fn lookup(lc: *LookupCache, gpa: Allocator, address: usize) SelfInfo.Error!Module; | |
| 141 | /// /// Returns a unique identifier for this `Module`, such as a load address. | |
| 142 | /// pub fn key(mod: *const Module) usize; | |
| 143 | /// /// Locates and loads location information for the symbol corresponding to `address`. | |
| 144 | /// pub fn getSymbolAtAddress( | |
| 145 | /// mod: *const Module, | |
| 146 | /// gpa: Allocator, | |
| 147 | /// di: *DebugInfo, | |
| 148 | /// address: usize, | |
| 149 | /// ) SelfInfo.Error!std.debug.Symbol; | |
| 150 | /// /// Whether a reliable stack unwinding strategy, such as DWARF unwinding, is available. | |
| 151 | /// pub const supports_unwinding: bool; | |
| 152 | /// /// Only required if `supports_unwinding == true`. | |
| 153 | /// pub const UnwindContext = struct { | |
| 154 | /// /// A PC value representing the location in the last frame. | |
| 155 | /// pc: usize, | |
| 156 | /// pub fn init(ctx: *std.debug.cpu_context.Native, gpa: Allocator) Allocator.Error!UnwindContext; | |
| 157 | /// pub fn deinit(uc: *UnwindContext, gpa: Allocator) void; | |
| 158 | /// /// Returns the frame pointer associated with the last unwound stack frame. If the frame | |
| 159 | /// /// pointer is unknown, 0 may be returned instead. | |
| 160 | /// pub fn getFp(uc: *UnwindContext) usize; | |
| 161 | /// }; | |
| 162 | /// /// Only required if `supports_unwinding == true`. Unwinds a single stack frame, and returns | |
| 163 | /// /// the frame's return address. | |
| 164 | /// pub fn unwindFrame( | |
| 165 | /// mod: *const Module, | |
| 166 | /// gpa: Allocator, | |
| 167 | /// di: *DebugInfo, | |
| 168 | /// ctx: *UnwindContext, | |
| 169 | /// ) SelfInfo.Error!usize; | |
| 170 | /// ``` | |
| 171 | const Module: type = Module: { | |
| 172 | // Allow overriding the target-specific `SelfInfo` implementation by exposing `root.debug.Module`. | |
| 173 | if (@hasDecl(root, "debug") and @hasDecl(root.debug, "Module")) { | |
| 174 | break :Module root.debug.Module; | |
| 175 | } | |
| 176 | break :Module switch (builtin.os.tag) { | |
| 177 | .linux, | |
| 178 | .netbsd, | |
| 179 | .freebsd, | |
| 180 | .dragonfly, | |
| 181 | .openbsd, | |
| 182 | .solaris, | |
| 183 | .illumos, | |
| 184 | => @import("SelfInfo/ElfModule.zig"), | |
| 185 | ||
| 186 | .macos, | |
| 187 | .ios, | |
| 188 | .watchos, | |
| 189 | .tvos, | |
| 190 | .visionos, | |
| 191 | => @import("SelfInfo/DarwinModule.zig"), | |
| 192 | ||
| 193 | .uefi, | |
| 194 | .windows, | |
| 195 | => @import("SelfInfo/WindowsModule.zig"), | |
| 196 | ||
| 197 | else => void, | |
| 198 | }; | |
| 199 | }; | |
| 200 | ||
| 201 | /// An implementation of `UnwindContext` useful for DWARF-based unwinders. The `Module.unwindFrame` | |
| 202 | /// implementation should wrap `DwarfUnwindContext.unwindFrame`. | |
| 203 | pub const DwarfUnwindContext = struct { | |
| 204 | cfa: ?usize, | |
| 205 | pc: usize, | |
| 206 | cpu_context: CpuContext, | |
| 207 | vm: Dwarf.Unwind.VirtualMachine, | |
| 208 | stack_machine: Dwarf.expression.StackMachine(.{ .call_frame_context = true }), | |
| 209 | ||
| 210 | pub const Cache = struct { | |
| 211 | /// TODO: to allow `DwarfUnwindContext` to work on freestanding, we currently just don't use | |
| 212 | /// this mutex there. That's a bad solution, but a better one depends on the standard | |
| 213 | /// library's general support for "bring your own OS" being improved. | |
| 214 | mutex: switch (builtin.os.tag) { | |
| 215 | else => std.Thread.Mutex, | |
| 216 | .freestanding, .other => struct { | |
| 217 | fn lock(_: @This()) void {} | |
| 218 | fn unlock(_: @This()) void {} | |
| 219 | }, | |
| 220 | }, | |
| 221 | buf: [num_slots]Slot, | |
| 222 | const num_slots = 2048; | |
| 223 | const Slot = struct { | |
| 224 | const max_regs = 32; | |
| 225 | pc: usize, | |
| 226 | cie: *const Dwarf.Unwind.CommonInformationEntry, | |
| 227 | cfa_rule: Dwarf.Unwind.VirtualMachine.CfaRule, | |
| 228 | rules_regs: [max_regs]u16, | |
| 229 | rules: [max_regs]Dwarf.Unwind.VirtualMachine.RegisterRule, | |
| 230 | num_rules: u8, | |
| 231 | }; | |
| 232 | /// This is a function rather than a declaration to avoid lowering a very large struct value | |
| 233 | /// into the binary when most of it is `undefined`. | |
| 234 | pub fn init(c: *Cache) void { | |
| 235 | c.mutex = .{}; | |
| 236 | for (&c.buf) |*slot| slot.pc = 0; | |
| 237 | } | |
| 238 | }; | |
| 239 | ||
| 240 | pub fn init(cpu_context: *const CpuContext) DwarfUnwindContext { | |
| 241 | comptime assert(supports_unwinding); | |
| 242 | ||
| 243 | // `@constCast` is safe because we aren't going to store to the resulting pointer. | |
| 244 | const raw_pc_ptr = regNative(@constCast(cpu_context), ip_reg_num) catch |err| switch (err) { | |
| 245 | error.InvalidRegister => unreachable, // `ip_reg_num` is definitely valid | |
| 246 | error.UnsupportedRegister => unreachable, // the implementation needs to support ip | |
| 247 | error.IncompatibleRegisterSize => unreachable, // ip is definitely `usize`-sized | |
| 248 | }; | |
| 249 | const pc = stripInstructionPtrAuthCode(raw_pc_ptr.*); | |
| 250 | ||
| 251 | return .{ | |
| 252 | .cfa = null, | |
| 253 | .pc = pc, | |
| 254 | .cpu_context = cpu_context.*, | |
| 255 | .vm = .{}, | |
| 256 | .stack_machine = .{}, | |
| 257 | }; | |
| 258 | } | |
| 259 | ||
| 260 | pub fn deinit(self: *DwarfUnwindContext, gpa: Allocator) void { | |
| 261 | self.vm.deinit(gpa); | |
| 262 | self.stack_machine.deinit(gpa); | |
| 263 | self.* = undefined; | |
| 264 | } | |
| 265 | ||
| 266 | pub fn getFp(self: *const DwarfUnwindContext) usize { | |
| 267 | // `@constCast` is safe because we aren't going to store to the resulting pointer. | |
| 268 | const ptr = regNative(@constCast(&self.cpu_context), fp_reg_num) catch |err| switch (err) { | |
| 269 | error.InvalidRegister => unreachable, // `fp_reg_num` is definitely valid | |
| 270 | error.UnsupportedRegister => unreachable, // the implementation needs to support fp | |
| 271 | error.IncompatibleRegisterSize => unreachable, // fp is a pointer so is `usize`-sized | |
| 272 | }; | |
| 273 | return ptr.*; | |
| 274 | } | |
| 275 | ||
| 276 | /// Unwind a stack frame using DWARF unwinding info, updating the register context. | |
| 277 | /// | |
| 278 | /// If `.eh_frame_hdr` is available and complete, it will be used to binary search for the FDE. | |
| 279 | /// Otherwise, a linear scan of `.eh_frame` and `.debug_frame` is done to find the FDE. The latter | |
| 280 | /// may require lazily loading the data in those sections. | |
| 281 | /// | |
| 282 | /// `explicit_fde_offset` is for cases where the FDE offset is known, such as when using macOS' | |
| 283 | /// `__unwind_info` section. | |
| 284 | pub fn unwindFrame( | |
| 285 | context: *DwarfUnwindContext, | |
| 286 | cache: *Cache, | |
| 287 | gpa: Allocator, | |
| 288 | unwind: *const Dwarf.Unwind, | |
| 289 | load_offset: usize, | |
| 290 | explicit_fde_offset: ?usize, | |
| 291 | ) Error!usize { | |
| 292 | return unwindFrameInner(context, cache, gpa, unwind, load_offset, explicit_fde_offset) catch |err| switch (err) { | |
| 293 | error.InvalidDebugInfo, | |
| 294 | error.MissingDebugInfo, | |
| 295 | error.UnsupportedDebugInfo, | |
| 296 | error.OutOfMemory, | |
| 297 | => |e| return e, | |
| 298 | ||
| 299 | error.UnsupportedAddrSize, | |
| 300 | error.UnimplementedUserOpcode, | |
| 301 | error.UnimplementedExpressionCall, | |
| 302 | error.UnimplementedOpcode, | |
| 303 | error.UnimplementedTypedComparison, | |
| 304 | error.UnimplementedTypeConversion, | |
| 305 | error.UnknownExpressionOpcode, | |
| 306 | error.UnsupportedRegister, | |
| 307 | => return error.UnsupportedDebugInfo, | |
| 308 | ||
| 309 | error.InvalidRegister, | |
| 310 | error.ReadFailed, | |
| 311 | error.EndOfStream, | |
| 312 | error.IncompatibleRegisterSize, | |
| 313 | error.Overflow, | |
| 314 | error.StreamTooLong, | |
| 315 | error.InvalidOperand, | |
| 316 | error.InvalidOpcode, | |
| 317 | error.InvalidOperation, | |
| 318 | error.InvalidCFARule, | |
| 319 | error.IncompleteExpressionContext, | |
| 320 | error.InvalidCFAOpcode, | |
| 321 | error.InvalidExpression, | |
| 322 | error.InvalidFrameBase, | |
| 323 | error.InvalidIntegralTypeSize, | |
| 324 | error.InvalidSubExpression, | |
| 325 | error.InvalidTypeLength, | |
| 326 | error.TruncatedIntegralType, | |
| 327 | error.DivisionByZero, | |
| 328 | error.InvalidExpressionValue, | |
| 329 | error.NoExpressionValue, | |
| 330 | error.RegisterSizeMismatch, | |
| 331 | => return error.InvalidDebugInfo, | |
| 332 | }; | |
| 333 | } | |
| 334 | fn unwindFrameInner( | |
| 335 | context: *DwarfUnwindContext, | |
| 336 | cache: *Cache, | |
| 337 | gpa: Allocator, | |
| 338 | unwind: *const Dwarf.Unwind, | |
| 339 | load_offset: usize, | |
| 340 | explicit_fde_offset: ?usize, | |
| 341 | ) !usize { | |
| 342 | comptime assert(supports_unwinding); | |
| 343 | ||
| 344 | if (context.pc == 0) return 0; | |
| 345 | ||
| 346 | const pc_vaddr = context.pc - load_offset; | |
| 347 | ||
| 348 | const cache_slot: Cache.Slot = slot: { | |
| 349 | const slot_idx = std.hash.int(pc_vaddr) % Cache.num_slots; | |
| 350 | ||
| 351 | { | |
| 352 | cache.mutex.lock(); | |
| 353 | defer cache.mutex.unlock(); | |
| 354 | if (cache.buf[slot_idx].pc == pc_vaddr) break :slot cache.buf[slot_idx]; | |
| 355 | } | |
| 356 | ||
| 357 | const fde_offset = explicit_fde_offset orelse try unwind.lookupPc( | |
| 358 | pc_vaddr, | |
| 359 | @sizeOf(usize), | |
| 360 | native_endian, | |
| 361 | ) orelse return error.MissingDebugInfo; | |
| 362 | const cie, const fde = try unwind.getFde(fde_offset, native_endian); | |
| 363 | ||
| 364 | // Check if the FDE *actually* includes the pc (`lookupPc` can return false positives). | |
| 365 | if (pc_vaddr < fde.pc_begin or pc_vaddr >= fde.pc_begin + fde.pc_range) { | |
| 366 | return error.MissingDebugInfo; | |
| 367 | } | |
| 368 | ||
| 369 | context.vm.reset(); | |
| 370 | ||
| 371 | const row = try context.vm.runTo(gpa, pc_vaddr, cie, &fde, @sizeOf(usize), native_endian); | |
| 372 | ||
| 373 | if (row.columns.len > Cache.Slot.max_regs) return error.UnsupportedDebugInfo; | |
| 374 | ||
| 375 | var slot: Cache.Slot = .{ | |
| 376 | .pc = pc_vaddr, | |
| 377 | .cie = cie, | |
| 378 | .cfa_rule = row.cfa, | |
| 379 | .rules_regs = undefined, | |
| 380 | .rules = undefined, | |
| 381 | .num_rules = 0, | |
| 382 | }; | |
| 383 | for (context.vm.rowColumns(&row)) |col| { | |
| 384 | const i = slot.num_rules; | |
| 385 | slot.rules_regs[i] = col.register; | |
| 386 | slot.rules[i] = col.rule; | |
| 387 | slot.num_rules += 1; | |
| 388 | } | |
| 389 | ||
| 390 | { | |
| 391 | cache.mutex.lock(); | |
| 392 | defer cache.mutex.unlock(); | |
| 393 | cache.buf[slot_idx] = slot; | |
| 394 | } | |
| 395 | ||
| 396 | break :slot slot; | |
| 397 | }; | |
| 398 | ||
| 399 | const format = cache_slot.cie.format; | |
| 400 | const return_address_register = cache_slot.cie.return_address_register; | |
| 401 | ||
| 402 | context.cfa = switch (cache_slot.cfa_rule) { | |
| 403 | .none => return error.InvalidCFARule, | |
| 404 | .reg_off => |ro| cfa: { | |
| 405 | const ptr = try regNative(&context.cpu_context, ro.register); | |
| 406 | break :cfa try applyOffset(ptr.*, ro.offset); | |
| 407 | }, | |
| 408 | .expression => |expr| cfa: { | |
| 409 | context.stack_machine.reset(); | |
| 410 | const value = try context.stack_machine.run(expr, gpa, .{ | |
| 411 | .format = format, | |
| 412 | .cpu_context = &context.cpu_context, | |
| 413 | }, context.cfa) orelse return error.NoExpressionValue; | |
| 414 | switch (value) { | |
| 415 | .generic => |g| break :cfa g, | |
| 416 | else => return error.InvalidExpressionValue, | |
| 417 | } | |
| 418 | }, | |
| 419 | }; | |
| 420 | ||
| 421 | // If unspecified, we'll use the default rule for the return address register, which is | |
| 422 | // typically equivalent to `.undefined` (meaning there is no return address), but may be | |
| 423 | // overriden by ABIs. | |
| 424 | var has_return_address: bool = builtin.cpu.arch.isAARCH64() and | |
| 425 | return_address_register >= 19 and | |
| 426 | return_address_register <= 28; | |
| 427 | ||
| 428 | // Create a copy of the CPU context, to which we will apply the new rules. | |
| 429 | var new_cpu_context = context.cpu_context; | |
| 430 | ||
| 431 | // On all implemented architectures, the CFA is defined as being the previous frame's SP | |
| 432 | (try regNative(&new_cpu_context, sp_reg_num)).* = context.cfa.?; | |
| 433 | ||
| 434 | const rules_len = cache_slot.num_rules; | |
| 435 | for (cache_slot.rules_regs[0..rules_len], cache_slot.rules[0..rules_len]) |register, rule| { | |
| 436 | const new_val: union(enum) { | |
| 437 | same, | |
| 438 | undefined, | |
| 439 | val: usize, | |
| 440 | bytes: []const u8, | |
| 441 | } = switch (rule) { | |
| 442 | .default => val: { | |
| 443 | // The default rule is typically equivalent to `.undefined`, but ABIs may override it. | |
| 444 | if (builtin.cpu.arch.isAARCH64() and register >= 19 and register <= 28) { | |
| 445 | break :val .same; | |
| 446 | } | |
| 447 | break :val .undefined; | |
| 448 | }, | |
| 449 | .undefined => .undefined, | |
| 450 | .same_value => .same, | |
| 451 | .offset => |offset| val: { | |
| 452 | const ptr: *const usize = @ptrFromInt(try applyOffset(context.cfa.?, offset)); | |
| 453 | break :val .{ .val = ptr.* }; | |
| 454 | }, | |
| 455 | .val_offset => |offset| .{ .val = try applyOffset(context.cfa.?, offset) }, | |
| 456 | .register => |r| .{ .bytes = try context.cpu_context.dwarfRegisterBytes(r) }, | |
| 457 | .expression => |expr| val: { | |
| 458 | context.stack_machine.reset(); | |
| 459 | const value = try context.stack_machine.run(expr, gpa, .{ | |
| 460 | .format = format, | |
| 461 | .cpu_context = &context.cpu_context, | |
| 462 | }, context.cfa.?) orelse return error.NoExpressionValue; | |
| 463 | const ptr: *const usize = switch (value) { | |
| 464 | .generic => |addr| @ptrFromInt(addr), | |
| 465 | else => return error.InvalidExpressionValue, | |
| 466 | }; | |
| 467 | break :val .{ .val = ptr.* }; | |
| 468 | }, | |
| 469 | .val_expression => |expr| val: { | |
| 470 | context.stack_machine.reset(); | |
| 471 | const value = try context.stack_machine.run(expr, gpa, .{ | |
| 472 | .format = format, | |
| 473 | .cpu_context = &context.cpu_context, | |
| 474 | }, context.cfa.?) orelse return error.NoExpressionValue; | |
| 475 | switch (value) { | |
| 476 | .generic => |val| break :val .{ .val = val }, | |
| 477 | else => return error.InvalidExpressionValue, | |
| 478 | } | |
| 479 | }, | |
| 480 | }; | |
| 481 | switch (new_val) { | |
| 482 | .same => {}, | |
| 483 | .undefined => { | |
| 484 | const dest = try new_cpu_context.dwarfRegisterBytes(@intCast(register)); | |
| 485 | @memset(dest, undefined); | |
| 486 | }, | |
| 487 | .val => |val| { | |
| 488 | const dest = try new_cpu_context.dwarfRegisterBytes(@intCast(register)); | |
| 489 | if (dest.len != @sizeOf(usize)) return error.RegisterSizeMismatch; | |
| 490 | const dest_ptr: *align(1) usize = @ptrCast(dest); | |
| 491 | dest_ptr.* = val; | |
| 492 | }, | |
| 493 | .bytes => |src| { | |
| 494 | const dest = try new_cpu_context.dwarfRegisterBytes(@intCast(register)); | |
| 495 | if (dest.len != src.len) return error.RegisterSizeMismatch; | |
| 496 | @memcpy(dest, src); | |
| 497 | }, | |
| 498 | } | |
| 499 | if (register == return_address_register) { | |
| 500 | has_return_address = new_val != .undefined; | |
| 501 | } | |
| 502 | } | |
| 503 | ||
| 504 | const return_address: usize = if (has_return_address) pc: { | |
| 505 | const raw_ptr = try regNative(&new_cpu_context, return_address_register); | |
| 506 | break :pc stripInstructionPtrAuthCode(raw_ptr.*); | |
| 507 | } else 0; | |
| 508 | ||
| 509 | (try regNative(&new_cpu_context, ip_reg_num)).* = return_address; | |
| 510 | ||
| 511 | // The new CPU context is complete; flush changes. | |
| 512 | context.cpu_context = new_cpu_context; | |
| 513 | ||
| 514 | // The caller will subtract 1 from the return address to get an address corresponding to the | |
| 515 | // function call. However, if this is a signal frame, that's actually incorrect, because the | |
| 516 | // "return address" we have is the instruction which triggered the signal (if the signal | |
| 517 | // handler returned, the instruction would be re-run). Compensate for this by incrementing | |
| 518 | // the address in that case. | |
| 519 | const adjusted_ret_addr = if (cache_slot.cie.is_signal_frame) return_address +| 1 else return_address; | |
| 520 | ||
| 521 | // We also want to do that same subtraction here to get the PC for the next frame's FDE. | |
| 522 | // This is because if the callee was noreturn, then the function call might be the caller's | |
| 523 | // last instruction, so `return_address` might actually point outside of it! | |
| 524 | context.pc = adjusted_ret_addr -| 1; | |
| 525 | ||
| 526 | return adjusted_ret_addr; | |
| 527 | } | |
| 528 | /// Since register rules are applied (usually) during a panic, | |
| 529 | /// checked addition / subtraction is used so that we can return | |
| 530 | /// an error and fall back to FP-based unwinding. | |
| 531 | fn applyOffset(base: usize, offset: i64) !usize { | |
| 532 | return if (offset >= 0) | |
| 533 | try std.math.add(usize, base, @as(usize, @intCast(offset))) | |
| 534 | else | |
| 535 | try std.math.sub(usize, base, @as(usize, @intCast(-offset))); | |
| 536 | } | |
| 537 | ||
| 538 | pub fn regNative(ctx: *CpuContext, num: u16) error{ | |
| 539 | InvalidRegister, | |
| 540 | UnsupportedRegister, | |
| 541 | IncompatibleRegisterSize, | |
| 542 | }!*align(1) usize { | |
| 543 | const bytes = try ctx.dwarfRegisterBytes(num); | |
| 544 | if (bytes.len != @sizeOf(usize)) return error.IncompatibleRegisterSize; | |
| 545 | return @ptrCast(bytes); | |
| 546 | } | |
| 547 | ||
| 548 | const ip_reg_num = Dwarf.ipRegNum(native_arch).?; | |
| 549 | const fp_reg_num = Dwarf.fpRegNum(native_arch); | |
| 550 | const sp_reg_num = Dwarf.spRegNum(native_arch); | |
| 551 | }; |
lib/std/debug/SelfInfo/Darwin.zig created+993| ... | ... | @@ -0,0 +1,993 @@ |
| 1 | mutex: std.Thread.Mutex, | |
| 2 | /// Accessed through `Module.Adapter`. | |
| 3 | modules: std.ArrayHashMapUnmanaged(Module, void, Module.Context, false), | |
| 4 | ofiles: std.StringArrayHashMapUnmanaged(?OFile), | |
| 5 | ||
| 6 | pub const init: SelfInfo = .{ | |
| 7 | .mutex = .{}, | |
| 8 | .modules = .empty, | |
| 9 | .ofiles = .empty, | |
| 10 | }; | |
| 11 | pub fn deinit(si: *SelfInfo, gpa: Allocator) void { | |
| 12 | for (si.modules.keys()) |*module| { | |
| 13 | unwind: { | |
| 14 | const u = &(module.unwind orelse break :unwind catch break :unwind); | |
| 15 | if (u.dwarf) |*dwarf| dwarf.deinit(gpa); | |
| 16 | } | |
| 17 | loaded: { | |
| 18 | const l = &(module.loaded_macho orelse break :loaded catch break :loaded); | |
| 19 | gpa.free(l.symbols); | |
| 20 | posix.munmap(l.mapped_memory); | |
| 21 | } | |
| 22 | } | |
| 23 | for (si.ofiles.values()) |*opt_ofile| { | |
| 24 | const ofile = &(opt_ofile.* orelse continue); | |
| 25 | ofile.dwarf.deinit(gpa); | |
| 26 | ofile.symbols_by_name.deinit(gpa); | |
| 27 | posix.munmap(ofile.mapped_memory); | |
| 28 | } | |
| 29 | si.modules.deinit(gpa); | |
| 30 | si.ofiles.deinit(gpa); | |
| 31 | } | |
| 32 | ||
| 33 | pub fn getSymbol(si: *SelfInfo, gpa: Allocator, address: usize) Error!std.debug.Symbol { | |
| 34 | const module = try si.findModule(gpa, address); | |
| 35 | defer si.mutex.unlock(); | |
| 36 | ||
| 37 | const loaded_macho = try module.getLoadedMachO(gpa); | |
| 38 | ||
| 39 | const vaddr = address - loaded_macho.vaddr_offset; | |
| 40 | const symbol = MachoSymbol.find(loaded_macho.symbols, vaddr) orelse return .unknown; | |
| 41 | ||
| 42 | // offset of `address` from start of `symbol` | |
| 43 | const address_symbol_offset = vaddr - symbol.addr; | |
| 44 | ||
| 45 | // Take the symbol name from the N_FUN STAB entry, we're going to | |
| 46 | // use it if we fail to find the DWARF infos | |
| 47 | const stab_symbol = mem.sliceTo(loaded_macho.strings[symbol.strx..], 0); | |
| 48 | ||
| 49 | // If any information is missing, we can at least return this from now on. | |
| 50 | const sym_only_result: std.debug.Symbol = .{ | |
| 51 | .name = stab_symbol, | |
| 52 | .compile_unit_name = null, | |
| 53 | .source_location = null, | |
| 54 | }; | |
| 55 | ||
| 56 | if (symbol.ofile == MachoSymbol.unknown_ofile) { | |
| 57 | // We don't have STAB info, so can't track down the object file; all we can do is the symbol name. | |
| 58 | return sym_only_result; | |
| 59 | } | |
| 60 | ||
| 61 | const o_file: *OFile = of: { | |
| 62 | const path = mem.sliceTo(loaded_macho.strings[symbol.ofile..], 0); | |
| 63 | const gop = try si.ofiles.getOrPut(gpa, path); | |
| 64 | if (!gop.found_existing) { | |
| 65 | gop.value_ptr.* = loadOFile(gpa, path) catch null; | |
| 66 | } | |
| 67 | if (gop.value_ptr.*) |*o_file| { | |
| 68 | break :of o_file; | |
| 69 | } else { | |
| 70 | return sym_only_result; | |
| 71 | } | |
| 72 | }; | |
| 73 | ||
| 74 | const symbol_index = o_file.symbols_by_name.getKeyAdapted( | |
| 75 | @as([]const u8, stab_symbol), | |
| 76 | @as(OFile.SymbolAdapter, .{ .strtab = o_file.strtab, .symtab = o_file.symtab }), | |
| 77 | ) orelse return sym_only_result; | |
| 78 | const symbol_ofile_vaddr = o_file.symtab[symbol_index].n_value; | |
| 79 | ||
| 80 | const compile_unit = o_file.dwarf.findCompileUnit(native_endian, symbol_ofile_vaddr) catch return sym_only_result; | |
| 81 | ||
| 82 | return .{ | |
| 83 | .name = o_file.dwarf.getSymbolName(symbol_ofile_vaddr + address_symbol_offset) orelse stab_symbol, | |
| 84 | .compile_unit_name = compile_unit.die.getAttrString( | |
| 85 | &o_file.dwarf, | |
| 86 | native_endian, | |
| 87 | std.dwarf.AT.name, | |
| 88 | o_file.dwarf.section(.debug_str), | |
| 89 | compile_unit, | |
| 90 | ) catch |err| switch (err) { | |
| 91 | error.MissingDebugInfo, error.InvalidDebugInfo => null, | |
| 92 | }, | |
| 93 | .source_location = o_file.dwarf.getLineNumberInfo( | |
| 94 | gpa, | |
| 95 | native_endian, | |
| 96 | compile_unit, | |
| 97 | symbol_ofile_vaddr + address_symbol_offset, | |
| 98 | ) catch null, | |
| 99 | }; | |
| 100 | } | |
| 101 | pub fn getModuleName(si: *SelfInfo, gpa: Allocator, address: usize) Error![]const u8 { | |
| 102 | const module = try si.findModule(gpa, address); | |
| 103 | defer si.mutex.unlock(); | |
| 104 | return module.name; | |
| 105 | } | |
| 106 | ||
| 107 | pub const can_unwind: bool = true; | |
| 108 | pub const UnwindContext = std.debug.Dwarf.SelfUnwinder; | |
| 109 | /// Unwind a frame using MachO compact unwind info (from `__unwind_info`). | |
| 110 | /// If the compact encoding can't encode a way to unwind a frame, it will | |
| 111 | /// defer unwinding to DWARF, in which case `__eh_frame` will be used if available. | |
| 112 | pub fn unwindFrame(si: *SelfInfo, gpa: Allocator, context: *UnwindContext) Error!usize { | |
| 113 | return unwindFrameInner(si, gpa, context) catch |err| switch (err) { | |
| 114 | error.InvalidDebugInfo, | |
| 115 | error.MissingDebugInfo, | |
| 116 | error.UnsupportedDebugInfo, | |
| 117 | error.ReadFailed, | |
| 118 | error.OutOfMemory, | |
| 119 | error.Unexpected, | |
| 120 | => |e| return e, | |
| 121 | error.UnsupportedRegister, | |
| 122 | error.UnsupportedAddrSize, | |
| 123 | error.UnimplementedUserOpcode, | |
| 124 | => return error.UnsupportedDebugInfo, | |
| 125 | error.Overflow, | |
| 126 | error.EndOfStream, | |
| 127 | error.StreamTooLong, | |
| 128 | error.InvalidOpcode, | |
| 129 | error.InvalidOperation, | |
| 130 | error.InvalidOperand, | |
| 131 | error.InvalidRegister, | |
| 132 | error.IncompatibleRegisterSize, | |
| 133 | => return error.InvalidDebugInfo, | |
| 134 | }; | |
| 135 | } | |
| 136 | fn unwindFrameInner(si: *SelfInfo, gpa: Allocator, context: *UnwindContext) !usize { | |
| 137 | const module = try si.findModule(gpa, context.pc); | |
| 138 | defer si.mutex.unlock(); | |
| 139 | ||
| 140 | const unwind: *Module.Unwind = try module.getUnwindInfo(gpa); | |
| 141 | ||
| 142 | const ip_reg_num = comptime Dwarf.ipRegNum(builtin.target.cpu.arch).?; | |
| 143 | const fp_reg_num = comptime Dwarf.fpRegNum(builtin.target.cpu.arch); | |
| 144 | const sp_reg_num = comptime Dwarf.spRegNum(builtin.target.cpu.arch); | |
| 145 | ||
| 146 | const unwind_info = unwind.unwind_info orelse return error.MissingDebugInfo; | |
| 147 | if (unwind_info.len < @sizeOf(macho.unwind_info_section_header)) return error.InvalidDebugInfo; | |
| 148 | const header: *align(1) const macho.unwind_info_section_header = @ptrCast(unwind_info); | |
| 149 | ||
| 150 | const index_byte_count = header.indexCount * @sizeOf(macho.unwind_info_section_header_index_entry); | |
| 151 | if (unwind_info.len < header.indexSectionOffset + index_byte_count) return error.InvalidDebugInfo; | |
| 152 | const indices: []align(1) const macho.unwind_info_section_header_index_entry = @ptrCast(unwind_info[header.indexSectionOffset..][0..index_byte_count]); | |
| 153 | if (indices.len == 0) return error.MissingDebugInfo; | |
| 154 | ||
| 155 | // offset of the PC into the `__TEXT` segment | |
| 156 | const pc_text_offset = context.pc - module.text_base; | |
| 157 | ||
| 158 | const start_offset: u32, const first_level_offset: u32 = index: { | |
| 159 | var left: usize = 0; | |
| 160 | var len: usize = indices.len; | |
| 161 | while (len > 1) { | |
| 162 | const mid = left + len / 2; | |
| 163 | if (pc_text_offset < indices[mid].functionOffset) { | |
| 164 | len /= 2; | |
| 165 | } else { | |
| 166 | left = mid; | |
| 167 | len -= len / 2; | |
| 168 | } | |
| 169 | } | |
| 170 | break :index .{ indices[left].secondLevelPagesSectionOffset, indices[left].functionOffset }; | |
| 171 | }; | |
| 172 | // An offset of 0 is a sentinel indicating a range does not have unwind info. | |
| 173 | if (start_offset == 0) return error.MissingDebugInfo; | |
| 174 | ||
| 175 | const common_encodings_byte_count = header.commonEncodingsArrayCount * @sizeOf(macho.compact_unwind_encoding_t); | |
| 176 | if (unwind_info.len < header.commonEncodingsArraySectionOffset + common_encodings_byte_count) return error.InvalidDebugInfo; | |
| 177 | const common_encodings: []align(1) const macho.compact_unwind_encoding_t = @ptrCast( | |
| 178 | unwind_info[header.commonEncodingsArraySectionOffset..][0..common_encodings_byte_count], | |
| 179 | ); | |
| 180 | ||
| 181 | if (unwind_info.len < start_offset + @sizeOf(macho.UNWIND_SECOND_LEVEL)) return error.InvalidDebugInfo; | |
| 182 | const kind: *align(1) const macho.UNWIND_SECOND_LEVEL = @ptrCast(unwind_info[start_offset..]); | |
| 183 | ||
| 184 | const entry: struct { | |
| 185 | function_offset: usize, | |
| 186 | raw_encoding: u32, | |
| 187 | } = switch (kind.*) { | |
| 188 | .REGULAR => entry: { | |
| 189 | if (unwind_info.len < start_offset + @sizeOf(macho.unwind_info_regular_second_level_page_header)) return error.InvalidDebugInfo; | |
| 190 | const page_header: *align(1) const macho.unwind_info_regular_second_level_page_header = @ptrCast(unwind_info[start_offset..]); | |
| 191 | ||
| 192 | const entries_byte_count = page_header.entryCount * @sizeOf(macho.unwind_info_regular_second_level_entry); | |
| 193 | if (unwind_info.len < start_offset + entries_byte_count) return error.InvalidDebugInfo; | |
| 194 | const entries: []align(1) const macho.unwind_info_regular_second_level_entry = @ptrCast( | |
| 195 | unwind_info[start_offset + page_header.entryPageOffset ..][0..entries_byte_count], | |
| 196 | ); | |
| 197 | if (entries.len == 0) return error.InvalidDebugInfo; | |
| 198 | ||
| 199 | var left: usize = 0; | |
| 200 | var len: usize = entries.len; | |
| 201 | while (len > 1) { | |
| 202 | const mid = left + len / 2; | |
| 203 | if (pc_text_offset < entries[mid].functionOffset) { | |
| 204 | len /= 2; | |
| 205 | } else { | |
| 206 | left = mid; | |
| 207 | len -= len / 2; | |
| 208 | } | |
| 209 | } | |
| 210 | break :entry .{ | |
| 211 | .function_offset = entries[left].functionOffset, | |
| 212 | .raw_encoding = entries[left].encoding, | |
| 213 | }; | |
| 214 | }, | |
| 215 | .COMPRESSED => entry: { | |
| 216 | if (unwind_info.len < start_offset + @sizeOf(macho.unwind_info_compressed_second_level_page_header)) return error.InvalidDebugInfo; | |
| 217 | const page_header: *align(1) const macho.unwind_info_compressed_second_level_page_header = @ptrCast(unwind_info[start_offset..]); | |
| 218 | ||
| 219 | const entries_byte_count = page_header.entryCount * @sizeOf(macho.UnwindInfoCompressedEntry); | |
| 220 | if (unwind_info.len < start_offset + entries_byte_count) return error.InvalidDebugInfo; | |
| 221 | const entries: []align(1) const macho.UnwindInfoCompressedEntry = @ptrCast( | |
| 222 | unwind_info[start_offset + page_header.entryPageOffset ..][0..entries_byte_count], | |
| 223 | ); | |
| 224 | if (entries.len == 0) return error.InvalidDebugInfo; | |
| 225 | ||
| 226 | var left: usize = 0; | |
| 227 | var len: usize = entries.len; | |
| 228 | while (len > 1) { | |
| 229 | const mid = left + len / 2; | |
| 230 | if (pc_text_offset < first_level_offset + entries[mid].funcOffset) { | |
| 231 | len /= 2; | |
| 232 | } else { | |
| 233 | left = mid; | |
| 234 | len -= len / 2; | |
| 235 | } | |
| 236 | } | |
| 237 | const entry = entries[left]; | |
| 238 | ||
| 239 | const function_offset = first_level_offset + entry.funcOffset; | |
| 240 | if (entry.encodingIndex < common_encodings.len) { | |
| 241 | break :entry .{ | |
| 242 | .function_offset = function_offset, | |
| 243 | .raw_encoding = common_encodings[entry.encodingIndex], | |
| 244 | }; | |
| 245 | } | |
| 246 | ||
| 247 | const local_index = entry.encodingIndex - common_encodings.len; | |
| 248 | const local_encodings_byte_count = page_header.encodingsCount * @sizeOf(macho.compact_unwind_encoding_t); | |
| 249 | if (unwind_info.len < start_offset + page_header.encodingsPageOffset + local_encodings_byte_count) return error.InvalidDebugInfo; | |
| 250 | const local_encodings: []align(1) const macho.compact_unwind_encoding_t = @ptrCast( | |
| 251 | unwind_info[start_offset + page_header.encodingsPageOffset ..][0..local_encodings_byte_count], | |
| 252 | ); | |
| 253 | if (local_index >= local_encodings.len) return error.InvalidDebugInfo; | |
| 254 | break :entry .{ | |
| 255 | .function_offset = function_offset, | |
| 256 | .raw_encoding = local_encodings[local_index], | |
| 257 | }; | |
| 258 | }, | |
| 259 | else => return error.InvalidDebugInfo, | |
| 260 | }; | |
| 261 | ||
| 262 | if (entry.raw_encoding == 0) return error.MissingDebugInfo; | |
| 263 | ||
| 264 | const encoding: macho.CompactUnwindEncoding = @bitCast(entry.raw_encoding); | |
| 265 | const new_ip = switch (builtin.cpu.arch) { | |
| 266 | .x86_64 => switch (encoding.mode.x86_64) { | |
| 267 | .OLD => return error.UnsupportedDebugInfo, | |
| 268 | .RBP_FRAME => ip: { | |
| 269 | const frame = encoding.value.x86_64.frame; | |
| 270 | ||
| 271 | const fp = (try dwarfRegNative(&context.cpu_state, fp_reg_num)).*; | |
| 272 | const new_sp = fp + 2 * @sizeOf(usize); | |
| 273 | ||
| 274 | const ip_ptr = fp + @sizeOf(usize); | |
| 275 | const new_ip = @as(*const usize, @ptrFromInt(ip_ptr)).*; | |
| 276 | const new_fp = @as(*const usize, @ptrFromInt(fp)).*; | |
| 277 | ||
| 278 | (try dwarfRegNative(&context.cpu_state, fp_reg_num)).* = new_fp; | |
| 279 | (try dwarfRegNative(&context.cpu_state, sp_reg_num)).* = new_sp; | |
| 280 | (try dwarfRegNative(&context.cpu_state, ip_reg_num)).* = new_ip; | |
| 281 | ||
| 282 | const regs: [5]u3 = .{ | |
| 283 | frame.reg0, | |
| 284 | frame.reg1, | |
| 285 | frame.reg2, | |
| 286 | frame.reg3, | |
| 287 | frame.reg4, | |
| 288 | }; | |
| 289 | for (regs, 0..) |reg, i| { | |
| 290 | if (reg == 0) continue; | |
| 291 | const addr = fp - frame.frame_offset * @sizeOf(usize) + i * @sizeOf(usize); | |
| 292 | const reg_number = try Dwarf.compactUnwindToDwarfRegNumber(reg); | |
| 293 | (try dwarfRegNative(&context.cpu_state, reg_number)).* = @as(*const usize, @ptrFromInt(addr)).*; | |
| 294 | } | |
| 295 | ||
| 296 | break :ip new_ip; | |
| 297 | }, | |
| 298 | .STACK_IMMD, | |
| 299 | .STACK_IND, | |
| 300 | => ip: { | |
| 301 | const frameless = encoding.value.x86_64.frameless; | |
| 302 | ||
| 303 | const sp = (try dwarfRegNative(&context.cpu_state, sp_reg_num)).*; | |
| 304 | const stack_size: usize = stack_size: { | |
| 305 | if (encoding.mode.x86_64 == .STACK_IMMD) { | |
| 306 | break :stack_size @as(usize, frameless.stack.direct.stack_size) * @sizeOf(usize); | |
| 307 | } | |
| 308 | // In .STACK_IND, the stack size is inferred from the subq instruction at the beginning of the function. | |
| 309 | const sub_offset_addr = | |
| 310 | module.text_base + | |
| 311 | entry.function_offset + | |
| 312 | frameless.stack.indirect.sub_offset; | |
| 313 | // `sub_offset_addr` points to the offset of the literal within the instruction | |
| 314 | const sub_operand = @as(*align(1) const u32, @ptrFromInt(sub_offset_addr)).*; | |
| 315 | break :stack_size sub_operand + @sizeOf(usize) * @as(usize, frameless.stack.indirect.stack_adjust); | |
| 316 | }; | |
| 317 | ||
| 318 | // Decode the Lehmer-coded sequence of registers. | |
| 319 | // For a description of the encoding see lib/libc/include/any-macos.13-any/mach-o/compact_unwind_encoding.h | |
| 320 | ||
| 321 | // Decode the variable-based permutation number into its digits. Each digit represents | |
| 322 | // an index into the list of register numbers that weren't yet used in the sequence at | |
| 323 | // the time the digit was added. | |
| 324 | const reg_count = frameless.stack_reg_count; | |
| 325 | const ip_ptr = ip_ptr: { | |
| 326 | var digits: [6]u3 = undefined; | |
| 327 | var accumulator: usize = frameless.stack_reg_permutation; | |
| 328 | var base: usize = 2; | |
| 329 | for (0..reg_count) |i| { | |
| 330 | const div = accumulator / base; | |
| 331 | digits[digits.len - 1 - i] = @intCast(accumulator - base * div); | |
| 332 | accumulator = div; | |
| 333 | base += 1; | |
| 334 | } | |
| 335 | ||
| 336 | var registers: [6]u3 = undefined; | |
| 337 | var used_indices: [6]bool = @splat(false); | |
| 338 | for (digits[digits.len - reg_count ..], 0..) |target_unused_index, i| { | |
| 339 | var unused_count: u8 = 0; | |
| 340 | const unused_index = for (used_indices, 0..) |used, index| { | |
| 341 | if (!used) { | |
| 342 | if (target_unused_index == unused_count) break index; | |
| 343 | unused_count += 1; | |
| 344 | } | |
| 345 | } else unreachable; | |
| 346 | registers[i] = @intCast(unused_index + 1); | |
| 347 | used_indices[unused_index] = true; | |
| 348 | } | |
| 349 | ||
| 350 | var reg_addr = sp + stack_size - @sizeOf(usize) * @as(usize, reg_count + 1); | |
| 351 | for (0..reg_count) |i| { | |
| 352 | const reg_number = try Dwarf.compactUnwindToDwarfRegNumber(registers[i]); | |
| 353 | (try dwarfRegNative(&context.cpu_state, reg_number)).* = @as(*const usize, @ptrFromInt(reg_addr)).*; | |
| 354 | reg_addr += @sizeOf(usize); | |
| 355 | } | |
| 356 | ||
| 357 | break :ip_ptr reg_addr; | |
| 358 | }; | |
| 359 | ||
| 360 | const new_ip = @as(*const usize, @ptrFromInt(ip_ptr)).*; | |
| 361 | const new_sp = ip_ptr + @sizeOf(usize); | |
| 362 | ||
| 363 | (try dwarfRegNative(&context.cpu_state, sp_reg_num)).* = new_sp; | |
| 364 | (try dwarfRegNative(&context.cpu_state, ip_reg_num)).* = new_ip; | |
| 365 | ||
| 366 | break :ip new_ip; | |
| 367 | }, | |
| 368 | .DWARF => { | |
| 369 | const dwarf = &(unwind.dwarf orelse return error.MissingDebugInfo); | |
| 370 | const rules = try context.computeRules(gpa, dwarf, unwind.vmaddr_slide, encoding.value.x86_64.dwarf); | |
| 371 | return context.next(gpa, &rules); | |
| 372 | }, | |
| 373 | }, | |
| 374 | .aarch64, .aarch64_be => switch (encoding.mode.arm64) { | |
| 375 | .OLD => return error.UnsupportedDebugInfo, | |
| 376 | .FRAMELESS => ip: { | |
| 377 | const sp = (try dwarfRegNative(&context.cpu_state, sp_reg_num)).*; | |
| 378 | const new_sp = sp + encoding.value.arm64.frameless.stack_size * 16; | |
| 379 | const new_ip = (try dwarfRegNative(&context.cpu_state, 30)).*; | |
| 380 | (try dwarfRegNative(&context.cpu_state, sp_reg_num)).* = new_sp; | |
| 381 | break :ip new_ip; | |
| 382 | }, | |
| 383 | .DWARF => { | |
| 384 | const dwarf = &(unwind.dwarf orelse return error.MissingDebugInfo); | |
| 385 | const rules = try context.computeRules(gpa, dwarf, unwind.vmaddr_slide, encoding.value.arm64.dwarf); | |
| 386 | return context.next(gpa, &rules); | |
| 387 | }, | |
| 388 | .FRAME => ip: { | |
| 389 | const frame = encoding.value.arm64.frame; | |
| 390 | ||
| 391 | const fp = (try dwarfRegNative(&context.cpu_state, fp_reg_num)).*; | |
| 392 | const ip_ptr = fp + @sizeOf(usize); | |
| 393 | ||
| 394 | var reg_addr = fp - @sizeOf(usize); | |
| 395 | inline for (@typeInfo(@TypeOf(frame.x_reg_pairs)).@"struct".fields, 0..) |field, i| { | |
| 396 | if (@field(frame.x_reg_pairs, field.name) != 0) { | |
| 397 | (try dwarfRegNative(&context.cpu_state, 19 + i)).* = @as(*const usize, @ptrFromInt(reg_addr)).*; | |
| 398 | reg_addr += @sizeOf(usize); | |
| 399 | (try dwarfRegNative(&context.cpu_state, 20 + i)).* = @as(*const usize, @ptrFromInt(reg_addr)).*; | |
| 400 | reg_addr += @sizeOf(usize); | |
| 401 | } | |
| 402 | } | |
| 403 | ||
| 404 | inline for (@typeInfo(@TypeOf(frame.d_reg_pairs)).@"struct".fields, 0..) |field, i| { | |
| 405 | if (@field(frame.d_reg_pairs, field.name) != 0) { | |
| 406 | // Only the lower half of the 128-bit V registers are restored during unwinding | |
| 407 | { | |
| 408 | const dest: *align(1) usize = @ptrCast(try context.cpu_state.dwarfRegisterBytes(64 + 8 + i)); | |
| 409 | dest.* = @as(*const usize, @ptrFromInt(reg_addr)).*; | |
| 410 | } | |
| 411 | reg_addr += @sizeOf(usize); | |
| 412 | { | |
| 413 | const dest: *align(1) usize = @ptrCast(try context.cpu_state.dwarfRegisterBytes(64 + 9 + i)); | |
| 414 | dest.* = @as(*const usize, @ptrFromInt(reg_addr)).*; | |
| 415 | } | |
| 416 | reg_addr += @sizeOf(usize); | |
| 417 | } | |
| 418 | } | |
| 419 | ||
| 420 | const new_ip = @as(*const usize, @ptrFromInt(ip_ptr)).*; | |
| 421 | const new_fp = @as(*const usize, @ptrFromInt(fp)).*; | |
| 422 | ||
| 423 | (try dwarfRegNative(&context.cpu_state, fp_reg_num)).* = new_fp; | |
| 424 | (try dwarfRegNative(&context.cpu_state, ip_reg_num)).* = new_ip; | |
| 425 | ||
| 426 | break :ip new_ip; | |
| 427 | }, | |
| 428 | }, | |
| 429 | else => comptime unreachable, // unimplemented | |
| 430 | }; | |
| 431 | ||
| 432 | const ret_addr = std.debug.stripInstructionPtrAuthCode(new_ip); | |
| 433 | ||
| 434 | // Like `Dwarf.SelfUnwinder.next`, adjust our next lookup pc in case the `call` was this | |
| 435 | // function's last instruction making `ret_addr` one byte past its end. | |
| 436 | context.pc = ret_addr -| 1; | |
| 437 | ||
| 438 | return ret_addr; | |
| 439 | } | |
| 440 | ||
| 441 | /// Acquires the mutex on success. | |
| 442 | fn findModule(si: *SelfInfo, gpa: Allocator, address: usize) Error!*Module { | |
| 443 | var info: std.c.dl_info = undefined; | |
| 444 | if (std.c.dladdr(@ptrFromInt(address), &info) == 0) { | |
| 445 | return error.MissingDebugInfo; | |
| 446 | } | |
| 447 | si.mutex.lock(); | |
| 448 | errdefer si.mutex.unlock(); | |
| 449 | const gop = try si.modules.getOrPutAdapted(gpa, @intFromPtr(info.fbase), Module.Adapter{}); | |
| 450 | errdefer comptime unreachable; | |
| 451 | if (!gop.found_existing) { | |
| 452 | gop.key_ptr.* = .{ | |
| 453 | .text_base = @intFromPtr(info.fbase), | |
| 454 | .name = std.mem.span(info.fname), | |
| 455 | .unwind = null, | |
| 456 | .loaded_macho = null, | |
| 457 | }; | |
| 458 | } | |
| 459 | return gop.key_ptr; | |
| 460 | } | |
| 461 | ||
| 462 | const Module = struct { | |
| 463 | text_base: usize, | |
| 464 | name: []const u8, | |
| 465 | unwind: ?(Error!Unwind), | |
| 466 | loaded_macho: ?(Error!LoadedMachO), | |
| 467 | ||
| 468 | const Adapter = struct { | |
| 469 | pub fn hash(_: Adapter, text_base: usize) u32 { | |
| 470 | return @truncate(std.hash.int(text_base)); | |
| 471 | } | |
| 472 | pub fn eql(_: Adapter, a_text_base: usize, b_module: Module, b_index: usize) bool { | |
| 473 | _ = b_index; | |
| 474 | return a_text_base == b_module.text_base; | |
| 475 | } | |
| 476 | }; | |
| 477 | const Context = struct { | |
| 478 | pub fn hash(_: Context, module: Module) u32 { | |
| 479 | return @truncate(std.hash.int(module.text_base)); | |
| 480 | } | |
| 481 | pub fn eql(_: Context, a_module: Module, b_module: Module, b_index: usize) bool { | |
| 482 | _ = b_index; | |
| 483 | return a_module.text_base == b_module.text_base; | |
| 484 | } | |
| 485 | }; | |
| 486 | ||
| 487 | const Unwind = struct { | |
| 488 | /// The slide applied to the `__unwind_info` and `__eh_frame` sections. | |
| 489 | /// So, `unwind_info.ptr` is this many bytes higher than the section's vmaddr. | |
| 490 | vmaddr_slide: u64, | |
| 491 | /// Backed by the in-memory section mapped by the loader. | |
| 492 | unwind_info: ?[]const u8, | |
| 493 | /// Backed by the in-memory `__eh_frame` section mapped by the loader. | |
| 494 | dwarf: ?Dwarf.Unwind, | |
| 495 | }; | |
| 496 | ||
| 497 | const LoadedMachO = struct { | |
| 498 | mapped_memory: []align(std.heap.page_size_min) const u8, | |
| 499 | symbols: []const MachoSymbol, | |
| 500 | strings: []const u8, | |
| 501 | /// This is not necessarily the same as the vmaddr_slide that dyld would report. This is | |
| 502 | /// because the segments in the file on disk might differ from the ones in memory. Normally | |
| 503 | /// we wouldn't necessarily expect that to work, but /usr/lib/dyld is incredibly annoying: | |
| 504 | /// it exists on disk (necessarily, because the kernel needs to load it!), but is also in | |
| 505 | /// the dyld cache (dyld actually restart itself from cache after loading it), and the two | |
| 506 | /// versions have (very) different segment base addresses. It's sort of like a large slide | |
| 507 | /// has been applied to all addresses in memory. For an optimal experience, we consider the | |
| 508 | /// on-disk vmaddr instead of the in-memory one. | |
| 509 | vaddr_offset: usize, | |
| 510 | }; | |
| 511 | ||
| 512 | fn getUnwindInfo(module: *Module, gpa: Allocator) Error!*Unwind { | |
| 513 | if (module.unwind == null) module.unwind = loadUnwindInfo(module, gpa); | |
| 514 | return if (module.unwind.?) |*unwind| unwind else |err| err; | |
| 515 | } | |
| 516 | fn loadUnwindInfo(module: *const Module, gpa: Allocator) Error!Unwind { | |
| 517 | const header: *std.macho.mach_header = @ptrFromInt(module.text_base); | |
| 518 | ||
| 519 | var it: macho.LoadCommandIterator = .{ | |
| 520 | .ncmds = header.ncmds, | |
| 521 | .buffer = @as([*]u8, @ptrCast(header))[@sizeOf(macho.mach_header_64)..][0..header.sizeofcmds], | |
| 522 | }; | |
| 523 | const sections, const text_vmaddr = while (it.next()) |load_cmd| { | |
| 524 | if (load_cmd.cmd() != .SEGMENT_64) continue; | |
| 525 | const segment_cmd = load_cmd.cast(macho.segment_command_64).?; | |
| 526 | if (!mem.eql(u8, segment_cmd.segName(), "__TEXT")) continue; | |
| 527 | break .{ load_cmd.getSections(), segment_cmd.vmaddr }; | |
| 528 | } else unreachable; | |
| 529 | ||
| 530 | const vmaddr_slide = module.text_base - text_vmaddr; | |
| 531 | ||
| 532 | var opt_unwind_info: ?[]const u8 = null; | |
| 533 | var opt_eh_frame: ?[]const u8 = null; | |
| 534 | for (sections) |sect| { | |
| 535 | if (mem.eql(u8, sect.sectName(), "__unwind_info")) { | |
| 536 | const sect_ptr: [*]u8 = @ptrFromInt(@as(usize, @intCast(vmaddr_slide + sect.addr))); | |
| 537 | opt_unwind_info = sect_ptr[0..@intCast(sect.size)]; | |
| 538 | } else if (mem.eql(u8, sect.sectName(), "__eh_frame")) { | |
| 539 | const sect_ptr: [*]u8 = @ptrFromInt(@as(usize, @intCast(vmaddr_slide + sect.addr))); | |
| 540 | opt_eh_frame = sect_ptr[0..@intCast(sect.size)]; | |
| 541 | } | |
| 542 | } | |
| 543 | const eh_frame = opt_eh_frame orelse return .{ | |
| 544 | .vmaddr_slide = vmaddr_slide, | |
| 545 | .unwind_info = opt_unwind_info, | |
| 546 | .dwarf = null, | |
| 547 | }; | |
| 548 | var dwarf: Dwarf.Unwind = .initSection(.eh_frame, @intFromPtr(eh_frame.ptr) - vmaddr_slide, eh_frame); | |
| 549 | errdefer dwarf.deinit(gpa); | |
| 550 | // We don't need lookups, so this call is just for scanning CIEs. | |
| 551 | dwarf.prepare(gpa, @sizeOf(usize), native_endian, false, true) catch |err| switch (err) { | |
| 552 | error.ReadFailed => unreachable, // it's all fixed buffers | |
| 553 | error.InvalidDebugInfo, | |
| 554 | error.MissingDebugInfo, | |
| 555 | error.OutOfMemory, | |
| 556 | => |e| return e, | |
| 557 | error.EndOfStream, | |
| 558 | error.Overflow, | |
| 559 | error.StreamTooLong, | |
| 560 | error.InvalidOperand, | |
| 561 | error.InvalidOpcode, | |
| 562 | error.InvalidOperation, | |
| 563 | => return error.InvalidDebugInfo, | |
| 564 | error.UnsupportedAddrSize, | |
| 565 | error.UnsupportedDwarfVersion, | |
| 566 | error.UnimplementedUserOpcode, | |
| 567 | => return error.UnsupportedDebugInfo, | |
| 568 | }; | |
| 569 | ||
| 570 | return .{ | |
| 571 | .vmaddr_slide = vmaddr_slide, | |
| 572 | .unwind_info = opt_unwind_info, | |
| 573 | .dwarf = dwarf, | |
| 574 | }; | |
| 575 | } | |
| 576 | ||
| 577 | fn getLoadedMachO(module: *Module, gpa: Allocator) Error!*LoadedMachO { | |
| 578 | if (module.loaded_macho == null) module.loaded_macho = loadMachO(module, gpa) catch |err| switch (err) { | |
| 579 | error.InvalidDebugInfo, error.MissingDebugInfo, error.OutOfMemory, error.Unexpected => |e| e, | |
| 580 | else => error.ReadFailed, | |
| 581 | }; | |
| 582 | return if (module.loaded_macho.?) |*lm| lm else |err| err; | |
| 583 | } | |
| 584 | fn loadMachO(module: *const Module, gpa: Allocator) Error!LoadedMachO { | |
| 585 | const all_mapped_memory = try mapDebugInfoFile(module.name); | |
| 586 | errdefer posix.munmap(all_mapped_memory); | |
| 587 | ||
| 588 | // In most cases, the file we just mapped is a Mach-O binary. However, it could be a "universal | |
| 589 | // binary": a simple file format which contains Mach-O binaries for multiple targets. For | |
| 590 | // instance, `/usr/lib/dyld` is currently distributed as a universal binary containing images | |
| 591 | // for both ARM64 macOS and x86_64 macOS. | |
| 592 | if (all_mapped_memory.len < 4) return error.InvalidDebugInfo; | |
| 593 | const magic = @as(*const u32, @ptrCast(all_mapped_memory.ptr)).*; | |
| 594 | // The contents of a Mach-O file, which may or may not be the whole of `all_mapped_memory`. | |
| 595 | const mapped_macho = switch (magic) { | |
| 596 | macho.MH_MAGIC_64 => all_mapped_memory, | |
| 597 | ||
| 598 | macho.FAT_CIGAM => mapped_macho: { | |
| 599 | // This is the universal binary format (aka a "fat binary"). Annoyingly, the whole thing | |
| 600 | // is big-endian, so we'll be swapping some bytes. | |
| 601 | if (all_mapped_memory.len < @sizeOf(macho.fat_header)) return error.InvalidDebugInfo; | |
| 602 | const hdr: *const macho.fat_header = @ptrCast(all_mapped_memory.ptr); | |
| 603 | const archs_ptr: [*]const macho.fat_arch = @ptrCast(all_mapped_memory.ptr + @sizeOf(macho.fat_header)); | |
| 604 | const archs: []const macho.fat_arch = archs_ptr[0..@byteSwap(hdr.nfat_arch)]; | |
| 605 | const native_cpu_type = switch (builtin.cpu.arch) { | |
| 606 | .x86_64 => macho.CPU_TYPE_X86_64, | |
| 607 | .aarch64 => macho.CPU_TYPE_ARM64, | |
| 608 | else => comptime unreachable, | |
| 609 | }; | |
| 610 | for (archs) |*arch| { | |
| 611 | if (@byteSwap(arch.cputype) != native_cpu_type) continue; | |
| 612 | const offset = @byteSwap(arch.offset); | |
| 613 | const size = @byteSwap(arch.size); | |
| 614 | break :mapped_macho all_mapped_memory[offset..][0..size]; | |
| 615 | } | |
| 616 | // Our native architecture was not present in the fat binary. | |
| 617 | return error.MissingDebugInfo; | |
| 618 | }, | |
| 619 | ||
| 620 | // Even on modern 64-bit targets, this format doesn't seem to be too extensively used. It | |
| 621 | // will be fairly easy to add support here if necessary; it's very similar to above. | |
| 622 | macho.FAT_CIGAM_64 => return error.UnsupportedDebugInfo, | |
| 623 | ||
| 624 | else => return error.InvalidDebugInfo, | |
| 625 | }; | |
| 626 | ||
| 627 | const hdr: *const macho.mach_header_64 = @ptrCast(@alignCast(mapped_macho.ptr)); | |
| 628 | if (hdr.magic != macho.MH_MAGIC_64) | |
| 629 | return error.InvalidDebugInfo; | |
| 630 | ||
| 631 | const symtab: macho.symtab_command, const text_vmaddr: u64 = lc_iter: { | |
| 632 | var it: macho.LoadCommandIterator = .{ | |
| 633 | .ncmds = hdr.ncmds, | |
| 634 | .buffer = mapped_macho[@sizeOf(macho.mach_header_64)..][0..hdr.sizeofcmds], | |
| 635 | }; | |
| 636 | var symtab: ?macho.symtab_command = null; | |
| 637 | var text_vmaddr: ?u64 = null; | |
| 638 | while (it.next()) |cmd| switch (cmd.cmd()) { | |
| 639 | .SYMTAB => symtab = cmd.cast(macho.symtab_command) orelse return error.InvalidDebugInfo, | |
| 640 | .SEGMENT_64 => if (cmd.cast(macho.segment_command_64)) |seg_cmd| { | |
| 641 | if (!mem.eql(u8, seg_cmd.segName(), "__TEXT")) continue; | |
| 642 | text_vmaddr = seg_cmd.vmaddr; | |
| 643 | }, | |
| 644 | else => {}, | |
| 645 | }; | |
| 646 | break :lc_iter .{ | |
| 647 | symtab orelse return error.MissingDebugInfo, | |
| 648 | text_vmaddr orelse return error.MissingDebugInfo, | |
| 649 | }; | |
| 650 | }; | |
| 651 | ||
| 652 | const syms_ptr: [*]align(1) const macho.nlist_64 = @ptrCast(mapped_macho[symtab.symoff..]); | |
| 653 | const syms = syms_ptr[0..symtab.nsyms]; | |
| 654 | const strings = mapped_macho[symtab.stroff..][0 .. symtab.strsize - 1]; | |
| 655 | ||
| 656 | var symbols: std.ArrayList(MachoSymbol) = try .initCapacity(gpa, syms.len); | |
| 657 | defer symbols.deinit(gpa); | |
| 658 | ||
| 659 | // This map is temporary; it is used only to detect duplicates here. This is | |
| 660 | // necessary because we prefer to use STAB ("symbolic debugging table") symbols, | |
| 661 | // but they might not be present, so we track normal symbols too. | |
| 662 | // Indices match 1-1 with those of `symbols`. | |
| 663 | var symbol_names: std.StringArrayHashMapUnmanaged(void) = .empty; | |
| 664 | defer symbol_names.deinit(gpa); | |
| 665 | try symbol_names.ensureUnusedCapacity(gpa, syms.len); | |
| 666 | ||
| 667 | var ofile: u32 = undefined; | |
| 668 | var last_sym: MachoSymbol = undefined; | |
| 669 | var state: enum { | |
| 670 | init, | |
| 671 | oso_open, | |
| 672 | oso_close, | |
| 673 | bnsym, | |
| 674 | fun_strx, | |
| 675 | fun_size, | |
| 676 | ensym, | |
| 677 | } = .init; | |
| 678 | ||
| 679 | for (syms) |*sym| { | |
| 680 | if (sym.n_type.bits.is_stab == 0) { | |
| 681 | if (sym.n_strx == 0) continue; | |
| 682 | switch (sym.n_type.bits.type) { | |
| 683 | .undf, .pbud, .indr, .abs, _ => continue, | |
| 684 | .sect => { | |
| 685 | const name = std.mem.sliceTo(strings[sym.n_strx..], 0); | |
| 686 | const gop = symbol_names.getOrPutAssumeCapacity(name); | |
| 687 | if (!gop.found_existing) { | |
| 688 | assert(gop.index == symbols.items.len); | |
| 689 | symbols.appendAssumeCapacity(.{ | |
| 690 | .strx = sym.n_strx, | |
| 691 | .addr = sym.n_value, | |
| 692 | .ofile = MachoSymbol.unknown_ofile, | |
| 693 | }); | |
| 694 | } | |
| 695 | }, | |
| 696 | } | |
| 697 | continue; | |
| 698 | } | |
| 699 | ||
| 700 | // TODO handle globals N_GSYM, and statics N_STSYM | |
| 701 | switch (sym.n_type.stab) { | |
| 702 | .oso => switch (state) { | |
| 703 | .init, .oso_close => { | |
| 704 | state = .oso_open; | |
| 705 | ofile = sym.n_strx; | |
| 706 | }, | |
| 707 | else => return error.InvalidDebugInfo, | |
| 708 | }, | |
| 709 | .bnsym => switch (state) { | |
| 710 | .oso_open, .ensym => { | |
| 711 | state = .bnsym; | |
| 712 | last_sym = .{ | |
| 713 | .strx = 0, | |
| 714 | .addr = sym.n_value, | |
| 715 | .ofile = ofile, | |
| 716 | }; | |
| 717 | }, | |
| 718 | else => return error.InvalidDebugInfo, | |
| 719 | }, | |
| 720 | .fun => switch (state) { | |
| 721 | .bnsym => { | |
| 722 | state = .fun_strx; | |
| 723 | last_sym.strx = sym.n_strx; | |
| 724 | }, | |
| 725 | .fun_strx => { | |
| 726 | state = .fun_size; | |
| 727 | }, | |
| 728 | else => return error.InvalidDebugInfo, | |
| 729 | }, | |
| 730 | .ensym => switch (state) { | |
| 731 | .fun_size => { | |
| 732 | state = .ensym; | |
| 733 | if (last_sym.strx != 0) { | |
| 734 | const name = std.mem.sliceTo(strings[last_sym.strx..], 0); | |
| 735 | const gop = symbol_names.getOrPutAssumeCapacity(name); | |
| 736 | if (!gop.found_existing) { | |
| 737 | assert(gop.index == symbols.items.len); | |
| 738 | symbols.appendAssumeCapacity(last_sym); | |
| 739 | } else { | |
| 740 | symbols.items[gop.index] = last_sym; | |
| 741 | } | |
| 742 | } | |
| 743 | }, | |
| 744 | else => return error.InvalidDebugInfo, | |
| 745 | }, | |
| 746 | .so => switch (state) { | |
| 747 | .init, .oso_close => {}, | |
| 748 | .oso_open, .ensym => { | |
| 749 | state = .oso_close; | |
| 750 | }, | |
| 751 | else => return error.InvalidDebugInfo, | |
| 752 | }, | |
| 753 | else => {}, | |
| 754 | } | |
| 755 | } | |
| 756 | ||
| 757 | switch (state) { | |
| 758 | .init => { | |
| 759 | // Missing STAB symtab entries is still okay, unless there were also no normal symbols. | |
| 760 | if (symbols.items.len == 0) return error.MissingDebugInfo; | |
| 761 | }, | |
| 762 | .oso_close => {}, | |
| 763 | else => return error.InvalidDebugInfo, // corrupted STAB entries in symtab | |
| 764 | } | |
| 765 | ||
| 766 | const symbols_slice = try symbols.toOwnedSlice(gpa); | |
| 767 | errdefer gpa.free(symbols_slice); | |
| 768 | ||
| 769 | // Even though lld emits symbols in ascending order, this debug code | |
| 770 | // should work for programs linked in any valid way. | |
| 771 | // This sort is so that we can binary search later. | |
| 772 | mem.sort(MachoSymbol, symbols_slice, {}, MachoSymbol.addressLessThan); | |
| 773 | ||
| 774 | return .{ | |
| 775 | .mapped_memory = all_mapped_memory, | |
| 776 | .symbols = symbols_slice, | |
| 777 | .strings = strings, | |
| 778 | .vaddr_offset = module.text_base - text_vmaddr, | |
| 779 | }; | |
| 780 | } | |
| 781 | }; | |
| 782 | ||
| 783 | const OFile = struct { | |
| 784 | mapped_memory: []align(std.heap.page_size_min) const u8, | |
| 785 | dwarf: Dwarf, | |
| 786 | strtab: []const u8, | |
| 787 | symtab: []align(1) const macho.nlist_64, | |
| 788 | /// All named symbols in `symtab`. Stored `u32` key is the index into `symtab`. Accessed | |
| 789 | /// through `SymbolAdapter`, so that the symbol name is used as the logical key. | |
| 790 | symbols_by_name: std.ArrayHashMapUnmanaged(u32, void, void, true), | |
| 791 | ||
| 792 | const SymbolAdapter = struct { | |
| 793 | strtab: []const u8, | |
| 794 | symtab: []align(1) const macho.nlist_64, | |
| 795 | pub fn hash(ctx: SymbolAdapter, sym_name: []const u8) u32 { | |
| 796 | _ = ctx; | |
| 797 | return @truncate(std.hash.Wyhash.hash(0, sym_name)); | |
| 798 | } | |
| 799 | pub fn eql(ctx: SymbolAdapter, a_sym_name: []const u8, b_sym_index: u32, b_index: usize) bool { | |
| 800 | _ = b_index; | |
| 801 | const b_sym = ctx.symtab[b_sym_index]; | |
| 802 | const b_sym_name = std.mem.sliceTo(ctx.strtab[b_sym.n_strx..], 0); | |
| 803 | return mem.eql(u8, a_sym_name, b_sym_name); | |
| 804 | } | |
| 805 | }; | |
| 806 | }; | |
| 807 | ||
| 808 | const MachoSymbol = struct { | |
| 809 | strx: u32, | |
| 810 | addr: u64, | |
| 811 | /// Value may be `unknown_ofile`. | |
| 812 | ofile: u32, | |
| 813 | const unknown_ofile = std.math.maxInt(u32); | |
| 814 | fn addressLessThan(context: void, lhs: MachoSymbol, rhs: MachoSymbol) bool { | |
| 815 | _ = context; | |
| 816 | return lhs.addr < rhs.addr; | |
| 817 | } | |
| 818 | /// Assumes that `symbols` is sorted in order of ascending `addr`. | |
| 819 | fn find(symbols: []const MachoSymbol, address: usize) ?*const MachoSymbol { | |
| 820 | if (symbols.len == 0) return null; // no potential match | |
| 821 | if (address < symbols[0].addr) return null; // address is before the lowest-address symbol | |
| 822 | var left: usize = 0; | |
| 823 | var len: usize = symbols.len; | |
| 824 | while (len > 1) { | |
| 825 | const mid = left + len / 2; | |
| 826 | if (address < symbols[mid].addr) { | |
| 827 | len /= 2; | |
| 828 | } else { | |
| 829 | left = mid; | |
| 830 | len -= len / 2; | |
| 831 | } | |
| 832 | } | |
| 833 | return &symbols[left]; | |
| 834 | } | |
| 835 | ||
| 836 | test find { | |
| 837 | const symbols: []const MachoSymbol = &.{ | |
| 838 | .{ .addr = 100, .strx = undefined, .ofile = undefined }, | |
| 839 | .{ .addr = 200, .strx = undefined, .ofile = undefined }, | |
| 840 | .{ .addr = 300, .strx = undefined, .ofile = undefined }, | |
| 841 | }; | |
| 842 | ||
| 843 | try testing.expectEqual(null, find(symbols, 0)); | |
| 844 | try testing.expectEqual(null, find(symbols, 99)); | |
| 845 | try testing.expectEqual(&symbols[0], find(symbols, 100).?); | |
| 846 | try testing.expectEqual(&symbols[0], find(symbols, 150).?); | |
| 847 | try testing.expectEqual(&symbols[0], find(symbols, 199).?); | |
| 848 | ||
| 849 | try testing.expectEqual(&symbols[1], find(symbols, 200).?); | |
| 850 | try testing.expectEqual(&symbols[1], find(symbols, 250).?); | |
| 851 | try testing.expectEqual(&symbols[1], find(symbols, 299).?); | |
| 852 | ||
| 853 | try testing.expectEqual(&symbols[2], find(symbols, 300).?); | |
| 854 | try testing.expectEqual(&symbols[2], find(symbols, 301).?); | |
| 855 | try testing.expectEqual(&symbols[2], find(symbols, 5000).?); | |
| 856 | } | |
| 857 | }; | |
| 858 | test { | |
| 859 | _ = MachoSymbol; | |
| 860 | } | |
| 861 | ||
| 862 | /// Uses `mmap` to map the file at `path` into memory. | |
| 863 | fn mapDebugInfoFile(path: []const u8) ![]align(std.heap.page_size_min) const u8 { | |
| 864 | const file = std.fs.cwd().openFile(path, .{}) catch |err| switch (err) { | |
| 865 | error.FileNotFound => return error.MissingDebugInfo, | |
| 866 | else => return error.ReadFailed, | |
| 867 | }; | |
| 868 | defer file.close(); | |
| 869 | ||
| 870 | const file_end_pos = file.getEndPos() catch |err| switch (err) { | |
| 871 | error.Unexpected => |e| return e, | |
| 872 | else => return error.ReadFailed, | |
| 873 | }; | |
| 874 | const file_len = std.math.cast(usize, file_end_pos) orelse return error.InvalidDebugInfo; | |
| 875 | ||
| 876 | return posix.mmap( | |
| 877 | null, | |
| 878 | file_len, | |
| 879 | posix.PROT.READ, | |
| 880 | .{ .TYPE = .SHARED }, | |
| 881 | file.handle, | |
| 882 | 0, | |
| 883 | ) catch |err| switch (err) { | |
| 884 | error.Unexpected => |e| return e, | |
| 885 | else => return error.ReadFailed, | |
| 886 | }; | |
| 887 | } | |
| 888 | ||
| 889 | fn loadOFile(gpa: Allocator, o_file_path: []const u8) !OFile { | |
| 890 | const mapped_mem = try mapDebugInfoFile(o_file_path); | |
| 891 | errdefer posix.munmap(mapped_mem); | |
| 892 | ||
| 893 | if (mapped_mem.len < @sizeOf(macho.mach_header_64)) return error.InvalidDebugInfo; | |
| 894 | const hdr: *const macho.mach_header_64 = @ptrCast(@alignCast(mapped_mem.ptr)); | |
| 895 | if (hdr.magic != std.macho.MH_MAGIC_64) return error.InvalidDebugInfo; | |
| 896 | ||
| 897 | const seg_cmd: macho.LoadCommandIterator.LoadCommand, const symtab_cmd: macho.symtab_command = cmds: { | |
| 898 | var seg_cmd: ?macho.LoadCommandIterator.LoadCommand = null; | |
| 899 | var symtab_cmd: ?macho.symtab_command = null; | |
| 900 | var it: macho.LoadCommandIterator = .{ | |
| 901 | .ncmds = hdr.ncmds, | |
| 902 | .buffer = mapped_mem[@sizeOf(macho.mach_header_64)..][0..hdr.sizeofcmds], | |
| 903 | }; | |
| 904 | while (it.next()) |cmd| switch (cmd.cmd()) { | |
| 905 | .SEGMENT_64 => seg_cmd = cmd, | |
| 906 | .SYMTAB => symtab_cmd = cmd.cast(macho.symtab_command) orelse return error.InvalidDebugInfo, | |
| 907 | else => {}, | |
| 908 | }; | |
| 909 | break :cmds .{ | |
| 910 | seg_cmd orelse return error.MissingDebugInfo, | |
| 911 | symtab_cmd orelse return error.MissingDebugInfo, | |
| 912 | }; | |
| 913 | }; | |
| 914 | ||
| 915 | if (mapped_mem.len < symtab_cmd.stroff + symtab_cmd.strsize) return error.InvalidDebugInfo; | |
| 916 | if (mapped_mem[symtab_cmd.stroff + symtab_cmd.strsize - 1] != 0) return error.InvalidDebugInfo; | |
| 917 | const strtab = mapped_mem[symtab_cmd.stroff..][0 .. symtab_cmd.strsize - 1]; | |
| 918 | ||
| 919 | const n_sym_bytes = symtab_cmd.nsyms * @sizeOf(macho.nlist_64); | |
| 920 | if (mapped_mem.len < symtab_cmd.symoff + n_sym_bytes) return error.InvalidDebugInfo; | |
| 921 | const symtab: []align(1) const macho.nlist_64 = @ptrCast(mapped_mem[symtab_cmd.symoff..][0..n_sym_bytes]); | |
| 922 | ||
| 923 | // TODO handle tentative (common) symbols | |
| 924 | var symbols_by_name: std.ArrayHashMapUnmanaged(u32, void, void, true) = .empty; | |
| 925 | defer symbols_by_name.deinit(gpa); | |
| 926 | try symbols_by_name.ensureUnusedCapacity(gpa, @intCast(symtab.len)); | |
| 927 | for (symtab, 0..) |sym, sym_index| { | |
| 928 | if (sym.n_strx == 0) continue; | |
| 929 | switch (sym.n_type.bits.type) { | |
| 930 | .undf => continue, // includes tentative symbols | |
| 931 | .abs => continue, | |
| 932 | else => {}, | |
| 933 | } | |
| 934 | const sym_name = mem.sliceTo(strtab[sym.n_strx..], 0); | |
| 935 | const gop = symbols_by_name.getOrPutAssumeCapacityAdapted( | |
| 936 | @as([]const u8, sym_name), | |
| 937 | @as(OFile.SymbolAdapter, .{ .strtab = strtab, .symtab = symtab }), | |
| 938 | ); | |
| 939 | if (gop.found_existing) return error.InvalidDebugInfo; | |
| 940 | gop.key_ptr.* = @intCast(sym_index); | |
| 941 | } | |
| 942 | ||
| 943 | var sections: Dwarf.SectionArray = @splat(null); | |
| 944 | for (seg_cmd.getSections()) |sect| { | |
| 945 | if (!std.mem.eql(u8, "__DWARF", sect.segName())) continue; | |
| 946 | ||
| 947 | const section_index: usize = inline for (@typeInfo(Dwarf.Section.Id).@"enum".fields, 0..) |section, i| { | |
| 948 | if (mem.eql(u8, "__" ++ section.name, sect.sectName())) break i; | |
| 949 | } else continue; | |
| 950 | ||
| 951 | if (mapped_mem.len < sect.offset + sect.size) return error.InvalidDebugInfo; | |
| 952 | const section_bytes = mapped_mem[sect.offset..][0..sect.size]; | |
| 953 | sections[section_index] = .{ | |
| 954 | .data = section_bytes, | |
| 955 | .owned = false, | |
| 956 | }; | |
| 957 | } | |
| 958 | ||
| 959 | const missing_debug_info = | |
| 960 | sections[@intFromEnum(Dwarf.Section.Id.debug_info)] == null or | |
| 961 | sections[@intFromEnum(Dwarf.Section.Id.debug_abbrev)] == null or | |
| 962 | sections[@intFromEnum(Dwarf.Section.Id.debug_str)] == null or | |
| 963 | sections[@intFromEnum(Dwarf.Section.Id.debug_line)] == null; | |
| 964 | if (missing_debug_info) return error.MissingDebugInfo; | |
| 965 | ||
| 966 | var dwarf: Dwarf = .{ .sections = sections }; | |
| 967 | errdefer dwarf.deinit(gpa); | |
| 968 | try dwarf.open(gpa, native_endian); | |
| 969 | ||
| 970 | return .{ | |
| 971 | .mapped_memory = mapped_mem, | |
| 972 | .dwarf = dwarf, | |
| 973 | .strtab = strtab, | |
| 974 | .symtab = symtab, | |
| 975 | .symbols_by_name = symbols_by_name.move(), | |
| 976 | }; | |
| 977 | } | |
| 978 | ||
| 979 | const std = @import("std"); | |
| 980 | const Allocator = std.mem.Allocator; | |
| 981 | const Dwarf = std.debug.Dwarf; | |
| 982 | const Error = std.debug.SelfInfoError; | |
| 983 | const assert = std.debug.assert; | |
| 984 | const posix = std.posix; | |
| 985 | const macho = std.macho; | |
| 986 | const mem = std.mem; | |
| 987 | const testing = std.testing; | |
| 988 | const dwarfRegNative = std.debug.Dwarf.SelfUnwinder.regNative; | |
| 989 | ||
| 990 | const builtin = @import("builtin"); | |
| 991 | const native_endian = builtin.target.cpu.arch.endian(); | |
| 992 | ||
| 993 | const SelfInfo = @This(); |
lib/std/debug/SelfInfo/DarwinModule.zig deleted-954| ... | ... | @@ -1,954 +0,0 @@ |
| 1 | /// The runtime address where __TEXT is loaded. | |
| 2 | text_base: usize, | |
| 3 | name: []const u8, | |
| 4 | ||
| 5 | pub fn key(m: *const DarwinModule) usize { | |
| 6 | return m.text_base; | |
| 7 | } | |
| 8 | ||
| 9 | /// No cache needed, because `_dyld_get_image_header` etc are already fast. | |
| 10 | pub const LookupCache = void; | |
| 11 | pub fn lookup(cache: *LookupCache, gpa: Allocator, address: usize) Error!DarwinModule { | |
| 12 | _ = cache; | |
| 13 | _ = gpa; | |
| 14 | var info: std.c.dl_info = undefined; | |
| 15 | switch (std.c.dladdr(@ptrFromInt(address), &info)) { | |
| 16 | 0 => return error.MissingDebugInfo, | |
| 17 | else => return .{ | |
| 18 | .name = std.mem.span(info.fname), | |
| 19 | .text_base = @intFromPtr(info.fbase), | |
| 20 | }, | |
| 21 | } | |
| 22 | } | |
| 23 | fn loadUnwindInfo(module: *const DarwinModule, gpa: Allocator, out: *DebugInfo) !void { | |
| 24 | const header: *std.macho.mach_header = @ptrFromInt(module.text_base); | |
| 25 | ||
| 26 | var it: macho.LoadCommandIterator = .{ | |
| 27 | .ncmds = header.ncmds, | |
| 28 | .buffer = @as([*]u8, @ptrCast(header))[@sizeOf(macho.mach_header_64)..][0..header.sizeofcmds], | |
| 29 | }; | |
| 30 | const sections, const text_vmaddr = while (it.next()) |load_cmd| { | |
| 31 | if (load_cmd.cmd() != .SEGMENT_64) continue; | |
| 32 | const segment_cmd = load_cmd.cast(macho.segment_command_64).?; | |
| 33 | if (!mem.eql(u8, segment_cmd.segName(), "__TEXT")) continue; | |
| 34 | break .{ load_cmd.getSections(), segment_cmd.vmaddr }; | |
| 35 | } else unreachable; | |
| 36 | ||
| 37 | const vmaddr_slide = module.text_base - text_vmaddr; | |
| 38 | ||
| 39 | var opt_unwind_info: ?[]const u8 = null; | |
| 40 | var opt_eh_frame: ?[]const u8 = null; | |
| 41 | for (sections) |sect| { | |
| 42 | if (mem.eql(u8, sect.sectName(), "__unwind_info")) { | |
| 43 | const sect_ptr: [*]u8 = @ptrFromInt(@as(usize, @intCast(vmaddr_slide + sect.addr))); | |
| 44 | opt_unwind_info = sect_ptr[0..@intCast(sect.size)]; | |
| 45 | } else if (mem.eql(u8, sect.sectName(), "__eh_frame")) { | |
| 46 | const sect_ptr: [*]u8 = @ptrFromInt(@as(usize, @intCast(vmaddr_slide + sect.addr))); | |
| 47 | opt_eh_frame = sect_ptr[0..@intCast(sect.size)]; | |
| 48 | } | |
| 49 | } | |
| 50 | const eh_frame = opt_eh_frame orelse { | |
| 51 | out.unwind = .{ | |
| 52 | .vmaddr_slide = vmaddr_slide, | |
| 53 | .unwind_info = opt_unwind_info, | |
| 54 | .dwarf = null, | |
| 55 | .dwarf_cache = undefined, | |
| 56 | }; | |
| 57 | return; | |
| 58 | }; | |
| 59 | var dwarf: Dwarf.Unwind = .initSection(.eh_frame, @intFromPtr(eh_frame.ptr) - vmaddr_slide, eh_frame); | |
| 60 | errdefer dwarf.deinit(gpa); | |
| 61 | // We don't need lookups, so this call is just for scanning CIEs. | |
| 62 | dwarf.prepare(gpa, @sizeOf(usize), native_endian, false, true) catch |err| switch (err) { | |
| 63 | error.ReadFailed => unreachable, // it's all fixed buffers | |
| 64 | error.InvalidDebugInfo, | |
| 65 | error.MissingDebugInfo, | |
| 66 | error.OutOfMemory, | |
| 67 | => |e| return e, | |
| 68 | error.EndOfStream, | |
| 69 | error.Overflow, | |
| 70 | error.StreamTooLong, | |
| 71 | error.InvalidOperand, | |
| 72 | error.InvalidOpcode, | |
| 73 | error.InvalidOperation, | |
| 74 | => return error.InvalidDebugInfo, | |
| 75 | error.UnsupportedAddrSize, | |
| 76 | error.UnsupportedDwarfVersion, | |
| 77 | error.UnimplementedUserOpcode, | |
| 78 | => return error.UnsupportedDebugInfo, | |
| 79 | }; | |
| 80 | ||
| 81 | const dwarf_cache = try gpa.create(UnwindContext.Cache); | |
| 82 | errdefer gpa.destroy(dwarf_cache); | |
| 83 | dwarf_cache.init(); | |
| 84 | ||
| 85 | out.unwind = .{ | |
| 86 | .vmaddr_slide = vmaddr_slide, | |
| 87 | .unwind_info = opt_unwind_info, | |
| 88 | .dwarf = dwarf, | |
| 89 | .dwarf_cache = dwarf_cache, | |
| 90 | }; | |
| 91 | } | |
| 92 | fn loadMachO(module: *const DarwinModule, gpa: Allocator) !DebugInfo.LoadedMachO { | |
| 93 | const all_mapped_memory = try mapDebugInfoFile(module.name); | |
| 94 | errdefer posix.munmap(all_mapped_memory); | |
| 95 | ||
| 96 | // In most cases, the file we just mapped is a Mach-O binary. However, it could be a "universal | |
| 97 | // binary": a simple file format which contains Mach-O binaries for multiple targets. For | |
| 98 | // instance, `/usr/lib/dyld` is currently distributed as a universal binary containing images | |
| 99 | // for both ARM64 Macs and x86_64 Macs. | |
| 100 | if (all_mapped_memory.len < 4) return error.InvalidDebugInfo; | |
| 101 | const magic = @as(*const u32, @ptrCast(all_mapped_memory.ptr)).*; | |
| 102 | // The contents of a Mach-O file, which may or may not be the whole of `all_mapped_memory`. | |
| 103 | const mapped_macho = switch (magic) { | |
| 104 | macho.MH_MAGIC_64 => all_mapped_memory, | |
| 105 | ||
| 106 | macho.FAT_CIGAM => mapped_macho: { | |
| 107 | // This is the universal binary format (aka a "fat binary"). Annoyingly, the whole thing | |
| 108 | // is big-endian, so we'll be swapping some bytes. | |
| 109 | if (all_mapped_memory.len < @sizeOf(macho.fat_header)) return error.InvalidDebugInfo; | |
| 110 | const hdr: *const macho.fat_header = @ptrCast(all_mapped_memory.ptr); | |
| 111 | const archs_ptr: [*]const macho.fat_arch = @ptrCast(all_mapped_memory.ptr + @sizeOf(macho.fat_header)); | |
| 112 | const archs: []const macho.fat_arch = archs_ptr[0..@byteSwap(hdr.nfat_arch)]; | |
| 113 | const native_cpu_type = switch (builtin.cpu.arch) { | |
| 114 | .x86_64 => macho.CPU_TYPE_X86_64, | |
| 115 | .aarch64 => macho.CPU_TYPE_ARM64, | |
| 116 | else => comptime unreachable, | |
| 117 | }; | |
| 118 | for (archs) |*arch| { | |
| 119 | if (@byteSwap(arch.cputype) != native_cpu_type) continue; | |
| 120 | const offset = @byteSwap(arch.offset); | |
| 121 | const size = @byteSwap(arch.size); | |
| 122 | break :mapped_macho all_mapped_memory[offset..][0..size]; | |
| 123 | } | |
| 124 | // Our native architecture was not present in the fat binary. | |
| 125 | return error.MissingDebugInfo; | |
| 126 | }, | |
| 127 | ||
| 128 | // Even on modern 64-bit targets, this format doesn't seem to be too extensively used. It | |
| 129 | // will be fairly easy to add support here if necessary; it's very similar to above. | |
| 130 | macho.FAT_CIGAM_64 => return error.UnsupportedDebugInfo, | |
| 131 | ||
| 132 | else => return error.InvalidDebugInfo, | |
| 133 | }; | |
| 134 | ||
| 135 | const hdr: *const macho.mach_header_64 = @ptrCast(@alignCast(mapped_macho.ptr)); | |
| 136 | if (hdr.magic != macho.MH_MAGIC_64) | |
| 137 | return error.InvalidDebugInfo; | |
| 138 | ||
| 139 | const symtab: macho.symtab_command, const text_vmaddr: u64 = lc_iter: { | |
| 140 | var it: macho.LoadCommandIterator = .{ | |
| 141 | .ncmds = hdr.ncmds, | |
| 142 | .buffer = mapped_macho[@sizeOf(macho.mach_header_64)..][0..hdr.sizeofcmds], | |
| 143 | }; | |
| 144 | var symtab: ?macho.symtab_command = null; | |
| 145 | var text_vmaddr: ?u64 = null; | |
| 146 | while (it.next()) |cmd| switch (cmd.cmd()) { | |
| 147 | .SYMTAB => symtab = cmd.cast(macho.symtab_command) orelse return error.InvalidDebugInfo, | |
| 148 | .SEGMENT_64 => if (cmd.cast(macho.segment_command_64)) |seg_cmd| { | |
| 149 | if (!mem.eql(u8, seg_cmd.segName(), "__TEXT")) continue; | |
| 150 | text_vmaddr = seg_cmd.vmaddr; | |
| 151 | }, | |
| 152 | else => {}, | |
| 153 | }; | |
| 154 | break :lc_iter .{ | |
| 155 | symtab orelse return error.MissingDebugInfo, | |
| 156 | text_vmaddr orelse return error.MissingDebugInfo, | |
| 157 | }; | |
| 158 | }; | |
| 159 | ||
| 160 | const syms_ptr: [*]align(1) const macho.nlist_64 = @ptrCast(mapped_macho[symtab.symoff..]); | |
| 161 | const syms = syms_ptr[0..symtab.nsyms]; | |
| 162 | const strings = mapped_macho[symtab.stroff..][0 .. symtab.strsize - 1]; | |
| 163 | ||
| 164 | var symbols: std.ArrayList(MachoSymbol) = try .initCapacity(gpa, syms.len); | |
| 165 | defer symbols.deinit(gpa); | |
| 166 | ||
| 167 | // This map is temporary; it is used only to detect duplicates here. This is | |
| 168 | // necessary because we prefer to use STAB ("symbolic debugging table") symbols, | |
| 169 | // but they might not be present, so we track normal symbols too. | |
| 170 | // Indices match 1-1 with those of `symbols`. | |
| 171 | var symbol_names: std.StringArrayHashMapUnmanaged(void) = .empty; | |
| 172 | defer symbol_names.deinit(gpa); | |
| 173 | try symbol_names.ensureUnusedCapacity(gpa, syms.len); | |
| 174 | ||
| 175 | var ofile: u32 = undefined; | |
| 176 | var last_sym: MachoSymbol = undefined; | |
| 177 | var state: enum { | |
| 178 | init, | |
| 179 | oso_open, | |
| 180 | oso_close, | |
| 181 | bnsym, | |
| 182 | fun_strx, | |
| 183 | fun_size, | |
| 184 | ensym, | |
| 185 | } = .init; | |
| 186 | ||
| 187 | for (syms) |*sym| { | |
| 188 | if (sym.n_type.bits.is_stab == 0) { | |
| 189 | if (sym.n_strx == 0) continue; | |
| 190 | switch (sym.n_type.bits.type) { | |
| 191 | .undf, .pbud, .indr, .abs, _ => continue, | |
| 192 | .sect => { | |
| 193 | const name = std.mem.sliceTo(strings[sym.n_strx..], 0); | |
| 194 | const gop = symbol_names.getOrPutAssumeCapacity(name); | |
| 195 | if (!gop.found_existing) { | |
| 196 | assert(gop.index == symbols.items.len); | |
| 197 | symbols.appendAssumeCapacity(.{ | |
| 198 | .strx = sym.n_strx, | |
| 199 | .addr = sym.n_value, | |
| 200 | .ofile = MachoSymbol.unknown_ofile, | |
| 201 | }); | |
| 202 | } | |
| 203 | }, | |
| 204 | } | |
| 205 | continue; | |
| 206 | } | |
| 207 | ||
| 208 | // TODO handle globals N_GSYM, and statics N_STSYM | |
| 209 | switch (sym.n_type.stab) { | |
| 210 | .oso => switch (state) { | |
| 211 | .init, .oso_close => { | |
| 212 | state = .oso_open; | |
| 213 | ofile = sym.n_strx; | |
| 214 | }, | |
| 215 | else => return error.InvalidDebugInfo, | |
| 216 | }, | |
| 217 | .bnsym => switch (state) { | |
| 218 | .oso_open, .ensym => { | |
| 219 | state = .bnsym; | |
| 220 | last_sym = .{ | |
| 221 | .strx = 0, | |
| 222 | .addr = sym.n_value, | |
| 223 | .ofile = ofile, | |
| 224 | }; | |
| 225 | }, | |
| 226 | else => return error.InvalidDebugInfo, | |
| 227 | }, | |
| 228 | .fun => switch (state) { | |
| 229 | .bnsym => { | |
| 230 | state = .fun_strx; | |
| 231 | last_sym.strx = sym.n_strx; | |
| 232 | }, | |
| 233 | .fun_strx => { | |
| 234 | state = .fun_size; | |
| 235 | }, | |
| 236 | else => return error.InvalidDebugInfo, | |
| 237 | }, | |
| 238 | .ensym => switch (state) { | |
| 239 | .fun_size => { | |
| 240 | state = .ensym; | |
| 241 | if (last_sym.strx != 0) { | |
| 242 | const name = std.mem.sliceTo(strings[last_sym.strx..], 0); | |
| 243 | const gop = symbol_names.getOrPutAssumeCapacity(name); | |
| 244 | if (!gop.found_existing) { | |
| 245 | assert(gop.index == symbols.items.len); | |
| 246 | symbols.appendAssumeCapacity(last_sym); | |
| 247 | } else { | |
| 248 | symbols.items[gop.index] = last_sym; | |
| 249 | } | |
| 250 | } | |
| 251 | }, | |
| 252 | else => return error.InvalidDebugInfo, | |
| 253 | }, | |
| 254 | .so => switch (state) { | |
| 255 | .init, .oso_close => {}, | |
| 256 | .oso_open, .ensym => { | |
| 257 | state = .oso_close; | |
| 258 | }, | |
| 259 | else => return error.InvalidDebugInfo, | |
| 260 | }, | |
| 261 | else => {}, | |
| 262 | } | |
| 263 | } | |
| 264 | ||
| 265 | switch (state) { | |
| 266 | .init => { | |
| 267 | // Missing STAB symtab entries is still okay, unless there were also no normal symbols. | |
| 268 | if (symbols.items.len == 0) return error.MissingDebugInfo; | |
| 269 | }, | |
| 270 | .oso_close => {}, | |
| 271 | else => return error.InvalidDebugInfo, // corrupted STAB entries in symtab | |
| 272 | } | |
| 273 | ||
| 274 | const symbols_slice = try symbols.toOwnedSlice(gpa); | |
| 275 | errdefer gpa.free(symbols_slice); | |
| 276 | ||
| 277 | // Even though lld emits symbols in ascending order, this debug code | |
| 278 | // should work for programs linked in any valid way. | |
| 279 | // This sort is so that we can binary search later. | |
| 280 | mem.sort(MachoSymbol, symbols_slice, {}, MachoSymbol.addressLessThan); | |
| 281 | ||
| 282 | return .{ | |
| 283 | .mapped_memory = all_mapped_memory, | |
| 284 | .symbols = symbols_slice, | |
| 285 | .strings = strings, | |
| 286 | .ofiles = .empty, | |
| 287 | .vaddr_offset = module.text_base - text_vmaddr, | |
| 288 | }; | |
| 289 | } | |
| 290 | pub fn getSymbolAtAddress(module: *const DarwinModule, gpa: Allocator, di: *DebugInfo, address: usize) Error!std.debug.Symbol { | |
| 291 | // We need the lock for a few things: | |
| 292 | // * loading the Mach-O module | |
| 293 | // * loading the referenced object file | |
| 294 | // * scanning the DWARF of that object file | |
| 295 | // * building the line number table of that object file | |
| 296 | // That's enough that it doesn't really seem worth scoping the lock more tightly than the whole function.. | |
| 297 | di.mutex.lock(); | |
| 298 | defer di.mutex.unlock(); | |
| 299 | ||
| 300 | if (di.loaded_macho == null) di.loaded_macho = module.loadMachO(gpa) catch |err| switch (err) { | |
| 301 | error.InvalidDebugInfo, error.MissingDebugInfo, error.OutOfMemory, error.Unexpected => |e| return e, | |
| 302 | else => return error.ReadFailed, | |
| 303 | }; | |
| 304 | const loaded_macho = &di.loaded_macho.?; | |
| 305 | ||
| 306 | const vaddr = address - loaded_macho.vaddr_offset; | |
| 307 | const symbol = MachoSymbol.find(loaded_macho.symbols, vaddr) orelse return .unknown; | |
| 308 | ||
| 309 | // offset of `address` from start of `symbol` | |
| 310 | const address_symbol_offset = vaddr - symbol.addr; | |
| 311 | ||
| 312 | // Take the symbol name from the N_FUN STAB entry, we're going to | |
| 313 | // use it if we fail to find the DWARF infos | |
| 314 | const stab_symbol = mem.sliceTo(loaded_macho.strings[symbol.strx..], 0); | |
| 315 | ||
| 316 | // If any information is missing, we can at least return this from now on. | |
| 317 | const sym_only_result: std.debug.Symbol = .{ | |
| 318 | .name = stab_symbol, | |
| 319 | .compile_unit_name = null, | |
| 320 | .source_location = null, | |
| 321 | }; | |
| 322 | ||
| 323 | if (symbol.ofile == MachoSymbol.unknown_ofile) { | |
| 324 | // We don't have STAB info, so can't track down the object file; all we can do is the symbol name. | |
| 325 | return sym_only_result; | |
| 326 | } | |
| 327 | ||
| 328 | const o_file: *DebugInfo.OFile = of: { | |
| 329 | const gop = try loaded_macho.ofiles.getOrPut(gpa, symbol.ofile); | |
| 330 | if (!gop.found_existing) { | |
| 331 | const o_file_path = mem.sliceTo(loaded_macho.strings[symbol.ofile..], 0); | |
| 332 | gop.value_ptr.* = DebugInfo.loadOFile(gpa, o_file_path) catch { | |
| 333 | _ = loaded_macho.ofiles.pop().?; | |
| 334 | return sym_only_result; | |
| 335 | }; | |
| 336 | } | |
| 337 | break :of gop.value_ptr; | |
| 338 | }; | |
| 339 | ||
| 340 | const symbol_index = o_file.symbols_by_name.getKeyAdapted( | |
| 341 | @as([]const u8, stab_symbol), | |
| 342 | @as(DebugInfo.OFile.SymbolAdapter, .{ .strtab = o_file.strtab, .symtab = o_file.symtab }), | |
| 343 | ) orelse return sym_only_result; | |
| 344 | const symbol_ofile_vaddr = o_file.symtab[symbol_index].n_value; | |
| 345 | ||
| 346 | const compile_unit = o_file.dwarf.findCompileUnit(native_endian, symbol_ofile_vaddr) catch return sym_only_result; | |
| 347 | ||
| 348 | return .{ | |
| 349 | .name = o_file.dwarf.getSymbolName(symbol_ofile_vaddr + address_symbol_offset) orelse stab_symbol, | |
| 350 | .compile_unit_name = compile_unit.die.getAttrString( | |
| 351 | &o_file.dwarf, | |
| 352 | native_endian, | |
| 353 | std.dwarf.AT.name, | |
| 354 | o_file.dwarf.section(.debug_str), | |
| 355 | compile_unit, | |
| 356 | ) catch |err| switch (err) { | |
| 357 | error.MissingDebugInfo, error.InvalidDebugInfo => null, | |
| 358 | }, | |
| 359 | .source_location = o_file.dwarf.getLineNumberInfo( | |
| 360 | gpa, | |
| 361 | native_endian, | |
| 362 | compile_unit, | |
| 363 | symbol_ofile_vaddr + address_symbol_offset, | |
| 364 | ) catch null, | |
| 365 | }; | |
| 366 | } | |
| 367 | pub const supports_unwinding: bool = true; | |
| 368 | pub const UnwindContext = std.debug.SelfInfo.DwarfUnwindContext; | |
| 369 | /// Unwind a frame using MachO compact unwind info (from __unwind_info). | |
| 370 | /// If the compact encoding can't encode a way to unwind a frame, it will | |
| 371 | /// defer unwinding to DWARF, in which case `.eh_frame` will be used if available. | |
| 372 | pub fn unwindFrame(module: *const DarwinModule, gpa: Allocator, di: *DebugInfo, context: *UnwindContext) Error!usize { | |
| 373 | return unwindFrameInner(module, gpa, di, context) catch |err| switch (err) { | |
| 374 | error.InvalidDebugInfo, | |
| 375 | error.MissingDebugInfo, | |
| 376 | error.UnsupportedDebugInfo, | |
| 377 | error.ReadFailed, | |
| 378 | error.OutOfMemory, | |
| 379 | error.Unexpected, | |
| 380 | => |e| return e, | |
| 381 | error.UnsupportedRegister, | |
| 382 | => return error.UnsupportedDebugInfo, | |
| 383 | error.InvalidRegister, | |
| 384 | error.IncompatibleRegisterSize, | |
| 385 | => return error.InvalidDebugInfo, | |
| 386 | }; | |
| 387 | } | |
| 388 | fn unwindFrameInner(module: *const DarwinModule, gpa: Allocator, di: *DebugInfo, context: *UnwindContext) !usize { | |
| 389 | const unwind: *DebugInfo.Unwind = u: { | |
| 390 | di.mutex.lock(); | |
| 391 | defer di.mutex.unlock(); | |
| 392 | if (di.unwind == null) try module.loadUnwindInfo(gpa, di); | |
| 393 | break :u &di.unwind.?; | |
| 394 | }; | |
| 395 | ||
| 396 | const unwind_info = unwind.unwind_info orelse return error.MissingDebugInfo; | |
| 397 | if (unwind_info.len < @sizeOf(macho.unwind_info_section_header)) return error.InvalidDebugInfo; | |
| 398 | const header: *align(1) const macho.unwind_info_section_header = @ptrCast(unwind_info); | |
| 399 | ||
| 400 | const index_byte_count = header.indexCount * @sizeOf(macho.unwind_info_section_header_index_entry); | |
| 401 | if (unwind_info.len < header.indexSectionOffset + index_byte_count) return error.InvalidDebugInfo; | |
| 402 | const indices: []align(1) const macho.unwind_info_section_header_index_entry = @ptrCast(unwind_info[header.indexSectionOffset..][0..index_byte_count]); | |
| 403 | if (indices.len == 0) return error.MissingDebugInfo; | |
| 404 | ||
| 405 | // offset of the PC into the `__TEXT` segment | |
| 406 | const pc_text_offset = context.pc - module.text_base; | |
| 407 | ||
| 408 | const start_offset: u32, const first_level_offset: u32 = index: { | |
| 409 | var left: usize = 0; | |
| 410 | var len: usize = indices.len; | |
| 411 | while (len > 1) { | |
| 412 | const mid = left + len / 2; | |
| 413 | if (pc_text_offset < indices[mid].functionOffset) { | |
| 414 | len /= 2; | |
| 415 | } else { | |
| 416 | left = mid; | |
| 417 | len -= len / 2; | |
| 418 | } | |
| 419 | } | |
| 420 | break :index .{ indices[left].secondLevelPagesSectionOffset, indices[left].functionOffset }; | |
| 421 | }; | |
| 422 | // An offset of 0 is a sentinel indicating a range does not have unwind info. | |
| 423 | if (start_offset == 0) return error.MissingDebugInfo; | |
| 424 | ||
| 425 | const common_encodings_byte_count = header.commonEncodingsArrayCount * @sizeOf(macho.compact_unwind_encoding_t); | |
| 426 | if (unwind_info.len < header.commonEncodingsArraySectionOffset + common_encodings_byte_count) return error.InvalidDebugInfo; | |
| 427 | const common_encodings: []align(1) const macho.compact_unwind_encoding_t = @ptrCast( | |
| 428 | unwind_info[header.commonEncodingsArraySectionOffset..][0..common_encodings_byte_count], | |
| 429 | ); | |
| 430 | ||
| 431 | if (unwind_info.len < start_offset + @sizeOf(macho.UNWIND_SECOND_LEVEL)) return error.InvalidDebugInfo; | |
| 432 | const kind: *align(1) const macho.UNWIND_SECOND_LEVEL = @ptrCast(unwind_info[start_offset..]); | |
| 433 | ||
| 434 | const entry: struct { | |
| 435 | function_offset: usize, | |
| 436 | raw_encoding: u32, | |
| 437 | } = switch (kind.*) { | |
| 438 | .REGULAR => entry: { | |
| 439 | if (unwind_info.len < start_offset + @sizeOf(macho.unwind_info_regular_second_level_page_header)) return error.InvalidDebugInfo; | |
| 440 | const page_header: *align(1) const macho.unwind_info_regular_second_level_page_header = @ptrCast(unwind_info[start_offset..]); | |
| 441 | ||
| 442 | const entries_byte_count = page_header.entryCount * @sizeOf(macho.unwind_info_regular_second_level_entry); | |
| 443 | if (unwind_info.len < start_offset + entries_byte_count) return error.InvalidDebugInfo; | |
| 444 | const entries: []align(1) const macho.unwind_info_regular_second_level_entry = @ptrCast( | |
| 445 | unwind_info[start_offset + page_header.entryPageOffset ..][0..entries_byte_count], | |
| 446 | ); | |
| 447 | if (entries.len == 0) return error.InvalidDebugInfo; | |
| 448 | ||
| 449 | var left: usize = 0; | |
| 450 | var len: usize = entries.len; | |
| 451 | while (len > 1) { | |
| 452 | const mid = left + len / 2; | |
| 453 | if (pc_text_offset < entries[mid].functionOffset) { | |
| 454 | len /= 2; | |
| 455 | } else { | |
| 456 | left = mid; | |
| 457 | len -= len / 2; | |
| 458 | } | |
| 459 | } | |
| 460 | break :entry .{ | |
| 461 | .function_offset = entries[left].functionOffset, | |
| 462 | .raw_encoding = entries[left].encoding, | |
| 463 | }; | |
| 464 | }, | |
| 465 | .COMPRESSED => entry: { | |
| 466 | if (unwind_info.len < start_offset + @sizeOf(macho.unwind_info_compressed_second_level_page_header)) return error.InvalidDebugInfo; | |
| 467 | const page_header: *align(1) const macho.unwind_info_compressed_second_level_page_header = @ptrCast(unwind_info[start_offset..]); | |
| 468 | ||
| 469 | const entries_byte_count = page_header.entryCount * @sizeOf(macho.UnwindInfoCompressedEntry); | |
| 470 | if (unwind_info.len < start_offset + entries_byte_count) return error.InvalidDebugInfo; | |
| 471 | const entries: []align(1) const macho.UnwindInfoCompressedEntry = @ptrCast( | |
| 472 | unwind_info[start_offset + page_header.entryPageOffset ..][0..entries_byte_count], | |
| 473 | ); | |
| 474 | if (entries.len == 0) return error.InvalidDebugInfo; | |
| 475 | ||
| 476 | var left: usize = 0; | |
| 477 | var len: usize = entries.len; | |
| 478 | while (len > 1) { | |
| 479 | const mid = left + len / 2; | |
| 480 | if (pc_text_offset < first_level_offset + entries[mid].funcOffset) { | |
| 481 | len /= 2; | |
| 482 | } else { | |
| 483 | left = mid; | |
| 484 | len -= len / 2; | |
| 485 | } | |
| 486 | } | |
| 487 | const entry = entries[left]; | |
| 488 | ||
| 489 | const function_offset = first_level_offset + entry.funcOffset; | |
| 490 | if (entry.encodingIndex < common_encodings.len) { | |
| 491 | break :entry .{ | |
| 492 | .function_offset = function_offset, | |
| 493 | .raw_encoding = common_encodings[entry.encodingIndex], | |
| 494 | }; | |
| 495 | } | |
| 496 | ||
| 497 | const local_index = entry.encodingIndex - common_encodings.len; | |
| 498 | const local_encodings_byte_count = page_header.encodingsCount * @sizeOf(macho.compact_unwind_encoding_t); | |
| 499 | if (unwind_info.len < start_offset + page_header.encodingsPageOffset + local_encodings_byte_count) return error.InvalidDebugInfo; | |
| 500 | const local_encodings: []align(1) const macho.compact_unwind_encoding_t = @ptrCast( | |
| 501 | unwind_info[start_offset + page_header.encodingsPageOffset ..][0..local_encodings_byte_count], | |
| 502 | ); | |
| 503 | if (local_index >= local_encodings.len) return error.InvalidDebugInfo; | |
| 504 | break :entry .{ | |
| 505 | .function_offset = function_offset, | |
| 506 | .raw_encoding = local_encodings[local_index], | |
| 507 | }; | |
| 508 | }, | |
| 509 | else => return error.InvalidDebugInfo, | |
| 510 | }; | |
| 511 | ||
| 512 | if (entry.raw_encoding == 0) return error.MissingDebugInfo; | |
| 513 | ||
| 514 | const encoding: macho.CompactUnwindEncoding = @bitCast(entry.raw_encoding); | |
| 515 | const new_ip = switch (builtin.cpu.arch) { | |
| 516 | .x86_64 => switch (encoding.mode.x86_64) { | |
| 517 | .OLD => return error.UnsupportedDebugInfo, | |
| 518 | .RBP_FRAME => ip: { | |
| 519 | const frame = encoding.value.x86_64.frame; | |
| 520 | ||
| 521 | const fp = (try dwarfRegNative(&context.cpu_context, fp_reg_num)).*; | |
| 522 | const new_sp = fp + 2 * @sizeOf(usize); | |
| 523 | ||
| 524 | const ip_ptr = fp + @sizeOf(usize); | |
| 525 | const new_ip = @as(*const usize, @ptrFromInt(ip_ptr)).*; | |
| 526 | const new_fp = @as(*const usize, @ptrFromInt(fp)).*; | |
| 527 | ||
| 528 | (try dwarfRegNative(&context.cpu_context, fp_reg_num)).* = new_fp; | |
| 529 | (try dwarfRegNative(&context.cpu_context, sp_reg_num)).* = new_sp; | |
| 530 | (try dwarfRegNative(&context.cpu_context, ip_reg_num)).* = new_ip; | |
| 531 | ||
| 532 | const regs: [5]u3 = .{ | |
| 533 | frame.reg0, | |
| 534 | frame.reg1, | |
| 535 | frame.reg2, | |
| 536 | frame.reg3, | |
| 537 | frame.reg4, | |
| 538 | }; | |
| 539 | for (regs, 0..) |reg, i| { | |
| 540 | if (reg == 0) continue; | |
| 541 | const addr = fp - frame.frame_offset * @sizeOf(usize) + i * @sizeOf(usize); | |
| 542 | const reg_number = try Dwarf.compactUnwindToDwarfRegNumber(reg); | |
| 543 | (try dwarfRegNative(&context.cpu_context, reg_number)).* = @as(*const usize, @ptrFromInt(addr)).*; | |
| 544 | } | |
| 545 | ||
| 546 | break :ip new_ip; | |
| 547 | }, | |
| 548 | .STACK_IMMD, | |
| 549 | .STACK_IND, | |
| 550 | => ip: { | |
| 551 | const frameless = encoding.value.x86_64.frameless; | |
| 552 | ||
| 553 | const sp = (try dwarfRegNative(&context.cpu_context, sp_reg_num)).*; | |
| 554 | const stack_size: usize = stack_size: { | |
| 555 | if (encoding.mode.x86_64 == .STACK_IMMD) { | |
| 556 | break :stack_size @as(usize, frameless.stack.direct.stack_size) * @sizeOf(usize); | |
| 557 | } | |
| 558 | // In .STACK_IND, the stack size is inferred from the subq instruction at the beginning of the function. | |
| 559 | const sub_offset_addr = | |
| 560 | module.text_base + | |
| 561 | entry.function_offset + | |
| 562 | frameless.stack.indirect.sub_offset; | |
| 563 | // `sub_offset_addr` points to the offset of the literal within the instruction | |
| 564 | const sub_operand = @as(*align(1) const u32, @ptrFromInt(sub_offset_addr)).*; | |
| 565 | break :stack_size sub_operand + @sizeOf(usize) * @as(usize, frameless.stack.indirect.stack_adjust); | |
| 566 | }; | |
| 567 | ||
| 568 | // Decode the Lehmer-coded sequence of registers. | |
| 569 | // For a description of the encoding see lib/libc/include/any-macos.13-any/mach-o/compact_unwind_encoding.h | |
| 570 | ||
| 571 | // Decode the variable-based permutation number into its digits. Each digit represents | |
| 572 | // an index into the list of register numbers that weren't yet used in the sequence at | |
| 573 | // the time the digit was added. | |
| 574 | const reg_count = frameless.stack_reg_count; | |
| 575 | const ip_ptr = ip_ptr: { | |
| 576 | var digits: [6]u3 = undefined; | |
| 577 | var accumulator: usize = frameless.stack_reg_permutation; | |
| 578 | var base: usize = 2; | |
| 579 | for (0..reg_count) |i| { | |
| 580 | const div = accumulator / base; | |
| 581 | digits[digits.len - 1 - i] = @intCast(accumulator - base * div); | |
| 582 | accumulator = div; | |
| 583 | base += 1; | |
| 584 | } | |
| 585 | ||
| 586 | var registers: [6]u3 = undefined; | |
| 587 | var used_indices: [6]bool = @splat(false); | |
| 588 | for (digits[digits.len - reg_count ..], 0..) |target_unused_index, i| { | |
| 589 | var unused_count: u8 = 0; | |
| 590 | const unused_index = for (used_indices, 0..) |used, index| { | |
| 591 | if (!used) { | |
| 592 | if (target_unused_index == unused_count) break index; | |
| 593 | unused_count += 1; | |
| 594 | } | |
| 595 | } else unreachable; | |
| 596 | registers[i] = @intCast(unused_index + 1); | |
| 597 | used_indices[unused_index] = true; | |
| 598 | } | |
| 599 | ||
| 600 | var reg_addr = sp + stack_size - @sizeOf(usize) * @as(usize, reg_count + 1); | |
| 601 | for (0..reg_count) |i| { | |
| 602 | const reg_number = try Dwarf.compactUnwindToDwarfRegNumber(registers[i]); | |
| 603 | (try dwarfRegNative(&context.cpu_context, reg_number)).* = @as(*const usize, @ptrFromInt(reg_addr)).*; | |
| 604 | reg_addr += @sizeOf(usize); | |
| 605 | } | |
| 606 | ||
| 607 | break :ip_ptr reg_addr; | |
| 608 | }; | |
| 609 | ||
| 610 | const new_ip = @as(*const usize, @ptrFromInt(ip_ptr)).*; | |
| 611 | const new_sp = ip_ptr + @sizeOf(usize); | |
| 612 | ||
| 613 | (try dwarfRegNative(&context.cpu_context, sp_reg_num)).* = new_sp; | |
| 614 | (try dwarfRegNative(&context.cpu_context, ip_reg_num)).* = new_ip; | |
| 615 | ||
| 616 | break :ip new_ip; | |
| 617 | }, | |
| 618 | .DWARF => { | |
| 619 | const dwarf = &(unwind.dwarf orelse return error.MissingDebugInfo); | |
| 620 | return context.unwindFrame(unwind.dwarf_cache, gpa, dwarf, unwind.vmaddr_slide, encoding.value.x86_64.dwarf); | |
| 621 | }, | |
| 622 | }, | |
| 623 | .aarch64, .aarch64_be => switch (encoding.mode.arm64) { | |
| 624 | .OLD => return error.UnsupportedDebugInfo, | |
| 625 | .FRAMELESS => ip: { | |
| 626 | const sp = (try dwarfRegNative(&context.cpu_context, sp_reg_num)).*; | |
| 627 | const new_sp = sp + encoding.value.arm64.frameless.stack_size * 16; | |
| 628 | const new_ip = (try dwarfRegNative(&context.cpu_context, 30)).*; | |
| 629 | (try dwarfRegNative(&context.cpu_context, sp_reg_num)).* = new_sp; | |
| 630 | break :ip new_ip; | |
| 631 | }, | |
| 632 | .DWARF => { | |
| 633 | const dwarf = &(unwind.dwarf orelse return error.MissingDebugInfo); | |
| 634 | return context.unwindFrame(unwind.dwarf_cache, gpa, dwarf, unwind.vmaddr_slide, encoding.value.arm64.dwarf); | |
| 635 | }, | |
| 636 | .FRAME => ip: { | |
| 637 | const frame = encoding.value.arm64.frame; | |
| 638 | ||
| 639 | const fp = (try dwarfRegNative(&context.cpu_context, fp_reg_num)).*; | |
| 640 | const ip_ptr = fp + @sizeOf(usize); | |
| 641 | ||
| 642 | var reg_addr = fp - @sizeOf(usize); | |
| 643 | inline for (@typeInfo(@TypeOf(frame.x_reg_pairs)).@"struct".fields, 0..) |field, i| { | |
| 644 | if (@field(frame.x_reg_pairs, field.name) != 0) { | |
| 645 | (try dwarfRegNative(&context.cpu_context, 19 + i)).* = @as(*const usize, @ptrFromInt(reg_addr)).*; | |
| 646 | reg_addr += @sizeOf(usize); | |
| 647 | (try dwarfRegNative(&context.cpu_context, 20 + i)).* = @as(*const usize, @ptrFromInt(reg_addr)).*; | |
| 648 | reg_addr += @sizeOf(usize); | |
| 649 | } | |
| 650 | } | |
| 651 | ||
| 652 | inline for (@typeInfo(@TypeOf(frame.d_reg_pairs)).@"struct".fields, 0..) |field, i| { | |
| 653 | if (@field(frame.d_reg_pairs, field.name) != 0) { | |
| 654 | // Only the lower half of the 128-bit V registers are restored during unwinding | |
| 655 | { | |
| 656 | const dest: *align(1) usize = @ptrCast(try context.cpu_context.dwarfRegisterBytes(64 + 8 + i)); | |
| 657 | dest.* = @as(*const usize, @ptrFromInt(reg_addr)).*; | |
| 658 | } | |
| 659 | reg_addr += @sizeOf(usize); | |
| 660 | { | |
| 661 | const dest: *align(1) usize = @ptrCast(try context.cpu_context.dwarfRegisterBytes(64 + 9 + i)); | |
| 662 | dest.* = @as(*const usize, @ptrFromInt(reg_addr)).*; | |
| 663 | } | |
| 664 | reg_addr += @sizeOf(usize); | |
| 665 | } | |
| 666 | } | |
| 667 | ||
| 668 | const new_ip = @as(*const usize, @ptrFromInt(ip_ptr)).*; | |
| 669 | const new_fp = @as(*const usize, @ptrFromInt(fp)).*; | |
| 670 | ||
| 671 | (try dwarfRegNative(&context.cpu_context, fp_reg_num)).* = new_fp; | |
| 672 | (try dwarfRegNative(&context.cpu_context, ip_reg_num)).* = new_ip; | |
| 673 | ||
| 674 | break :ip new_ip; | |
| 675 | }, | |
| 676 | }, | |
| 677 | else => comptime unreachable, // unimplemented | |
| 678 | }; | |
| 679 | ||
| 680 | const ret_addr = std.debug.stripInstructionPtrAuthCode(new_ip); | |
| 681 | ||
| 682 | // Like `DwarfUnwindContext.unwindFrame`, adjust our next lookup pc in case the `call` was this | |
| 683 | // function's last instruction making `ret_addr` one byte past its end. | |
| 684 | context.pc = ret_addr -| 1; | |
| 685 | ||
| 686 | return ret_addr; | |
| 687 | } | |
| 688 | pub const DebugInfo = struct { | |
| 689 | /// Held while checking and/or populating `unwind` or `loaded_macho`. | |
| 690 | /// Once a field is populated and the pointer `&di.loaded_macho.?` or `&di.unwind.?` has been | |
| 691 | /// gotten, the lock is released; i.e. it is not held while *using* the loaded info. | |
| 692 | mutex: std.Thread.Mutex, | |
| 693 | ||
| 694 | unwind: ?Unwind, | |
| 695 | loaded_macho: ?LoadedMachO, | |
| 696 | ||
| 697 | pub const init: DebugInfo = .{ | |
| 698 | .mutex = .{}, | |
| 699 | ||
| 700 | .unwind = null, | |
| 701 | .loaded_macho = null, | |
| 702 | }; | |
| 703 | ||
| 704 | pub fn deinit(di: *DebugInfo, gpa: Allocator) void { | |
| 705 | if (di.loaded_macho) |*loaded_macho| { | |
| 706 | for (loaded_macho.ofiles.values()) |*ofile| { | |
| 707 | ofile.dwarf.deinit(gpa); | |
| 708 | ofile.symbols_by_name.deinit(gpa); | |
| 709 | posix.munmap(ofile.mapped_memory); | |
| 710 | } | |
| 711 | loaded_macho.ofiles.deinit(gpa); | |
| 712 | gpa.free(loaded_macho.symbols); | |
| 713 | posix.munmap(loaded_macho.mapped_memory); | |
| 714 | } | |
| 715 | } | |
| 716 | ||
| 717 | const Unwind = struct { | |
| 718 | /// The slide applied to the `__unwind_info` and `__eh_frame` sections. | |
| 719 | /// So, `unwind_info.ptr` is this many bytes higher than the section's vmaddr. | |
| 720 | vmaddr_slide: u64, | |
| 721 | /// Backed by the in-memory section mapped by the loader. | |
| 722 | unwind_info: ?[]const u8, | |
| 723 | /// Backed by the in-memory `__eh_frame` section mapped by the loader. | |
| 724 | dwarf: ?Dwarf.Unwind, | |
| 725 | /// This is `undefined` if `dwarf == null`. | |
| 726 | dwarf_cache: *UnwindContext.Cache, | |
| 727 | }; | |
| 728 | ||
| 729 | const LoadedMachO = struct { | |
| 730 | mapped_memory: []align(std.heap.page_size_min) const u8, | |
| 731 | symbols: []const MachoSymbol, | |
| 732 | strings: []const u8, | |
| 733 | /// Key is index into `strings` of the file path. | |
| 734 | ofiles: std.AutoArrayHashMapUnmanaged(u32, OFile), | |
| 735 | /// This is not necessarily the same as the vmaddr_slide that dyld would report. This is | |
| 736 | /// because the segments in the file on disk might differ from the ones in memory. Normally | |
| 737 | /// we wouldn't necessarily expect that to work, but /usr/lib/dyld is incredibly annoying: | |
| 738 | /// it exists on disk (necessarily, because the kernel needs to load it!), but is also in | |
| 739 | /// the dyld cache (dyld actually restart itself from cache after loading it), and the two | |
| 740 | /// versions have (very) different segment base addresses. It's sort of like a large slide | |
| 741 | /// has been applied to all addresses in memory. For an optimal experience, we consider the | |
| 742 | /// on-disk vmaddr instead of the in-memory one. | |
| 743 | vaddr_offset: usize, | |
| 744 | }; | |
| 745 | ||
| 746 | const OFile = struct { | |
| 747 | mapped_memory: []align(std.heap.page_size_min) const u8, | |
| 748 | dwarf: Dwarf, | |
| 749 | strtab: []const u8, | |
| 750 | symtab: []align(1) const macho.nlist_64, | |
| 751 | /// All named symbols in `symtab`. Stored `u32` key is the index into `symtab`. Accessed | |
| 752 | /// through `SymbolAdapter`, so that the symbol name is used as the logical key. | |
| 753 | symbols_by_name: std.ArrayHashMapUnmanaged(u32, void, void, true), | |
| 754 | ||
| 755 | const SymbolAdapter = struct { | |
| 756 | strtab: []const u8, | |
| 757 | symtab: []align(1) const macho.nlist_64, | |
| 758 | pub fn hash(ctx: SymbolAdapter, sym_name: []const u8) u32 { | |
| 759 | _ = ctx; | |
| 760 | return @truncate(std.hash.Wyhash.hash(0, sym_name)); | |
| 761 | } | |
| 762 | pub fn eql(ctx: SymbolAdapter, a_sym_name: []const u8, b_sym_index: u32, b_index: usize) bool { | |
| 763 | _ = b_index; | |
| 764 | const b_sym = ctx.symtab[b_sym_index]; | |
| 765 | const b_sym_name = std.mem.sliceTo(ctx.strtab[b_sym.n_strx..], 0); | |
| 766 | return mem.eql(u8, a_sym_name, b_sym_name); | |
| 767 | } | |
| 768 | }; | |
| 769 | }; | |
| 770 | ||
| 771 | fn loadOFile(gpa: Allocator, o_file_path: []const u8) !OFile { | |
| 772 | const mapped_mem = try mapDebugInfoFile(o_file_path); | |
| 773 | errdefer posix.munmap(mapped_mem); | |
| 774 | ||
| 775 | if (mapped_mem.len < @sizeOf(macho.mach_header_64)) return error.InvalidDebugInfo; | |
| 776 | const hdr: *const macho.mach_header_64 = @ptrCast(@alignCast(mapped_mem.ptr)); | |
| 777 | if (hdr.magic != std.macho.MH_MAGIC_64) return error.InvalidDebugInfo; | |
| 778 | ||
| 779 | const seg_cmd: macho.LoadCommandIterator.LoadCommand, const symtab_cmd: macho.symtab_command = cmds: { | |
| 780 | var seg_cmd: ?macho.LoadCommandIterator.LoadCommand = null; | |
| 781 | var symtab_cmd: ?macho.symtab_command = null; | |
| 782 | var it: macho.LoadCommandIterator = .{ | |
| 783 | .ncmds = hdr.ncmds, | |
| 784 | .buffer = mapped_mem[@sizeOf(macho.mach_header_64)..][0..hdr.sizeofcmds], | |
| 785 | }; | |
| 786 | while (it.next()) |cmd| switch (cmd.cmd()) { | |
| 787 | .SEGMENT_64 => seg_cmd = cmd, | |
| 788 | .SYMTAB => symtab_cmd = cmd.cast(macho.symtab_command) orelse return error.InvalidDebugInfo, | |
| 789 | else => {}, | |
| 790 | }; | |
| 791 | break :cmds .{ | |
| 792 | seg_cmd orelse return error.MissingDebugInfo, | |
| 793 | symtab_cmd orelse return error.MissingDebugInfo, | |
| 794 | }; | |
| 795 | }; | |
| 796 | ||
| 797 | if (mapped_mem.len < symtab_cmd.stroff + symtab_cmd.strsize) return error.InvalidDebugInfo; | |
| 798 | if (mapped_mem[symtab_cmd.stroff + symtab_cmd.strsize - 1] != 0) return error.InvalidDebugInfo; | |
| 799 | const strtab = mapped_mem[symtab_cmd.stroff..][0 .. symtab_cmd.strsize - 1]; | |
| 800 | ||
| 801 | const n_sym_bytes = symtab_cmd.nsyms * @sizeOf(macho.nlist_64); | |
| 802 | if (mapped_mem.len < symtab_cmd.symoff + n_sym_bytes) return error.InvalidDebugInfo; | |
| 803 | const symtab: []align(1) const macho.nlist_64 = @ptrCast(mapped_mem[symtab_cmd.symoff..][0..n_sym_bytes]); | |
| 804 | ||
| 805 | // TODO handle tentative (common) symbols | |
| 806 | var symbols_by_name: std.ArrayHashMapUnmanaged(u32, void, void, true) = .empty; | |
| 807 | defer symbols_by_name.deinit(gpa); | |
| 808 | try symbols_by_name.ensureUnusedCapacity(gpa, @intCast(symtab.len)); | |
| 809 | for (symtab, 0..) |sym, sym_index| { | |
| 810 | if (sym.n_strx == 0) continue; | |
| 811 | switch (sym.n_type.bits.type) { | |
| 812 | .undf => continue, // includes tentative symbols | |
| 813 | .abs => continue, | |
| 814 | else => {}, | |
| 815 | } | |
| 816 | const sym_name = mem.sliceTo(strtab[sym.n_strx..], 0); | |
| 817 | const gop = symbols_by_name.getOrPutAssumeCapacityAdapted( | |
| 818 | @as([]const u8, sym_name), | |
| 819 | @as(DebugInfo.OFile.SymbolAdapter, .{ .strtab = strtab, .symtab = symtab }), | |
| 820 | ); | |
| 821 | if (gop.found_existing) return error.InvalidDebugInfo; | |
| 822 | gop.key_ptr.* = @intCast(sym_index); | |
| 823 | } | |
| 824 | ||
| 825 | var sections: Dwarf.SectionArray = @splat(null); | |
| 826 | for (seg_cmd.getSections()) |sect| { | |
| 827 | if (!std.mem.eql(u8, "__DWARF", sect.segName())) continue; | |
| 828 | ||
| 829 | const section_index: usize = inline for (@typeInfo(Dwarf.Section.Id).@"enum".fields, 0..) |section, i| { | |
| 830 | if (mem.eql(u8, "__" ++ section.name, sect.sectName())) break i; | |
| 831 | } else continue; | |
| 832 | ||
| 833 | if (mapped_mem.len < sect.offset + sect.size) return error.InvalidDebugInfo; | |
| 834 | const section_bytes = mapped_mem[sect.offset..][0..sect.size]; | |
| 835 | sections[section_index] = .{ | |
| 836 | .data = section_bytes, | |
| 837 | .owned = false, | |
| 838 | }; | |
| 839 | } | |
| 840 | ||
| 841 | const missing_debug_info = | |
| 842 | sections[@intFromEnum(Dwarf.Section.Id.debug_info)] == null or | |
| 843 | sections[@intFromEnum(Dwarf.Section.Id.debug_abbrev)] == null or | |
| 844 | sections[@intFromEnum(Dwarf.Section.Id.debug_str)] == null or | |
| 845 | sections[@intFromEnum(Dwarf.Section.Id.debug_line)] == null; | |
| 846 | if (missing_debug_info) return error.MissingDebugInfo; | |
| 847 | ||
| 848 | var dwarf: Dwarf = .{ .sections = sections }; | |
| 849 | errdefer dwarf.deinit(gpa); | |
| 850 | try dwarf.open(gpa, native_endian); | |
| 851 | ||
| 852 | return .{ | |
| 853 | .mapped_memory = mapped_mem, | |
| 854 | .dwarf = dwarf, | |
| 855 | .strtab = strtab, | |
| 856 | .symtab = symtab, | |
| 857 | .symbols_by_name = symbols_by_name.move(), | |
| 858 | }; | |
| 859 | } | |
| 860 | }; | |
| 861 | ||
| 862 | const MachoSymbol = struct { | |
| 863 | strx: u32, | |
| 864 | addr: u64, | |
| 865 | /// Value may be `unknown_ofile`. | |
| 866 | ofile: u32, | |
| 867 | const unknown_ofile = std.math.maxInt(u32); | |
| 868 | fn addressLessThan(context: void, lhs: MachoSymbol, rhs: MachoSymbol) bool { | |
| 869 | _ = context; | |
| 870 | return lhs.addr < rhs.addr; | |
| 871 | } | |
| 872 | /// Assumes that `symbols` is sorted in order of ascending `addr`. | |
| 873 | fn find(symbols: []const MachoSymbol, address: usize) ?*const MachoSymbol { | |
| 874 | if (symbols.len == 0) return null; // no potential match | |
| 875 | if (address < symbols[0].addr) return null; // address is before the lowest-address symbol | |
| 876 | var left: usize = 0; | |
| 877 | var len: usize = symbols.len; | |
| 878 | while (len > 1) { | |
| 879 | const mid = left + len / 2; | |
| 880 | if (address < symbols[mid].addr) { | |
| 881 | len /= 2; | |
| 882 | } else { | |
| 883 | left = mid; | |
| 884 | len -= len / 2; | |
| 885 | } | |
| 886 | } | |
| 887 | return &symbols[left]; | |
| 888 | } | |
| 889 | ||
| 890 | test find { | |
| 891 | const symbols: []const MachoSymbol = &.{ | |
| 892 | .{ .addr = 100, .strx = undefined, .ofile = undefined }, | |
| 893 | .{ .addr = 200, .strx = undefined, .ofile = undefined }, | |
| 894 | .{ .addr = 300, .strx = undefined, .ofile = undefined }, | |
| 895 | }; | |
| 896 | ||
| 897 | try testing.expectEqual(null, find(symbols, 0)); | |
| 898 | try testing.expectEqual(null, find(symbols, 99)); | |
| 899 | try testing.expectEqual(&symbols[0], find(symbols, 100).?); | |
| 900 | try testing.expectEqual(&symbols[0], find(symbols, 150).?); | |
| 901 | try testing.expectEqual(&symbols[0], find(symbols, 199).?); | |
| 902 | ||
| 903 | try testing.expectEqual(&symbols[1], find(symbols, 200).?); | |
| 904 | try testing.expectEqual(&symbols[1], find(symbols, 250).?); | |
| 905 | try testing.expectEqual(&symbols[1], find(symbols, 299).?); | |
| 906 | ||
| 907 | try testing.expectEqual(&symbols[2], find(symbols, 300).?); | |
| 908 | try testing.expectEqual(&symbols[2], find(symbols, 301).?); | |
| 909 | try testing.expectEqual(&symbols[2], find(symbols, 5000).?); | |
| 910 | } | |
| 911 | }; | |
| 912 | test { | |
| 913 | _ = MachoSymbol; | |
| 914 | } | |
| 915 | ||
| 916 | const ip_reg_num = Dwarf.ipRegNum(builtin.target.cpu.arch).?; | |
| 917 | const fp_reg_num = Dwarf.fpRegNum(builtin.target.cpu.arch); | |
| 918 | const sp_reg_num = Dwarf.spRegNum(builtin.target.cpu.arch); | |
| 919 | ||
| 920 | /// Uses `mmap` to map the file at `path` into memory. | |
| 921 | fn mapDebugInfoFile(path: []const u8) ![]align(std.heap.page_size_min) const u8 { | |
| 922 | const file = std.fs.cwd().openFile(path, .{}) catch |err| switch (err) { | |
| 923 | error.FileNotFound => return error.MissingDebugInfo, | |
| 924 | else => return error.ReadFailed, | |
| 925 | }; | |
| 926 | defer file.close(); | |
| 927 | ||
| 928 | const file_len = std.math.cast(usize, try file.getEndPos()) orelse return error.InvalidDebugInfo; | |
| 929 | ||
| 930 | return posix.mmap( | |
| 931 | null, | |
| 932 | file_len, | |
| 933 | posix.PROT.READ, | |
| 934 | .{ .TYPE = .SHARED }, | |
| 935 | file.handle, | |
| 936 | 0, | |
| 937 | ); | |
| 938 | } | |
| 939 | ||
| 940 | const DarwinModule = @This(); | |
| 941 | ||
| 942 | const std = @import("../../std.zig"); | |
| 943 | const Allocator = std.mem.Allocator; | |
| 944 | const Dwarf = std.debug.Dwarf; | |
| 945 | const assert = std.debug.assert; | |
| 946 | const macho = std.macho; | |
| 947 | const mem = std.mem; | |
| 948 | const posix = std.posix; | |
| 949 | const testing = std.testing; | |
| 950 | const Error = std.debug.SelfInfo.Error; | |
| 951 | const dwarfRegNative = std.debug.SelfInfo.DwarfUnwindContext.regNative; | |
| 952 | ||
| 953 | const builtin = @import("builtin"); | |
| 954 | const native_endian = builtin.target.cpu.arch.endian(); |
lib/std/debug/SelfInfo/Elf.zig created+427| ... | ... | @@ -0,0 +1,427 @@ |
| 1 | rwlock: std.Thread.RwLock, | |
| 2 | ||
| 3 | modules: std.ArrayList(Module), | |
| 4 | ranges: std.ArrayList(Module.Range), | |
| 5 | ||
| 6 | unwind_cache: if (can_unwind) ?[]Dwarf.SelfUnwinder.CacheEntry else ?noreturn, | |
| 7 | ||
| 8 | pub const init: SelfInfo = .{ | |
| 9 | .rwlock = .{}, | |
| 10 | .modules = .empty, | |
| 11 | .ranges = .empty, | |
| 12 | .unwind_cache = null, | |
| 13 | }; | |
| 14 | pub fn deinit(si: *SelfInfo, gpa: Allocator) void { | |
| 15 | for (si.modules.items) |*mod| { | |
| 16 | unwind: { | |
| 17 | const u = &(mod.unwind orelse break :unwind catch break :unwind); | |
| 18 | for (u.buf[0..u.len]) |*unwind| unwind.deinit(gpa); | |
| 19 | } | |
| 20 | loaded: { | |
| 21 | const l = &(mod.loaded_elf orelse break :loaded catch break :loaded); | |
| 22 | l.file.deinit(gpa); | |
| 23 | } | |
| 24 | } | |
| 25 | ||
| 26 | si.modules.deinit(gpa); | |
| 27 | si.ranges.deinit(gpa); | |
| 28 | if (si.unwind_cache) |cache| gpa.free(cache); | |
| 29 | } | |
| 30 | ||
| 31 | pub fn getSymbol(si: *SelfInfo, gpa: Allocator, address: usize) Error!std.debug.Symbol { | |
| 32 | const module = try si.findModule(gpa, address, .exclusive); | |
| 33 | defer si.rwlock.unlock(); | |
| 34 | ||
| 35 | const vaddr = address - module.load_offset; | |
| 36 | ||
| 37 | const loaded_elf = try module.getLoadedElf(gpa); | |
| 38 | if (loaded_elf.file.dwarf) |*dwarf| { | |
| 39 | if (!loaded_elf.scanned_dwarf) { | |
| 40 | dwarf.open(gpa, native_endian) catch |err| switch (err) { | |
| 41 | error.InvalidDebugInfo, | |
| 42 | error.MissingDebugInfo, | |
| 43 | error.OutOfMemory, | |
| 44 | => |e| return e, | |
| 45 | error.EndOfStream, | |
| 46 | error.Overflow, | |
| 47 | error.ReadFailed, | |
| 48 | error.StreamTooLong, | |
| 49 | => return error.InvalidDebugInfo, | |
| 50 | }; | |
| 51 | loaded_elf.scanned_dwarf = true; | |
| 52 | } | |
| 53 | if (dwarf.getSymbol(gpa, native_endian, vaddr)) |sym| { | |
| 54 | return sym; | |
| 55 | } else |err| switch (err) { | |
| 56 | error.MissingDebugInfo => {}, | |
| 57 | ||
| 58 | error.InvalidDebugInfo, | |
| 59 | error.OutOfMemory, | |
| 60 | => |e| return e, | |
| 61 | ||
| 62 | error.ReadFailed, | |
| 63 | error.EndOfStream, | |
| 64 | error.Overflow, | |
| 65 | error.StreamTooLong, | |
| 66 | => return error.InvalidDebugInfo, | |
| 67 | } | |
| 68 | } | |
| 69 | // When DWARF is unavailable, fall back to searching the symtab. | |
| 70 | return loaded_elf.file.searchSymtab(gpa, vaddr) catch |err| switch (err) { | |
| 71 | error.NoSymtab, error.NoStrtab => return error.MissingDebugInfo, | |
| 72 | error.BadSymtab => return error.InvalidDebugInfo, | |
| 73 | error.OutOfMemory => |e| return e, | |
| 74 | }; | |
| 75 | } | |
| 76 | pub fn getModuleName(si: *SelfInfo, gpa: Allocator, address: usize) Error![]const u8 { | |
| 77 | const module = try si.findModule(gpa, address, .shared); | |
| 78 | defer si.rwlock.unlockShared(); | |
| 79 | if (module.name.len == 0) return error.MissingDebugInfo; | |
| 80 | return module.name; | |
| 81 | } | |
| 82 | ||
| 83 | pub const can_unwind: bool = s: { | |
| 84 | // Notably, we are yet to support unwinding on ARM. There, unwinding is not done through | |
| 85 | // `.eh_frame`, but instead with the `.ARM.exidx` section, which has a different format. | |
| 86 | const archs: []const std.Target.Cpu.Arch = switch (builtin.target.os.tag) { | |
| 87 | .linux => &.{ .x86, .x86_64, .aarch64, .aarch64_be }, | |
| 88 | .netbsd => &.{ .x86, .x86_64, .aarch64, .aarch64_be }, | |
| 89 | .freebsd => &.{ .x86_64, .aarch64, .aarch64_be }, | |
| 90 | .openbsd => &.{.x86_64}, | |
| 91 | .solaris => &.{ .x86, .x86_64 }, | |
| 92 | .illumos => &.{ .x86, .x86_64 }, | |
| 93 | else => unreachable, | |
| 94 | }; | |
| 95 | for (archs) |a| { | |
| 96 | if (builtin.target.cpu.arch == a) break :s true; | |
| 97 | } | |
| 98 | break :s false; | |
| 99 | }; | |
| 100 | comptime { | |
| 101 | if (can_unwind) { | |
| 102 | std.debug.assert(Dwarf.supportsUnwinding(&builtin.target)); | |
| 103 | } | |
| 104 | } | |
| 105 | pub const UnwindContext = Dwarf.SelfUnwinder; | |
| 106 | pub fn unwindFrame(si: *SelfInfo, gpa: Allocator, context: *UnwindContext) Error!usize { | |
| 107 | comptime assert(can_unwind); | |
| 108 | ||
| 109 | { | |
| 110 | si.rwlock.lockShared(); | |
| 111 | defer si.rwlock.unlockShared(); | |
| 112 | if (si.unwind_cache) |cache| { | |
| 113 | if (Dwarf.SelfUnwinder.CacheEntry.find(cache, context.pc)) |entry| { | |
| 114 | return context.next(gpa, entry); | |
| 115 | } | |
| 116 | } | |
| 117 | } | |
| 118 | ||
| 119 | const module = try si.findModule(gpa, context.pc, .exclusive); | |
| 120 | defer si.rwlock.unlock(); | |
| 121 | ||
| 122 | if (si.unwind_cache == null) { | |
| 123 | si.unwind_cache = try gpa.alloc(Dwarf.SelfUnwinder.CacheEntry, 2048); | |
| 124 | @memset(si.unwind_cache.?, .empty); | |
| 125 | } | |
| 126 | ||
| 127 | const unwind_sections = try module.getUnwindSections(gpa); | |
| 128 | for (unwind_sections) |*unwind| { | |
| 129 | if (context.computeRules(gpa, unwind, module.load_offset, null)) |entry| { | |
| 130 | entry.populate(si.unwind_cache.?); | |
| 131 | return context.next(gpa, &entry); | |
| 132 | } else |err| switch (err) { | |
| 133 | error.MissingDebugInfo => continue, | |
| 134 | ||
| 135 | error.InvalidDebugInfo, | |
| 136 | error.UnsupportedDebugInfo, | |
| 137 | error.OutOfMemory, | |
| 138 | => |e| return e, | |
| 139 | ||
| 140 | error.EndOfStream, | |
| 141 | error.StreamTooLong, | |
| 142 | error.ReadFailed, | |
| 143 | error.Overflow, | |
| 144 | error.InvalidOpcode, | |
| 145 | error.InvalidOperation, | |
| 146 | error.InvalidOperand, | |
| 147 | => return error.InvalidDebugInfo, | |
| 148 | ||
| 149 | error.UnimplementedUserOpcode, | |
| 150 | error.UnsupportedAddrSize, | |
| 151 | => return error.UnsupportedDebugInfo, | |
| 152 | } | |
| 153 | } | |
| 154 | return error.MissingDebugInfo; | |
| 155 | } | |
| 156 | ||
| 157 | const Module = struct { | |
| 158 | load_offset: usize, | |
| 159 | name: []const u8, | |
| 160 | build_id: ?[]const u8, | |
| 161 | gnu_eh_frame: ?[]const u8, | |
| 162 | ||
| 163 | /// `null` means unwind information has not yet been loaded. | |
| 164 | unwind: ?(Error!UnwindSections), | |
| 165 | ||
| 166 | /// `null` means the ELF file has not yet been loaded. | |
| 167 | loaded_elf: ?(Error!LoadedElf), | |
| 168 | ||
| 169 | const LoadedElf = struct { | |
| 170 | file: std.debug.ElfFile, | |
| 171 | scanned_dwarf: bool, | |
| 172 | }; | |
| 173 | ||
| 174 | const UnwindSections = struct { | |
| 175 | buf: [2]Dwarf.Unwind, | |
| 176 | len: usize, | |
| 177 | }; | |
| 178 | ||
| 179 | const Range = struct { | |
| 180 | start: usize, | |
| 181 | len: usize, | |
| 182 | /// Index into `modules` | |
| 183 | module_index: usize, | |
| 184 | }; | |
| 185 | ||
| 186 | /// Assumes we already hold an exclusive lock. | |
| 187 | fn getUnwindSections(mod: *Module, gpa: Allocator) Error![]Dwarf.Unwind { | |
| 188 | if (mod.unwind == null) mod.unwind = loadUnwindSections(mod, gpa); | |
| 189 | const us = &(mod.unwind.? catch |err| return err); | |
| 190 | return us.buf[0..us.len]; | |
| 191 | } | |
| 192 | fn loadUnwindSections(mod: *Module, gpa: Allocator) Error!UnwindSections { | |
| 193 | var us: UnwindSections = .{ | |
| 194 | .buf = undefined, | |
| 195 | .len = 0, | |
| 196 | }; | |
| 197 | if (mod.gnu_eh_frame) |section_bytes| { | |
| 198 | const section_vaddr: u64 = @intFromPtr(section_bytes.ptr) - mod.load_offset; | |
| 199 | const header = Dwarf.Unwind.EhFrameHeader.parse(section_vaddr, section_bytes, @sizeOf(usize), native_endian) catch |err| switch (err) { | |
| 200 | error.ReadFailed => unreachable, // it's all fixed buffers | |
| 201 | error.InvalidDebugInfo => |e| return e, | |
| 202 | error.EndOfStream, error.Overflow => return error.InvalidDebugInfo, | |
| 203 | error.UnsupportedAddrSize => return error.UnsupportedDebugInfo, | |
| 204 | }; | |
| 205 | us.buf[us.len] = .initEhFrameHdr(header, section_vaddr, @ptrFromInt(@as(usize, @intCast(mod.load_offset + header.eh_frame_vaddr)))); | |
| 206 | us.len += 1; | |
| 207 | } else { | |
| 208 | // There is no `.eh_frame_hdr` section. There may still be an `.eh_frame` or `.debug_frame` | |
| 209 | // section, but we'll have to load the binary to get at it. | |
| 210 | const loaded = try mod.getLoadedElf(gpa); | |
| 211 | // If both are present, we can't just pick one -- the info could be split between them. | |
| 212 | // `.debug_frame` is likely to be the more complete section, so we'll prioritize that one. | |
| 213 | if (loaded.file.debug_frame) |*debug_frame| { | |
| 214 | us.buf[us.len] = .initSection(.debug_frame, debug_frame.vaddr, debug_frame.bytes); | |
| 215 | us.len += 1; | |
| 216 | } | |
| 217 | if (loaded.file.eh_frame) |*eh_frame| { | |
| 218 | us.buf[us.len] = .initSection(.eh_frame, eh_frame.vaddr, eh_frame.bytes); | |
| 219 | us.len += 1; | |
| 220 | } | |
| 221 | } | |
| 222 | errdefer for (us.buf[0..us.len]) |*u| u.deinit(gpa); | |
| 223 | for (us.buf[0..us.len]) |*u| u.prepare(gpa, @sizeOf(usize), native_endian, true, false) catch |err| switch (err) { | |
| 224 | error.ReadFailed => unreachable, // it's all fixed buffers | |
| 225 | error.InvalidDebugInfo, | |
| 226 | error.MissingDebugInfo, | |
| 227 | error.OutOfMemory, | |
| 228 | => |e| return e, | |
| 229 | error.EndOfStream, | |
| 230 | error.Overflow, | |
| 231 | error.StreamTooLong, | |
| 232 | error.InvalidOperand, | |
| 233 | error.InvalidOpcode, | |
| 234 | error.InvalidOperation, | |
| 235 | => return error.InvalidDebugInfo, | |
| 236 | error.UnsupportedAddrSize, | |
| 237 | error.UnsupportedDwarfVersion, | |
| 238 | error.UnimplementedUserOpcode, | |
| 239 | => return error.UnsupportedDebugInfo, | |
| 240 | }; | |
| 241 | return us; | |
| 242 | } | |
| 243 | ||
| 244 | /// Assumes we already hold an exclusive lock. | |
| 245 | fn getLoadedElf(mod: *Module, gpa: Allocator) Error!*LoadedElf { | |
| 246 | if (mod.loaded_elf == null) mod.loaded_elf = loadElf(mod, gpa); | |
| 247 | return if (mod.loaded_elf.?) |*elf| elf else |err| err; | |
| 248 | } | |
| 249 | fn loadElf(mod: *Module, gpa: Allocator) Error!LoadedElf { | |
| 250 | const load_result = if (mod.name.len > 0) res: { | |
| 251 | var file = std.fs.cwd().openFile(mod.name, .{}) catch return error.MissingDebugInfo; | |
| 252 | defer file.close(); | |
| 253 | break :res std.debug.ElfFile.load(gpa, file, mod.build_id, &.native(mod.name)); | |
| 254 | } else res: { | |
| 255 | const path = std.fs.selfExePathAlloc(gpa) catch |err| switch (err) { | |
| 256 | error.OutOfMemory => |e| return e, | |
| 257 | else => return error.ReadFailed, | |
| 258 | }; | |
| 259 | defer gpa.free(path); | |
| 260 | var file = std.fs.cwd().openFile(path, .{}) catch return error.MissingDebugInfo; | |
| 261 | defer file.close(); | |
| 262 | break :res std.debug.ElfFile.load(gpa, file, mod.build_id, &.native(path)); | |
| 263 | }; | |
| 264 | ||
| 265 | var elf_file = load_result catch |err| switch (err) { | |
| 266 | error.OutOfMemory, | |
| 267 | error.Unexpected, | |
| 268 | => |e| return e, | |
| 269 | ||
| 270 | error.Overflow, | |
| 271 | error.TruncatedElfFile, | |
| 272 | error.InvalidCompressedSection, | |
| 273 | error.InvalidElfMagic, | |
| 274 | error.InvalidElfVersion, | |
| 275 | error.InvalidElfClass, | |
| 276 | error.InvalidElfEndian, | |
| 277 | => return error.InvalidDebugInfo, | |
| 278 | ||
| 279 | error.SystemResources, | |
| 280 | error.MemoryMappingNotSupported, | |
| 281 | error.AccessDenied, | |
| 282 | error.LockedMemoryLimitExceeded, | |
| 283 | error.ProcessFdQuotaExceeded, | |
| 284 | error.SystemFdQuotaExceeded, | |
| 285 | => return error.ReadFailed, | |
| 286 | }; | |
| 287 | errdefer elf_file.deinit(gpa); | |
| 288 | ||
| 289 | if (elf_file.endian != native_endian) return error.InvalidDebugInfo; | |
| 290 | if (elf_file.is_64 != (@sizeOf(usize) == 8)) return error.InvalidDebugInfo; | |
| 291 | ||
| 292 | return .{ | |
| 293 | .file = elf_file, | |
| 294 | .scanned_dwarf = false, | |
| 295 | }; | |
| 296 | } | |
| 297 | }; | |
| 298 | ||
| 299 | fn findModule(si: *SelfInfo, gpa: Allocator, address: usize, lock: enum { shared, exclusive }) Error!*Module { | |
| 300 | // With the requested lock, scan the module ranges looking for `address`. | |
| 301 | switch (lock) { | |
| 302 | .shared => si.rwlock.lockShared(), | |
| 303 | .exclusive => si.rwlock.lock(), | |
| 304 | } | |
| 305 | for (si.ranges.items) |*range| { | |
| 306 | if (address >= range.start and address < range.start + range.len) { | |
| 307 | return &si.modules.items[range.module_index]; | |
| 308 | } | |
| 309 | } | |
| 310 | // The address wasn't in a known range. We will rebuild the module/range lists, since it's possible | |
| 311 | // a new module was loaded. Upgrade to an exclusive lock if necessary. | |
| 312 | switch (lock) { | |
| 313 | .shared => { | |
| 314 | si.rwlock.unlockShared(); | |
| 315 | si.rwlock.lock(); | |
| 316 | }, | |
| 317 | .exclusive => {}, | |
| 318 | } | |
| 319 | // Rebuild module list with the exclusive lock. | |
| 320 | { | |
| 321 | errdefer si.rwlock.unlock(); | |
| 322 | for (si.modules.items) |*mod| { | |
| 323 | unwind: { | |
| 324 | const u = &(mod.unwind orelse break :unwind catch break :unwind); | |
| 325 | for (u.buf[0..u.len]) |*unwind| unwind.deinit(gpa); | |
| 326 | } | |
| 327 | loaded: { | |
| 328 | const l = &(mod.loaded_elf orelse break :loaded catch break :loaded); | |
| 329 | l.file.deinit(gpa); | |
| 330 | } | |
| 331 | } | |
| 332 | si.modules.clearRetainingCapacity(); | |
| 333 | si.ranges.clearRetainingCapacity(); | |
| 334 | var ctx: DlIterContext = .{ .si = si, .gpa = gpa }; | |
| 335 | try std.posix.dl_iterate_phdr(&ctx, error{OutOfMemory}, DlIterContext.callback); | |
| 336 | } | |
| 337 | // Downgrade the lock back to shared if necessary. | |
| 338 | switch (lock) { | |
| 339 | .shared => { | |
| 340 | si.rwlock.unlock(); | |
| 341 | si.rwlock.lockShared(); | |
| 342 | }, | |
| 343 | .exclusive => {}, | |
| 344 | } | |
| 345 | // Scan the newly rebuilt module ranges. | |
| 346 | for (si.ranges.items) |*range| { | |
| 347 | if (address >= range.start and address < range.start + range.len) { | |
| 348 | return &si.modules.items[range.module_index]; | |
| 349 | } | |
| 350 | } | |
| 351 | // Still nothing; unlock and error. | |
| 352 | switch (lock) { | |
| 353 | .shared => si.rwlock.unlockShared(), | |
| 354 | .exclusive => si.rwlock.unlock(), | |
| 355 | } | |
| 356 | return error.MissingDebugInfo; | |
| 357 | } | |
| 358 | const DlIterContext = struct { | |
| 359 | si: *SelfInfo, | |
| 360 | gpa: Allocator, | |
| 361 | ||
| 362 | fn callback(info: *std.posix.dl_phdr_info, size: usize, context: *@This()) !void { | |
| 363 | _ = size; | |
| 364 | ||
| 365 | var build_id: ?[]const u8 = null; | |
| 366 | var gnu_eh_frame: ?[]const u8 = null; | |
| 367 | ||
| 368 | // Populate `build_id` and `gnu_eh_frame` | |
| 369 | for (info.phdr[0..info.phnum]) |phdr| { | |
| 370 | switch (phdr.p_type) { | |
| 371 | std.elf.PT_NOTE => { | |
| 372 | // Look for .note.gnu.build-id | |
| 373 | const segment_ptr: [*]const u8 = @ptrFromInt(info.addr + phdr.p_vaddr); | |
| 374 | var r: std.Io.Reader = .fixed(segment_ptr[0..phdr.p_memsz]); | |
| 375 | const name_size = r.takeInt(u32, native_endian) catch continue; | |
| 376 | const desc_size = r.takeInt(u32, native_endian) catch continue; | |
| 377 | const note_type = r.takeInt(u32, native_endian) catch continue; | |
| 378 | const name = r.take(name_size) catch continue; | |
| 379 | if (note_type != std.elf.NT_GNU_BUILD_ID) continue; | |
| 380 | if (!std.mem.eql(u8, name, "GNU\x00")) continue; | |
| 381 | const desc = r.take(desc_size) catch continue; | |
| 382 | build_id = desc; | |
| 383 | }, | |
| 384 | std.elf.PT_GNU_EH_FRAME => { | |
| 385 | const segment_ptr: [*]const u8 = @ptrFromInt(info.addr + phdr.p_vaddr); | |
| 386 | gnu_eh_frame = segment_ptr[0..phdr.p_memsz]; | |
| 387 | }, | |
| 388 | else => {}, | |
| 389 | } | |
| 390 | } | |
| 391 | ||
| 392 | const gpa = context.gpa; | |
| 393 | const si = context.si; | |
| 394 | ||
| 395 | const module_index = si.modules.items.len; | |
| 396 | try si.modules.append(gpa, .{ | |
| 397 | .load_offset = info.addr, | |
| 398 | // Android libc uses NULL instead of "" to mark the main program | |
| 399 | .name = std.mem.sliceTo(info.name, 0) orelse "", | |
| 400 | .build_id = build_id, | |
| 401 | .gnu_eh_frame = gnu_eh_frame, | |
| 402 | .unwind = null, | |
| 403 | .loaded_elf = null, | |
| 404 | }); | |
| 405 | ||
| 406 | for (info.phdr[0..info.phnum]) |phdr| { | |
| 407 | if (phdr.p_type != std.elf.PT_LOAD) continue; | |
| 408 | try context.si.ranges.append(gpa, .{ | |
| 409 | // Overflowing addition handles VSDOs having p_vaddr = 0xffffffffff700000 | |
| 410 | .start = info.addr +% phdr.p_vaddr, | |
| 411 | .len = phdr.p_memsz, | |
| 412 | .module_index = module_index, | |
| 413 | }); | |
| 414 | } | |
| 415 | } | |
| 416 | }; | |
| 417 | ||
| 418 | const std = @import("std"); | |
| 419 | const Allocator = std.mem.Allocator; | |
| 420 | const Dwarf = std.debug.Dwarf; | |
| 421 | const Error = std.debug.SelfInfoError; | |
| 422 | const assert = std.debug.assert; | |
| 423 | ||
| 424 | const builtin = @import("builtin"); | |
| 425 | const native_endian = builtin.target.cpu.arch.endian(); | |
| 426 | ||
| 427 | const SelfInfo = @This(); |
lib/std/debug/SelfInfo/ElfModule.zig deleted-349| ... | ... | @@ -1,349 +0,0 @@ |
| 1 | load_offset: usize, | |
| 2 | name: []const u8, | |
| 3 | build_id: ?[]const u8, | |
| 4 | gnu_eh_frame: ?[]const u8, | |
| 5 | ||
| 6 | pub const LookupCache = struct { | |
| 7 | rwlock: std.Thread.RwLock, | |
| 8 | ranges: std.ArrayList(Range), | |
| 9 | const Range = struct { | |
| 10 | start: usize, | |
| 11 | len: usize, | |
| 12 | mod: ElfModule, | |
| 13 | }; | |
| 14 | pub const init: LookupCache = .{ | |
| 15 | .rwlock = .{}, | |
| 16 | .ranges = .empty, | |
| 17 | }; | |
| 18 | pub fn deinit(lc: *LookupCache, gpa: Allocator) void { | |
| 19 | lc.ranges.deinit(gpa); | |
| 20 | } | |
| 21 | }; | |
| 22 | ||
| 23 | pub const DebugInfo = struct { | |
| 24 | /// Held while checking and/or populating `loaded_elf`/`scanned_dwarf`/`unwind`. | |
| 25 | /// Once data is populated and a pointer to the field has been gotten, the lock | |
| 26 | /// is released; i.e. it is not held while *using* the loaded debug info. | |
| 27 | mutex: std.Thread.Mutex, | |
| 28 | ||
| 29 | loaded_elf: ?ElfFile, | |
| 30 | scanned_dwarf: bool, | |
| 31 | unwind: if (supports_unwinding) [2]?Dwarf.Unwind else void, | |
| 32 | unwind_cache: if (supports_unwinding) *UnwindContext.Cache else void, | |
| 33 | ||
| 34 | pub const init: DebugInfo = .{ | |
| 35 | .mutex = .{}, | |
| 36 | .loaded_elf = null, | |
| 37 | .scanned_dwarf = false, | |
| 38 | .unwind = if (supports_unwinding) @splat(null), | |
| 39 | .unwind_cache = undefined, | |
| 40 | }; | |
| 41 | pub fn deinit(di: *DebugInfo, gpa: Allocator) void { | |
| 42 | if (di.loaded_elf) |*loaded_elf| loaded_elf.deinit(gpa); | |
| 43 | if (supports_unwinding) { | |
| 44 | if (di.unwind[0] != null) gpa.destroy(di.unwind_cache); | |
| 45 | for (&di.unwind) |*opt_unwind| { | |
| 46 | const unwind = &(opt_unwind.* orelse continue); | |
| 47 | unwind.deinit(gpa); | |
| 48 | } | |
| 49 | } | |
| 50 | } | |
| 51 | }; | |
| 52 | ||
| 53 | pub fn key(m: ElfModule) usize { | |
| 54 | return m.load_offset; | |
| 55 | } | |
| 56 | pub fn lookup(cache: *LookupCache, gpa: Allocator, address: usize) Error!ElfModule { | |
| 57 | if (lookupInCache(cache, address)) |m| return m; | |
| 58 | ||
| 59 | { | |
| 60 | // Check a new module hasn't been loaded | |
| 61 | cache.rwlock.lock(); | |
| 62 | defer cache.rwlock.unlock(); | |
| 63 | const DlIterContext = struct { | |
| 64 | ranges: *std.ArrayList(LookupCache.Range), | |
| 65 | gpa: Allocator, | |
| 66 | ||
| 67 | fn callback(info: *std.posix.dl_phdr_info, size: usize, context: *@This()) !void { | |
| 68 | _ = size; | |
| 69 | ||
| 70 | var mod: ElfModule = .{ | |
| 71 | .load_offset = info.addr, | |
| 72 | // Android libc uses NULL instead of "" to mark the main program | |
| 73 | .name = mem.sliceTo(info.name, 0) orelse "", | |
| 74 | .build_id = null, | |
| 75 | .gnu_eh_frame = null, | |
| 76 | }; | |
| 77 | ||
| 78 | // Populate `build_id` and `gnu_eh_frame` | |
| 79 | for (info.phdr[0..info.phnum]) |phdr| { | |
| 80 | switch (phdr.p_type) { | |
| 81 | elf.PT_NOTE => { | |
| 82 | // Look for .note.gnu.build-id | |
| 83 | const segment_ptr: [*]const u8 = @ptrFromInt(info.addr + phdr.p_vaddr); | |
| 84 | var r: std.Io.Reader = .fixed(segment_ptr[0..phdr.p_memsz]); | |
| 85 | const name_size = r.takeInt(u32, native_endian) catch continue; | |
| 86 | const desc_size = r.takeInt(u32, native_endian) catch continue; | |
| 87 | const note_type = r.takeInt(u32, native_endian) catch continue; | |
| 88 | const name = r.take(name_size) catch continue; | |
| 89 | if (note_type != elf.NT_GNU_BUILD_ID) continue; | |
| 90 | if (!mem.eql(u8, name, "GNU\x00")) continue; | |
| 91 | const desc = r.take(desc_size) catch continue; | |
| 92 | mod.build_id = desc; | |
| 93 | }, | |
| 94 | elf.PT_GNU_EH_FRAME => { | |
| 95 | const segment_ptr: [*]const u8 = @ptrFromInt(info.addr + phdr.p_vaddr); | |
| 96 | mod.gnu_eh_frame = segment_ptr[0..phdr.p_memsz]; | |
| 97 | }, | |
| 98 | else => {}, | |
| 99 | } | |
| 100 | } | |
| 101 | ||
| 102 | // Now that `mod` is populated, create the ranges | |
| 103 | for (info.phdr[0..info.phnum]) |phdr| { | |
| 104 | if (phdr.p_type != elf.PT_LOAD) continue; | |
| 105 | try context.ranges.append(context.gpa, .{ | |
| 106 | // Overflowing addition handles VSDOs having p_vaddr = 0xffffffffff700000 | |
| 107 | .start = info.addr +% phdr.p_vaddr, | |
| 108 | .len = phdr.p_memsz, | |
| 109 | .mod = mod, | |
| 110 | }); | |
| 111 | } | |
| 112 | } | |
| 113 | }; | |
| 114 | cache.ranges.clearRetainingCapacity(); | |
| 115 | var ctx: DlIterContext = .{ | |
| 116 | .ranges = &cache.ranges, | |
| 117 | .gpa = gpa, | |
| 118 | }; | |
| 119 | try std.posix.dl_iterate_phdr(&ctx, error{OutOfMemory}, DlIterContext.callback); | |
| 120 | } | |
| 121 | ||
| 122 | if (lookupInCache(cache, address)) |m| return m; | |
| 123 | return error.MissingDebugInfo; | |
| 124 | } | |
| 125 | fn lookupInCache(cache: *LookupCache, address: usize) ?ElfModule { | |
| 126 | cache.rwlock.lockShared(); | |
| 127 | defer cache.rwlock.unlockShared(); | |
| 128 | for (cache.ranges.items) |*range| { | |
| 129 | if (address >= range.start and address < range.start + range.len) { | |
| 130 | return range.mod; | |
| 131 | } | |
| 132 | } | |
| 133 | return null; | |
| 134 | } | |
| 135 | fn loadElf(module: *const ElfModule, gpa: Allocator, di: *DebugInfo) Error!void { | |
| 136 | std.debug.assert(di.loaded_elf == null); | |
| 137 | std.debug.assert(!di.scanned_dwarf); | |
| 138 | ||
| 139 | const load_result = if (module.name.len > 0) res: { | |
| 140 | var file = std.fs.cwd().openFile(module.name, .{}) catch return error.MissingDebugInfo; | |
| 141 | defer file.close(); | |
| 142 | break :res ElfFile.load(gpa, file, module.build_id, &.native(module.name)); | |
| 143 | } else res: { | |
| 144 | const path = std.fs.selfExePathAlloc(gpa) catch |err| switch (err) { | |
| 145 | error.OutOfMemory => |e| return e, | |
| 146 | else => return error.ReadFailed, | |
| 147 | }; | |
| 148 | defer gpa.free(path); | |
| 149 | var file = std.fs.cwd().openFile(path, .{}) catch return error.MissingDebugInfo; | |
| 150 | defer file.close(); | |
| 151 | break :res ElfFile.load(gpa, file, module.build_id, &.native(path)); | |
| 152 | }; | |
| 153 | di.loaded_elf = load_result catch |err| switch (err) { | |
| 154 | error.OutOfMemory, | |
| 155 | error.Unexpected, | |
| 156 | => |e| return e, | |
| 157 | ||
| 158 | error.Overflow, | |
| 159 | error.TruncatedElfFile, | |
| 160 | error.InvalidCompressedSection, | |
| 161 | error.InvalidElfMagic, | |
| 162 | error.InvalidElfVersion, | |
| 163 | error.InvalidElfClass, | |
| 164 | error.InvalidElfEndian, | |
| 165 | => return error.InvalidDebugInfo, | |
| 166 | ||
| 167 | error.SystemResources, | |
| 168 | error.MemoryMappingNotSupported, | |
| 169 | error.AccessDenied, | |
| 170 | error.LockedMemoryLimitExceeded, | |
| 171 | error.ProcessFdQuotaExceeded, | |
| 172 | error.SystemFdQuotaExceeded, | |
| 173 | => return error.ReadFailed, | |
| 174 | }; | |
| 175 | ||
| 176 | const matches_native = | |
| 177 | di.loaded_elf.?.endian == native_endian and | |
| 178 | di.loaded_elf.?.is_64 == (@sizeOf(usize) == 8); | |
| 179 | ||
| 180 | if (!matches_native) { | |
| 181 | di.loaded_elf.?.deinit(gpa); | |
| 182 | di.loaded_elf = null; | |
| 183 | return error.InvalidDebugInfo; | |
| 184 | } | |
| 185 | } | |
| 186 | pub fn getSymbolAtAddress(module: *const ElfModule, gpa: Allocator, di: *DebugInfo, address: usize) Error!std.debug.Symbol { | |
| 187 | const vaddr = address - module.load_offset; | |
| 188 | { | |
| 189 | di.mutex.lock(); | |
| 190 | defer di.mutex.unlock(); | |
| 191 | if (di.loaded_elf == null) try module.loadElf(gpa, di); | |
| 192 | const loaded_elf = &di.loaded_elf.?; | |
| 193 | // We need the lock if using DWARF, as we might scan the DWARF or build a line number table. | |
| 194 | if (loaded_elf.dwarf) |*dwarf| { | |
| 195 | if (!di.scanned_dwarf) { | |
| 196 | dwarf.open(gpa, native_endian) catch |err| switch (err) { | |
| 197 | error.InvalidDebugInfo, | |
| 198 | error.MissingDebugInfo, | |
| 199 | error.OutOfMemory, | |
| 200 | => |e| return e, | |
| 201 | error.EndOfStream, | |
| 202 | error.Overflow, | |
| 203 | error.ReadFailed, | |
| 204 | error.StreamTooLong, | |
| 205 | => return error.InvalidDebugInfo, | |
| 206 | }; | |
| 207 | di.scanned_dwarf = true; | |
| 208 | } | |
| 209 | return dwarf.getSymbol(gpa, native_endian, vaddr) catch |err| switch (err) { | |
| 210 | error.InvalidDebugInfo, | |
| 211 | error.MissingDebugInfo, | |
| 212 | error.OutOfMemory, | |
| 213 | => |e| return e, | |
| 214 | error.ReadFailed, | |
| 215 | error.EndOfStream, | |
| 216 | error.Overflow, | |
| 217 | error.StreamTooLong, | |
| 218 | => return error.InvalidDebugInfo, | |
| 219 | }; | |
| 220 | } | |
| 221 | // Otherwise, we're just going to scan the symtab, which we don't need the lock for; fall out of this block. | |
| 222 | } | |
| 223 | // When there's no DWARF available, fall back to searching the symtab. | |
| 224 | return di.loaded_elf.?.searchSymtab(gpa, vaddr) catch |err| switch (err) { | |
| 225 | error.NoSymtab, error.NoStrtab => return error.MissingDebugInfo, | |
| 226 | error.BadSymtab => return error.InvalidDebugInfo, | |
| 227 | error.OutOfMemory => |e| return e, | |
| 228 | }; | |
| 229 | } | |
| 230 | fn prepareUnwindLookup(unwind: *Dwarf.Unwind, gpa: Allocator) Error!void { | |
| 231 | unwind.prepare(gpa, @sizeOf(usize), native_endian, true, false) catch |err| switch (err) { | |
| 232 | error.ReadFailed => unreachable, // it's all fixed buffers | |
| 233 | error.InvalidDebugInfo, | |
| 234 | error.MissingDebugInfo, | |
| 235 | error.OutOfMemory, | |
| 236 | => |e| return e, | |
| 237 | error.EndOfStream, | |
| 238 | error.Overflow, | |
| 239 | error.StreamTooLong, | |
| 240 | error.InvalidOperand, | |
| 241 | error.InvalidOpcode, | |
| 242 | error.InvalidOperation, | |
| 243 | => return error.InvalidDebugInfo, | |
| 244 | error.UnsupportedAddrSize, | |
| 245 | error.UnsupportedDwarfVersion, | |
| 246 | error.UnimplementedUserOpcode, | |
| 247 | => return error.UnsupportedDebugInfo, | |
| 248 | }; | |
| 249 | } | |
| 250 | fn loadUnwindInfo(module: *const ElfModule, gpa: Allocator, di: *DebugInfo) Error!void { | |
| 251 | var buf: [2]Dwarf.Unwind = undefined; | |
| 252 | const unwinds: []Dwarf.Unwind = if (module.gnu_eh_frame) |section_bytes| unwinds: { | |
| 253 | const section_vaddr: u64 = @intFromPtr(section_bytes.ptr) - module.load_offset; | |
| 254 | const header = Dwarf.Unwind.EhFrameHeader.parse(section_vaddr, section_bytes, @sizeOf(usize), native_endian) catch |err| switch (err) { | |
| 255 | error.ReadFailed => unreachable, // it's all fixed buffers | |
| 256 | error.InvalidDebugInfo => |e| return e, | |
| 257 | error.EndOfStream, error.Overflow => return error.InvalidDebugInfo, | |
| 258 | error.UnsupportedAddrSize => return error.UnsupportedDebugInfo, | |
| 259 | }; | |
| 260 | buf[0] = .initEhFrameHdr(header, section_vaddr, @ptrFromInt(@as(usize, @intCast(module.load_offset + header.eh_frame_vaddr)))); | |
| 261 | break :unwinds buf[0..1]; | |
| 262 | } else unwinds: { | |
| 263 | // There is no `.eh_frame_hdr` section. There may still be an `.eh_frame` or `.debug_frame` | |
| 264 | // section, but we'll have to load the binary to get at it. | |
| 265 | if (di.loaded_elf == null) try module.loadElf(gpa, di); | |
| 266 | const opt_debug_frame = &di.loaded_elf.?.debug_frame; | |
| 267 | const opt_eh_frame = &di.loaded_elf.?.eh_frame; | |
| 268 | var i: usize = 0; | |
| 269 | // If both are present, we can't just pick one -- the info could be split between them. | |
| 270 | // `.debug_frame` is likely to be the more complete section, so we'll prioritize that one. | |
| 271 | if (opt_debug_frame.*) |*debug_frame| { | |
| 272 | buf[i] = .initSection(.debug_frame, debug_frame.vaddr, debug_frame.bytes); | |
| 273 | i += 1; | |
| 274 | } | |
| 275 | if (opt_eh_frame.*) |*eh_frame| { | |
| 276 | buf[i] = .initSection(.eh_frame, eh_frame.vaddr, eh_frame.bytes); | |
| 277 | i += 1; | |
| 278 | } | |
| 279 | if (i == 0) return error.MissingDebugInfo; | |
| 280 | break :unwinds buf[0..i]; | |
| 281 | }; | |
| 282 | errdefer for (unwinds) |*u| u.deinit(gpa); | |
| 283 | for (unwinds) |*u| try prepareUnwindLookup(u, gpa); | |
| 284 | ||
| 285 | const unwind_cache = try gpa.create(UnwindContext.Cache); | |
| 286 | errdefer gpa.destroy(unwind_cache); | |
| 287 | unwind_cache.init(); | |
| 288 | ||
| 289 | switch (unwinds.len) { | |
| 290 | 0 => unreachable, | |
| 291 | 1 => di.unwind = .{ unwinds[0], null }, | |
| 292 | 2 => di.unwind = .{ unwinds[0], unwinds[1] }, | |
| 293 | else => unreachable, | |
| 294 | } | |
| 295 | di.unwind_cache = unwind_cache; | |
| 296 | } | |
| 297 | pub fn unwindFrame(module: *const ElfModule, gpa: Allocator, di: *DebugInfo, context: *UnwindContext) Error!usize { | |
| 298 | const unwinds: *const [2]?Dwarf.Unwind = u: { | |
| 299 | di.mutex.lock(); | |
| 300 | defer di.mutex.unlock(); | |
| 301 | if (di.unwind[0] == null) try module.loadUnwindInfo(gpa, di); | |
| 302 | std.debug.assert(di.unwind[0] != null); | |
| 303 | break :u &di.unwind; | |
| 304 | }; | |
| 305 | for (unwinds) |*opt_unwind| { | |
| 306 | const unwind = &(opt_unwind.* orelse break); | |
| 307 | return context.unwindFrame(di.unwind_cache, gpa, unwind, module.load_offset, null) catch |err| switch (err) { | |
| 308 | error.MissingDebugInfo => continue, // try the next one | |
| 309 | else => |e| return e, | |
| 310 | }; | |
| 311 | } | |
| 312 | return error.MissingDebugInfo; | |
| 313 | } | |
| 314 | pub const UnwindContext = std.debug.SelfInfo.DwarfUnwindContext; | |
| 315 | pub const supports_unwinding: bool = s: { | |
| 316 | // Notably, we are yet to support unwinding on ARM. There, unwinding is not done through | |
| 317 | // `.eh_frame`, but instead with the `.ARM.exidx` section, which has a different format. | |
| 318 | const archs: []const std.Target.Cpu.Arch = switch (builtin.target.os.tag) { | |
| 319 | .linux => &.{ .x86, .x86_64, .aarch64, .aarch64_be }, | |
| 320 | .netbsd => &.{ .x86, .x86_64, .aarch64, .aarch64_be }, | |
| 321 | .freebsd => &.{ .x86_64, .aarch64, .aarch64_be }, | |
| 322 | .openbsd => &.{.x86_64}, | |
| 323 | .solaris => &.{ .x86, .x86_64 }, | |
| 324 | .illumos => &.{ .x86, .x86_64 }, | |
| 325 | else => unreachable, | |
| 326 | }; | |
| 327 | for (archs) |a| { | |
| 328 | if (builtin.target.cpu.arch == a) break :s true; | |
| 329 | } | |
| 330 | break :s false; | |
| 331 | }; | |
| 332 | comptime { | |
| 333 | if (supports_unwinding) { | |
| 334 | std.debug.assert(Dwarf.supportsUnwinding(&builtin.target)); | |
| 335 | } | |
| 336 | } | |
| 337 | ||
| 338 | const ElfModule = @This(); | |
| 339 | ||
| 340 | const std = @import("../../std.zig"); | |
| 341 | const Allocator = std.mem.Allocator; | |
| 342 | const Dwarf = std.debug.Dwarf; | |
| 343 | const ElfFile = std.debug.ElfFile; | |
| 344 | const elf = std.elf; | |
| 345 | const mem = std.mem; | |
| 346 | const Error = std.debug.SelfInfo.Error; | |
| 347 | ||
| 348 | const builtin = @import("builtin"); | |
| 349 | const native_endian = builtin.target.cpu.arch.endian(); |
lib/std/debug/SelfInfo/Windows.zig created+559| ... | ... | @@ -0,0 +1,559 @@ |
| 1 | mutex: std.Thread.Mutex, | |
| 2 | modules: std.ArrayListUnmanaged(Module), | |
| 3 | module_name_arena: std.heap.ArenaAllocator.State, | |
| 4 | ||
| 5 | pub const init: SelfInfo = .{ | |
| 6 | .mutex = .{}, | |
| 7 | .modules = .empty, | |
| 8 | .module_name_arena = .{}, | |
| 9 | }; | |
| 10 | pub fn deinit(si: *SelfInfo, gpa: Allocator) void { | |
| 11 | for (si.modules.items) |*module| { | |
| 12 | di: { | |
| 13 | const di = &(module.di orelse break :di catch break :di); | |
| 14 | di.deinit(gpa); | |
| 15 | } | |
| 16 | } | |
| 17 | si.modules.deinit(gpa); | |
| 18 | ||
| 19 | var module_name_arena = si.module_name_arena.promote(gpa); | |
| 20 | module_name_arena.deinit(); | |
| 21 | } | |
| 22 | ||
| 23 | pub fn getSymbol(si: *SelfInfo, gpa: Allocator, address: usize) Error!std.debug.Symbol { | |
| 24 | si.mutex.lock(); | |
| 25 | defer si.mutex.unlock(); | |
| 26 | const module = try si.findModule(gpa, address); | |
| 27 | const di = try module.getDebugInfo(gpa); | |
| 28 | return di.getSymbol(gpa, address - module.base_address); | |
| 29 | } | |
| 30 | pub fn getModuleName(si: *SelfInfo, gpa: Allocator, address: usize) Error![]const u8 { | |
| 31 | si.mutex.lock(); | |
| 32 | defer si.mutex.unlock(); | |
| 33 | const module = try si.findModule(gpa, address); | |
| 34 | return module.name; | |
| 35 | } | |
| 36 | ||
| 37 | pub const can_unwind: bool = switch (builtin.cpu.arch) { | |
| 38 | else => true, | |
| 39 | // On x86, `RtlVirtualUnwind` does not exist. We could in theory use `RtlCaptureStackBackTrace` | |
| 40 | // instead, but on x86, it turns out that function is just... doing FP unwinding with esp! It's | |
| 41 | // hard to find implementation details to confirm that, but the most authoritative source I have | |
| 42 | // is an entry in the LLVM mailing list from 2020/08/16 which contains this quote: | |
| 43 | // | |
| 44 | // > x86 doesn't have what most architectures would consider an "unwinder" in the sense of | |
| 45 | // > restoring registers; there is simply a linked list of frames that participate in SEH and | |
| 46 | // > that desire to be called for a dynamic unwind operation, so RtlCaptureStackBackTrace | |
| 47 | // > assumes that EBP-based frames are in use and walks an EBP-based frame chain on x86 - not | |
| 48 | // > all x86 code is written with EBP-based frames so while even though we generally build the | |
| 49 | // > OS that way, you might always run the risk of encountering external code that uses EBP as a | |
| 50 | // > general purpose register for which such an unwind attempt for a stack trace would fail. | |
| 51 | // | |
| 52 | // Regardless, it's easy to effectively confirm this hypothesis just by compiling some code with | |
| 53 | // `-fomit-frame-pointer -OReleaseFast` and observing that `RtlCaptureStackBackTrace` returns an | |
| 54 | // empty trace when it's called in such an application. Note that without `-OReleaseFast` or | |
| 55 | // similar, LLVM seems reluctant to ever clobber ebp, so you'll get a trace returned which just | |
| 56 | // contains all of the kernel32/ntdll frames but none of your own. Don't be deceived---this is | |
| 57 | // just coincidental! | |
| 58 | // | |
| 59 | // Anyway, the point is, the only stack walking primitive on x86-windows is FP unwinding. We | |
| 60 | // *could* ask Microsoft to do that for us with `RtlCaptureStackBackTrace`... but better to just | |
| 61 | // use our existing FP unwinder in `std.debug`! | |
| 62 | .x86 => false, | |
| 63 | }; | |
| 64 | pub const UnwindContext = struct { | |
| 65 | pc: usize, | |
| 66 | cur: windows.CONTEXT, | |
| 67 | history_table: windows.UNWIND_HISTORY_TABLE, | |
| 68 | pub fn init(ctx: *const std.debug.cpu_context.Native) UnwindContext { | |
| 69 | return .{ | |
| 70 | .pc = @returnAddress(), | |
| 71 | .cur = switch (builtin.cpu.arch) { | |
| 72 | .x86_64 => std.mem.zeroInit(windows.CONTEXT, .{ | |
| 73 | .Rax = ctx.gprs.get(.rax), | |
| 74 | .Rcx = ctx.gprs.get(.rcx), | |
| 75 | .Rdx = ctx.gprs.get(.rdx), | |
| 76 | .Rbx = ctx.gprs.get(.rbx), | |
| 77 | .Rsp = ctx.gprs.get(.rsp), | |
| 78 | .Rbp = ctx.gprs.get(.rbp), | |
| 79 | .Rsi = ctx.gprs.get(.rsi), | |
| 80 | .Rdi = ctx.gprs.get(.rdi), | |
| 81 | .R8 = ctx.gprs.get(.r8), | |
| 82 | .R9 = ctx.gprs.get(.r9), | |
| 83 | .R10 = ctx.gprs.get(.r10), | |
| 84 | .R11 = ctx.gprs.get(.r11), | |
| 85 | .R12 = ctx.gprs.get(.r12), | |
| 86 | .R13 = ctx.gprs.get(.r13), | |
| 87 | .R14 = ctx.gprs.get(.r14), | |
| 88 | .R15 = ctx.gprs.get(.r15), | |
| 89 | .Rip = ctx.gprs.get(.rip), | |
| 90 | }), | |
| 91 | .aarch64, .aarch64_be => .{ | |
| 92 | .ContextFlags = 0, | |
| 93 | .Cpsr = 0, | |
| 94 | .DUMMYUNIONNAME = .{ .X = ctx.x }, | |
| 95 | .Sp = ctx.sp, | |
| 96 | .Pc = ctx.pc, | |
| 97 | .V = @splat(.{ .B = @splat(0) }), | |
| 98 | .Fpcr = 0, | |
| 99 | .Fpsr = 0, | |
| 100 | .Bcr = @splat(0), | |
| 101 | .Bvr = @splat(0), | |
| 102 | .Wcr = @splat(0), | |
| 103 | .Wvr = @splat(0), | |
| 104 | }, | |
| 105 | .thumb => .{ | |
| 106 | .ContextFlags = 0, | |
| 107 | .R0 = ctx.r[0], | |
| 108 | .R1 = ctx.r[1], | |
| 109 | .R2 = ctx.r[2], | |
| 110 | .R3 = ctx.r[3], | |
| 111 | .R4 = ctx.r[4], | |
| 112 | .R5 = ctx.r[5], | |
| 113 | .R6 = ctx.r[6], | |
| 114 | .R7 = ctx.r[7], | |
| 115 | .R8 = ctx.r[8], | |
| 116 | .R9 = ctx.r[9], | |
| 117 | .R10 = ctx.r[10], | |
| 118 | .R11 = ctx.r[11], | |
| 119 | .R12 = ctx.r[12], | |
| 120 | .Sp = ctx.r[13], | |
| 121 | .Lr = ctx.r[14], | |
| 122 | .Pc = ctx.r[15], | |
| 123 | .Cpsr = 0, | |
| 124 | .Fpcsr = 0, | |
| 125 | .Padding = 0, | |
| 126 | .DUMMYUNIONNAME = .{ .S = @splat(0) }, | |
| 127 | .Bvr = @splat(0), | |
| 128 | .Bcr = @splat(0), | |
| 129 | .Wvr = @splat(0), | |
| 130 | .Wcr = @splat(0), | |
| 131 | .Padding2 = @splat(0), | |
| 132 | }, | |
| 133 | else => comptime unreachable, | |
| 134 | }, | |
| 135 | .history_table = std.mem.zeroes(windows.UNWIND_HISTORY_TABLE), | |
| 136 | }; | |
| 137 | } | |
| 138 | pub fn deinit(ctx: *UnwindContext, gpa: Allocator) void { | |
| 139 | _ = ctx; | |
| 140 | _ = gpa; | |
| 141 | } | |
| 142 | pub fn getFp(ctx: *UnwindContext) usize { | |
| 143 | return ctx.cur.getRegs().bp; | |
| 144 | } | |
| 145 | }; | |
| 146 | pub fn unwindFrame(si: *SelfInfo, gpa: Allocator, context: *UnwindContext) Error!usize { | |
| 147 | _ = si; | |
| 148 | _ = gpa; | |
| 149 | ||
| 150 | const current_regs = context.cur.getRegs(); | |
| 151 | var image_base: windows.DWORD64 = undefined; | |
| 152 | if (windows.ntdll.RtlLookupFunctionEntry(current_regs.ip, &image_base, &context.history_table)) |runtime_function| { | |
| 153 | var handler_data: ?*anyopaque = null; | |
| 154 | var establisher_frame: u64 = undefined; | |
| 155 | _ = windows.ntdll.RtlVirtualUnwind( | |
| 156 | windows.UNW_FLAG_NHANDLER, | |
| 157 | image_base, | |
| 158 | current_regs.ip, | |
| 159 | runtime_function, | |
| 160 | &context.cur, | |
| 161 | &handler_data, | |
| 162 | &establisher_frame, | |
| 163 | null, | |
| 164 | ); | |
| 165 | } else { | |
| 166 | // leaf function | |
| 167 | context.cur.setIp(@as(*const usize, @ptrFromInt(current_regs.sp)).*); | |
| 168 | context.cur.setSp(current_regs.sp + @sizeOf(usize)); | |
| 169 | } | |
| 170 | ||
| 171 | const next_regs = context.cur.getRegs(); | |
| 172 | const tib = &windows.teb().NtTib; | |
| 173 | if (next_regs.sp < @intFromPtr(tib.StackLimit) or next_regs.sp > @intFromPtr(tib.StackBase)) { | |
| 174 | context.pc = 0; | |
| 175 | return 0; | |
| 176 | } | |
| 177 | // Like `DwarfUnwindContext.unwindFrame`, adjust our next lookup pc in case the `call` was this | |
| 178 | // function's last instruction making `next_regs.ip` one byte past its end. | |
| 179 | context.pc = next_regs.ip -| 1; | |
| 180 | return next_regs.ip; | |
| 181 | } | |
| 182 | ||
| 183 | const Module = struct { | |
| 184 | base_address: usize, | |
| 185 | size: u32, | |
| 186 | name: []const u8, | |
| 187 | handle: windows.HMODULE, | |
| 188 | ||
| 189 | di: ?(Error!DebugInfo), | |
| 190 | ||
| 191 | const DebugInfo = struct { | |
| 192 | arena: std.heap.ArenaAllocator.State, | |
| 193 | coff_image_base: u64, | |
| 194 | mapped_file: ?MappedFile, | |
| 195 | dwarf: ?Dwarf, | |
| 196 | pdb: ?Pdb, | |
| 197 | coff_section_headers: []coff.SectionHeader, | |
| 198 | ||
| 199 | const MappedFile = struct { | |
| 200 | file: fs.File, | |
| 201 | section_handle: windows.HANDLE, | |
| 202 | section_view: []const u8, | |
| 203 | fn deinit(mf: *const MappedFile) void { | |
| 204 | const process_handle = windows.GetCurrentProcess(); | |
| 205 | assert(windows.ntdll.NtUnmapViewOfSection(process_handle, @constCast(mf.section_view.ptr)) == .SUCCESS); | |
| 206 | windows.CloseHandle(mf.section_handle); | |
| 207 | mf.file.close(); | |
| 208 | } | |
| 209 | }; | |
| 210 | ||
| 211 | fn deinit(di: *DebugInfo, gpa: Allocator) void { | |
| 212 | if (di.dwarf) |*dwarf| dwarf.deinit(gpa); | |
| 213 | if (di.pdb) |*pdb| { | |
| 214 | pdb.file_reader.file.close(); | |
| 215 | pdb.deinit(); | |
| 216 | } | |
| 217 | if (di.mapped_file) |*mf| mf.deinit(); | |
| 218 | ||
| 219 | var arena = di.arena.promote(gpa); | |
| 220 | arena.deinit(); | |
| 221 | } | |
| 222 | ||
| 223 | fn getSymbol(di: *DebugInfo, gpa: Allocator, vaddr: usize) Error!std.debug.Symbol { | |
| 224 | pdb: { | |
| 225 | const pdb = &(di.pdb orelse break :pdb); | |
| 226 | var coff_section: *align(1) const coff.SectionHeader = undefined; | |
| 227 | const mod_index = for (pdb.sect_contribs) |sect_contrib| { | |
| 228 | if (sect_contrib.section > di.coff_section_headers.len) continue; | |
| 229 | // Remember that SectionContribEntry.Section is 1-based. | |
| 230 | coff_section = &di.coff_section_headers[sect_contrib.section - 1]; | |
| 231 | ||
| 232 | const vaddr_start = coff_section.virtual_address + sect_contrib.offset; | |
| 233 | const vaddr_end = vaddr_start + sect_contrib.size; | |
| 234 | if (vaddr >= vaddr_start and vaddr < vaddr_end) { | |
| 235 | break sect_contrib.module_index; | |
| 236 | } | |
| 237 | } else { | |
| 238 | // we have no information to add to the address | |
| 239 | break :pdb; | |
| 240 | }; | |
| 241 | const module = pdb.getModule(mod_index) catch |err| switch (err) { | |
| 242 | error.InvalidDebugInfo, | |
| 243 | error.MissingDebugInfo, | |
| 244 | error.OutOfMemory, | |
| 245 | => |e| return e, | |
| 246 | ||
| 247 | error.ReadFailed, | |
| 248 | error.EndOfStream, | |
| 249 | => return error.InvalidDebugInfo, | |
| 250 | } orelse { | |
| 251 | return error.InvalidDebugInfo; // bad module index | |
| 252 | }; | |
| 253 | return .{ | |
| 254 | .name = pdb.getSymbolName(module, vaddr - coff_section.virtual_address), | |
| 255 | .compile_unit_name = fs.path.basename(module.obj_file_name), | |
| 256 | .source_location = pdb.getLineNumberInfo(module, vaddr - coff_section.virtual_address) catch null, | |
| 257 | }; | |
| 258 | } | |
| 259 | dwarf: { | |
| 260 | const dwarf = &(di.dwarf orelse break :dwarf); | |
| 261 | const dwarf_address = vaddr + di.coff_image_base; | |
| 262 | return dwarf.getSymbol(gpa, native_endian, dwarf_address) catch |err| switch (err) { | |
| 263 | error.MissingDebugInfo => break :dwarf, | |
| 264 | ||
| 265 | error.InvalidDebugInfo, | |
| 266 | error.OutOfMemory, | |
| 267 | => |e| return e, | |
| 268 | ||
| 269 | error.ReadFailed, | |
| 270 | error.EndOfStream, | |
| 271 | error.Overflow, | |
| 272 | error.StreamTooLong, | |
| 273 | => return error.InvalidDebugInfo, | |
| 274 | }; | |
| 275 | } | |
| 276 | return error.MissingDebugInfo; | |
| 277 | } | |
| 278 | }; | |
| 279 | ||
| 280 | fn getDebugInfo(module: *Module, gpa: Allocator) Error!*DebugInfo { | |
| 281 | if (module.di == null) module.di = loadDebugInfo(module, gpa); | |
| 282 | return if (module.di.?) |*di| di else |err| err; | |
| 283 | } | |
| 284 | fn loadDebugInfo(module: *const Module, gpa: Allocator) Error!DebugInfo { | |
| 285 | const mapped_ptr: [*]const u8 = @ptrFromInt(module.base_address); | |
| 286 | const mapped = mapped_ptr[0..module.size]; | |
| 287 | var coff_obj = coff.Coff.init(mapped, true) catch return error.InvalidDebugInfo; | |
| 288 | ||
| 289 | var arena_instance: std.heap.ArenaAllocator = .init(gpa); | |
| 290 | errdefer arena_instance.deinit(); | |
| 291 | const arena = arena_instance.allocator(); | |
| 292 | ||
| 293 | // The string table is not mapped into memory by the loader, so if a section name is in the | |
| 294 | // string table then we have to map the full image file from disk. This can happen when | |
| 295 | // a binary is produced with -gdwarf, since the section names are longer than 8 bytes. | |
| 296 | const mapped_file: ?DebugInfo.MappedFile = mapped: { | |
| 297 | if (!coff_obj.strtabRequired()) break :mapped null; | |
| 298 | var name_buffer: [windows.PATH_MAX_WIDE + 4:0]u16 = undefined; | |
| 299 | name_buffer[0..4].* = .{ '\\', '?', '?', '\\' }; // openFileAbsoluteW requires the prefix to be present | |
| 300 | const process_handle = windows.GetCurrentProcess(); | |
| 301 | const len = windows.kernel32.GetModuleFileNameExW( | |
| 302 | process_handle, | |
| 303 | module.handle, | |
| 304 | name_buffer[4..], | |
| 305 | windows.PATH_MAX_WIDE, | |
| 306 | ); | |
| 307 | if (len == 0) return error.MissingDebugInfo; | |
| 308 | const coff_file = fs.openFileAbsoluteW(name_buffer[0 .. len + 4 :0], .{}) catch |err| switch (err) { | |
| 309 | error.Unexpected => |e| return e, | |
| 310 | error.FileNotFound => return error.MissingDebugInfo, | |
| 311 | ||
| 312 | error.FileTooBig, | |
| 313 | error.IsDir, | |
| 314 | error.NotDir, | |
| 315 | error.SymLinkLoop, | |
| 316 | error.NameTooLong, | |
| 317 | error.InvalidUtf8, | |
| 318 | error.InvalidWtf8, | |
| 319 | error.BadPathName, | |
| 320 | => return error.InvalidDebugInfo, | |
| 321 | ||
| 322 | error.SystemResources, | |
| 323 | error.WouldBlock, | |
| 324 | error.AccessDenied, | |
| 325 | error.ProcessNotFound, | |
| 326 | error.PermissionDenied, | |
| 327 | error.NoSpaceLeft, | |
| 328 | error.DeviceBusy, | |
| 329 | error.NoDevice, | |
| 330 | error.SharingViolation, | |
| 331 | error.PathAlreadyExists, | |
| 332 | error.PipeBusy, | |
| 333 | error.NetworkNotFound, | |
| 334 | error.AntivirusInterference, | |
| 335 | error.ProcessFdQuotaExceeded, | |
| 336 | error.SystemFdQuotaExceeded, | |
| 337 | error.FileLocksNotSupported, | |
| 338 | error.FileBusy, | |
| 339 | => return error.ReadFailed, | |
| 340 | }; | |
| 341 | errdefer coff_file.close(); | |
| 342 | var section_handle: windows.HANDLE = undefined; | |
| 343 | const create_section_rc = windows.ntdll.NtCreateSection( | |
| 344 | &section_handle, | |
| 345 | windows.STANDARD_RIGHTS_REQUIRED | windows.SECTION_QUERY | windows.SECTION_MAP_READ, | |
| 346 | null, | |
| 347 | null, | |
| 348 | windows.PAGE_READONLY, | |
| 349 | // The documentation states that if no AllocationAttribute is specified, then SEC_COMMIT is the default. | |
| 350 | // In practice, this isn't the case and specifying 0 will result in INVALID_PARAMETER_6. | |
| 351 | windows.SEC_COMMIT, | |
| 352 | coff_file.handle, | |
| 353 | ); | |
| 354 | if (create_section_rc != .SUCCESS) return error.MissingDebugInfo; | |
| 355 | errdefer windows.CloseHandle(section_handle); | |
| 356 | var coff_len: usize = 0; | |
| 357 | var section_view_ptr: ?[*]const u8 = null; | |
| 358 | const map_section_rc = windows.ntdll.NtMapViewOfSection( | |
| 359 | section_handle, | |
| 360 | process_handle, | |
| 361 | @ptrCast(&section_view_ptr), | |
| 362 | null, | |
| 363 | 0, | |
| 364 | null, | |
| 365 | &coff_len, | |
| 366 | .ViewUnmap, | |
| 367 | 0, | |
| 368 | windows.PAGE_READONLY, | |
| 369 | ); | |
| 370 | if (map_section_rc != .SUCCESS) return error.MissingDebugInfo; | |
| 371 | errdefer assert(windows.ntdll.NtUnmapViewOfSection(process_handle, @constCast(section_view_ptr.?)) == .SUCCESS); | |
| 372 | const section_view = section_view_ptr.?[0..coff_len]; | |
| 373 | coff_obj = coff.Coff.init(section_view, false) catch return error.InvalidDebugInfo; | |
| 374 | break :mapped .{ | |
| 375 | .file = coff_file, | |
| 376 | .section_handle = section_handle, | |
| 377 | .section_view = section_view, | |
| 378 | }; | |
| 379 | }; | |
| 380 | errdefer if (mapped_file) |*mf| mf.deinit(); | |
| 381 | ||
| 382 | const coff_image_base = coff_obj.getImageBase(); | |
| 383 | ||
| 384 | var opt_dwarf: ?Dwarf = dwarf: { | |
| 385 | if (coff_obj.getSectionByName(".debug_info") == null) break :dwarf null; | |
| 386 | ||
| 387 | var sections: Dwarf.SectionArray = undefined; | |
| 388 | inline for (@typeInfo(Dwarf.Section.Id).@"enum".fields, 0..) |section, i| { | |
| 389 | sections[i] = if (coff_obj.getSectionByName("." ++ section.name)) |section_header| .{ | |
| 390 | .data = try coff_obj.getSectionDataAlloc(section_header, arena), | |
| 391 | .owned = false, | |
| 392 | } else null; | |
| 393 | } | |
| 394 | break :dwarf .{ .sections = sections }; | |
| 395 | }; | |
| 396 | errdefer if (opt_dwarf) |*dwarf| dwarf.deinit(gpa); | |
| 397 | ||
| 398 | if (opt_dwarf) |*dwarf| { | |
| 399 | dwarf.open(gpa, native_endian) catch |err| switch (err) { | |
| 400 | error.Overflow, | |
| 401 | error.EndOfStream, | |
| 402 | error.StreamTooLong, | |
| 403 | error.ReadFailed, | |
| 404 | => return error.InvalidDebugInfo, | |
| 405 | ||
| 406 | error.InvalidDebugInfo, | |
| 407 | error.MissingDebugInfo, | |
| 408 | error.OutOfMemory, | |
| 409 | => |e| return e, | |
| 410 | }; | |
| 411 | } | |
| 412 | ||
| 413 | var opt_pdb: ?Pdb = pdb: { | |
| 414 | const path = coff_obj.getPdbPath() catch { | |
| 415 | return error.InvalidDebugInfo; | |
| 416 | } orelse { | |
| 417 | break :pdb null; | |
| 418 | }; | |
| 419 | const pdb_file_open_result = if (fs.path.isAbsolute(path)) res: { | |
| 420 | break :res std.fs.cwd().openFile(path, .{}); | |
| 421 | } else res: { | |
| 422 | const self_dir = fs.selfExeDirPathAlloc(gpa) catch |err| switch (err) { | |
| 423 | error.OutOfMemory, error.Unexpected => |e| return e, | |
| 424 | else => return error.ReadFailed, | |
| 425 | }; | |
| 426 | defer gpa.free(self_dir); | |
| 427 | const abs_path = try fs.path.join(gpa, &.{ self_dir, path }); | |
| 428 | defer gpa.free(abs_path); | |
| 429 | break :res std.fs.cwd().openFile(abs_path, .{}); | |
| 430 | }; | |
| 431 | const pdb_file = pdb_file_open_result catch |err| switch (err) { | |
| 432 | error.FileNotFound, error.IsDir => break :pdb null, | |
| 433 | else => return error.ReadFailed, | |
| 434 | }; | |
| 435 | errdefer pdb_file.close(); | |
| 436 | ||
| 437 | const pdb_reader = try arena.create(std.fs.File.Reader); | |
| 438 | pdb_reader.* = pdb_file.reader(try arena.alloc(u8, 4096)); | |
| 439 | ||
| 440 | var pdb = Pdb.init(gpa, pdb_reader) catch |err| switch (err) { | |
| 441 | error.OutOfMemory, error.ReadFailed, error.Unexpected => |e| return e, | |
| 442 | else => return error.InvalidDebugInfo, | |
| 443 | }; | |
| 444 | errdefer pdb.deinit(); | |
| 445 | pdb.parseInfoStream() catch |err| switch (err) { | |
| 446 | error.UnknownPDBVersion => return error.UnsupportedDebugInfo, | |
| 447 | error.EndOfStream => return error.InvalidDebugInfo, | |
| 448 | ||
| 449 | error.InvalidDebugInfo, | |
| 450 | error.MissingDebugInfo, | |
| 451 | error.OutOfMemory, | |
| 452 | error.ReadFailed, | |
| 453 | => |e| return e, | |
| 454 | }; | |
| 455 | pdb.parseDbiStream() catch |err| switch (err) { | |
| 456 | error.UnknownPDBVersion => return error.UnsupportedDebugInfo, | |
| 457 | ||
| 458 | error.EndOfStream, | |
| 459 | error.EOF, | |
| 460 | error.StreamTooLong, | |
| 461 | error.WriteFailed, | |
| 462 | => return error.InvalidDebugInfo, | |
| 463 | ||
| 464 | error.InvalidDebugInfo, | |
| 465 | error.OutOfMemory, | |
| 466 | error.ReadFailed, | |
| 467 | => |e| return e, | |
| 468 | }; | |
| 469 | ||
| 470 | if (!std.mem.eql(u8, &coff_obj.guid, &pdb.guid) or coff_obj.age != pdb.age) | |
| 471 | return error.InvalidDebugInfo; | |
| 472 | ||
| 473 | break :pdb pdb; | |
| 474 | }; | |
| 475 | errdefer if (opt_pdb) |*pdb| { | |
| 476 | pdb.file_reader.file.close(); | |
| 477 | pdb.deinit(); | |
| 478 | }; | |
| 479 | ||
| 480 | const coff_section_headers: []coff.SectionHeader = if (opt_pdb != null) csh: { | |
| 481 | break :csh try coff_obj.getSectionHeadersAlloc(arena); | |
| 482 | } else &.{}; | |
| 483 | ||
| 484 | return .{ | |
| 485 | .arena = arena_instance.state, | |
| 486 | .coff_image_base = coff_image_base, | |
| 487 | .mapped_file = mapped_file, | |
| 488 | .dwarf = opt_dwarf, | |
| 489 | .pdb = opt_pdb, | |
| 490 | .coff_section_headers = coff_section_headers, | |
| 491 | }; | |
| 492 | } | |
| 493 | }; | |
| 494 | ||
| 495 | /// Assumes we already hold `si.mutex`. | |
| 496 | fn findModule(si: *SelfInfo, gpa: Allocator, address: usize) error{ MissingDebugInfo, OutOfMemory, Unexpected }!*Module { | |
| 497 | for (si.modules.items) |*mod| { | |
| 498 | if (address >= mod.base_address and address < mod.base_address + mod.size) { | |
| 499 | return mod; | |
| 500 | } | |
| 501 | } | |
| 502 | ||
| 503 | // A new module might have been loaded; rebuild the list. | |
| 504 | { | |
| 505 | for (si.modules.items) |*mod| { | |
| 506 | const di = &(mod.di orelse continue catch continue); | |
| 507 | di.deinit(gpa); | |
| 508 | } | |
| 509 | si.modules.clearRetainingCapacity(); | |
| 510 | ||
| 511 | var module_name_arena = si.module_name_arena.promote(gpa); | |
| 512 | defer si.module_name_arena = module_name_arena.state; | |
| 513 | _ = module_name_arena.reset(.retain_capacity); | |
| 514 | ||
| 515 | const handle = windows.kernel32.CreateToolhelp32Snapshot(windows.TH32CS_SNAPMODULE | windows.TH32CS_SNAPMODULE32, 0); | |
| 516 | if (handle == windows.INVALID_HANDLE_VALUE) { | |
| 517 | return windows.unexpectedError(windows.GetLastError()); | |
| 518 | } | |
| 519 | defer windows.CloseHandle(handle); | |
| 520 | var entry: windows.MODULEENTRY32 = undefined; | |
| 521 | entry.dwSize = @sizeOf(windows.MODULEENTRY32); | |
| 522 | var result = windows.kernel32.Module32First(handle, &entry); | |
| 523 | while (result != 0) : (result = windows.kernel32.Module32Next(handle, &entry)) { | |
| 524 | try si.modules.append(gpa, .{ | |
| 525 | .base_address = @intFromPtr(entry.modBaseAddr), | |
| 526 | .size = entry.modBaseSize, | |
| 527 | .name = try module_name_arena.allocator().dupe( | |
| 528 | u8, | |
| 529 | std.mem.sliceTo(&entry.szModule, 0), | |
| 530 | ), | |
| 531 | .handle = entry.hModule, | |
| 532 | .di = null, | |
| 533 | }); | |
| 534 | } | |
| 535 | } | |
| 536 | ||
| 537 | for (si.modules.items) |*mod| { | |
| 538 | if (address >= mod.base_address and address < mod.base_address + mod.size) { | |
| 539 | return mod; | |
| 540 | } | |
| 541 | } | |
| 542 | ||
| 543 | return error.MissingDebugInfo; | |
| 544 | } | |
| 545 | ||
| 546 | const std = @import("std"); | |
| 547 | const Allocator = std.mem.Allocator; | |
| 548 | const Dwarf = std.debug.Dwarf; | |
| 549 | const Pdb = std.debug.Pdb; | |
| 550 | const Error = std.debug.SelfInfoError; | |
| 551 | const assert = std.debug.assert; | |
| 552 | const coff = std.coff; | |
| 553 | const fs = std.fs; | |
| 554 | const windows = std.os.windows; | |
| 555 | ||
| 556 | const builtin = @import("builtin"); | |
| 557 | const native_endian = builtin.target.cpu.arch.endian(); | |
| 558 | ||
| 559 | const SelfInfo = @This(); |
lib/std/debug/SelfInfo/WindowsModule.zig deleted-442| ... | ... | @@ -1,442 +0,0 @@ |
| 1 | base_address: usize, | |
| 2 | size: usize, | |
| 3 | name: []const u8, | |
| 4 | handle: windows.HMODULE, | |
| 5 | pub fn key(m: WindowsModule) usize { | |
| 6 | return m.base_address; | |
| 7 | } | |
| 8 | pub fn lookup(cache: *LookupCache, gpa: Allocator, address: usize) std.debug.SelfInfo.Error!WindowsModule { | |
| 9 | if (lookupInCache(cache, address)) |m| return m; | |
| 10 | { | |
| 11 | // Check a new module hasn't been loaded | |
| 12 | cache.rwlock.lock(); | |
| 13 | defer cache.rwlock.unlock(); | |
| 14 | cache.modules.clearRetainingCapacity(); | |
| 15 | const handle = windows.kernel32.CreateToolhelp32Snapshot(windows.TH32CS_SNAPMODULE | windows.TH32CS_SNAPMODULE32, 0); | |
| 16 | if (handle == windows.INVALID_HANDLE_VALUE) { | |
| 17 | return windows.unexpectedError(windows.GetLastError()); | |
| 18 | } | |
| 19 | defer windows.CloseHandle(handle); | |
| 20 | var entry: windows.MODULEENTRY32 = undefined; | |
| 21 | entry.dwSize = @sizeOf(windows.MODULEENTRY32); | |
| 22 | if (windows.kernel32.Module32First(handle, &entry) != 0) { | |
| 23 | try cache.modules.append(gpa, entry); | |
| 24 | while (windows.kernel32.Module32Next(handle, &entry) != 0) { | |
| 25 | try cache.modules.append(gpa, entry); | |
| 26 | } | |
| 27 | } | |
| 28 | } | |
| 29 | if (lookupInCache(cache, address)) |m| return m; | |
| 30 | return error.MissingDebugInfo; | |
| 31 | } | |
| 32 | pub fn getSymbolAtAddress(module: *const WindowsModule, gpa: Allocator, di: *DebugInfo, address: usize) std.debug.SelfInfo.Error!std.debug.Symbol { | |
| 33 | // The `Pdb` API doesn't really allow us *any* thread-safe access, and the `Dwarf` API isn't | |
| 34 | // great for it either; just lock the whole thing. | |
| 35 | di.mutex.lock(); | |
| 36 | defer di.mutex.unlock(); | |
| 37 | ||
| 38 | if (!di.loaded) module.loadDebugInfo(gpa, di) catch |err| switch (err) { | |
| 39 | error.OutOfMemory, error.InvalidDebugInfo, error.MissingDebugInfo, error.Unexpected => |e| return e, | |
| 40 | error.FileNotFound => return error.MissingDebugInfo, | |
| 41 | error.UnknownPDBVersion => return error.UnsupportedDebugInfo, | |
| 42 | else => return error.ReadFailed, | |
| 43 | }; | |
| 44 | ||
| 45 | // Translate the runtime address into a virtual address into the module | |
| 46 | const vaddr = address - module.base_address; | |
| 47 | ||
| 48 | if (di.pdb != null) { | |
| 49 | if (di.getSymbolFromPdb(vaddr) catch return error.InvalidDebugInfo) |symbol| return symbol; | |
| 50 | } | |
| 51 | ||
| 52 | if (di.dwarf) |*dwarf| { | |
| 53 | const dwarf_address = vaddr + di.coff_image_base; | |
| 54 | return dwarf.getSymbol(gpa, native_endian, dwarf_address) catch return error.InvalidDebugInfo; | |
| 55 | } | |
| 56 | ||
| 57 | return error.MissingDebugInfo; | |
| 58 | } | |
| 59 | fn lookupInCache(cache: *LookupCache, address: usize) ?WindowsModule { | |
| 60 | cache.rwlock.lockShared(); | |
| 61 | defer cache.rwlock.unlockShared(); | |
| 62 | for (cache.modules.items) |*entry| { | |
| 63 | const base_address = @intFromPtr(entry.modBaseAddr); | |
| 64 | if (address >= base_address and address < base_address + entry.modBaseSize) { | |
| 65 | return .{ | |
| 66 | .base_address = base_address, | |
| 67 | .size = entry.modBaseSize, | |
| 68 | .name = std.mem.sliceTo(&entry.szModule, 0), | |
| 69 | .handle = entry.hModule, | |
| 70 | }; | |
| 71 | } | |
| 72 | } | |
| 73 | return null; | |
| 74 | } | |
| 75 | fn loadDebugInfo(module: *const WindowsModule, gpa: Allocator, di: *DebugInfo) !void { | |
| 76 | const mapped_ptr: [*]const u8 = @ptrFromInt(module.base_address); | |
| 77 | const mapped = mapped_ptr[0..module.size]; | |
| 78 | var coff_obj = coff.Coff.init(mapped, true) catch return error.InvalidDebugInfo; | |
| 79 | // The string table is not mapped into memory by the loader, so if a section name is in the | |
| 80 | // string table then we have to map the full image file from disk. This can happen when | |
| 81 | // a binary is produced with -gdwarf, since the section names are longer than 8 bytes. | |
| 82 | if (coff_obj.strtabRequired()) { | |
| 83 | var name_buffer: [windows.PATH_MAX_WIDE + 4:0]u16 = undefined; | |
| 84 | name_buffer[0..4].* = .{ '\\', '?', '?', '\\' }; // openFileAbsoluteW requires the prefix to be present | |
| 85 | const process_handle = windows.GetCurrentProcess(); | |
| 86 | const len = windows.kernel32.GetModuleFileNameExW( | |
| 87 | process_handle, | |
| 88 | module.handle, | |
| 89 | name_buffer[4..], | |
| 90 | windows.PATH_MAX_WIDE, | |
| 91 | ); | |
| 92 | if (len == 0) return error.MissingDebugInfo; | |
| 93 | const coff_file = fs.openFileAbsoluteW(name_buffer[0 .. len + 4 :0], .{}) catch |err| switch (err) { | |
| 94 | error.FileNotFound => return error.MissingDebugInfo, | |
| 95 | else => |e| return e, | |
| 96 | }; | |
| 97 | errdefer coff_file.close(); | |
| 98 | var section_handle: windows.HANDLE = undefined; | |
| 99 | const create_section_rc = windows.ntdll.NtCreateSection( | |
| 100 | &section_handle, | |
| 101 | windows.STANDARD_RIGHTS_REQUIRED | windows.SECTION_QUERY | windows.SECTION_MAP_READ, | |
| 102 | null, | |
| 103 | null, | |
| 104 | windows.PAGE_READONLY, | |
| 105 | // The documentation states that if no AllocationAttribute is specified, then SEC_COMMIT is the default. | |
| 106 | // In practice, this isn't the case and specifying 0 will result in INVALID_PARAMETER_6. | |
| 107 | windows.SEC_COMMIT, | |
| 108 | coff_file.handle, | |
| 109 | ); | |
| 110 | if (create_section_rc != .SUCCESS) return error.MissingDebugInfo; | |
| 111 | errdefer windows.CloseHandle(section_handle); | |
| 112 | var coff_len: usize = 0; | |
| 113 | var section_view_ptr: ?[*]const u8 = null; | |
| 114 | const map_section_rc = windows.ntdll.NtMapViewOfSection( | |
| 115 | section_handle, | |
| 116 | process_handle, | |
| 117 | @ptrCast(&section_view_ptr), | |
| 118 | null, | |
| 119 | 0, | |
| 120 | null, | |
| 121 | &coff_len, | |
| 122 | .ViewUnmap, | |
| 123 | 0, | |
| 124 | windows.PAGE_READONLY, | |
| 125 | ); | |
| 126 | if (map_section_rc != .SUCCESS) return error.MissingDebugInfo; | |
| 127 | errdefer assert(windows.ntdll.NtUnmapViewOfSection(process_handle, @constCast(section_view_ptr.?)) == .SUCCESS); | |
| 128 | const section_view = section_view_ptr.?[0..coff_len]; | |
| 129 | coff_obj = coff.Coff.init(section_view, false) catch return error.InvalidDebugInfo; | |
| 130 | di.mapped_file = .{ | |
| 131 | .file = coff_file, | |
| 132 | .section_handle = section_handle, | |
| 133 | .section_view = section_view, | |
| 134 | }; | |
| 135 | } | |
| 136 | di.coff_image_base = coff_obj.getImageBase(); | |
| 137 | ||
| 138 | if (coff_obj.getSectionByName(".debug_info")) |_| { | |
| 139 | di.dwarf = .{}; | |
| 140 | ||
| 141 | inline for (@typeInfo(Dwarf.Section.Id).@"enum".fields, 0..) |section, i| { | |
| 142 | di.dwarf.?.sections[i] = if (coff_obj.getSectionByName("." ++ section.name)) |section_header| blk: { | |
| 143 | break :blk .{ | |
| 144 | .data = try coff_obj.getSectionDataAlloc(section_header, gpa), | |
| 145 | .owned = true, | |
| 146 | }; | |
| 147 | } else null; | |
| 148 | } | |
| 149 | ||
| 150 | try di.dwarf.?.open(gpa, native_endian); | |
| 151 | } | |
| 152 | ||
| 153 | if (coff_obj.getPdbPath() catch return error.InvalidDebugInfo) |raw_path| pdb: { | |
| 154 | const path = blk: { | |
| 155 | if (fs.path.isAbsolute(raw_path)) { | |
| 156 | break :blk raw_path; | |
| 157 | } else { | |
| 158 | const self_dir = try fs.selfExeDirPathAlloc(gpa); | |
| 159 | defer gpa.free(self_dir); | |
| 160 | break :blk try fs.path.join(gpa, &.{ self_dir, raw_path }); | |
| 161 | } | |
| 162 | }; | |
| 163 | defer if (path.ptr != raw_path.ptr) gpa.free(path); | |
| 164 | ||
| 165 | const pdb_file = std.fs.cwd().openFile(path, .{}) catch |err| switch (err) { | |
| 166 | error.FileNotFound, error.IsDir => break :pdb, | |
| 167 | else => |e| return e, | |
| 168 | }; | |
| 169 | errdefer pdb_file.close(); | |
| 170 | ||
| 171 | const pdb_reader = try gpa.create(std.fs.File.Reader); | |
| 172 | errdefer gpa.destroy(pdb_reader); | |
| 173 | ||
| 174 | pdb_reader.* = pdb_file.reader(try gpa.alloc(u8, 4096)); | |
| 175 | errdefer gpa.free(pdb_reader.interface.buffer); | |
| 176 | ||
| 177 | var pdb: Pdb = try .init(gpa, pdb_reader); | |
| 178 | errdefer pdb.deinit(); | |
| 179 | try pdb.parseInfoStream(); | |
| 180 | try pdb.parseDbiStream(); | |
| 181 | ||
| 182 | if (!mem.eql(u8, &coff_obj.guid, &pdb.guid) or coff_obj.age != pdb.age) | |
| 183 | return error.InvalidDebugInfo; | |
| 184 | ||
| 185 | di.coff_section_headers = try coff_obj.getSectionHeadersAlloc(gpa); | |
| 186 | ||
| 187 | di.pdb = pdb; | |
| 188 | } | |
| 189 | ||
| 190 | di.loaded = true; | |
| 191 | } | |
| 192 | pub const LookupCache = struct { | |
| 193 | rwlock: std.Thread.RwLock, | |
| 194 | modules: std.ArrayListUnmanaged(windows.MODULEENTRY32), | |
| 195 | pub const init: LookupCache = .{ | |
| 196 | .rwlock = .{}, | |
| 197 | .modules = .empty, | |
| 198 | }; | |
| 199 | pub fn deinit(lc: *LookupCache, gpa: Allocator) void { | |
| 200 | lc.modules.deinit(gpa); | |
| 201 | } | |
| 202 | }; | |
| 203 | pub const DebugInfo = struct { | |
| 204 | mutex: std.Thread.Mutex, | |
| 205 | ||
| 206 | loaded: bool, | |
| 207 | ||
| 208 | coff_image_base: u64, | |
| 209 | mapped_file: ?struct { | |
| 210 | file: fs.File, | |
| 211 | section_handle: windows.HANDLE, | |
| 212 | section_view: []const u8, | |
| 213 | }, | |
| 214 | ||
| 215 | dwarf: ?Dwarf, | |
| 216 | ||
| 217 | pdb: ?Pdb, | |
| 218 | /// Populated iff `pdb != null`; otherwise `&.{}`. | |
| 219 | coff_section_headers: []coff.SectionHeader, | |
| 220 | ||
| 221 | pub const init: DebugInfo = .{ | |
| 222 | .mutex = .{}, | |
| 223 | .loaded = false, | |
| 224 | .coff_image_base = undefined, | |
| 225 | .mapped_file = null, | |
| 226 | .dwarf = null, | |
| 227 | .pdb = null, | |
| 228 | .coff_section_headers = &.{}, | |
| 229 | }; | |
| 230 | ||
| 231 | pub fn deinit(di: *DebugInfo, gpa: Allocator) void { | |
| 232 | if (!di.loaded) return; | |
| 233 | if (di.dwarf) |*dwarf| dwarf.deinit(gpa); | |
| 234 | if (di.pdb) |*pdb| { | |
| 235 | pdb.file_reader.file.close(); | |
| 236 | gpa.free(pdb.file_reader.interface.buffer); | |
| 237 | gpa.destroy(pdb.file_reader); | |
| 238 | pdb.deinit(); | |
| 239 | } | |
| 240 | gpa.free(di.coff_section_headers); | |
| 241 | if (di.mapped_file) |mapped| { | |
| 242 | const process_handle = windows.GetCurrentProcess(); | |
| 243 | assert(windows.ntdll.NtUnmapViewOfSection(process_handle, @constCast(mapped.section_view.ptr)) == .SUCCESS); | |
| 244 | windows.CloseHandle(mapped.section_handle); | |
| 245 | mapped.file.close(); | |
| 246 | } | |
| 247 | } | |
| 248 | ||
| 249 | fn getSymbolFromPdb(di: *DebugInfo, relocated_address: usize) !?std.debug.Symbol { | |
| 250 | var coff_section: *align(1) const coff.SectionHeader = undefined; | |
| 251 | const mod_index = for (di.pdb.?.sect_contribs) |sect_contrib| { | |
| 252 | if (sect_contrib.section > di.coff_section_headers.len) continue; | |
| 253 | // Remember that SectionContribEntry.Section is 1-based. | |
| 254 | coff_section = &di.coff_section_headers[sect_contrib.section - 1]; | |
| 255 | ||
| 256 | const vaddr_start = coff_section.virtual_address + sect_contrib.offset; | |
| 257 | const vaddr_end = vaddr_start + sect_contrib.size; | |
| 258 | if (relocated_address >= vaddr_start and relocated_address < vaddr_end) { | |
| 259 | break sect_contrib.module_index; | |
| 260 | } | |
| 261 | } else { | |
| 262 | // we have no information to add to the address | |
| 263 | return null; | |
| 264 | }; | |
| 265 | ||
| 266 | const module = try di.pdb.?.getModule(mod_index) orelse return error.InvalidDebugInfo; | |
| 267 | ||
| 268 | return .{ | |
| 269 | .name = di.pdb.?.getSymbolName( | |
| 270 | module, | |
| 271 | relocated_address - coff_section.virtual_address, | |
| 272 | ), | |
| 273 | .compile_unit_name = fs.path.basename(module.obj_file_name), | |
| 274 | .source_location = try di.pdb.?.getLineNumberInfo( | |
| 275 | module, | |
| 276 | relocated_address - coff_section.virtual_address, | |
| 277 | ), | |
| 278 | }; | |
| 279 | } | |
| 280 | }; | |
| 281 | ||
| 282 | pub const supports_unwinding: bool = switch (builtin.cpu.arch) { | |
| 283 | else => true, | |
| 284 | // On x86, `RtlVirtualUnwind` does not exist. We could in theory use `RtlCaptureStackBackTrace` | |
| 285 | // instead, but on x86, it turns out that function is just... doing FP unwinding with esp! It's | |
| 286 | // hard to find implementation details to confirm that, but the most authoritative source I have | |
| 287 | // is an entry in the LLVM mailing list from 2020/08/16 which contains this quote: | |
| 288 | // | |
| 289 | // > x86 doesn't have what most architectures would consider an "unwinder" in the sense of | |
| 290 | // > restoring registers; there is simply a linked list of frames that participate in SEH and | |
| 291 | // > that desire to be called for a dynamic unwind operation, so RtlCaptureStackBackTrace | |
| 292 | // > assumes that EBP-based frames are in use and walks an EBP-based frame chain on x86 - not | |
| 293 | // > all x86 code is written with EBP-based frames so while even though we generally build the | |
| 294 | // > OS that way, you might always run the risk of encountering external code that uses EBP as a | |
| 295 | // > general purpose register for which such an unwind attempt for a stack trace would fail. | |
| 296 | // | |
| 297 | // Regardless, it's easy to effectively confirm this hypothesis just by compiling some code with | |
| 298 | // `-fomit-frame-pointer -OReleaseFast` and observing that `RtlCaptureStackBackTrace` returns an | |
| 299 | // empty trace when it's called in such an application. Note that without `-OReleaseFast` or | |
| 300 | // similar, LLVM seems reluctant to ever clobber ebp, so you'll get a trace returned which just | |
| 301 | // contains all of the kernel32/ntdll frames but none of your own. Don't be deceived---this is | |
| 302 | // just coincidental! | |
| 303 | // | |
| 304 | // Anyway, the point is, the only stack walking primitive on x86-windows is FP unwinding. We | |
| 305 | // *could* ask Microsoft to do that for us with `RtlCaptureStackBackTrace`... but better to just | |
| 306 | // use our existing FP unwinder in `std.debug`! | |
| 307 | .x86 => false, | |
| 308 | }; | |
| 309 | pub const UnwindContext = struct { | |
| 310 | pc: usize, | |
| 311 | cur: windows.CONTEXT, | |
| 312 | history_table: windows.UNWIND_HISTORY_TABLE, | |
| 313 | pub fn init(ctx: *const std.debug.cpu_context.Native) UnwindContext { | |
| 314 | return .{ | |
| 315 | .pc = @returnAddress(), | |
| 316 | .cur = switch (builtin.cpu.arch) { | |
| 317 | .x86_64 => std.mem.zeroInit(windows.CONTEXT, .{ | |
| 318 | .Rax = ctx.gprs.get(.rax), | |
| 319 | .Rcx = ctx.gprs.get(.rcx), | |
| 320 | .Rdx = ctx.gprs.get(.rdx), | |
| 321 | .Rbx = ctx.gprs.get(.rbx), | |
| 322 | .Rsp = ctx.gprs.get(.rsp), | |
| 323 | .Rbp = ctx.gprs.get(.rbp), | |
| 324 | .Rsi = ctx.gprs.get(.rsi), | |
| 325 | .Rdi = ctx.gprs.get(.rdi), | |
| 326 | .R8 = ctx.gprs.get(.r8), | |
| 327 | .R9 = ctx.gprs.get(.r9), | |
| 328 | .R10 = ctx.gprs.get(.r10), | |
| 329 | .R11 = ctx.gprs.get(.r11), | |
| 330 | .R12 = ctx.gprs.get(.r12), | |
| 331 | .R13 = ctx.gprs.get(.r13), | |
| 332 | .R14 = ctx.gprs.get(.r14), | |
| 333 | .R15 = ctx.gprs.get(.r15), | |
| 334 | .Rip = ctx.gprs.get(.rip), | |
| 335 | }), | |
| 336 | .aarch64, .aarch64_be => .{ | |
| 337 | .ContextFlags = 0, | |
| 338 | .Cpsr = 0, | |
| 339 | .DUMMYUNIONNAME = .{ .X = ctx.x }, | |
| 340 | .Sp = ctx.sp, | |
| 341 | .Pc = ctx.pc, | |
| 342 | .V = @splat(.{ .B = @splat(0) }), | |
| 343 | .Fpcr = 0, | |
| 344 | .Fpsr = 0, | |
| 345 | .Bcr = @splat(0), | |
| 346 | .Bvr = @splat(0), | |
| 347 | .Wcr = @splat(0), | |
| 348 | .Wvr = @splat(0), | |
| 349 | }, | |
| 350 | .thumb => .{ | |
| 351 | .ContextFlags = 0, | |
| 352 | .R0 = ctx.r[0], | |
| 353 | .R1 = ctx.r[1], | |
| 354 | .R2 = ctx.r[2], | |
| 355 | .R3 = ctx.r[3], | |
| 356 | .R4 = ctx.r[4], | |
| 357 | .R5 = ctx.r[5], | |
| 358 | .R6 = ctx.r[6], | |
| 359 | .R7 = ctx.r[7], | |
| 360 | .R8 = ctx.r[8], | |
| 361 | .R9 = ctx.r[9], | |
| 362 | .R10 = ctx.r[10], | |
| 363 | .R11 = ctx.r[11], | |
| 364 | .R12 = ctx.r[12], | |
| 365 | .Sp = ctx.r[13], | |
| 366 | .Lr = ctx.r[14], | |
| 367 | .Pc = ctx.r[15], | |
| 368 | .Cpsr = 0, | |
| 369 | .Fpcsr = 0, | |
| 370 | .Padding = 0, | |
| 371 | .DUMMYUNIONNAME = .{ .S = @splat(0) }, | |
| 372 | .Bvr = @splat(0), | |
| 373 | .Bcr = @splat(0), | |
| 374 | .Wvr = @splat(0), | |
| 375 | .Wcr = @splat(0), | |
| 376 | .Padding2 = @splat(0), | |
| 377 | }, | |
| 378 | else => comptime unreachable, | |
| 379 | }, | |
| 380 | .history_table = std.mem.zeroes(windows.UNWIND_HISTORY_TABLE), | |
| 381 | }; | |
| 382 | } | |
| 383 | pub fn deinit(ctx: *UnwindContext, gpa: Allocator) void { | |
| 384 | _ = ctx; | |
| 385 | _ = gpa; | |
| 386 | } | |
| 387 | pub fn getFp(ctx: *UnwindContext) usize { | |
| 388 | return ctx.cur.getRegs().bp; | |
| 389 | } | |
| 390 | }; | |
| 391 | pub fn unwindFrame(module: *const WindowsModule, gpa: Allocator, di: *DebugInfo, context: *UnwindContext) !usize { | |
| 392 | _ = module; | |
| 393 | _ = gpa; | |
| 394 | _ = di; | |
| 395 | ||
| 396 | const current_regs = context.cur.getRegs(); | |
| 397 | var image_base: windows.DWORD64 = undefined; | |
| 398 | if (windows.ntdll.RtlLookupFunctionEntry(current_regs.ip, &image_base, &context.history_table)) |runtime_function| { | |
| 399 | var handler_data: ?*anyopaque = null; | |
| 400 | var establisher_frame: u64 = undefined; | |
| 401 | _ = windows.ntdll.RtlVirtualUnwind( | |
| 402 | windows.UNW_FLAG_NHANDLER, | |
| 403 | image_base, | |
| 404 | current_regs.ip, | |
| 405 | runtime_function, | |
| 406 | &context.cur, | |
| 407 | &handler_data, | |
| 408 | &establisher_frame, | |
| 409 | null, | |
| 410 | ); | |
| 411 | } else { | |
| 412 | // leaf function | |
| 413 | context.cur.setIp(@as(*const usize, @ptrFromInt(current_regs.sp)).*); | |
| 414 | context.cur.setSp(current_regs.sp + @sizeOf(usize)); | |
| 415 | } | |
| 416 | ||
| 417 | const next_regs = context.cur.getRegs(); | |
| 418 | const tib = &windows.teb().NtTib; | |
| 419 | if (next_regs.sp < @intFromPtr(tib.StackLimit) or next_regs.sp > @intFromPtr(tib.StackBase)) { | |
| 420 | context.pc = 0; | |
| 421 | return 0; | |
| 422 | } | |
| 423 | // Like `DwarfUnwindContext.unwindFrame`, adjust our next lookup pc in case the `call` was this | |
| 424 | // function's last instruction making `next_regs.ip` one byte past its end. | |
| 425 | context.pc = next_regs.ip -| 1; | |
| 426 | return next_regs.ip; | |
| 427 | } | |
| 428 | ||
| 429 | const WindowsModule = @This(); | |
| 430 | ||
| 431 | const std = @import("../../std.zig"); | |
| 432 | const Allocator = std.mem.Allocator; | |
| 433 | const Dwarf = std.debug.Dwarf; | |
| 434 | const Pdb = std.debug.Pdb; | |
| 435 | const assert = std.debug.assert; | |
| 436 | const coff = std.coff; | |
| 437 | const fs = std.fs; | |
| 438 | const mem = std.mem; | |
| 439 | const windows = std.os.windows; | |
| 440 | ||
| 441 | const builtin = @import("builtin"); | |
| 442 | const native_endian = builtin.target.cpu.arch.endian(); |
test/standalone/coff_dwarf/main.zig+1-1| ... | ... | @@ -14,7 +14,7 @@ pub fn main() void { |
| 14 | 14 | var add_addr: usize = undefined; |
| 15 | 15 | _ = add(1, 2, &add_addr); |
| 16 | 16 | |
| 17 | const symbol = di.getSymbolAtAddress(gpa, add_addr) catch |err| fatal("failed to get symbol: {t}", .{err}); | |
| 17 | const symbol = di.getSymbol(gpa, add_addr) catch |err| fatal("failed to get symbol: {t}", .{err}); | |
| 18 | 18 | defer if (symbol.source_location) |sl| gpa.free(sl.file_name); |
| 19 | 19 | |
| 20 | 20 | if (symbol.name == null) fatal("failed to resolve symbol name", .{}); |