| 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,11 +19,85 @@ const root = @import("root"); |
| 19 | pub const Dwarf = @import("debug/Dwarf.zig"); | 19 | pub const Dwarf = @import("debug/Dwarf.zig"); |
| 20 | pub const Pdb = @import("debug/Pdb.zig"); | 20 | pub const Pdb = @import("debug/Pdb.zig"); |
| 21 | pub const ElfFile = @import("debug/ElfFile.zig"); | 21 | pub const ElfFile = @import("debug/ElfFile.zig"); |
| 22 | pub const SelfInfo = @import("debug/SelfInfo.zig"); | ||
| 23 | pub const Info = @import("debug/Info.zig"); | 22 | pub const Info = @import("debug/Info.zig"); |
| 24 | pub const Coverage = @import("debug/Coverage.zig"); | 23 | pub const Coverage = @import("debug/Coverage.zig"); |
| 25 | pub const cpu_context = @import("debug/cpu_context.zig"); | 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 | pub const simple_panic = @import("debug/simple_panic.zig"); | 101 | pub const simple_panic = @import("debug/simple_panic.zig"); |
| 28 | pub const no_panic = @import("debug/no_panic.zig"); | 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,7 +314,7 @@ pub fn print(comptime fmt: []const u8, args: anytype) void { |
| 240 | 314 | ||
| 241 | /// Marked `inline` to propagate a comptime-known error to callers. | 315 | /// Marked `inline` to propagate a comptime-known error to callers. |
| 242 | pub inline fn getSelfDebugInfo() !*SelfInfo { | 316 | pub inline fn getSelfDebugInfo() !*SelfInfo { |
| 243 | if (!SelfInfo.target_supported) return error.UnsupportedTarget; | 317 | if (SelfInfo == void) return error.UnsupportedTarget; |
| 244 | const S = struct { | 318 | const S = struct { |
| 245 | var self_info: SelfInfo = .init; | 319 | var self_info: SelfInfo = .init; |
| 246 | }; | 320 | }; |
| ... | @@ -640,7 +714,7 @@ pub fn writeCurrentStackTrace(options: StackUnwindOptions, writer: *Writer, tty_ | ... | @@ -640,7 +714,7 @@ pub fn writeCurrentStackTrace(options: StackUnwindOptions, writer: *Writer, tty_ |
| 640 | while (true) switch (it.next()) { | 714 | while (true) switch (it.next()) { |
| 641 | .switch_to_fp => |unwind_error| { | 715 | .switch_to_fp => |unwind_error| { |
| 642 | if (StackIterator.fp_unwind_is_safe) continue; // no need to even warn | 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 | const caption: []const u8 = switch (unwind_error.err) { | 718 | const caption: []const u8 = switch (unwind_error.err) { |
| 645 | error.MissingDebugInfo => "unwind info unavailable", | 719 | error.MissingDebugInfo => "unwind info unavailable", |
| 646 | error.InvalidDebugInfo => "unwind info invalid", | 720 | error.InvalidDebugInfo => "unwind info invalid", |
| ... | @@ -753,9 +827,9 @@ pub fn dumpStackTrace(st: *const std.builtin.StackTrace) void { | ... | @@ -753,9 +827,9 @@ pub fn dumpStackTrace(st: *const std.builtin.StackTrace) void { |
| 753 | 827 | ||
| 754 | const StackIterator = union(enum) { | 828 | const StackIterator = union(enum) { |
| 755 | /// Unwinding using debug info (e.g. DWARF CFI). | 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 | /// We will first report the *current* PC of this `UnwindContext`, then we will switch to `di`. | 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 | /// Naive frame-pointer-based unwinding. Very simple, but typically unreliable. | 833 | /// Naive frame-pointer-based unwinding. Very simple, but typically unreliable. |
| 760 | fp: usize, | 834 | fp: usize, |
| 761 | 835 | ||
| ... | @@ -772,7 +846,7 @@ const StackIterator = union(enum) { | ... | @@ -772,7 +846,7 @@ const StackIterator = union(enum) { |
| 772 | } | 846 | } |
| 773 | } | 847 | } |
| 774 | if (opt_context_ptr) |context_ptr| { | 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 | // Use `di_first` here so we report the PC in the context before unwinding any further. | 850 | // Use `di_first` here so we report the PC in the context before unwinding any further. |
| 777 | return .{ .di_first = .init(context_ptr) }; | 851 | return .{ .di_first = .init(context_ptr) }; |
| 778 | } | 852 | } |
| ... | @@ -780,7 +854,8 @@ const StackIterator = union(enum) { | ... | @@ -780,7 +854,8 @@ const StackIterator = union(enum) { |
| 780 | // call to `current`. This effectively constrains stack trace collection and dumping to FP | 854 | // call to `current`. This effectively constrains stack trace collection and dumping to FP |
| 781 | // unwinding when building with CBE for MSVC. | 855 | // unwinding when building with CBE for MSVC. |
| 782 | if (!(builtin.zig_backend == .stage2_c and builtin.target.abi == .msvc) and | 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 | cpu_context.Native != noreturn) | 859 | cpu_context.Native != noreturn) |
| 785 | { | 860 | { |
| 786 | // We don't need `di_first` here, because our PC is in `std.debug`; we're only interested | 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,7 +895,7 @@ const StackIterator = union(enum) { |
| 820 | /// We were using `SelfInfo.UnwindInfo`, but are now switching to FP unwinding due to this error. | 895 | /// We were using `SelfInfo.UnwindInfo`, but are now switching to FP unwinding due to this error. |
| 821 | switch_to_fp: struct { | 896 | switch_to_fp: struct { |
| 822 | address: usize, | 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,7 +1004,7 @@ pub inline fn stripInstructionPtrAuthCode(ptr: usize) usize { |
| 929 | } | 1004 | } |
| 930 | 1005 | ||
| 931 | fn printSourceAtAddress(gpa: Allocator, debug_info: *SelfInfo, writer: *Writer, address: usize, tty_config: tty.Config) Writer.Error!void { | 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 | error.MissingDebugInfo, | 1008 | error.MissingDebugInfo, |
| 934 | error.UnsupportedDebugInfo, | 1009 | error.UnsupportedDebugInfo, |
| 935 | error.InvalidDebugInfo, | 1010 | error.InvalidDebugInfo, |
| ... | @@ -953,7 +1028,7 @@ fn printSourceAtAddress(gpa: Allocator, debug_info: *SelfInfo, writer: *Writer, | ... | @@ -953,7 +1028,7 @@ fn printSourceAtAddress(gpa: Allocator, debug_info: *SelfInfo, writer: *Writer, |
| 953 | symbol.source_location, | 1028 | symbol.source_location, |
| 954 | address, | 1029 | address, |
| 955 | symbol.name orelse "???", | 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 | tty_config, | 1032 | tty_config, |
| 958 | ); | 1033 | ); |
| 959 | } | 1034 | } |
| ... | @@ -1386,7 +1461,7 @@ pub fn dumpStackPointerAddr(prefix: []const u8) void { | ... | @@ -1386,7 +1461,7 @@ pub fn dumpStackPointerAddr(prefix: []const u8) void { |
| 1386 | } | 1461 | } |
| 1387 | 1462 | ||
| 1388 | test "manage resources correctly" { | 1463 | test "manage resources correctly" { |
| 1389 | if (!SelfInfo.target_supported) return error.SkipZigTest; | 1464 | if (SelfInfo == void) return error.SkipZigTest; |
| 1390 | const S = struct { | 1465 | const S = struct { |
| 1391 | noinline fn showMyTrace() usize { | 1466 | noinline fn showMyTrace() usize { |
| 1392 | return @returnAddress(); | 1467 | return @returnAddress(); |
lib/std/debug/Dwarf.zig+3-2| ... | @@ -28,6 +28,7 @@ const Dwarf = @This(); | ... | @@ -28,6 +28,7 @@ const Dwarf = @This(); |
| 28 | 28 | ||
| 29 | pub const expression = @import("Dwarf/expression.zig"); | 29 | pub const expression = @import("Dwarf/expression.zig"); |
| 30 | pub const Unwind = @import("Dwarf/Unwind.zig"); | 30 | pub const Unwind = @import("Dwarf/Unwind.zig"); |
| 31 | pub const SelfUnwinder = @import("Dwarf/SelfUnwinder.zig"); | ||
| 31 | 32 | ||
| 32 | /// Useful to temporarily enable while working on this file. | 33 | /// Useful to temporarily enable while working on this file. |
| 33 | const debug_debug_mode = false; | 34 | const debug_debug_mode = false; |
| ... | @@ -1458,8 +1459,8 @@ pub fn spRegNum(arch: std.Target.Cpu.Arch) u16 { | ... | @@ -1458,8 +1459,8 @@ pub fn spRegNum(arch: std.Target.Cpu.Arch) u16 { |
| 1458 | 1459 | ||
| 1459 | /// Tells whether unwinding for this target is supported by the Dwarf standard. | 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 | /// See also `std.debug.SelfInfo.can_unwind` which tells whether the Zig standard |
| 1462 | /// standard library has a working implementation of unwinding for this target. | 1463 | /// library has a working implementation of unwinding for the current target. |
| 1463 | pub fn supportsUnwinding(target: *const std.Target) bool { | 1464 | pub fn supportsUnwinding(target: *const std.Target) bool { |
| 1464 | return switch (target.cpu.arch) { | 1465 | return switch (target.cpu.arch) { |
| 1465 | .amdgcn, | 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,16 +530,18 @@ pub fn prepare( |
| 530 | }; | 530 | }; |
| 531 | if (saw_terminator != expect_terminator) return bad(); | 531 | if (saw_terminator != expect_terminator) return bad(); |
| 532 | 532 | ||
| 533 | std.mem.sortUnstable(SortedFdeEntry, fde_list.items, {}, struct { | 533 | if (need_lookup) { |
| 534 | fn lessThan(ctx: void, a: SortedFdeEntry, b: SortedFdeEntry) bool { | 534 | std.mem.sortUnstable(SortedFdeEntry, fde_list.items, {}, struct { |
| 535 | ctx; | 535 | fn lessThan(ctx: void, a: SortedFdeEntry, b: SortedFdeEntry) bool { |
| 536 | return a.pc_begin < b.pc_begin; | 536 | ctx; |
| 537 | } | 537 | return a.pc_begin < b.pc_begin; |
| 538 | }.lessThan); | 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 | // 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 | const final_fdes = try fde_list.toOwnedSlice(gpa); |
| 542 | unwind.lookup = .{ .sorted_fdes = final_fdes }; | 543 | unwind.lookup = .{ .sorted_fdes = final_fdes }; |
| 544 | } | ||
| 543 | } | 545 | } |
| 544 | 546 | ||
| 545 | fn findCie(unwind: *const Unwind, offset: u64) ?*const CommonInformationEntry { | 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,7 +10,7 @@ const assert = std.debug.assert; |
| 10 | const testing = std.testing; | 10 | const testing = std.testing; |
| 11 | const Writer = std.Io.Writer; | 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 | const ip_reg_num = std.debug.Dwarf.ipRegNum(native_arch).?; | 15 | const ip_reg_num = std.debug.Dwarf.ipRegNum(native_arch).?; |
| 16 | const fp_reg_num = std.debug.Dwarf.fpRegNum(native_arch); | 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,7 +14,7 @@ pub fn main() void { |
| 14 | var add_addr: usize = undefined; | 14 | var add_addr: usize = undefined; |
| 15 | _ = add(1, 2, &add_addr); | 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 | defer if (symbol.source_location) |sl| gpa.free(sl.file_name); | 18 | defer if (symbol.source_location) |sl| gpa.free(sl.file_name); |
| 19 | 19 | ||
| 20 | if (symbol.name == null) fatal("failed to resolve symbol name", .{}); | 20 | if (symbol.name == null) fatal("failed to resolve symbol name", .{}); |