| author | |
| committer | |
| log | bc4da9a90743c11c7c0b3e485f46d365d57d87b7 |
| tree | f98c87a7e2f0b07d3510d3d9972ef5c9ef9f652d |
| parent | 14019a95a4f3519bc03d23f79eb3141b51a9b2c2 |
| parent | a4f95b1e619656b0306744177dc4f8662ae4879b |
| signature |
`std.debug`: LoongArch and RISC-V unwind support + some minor cleanups8 files changed, 1272 insertions(+), 1042 deletions(-)
lib/std/debug.zig+6-22| ... | @@ -61,28 +61,12 @@ pub const cpu_context = @import("debug/cpu_context.zig"); | ... | @@ -61,28 +61,12 @@ pub const cpu_context = @import("debug/cpu_context.zig"); |
| 61 | /// ``` | 61 | /// ``` |
| 62 | pub const SelfInfo = if (@hasDecl(root, "debug") and @hasDecl(root.debug, "SelfInfo")) | 62 | pub const SelfInfo = if (@hasDecl(root, "debug") and @hasDecl(root.debug, "SelfInfo")) |
| 63 | root.debug.SelfInfo | 63 | root.debug.SelfInfo |
| 64 | else switch (native_os) { | 64 | else switch (std.Target.ObjectFormat.default(native_os, native_arch)) { |
| 65 | .linux, | 65 | .coff => if (native_os == .windows) @import("debug/SelfInfo/Windows.zig") else void, |
| 66 | .netbsd, | 66 | .elf => @import("debug/SelfInfo/Elf.zig"), |
| 67 | .freebsd, | 67 | .macho => @import("debug/SelfInfo/MachO.zig"), |
| 68 | .dragonfly, | 68 | .goff, .plan9, .spirv, .wasm, .xcoff => void, |
| 69 | .openbsd, | 69 | .c, .hex, .raw => unreachable, |
| 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 | }; | 70 | }; |
| 87 | 71 | ||
| 88 | pub const SelfInfoError = error{ | 72 | pub const SelfInfoError = error{ |
lib/std/debug/Dwarf.zig+12-10| ... | @@ -1429,30 +1429,36 @@ pub fn compactUnwindToDwarfRegNumber(unwind_reg_number: u3) !u16 { | ... | @@ -1429,30 +1429,36 @@ pub fn compactUnwindToDwarfRegNumber(unwind_reg_number: u3) !u16 { |
| 1429 | /// Returns `null` for CPU architectures without an instruction pointer register. | 1429 | /// Returns `null` for CPU architectures without an instruction pointer register. |
| 1430 | pub fn ipRegNum(arch: std.Target.Cpu.Arch) ?u16 { | 1430 | pub fn ipRegNum(arch: std.Target.Cpu.Arch) ?u16 { |
| 1431 | return switch (arch) { | 1431 | return switch (arch) { |
| 1432 | .aarch64, .aarch64_be => 32, | ||
| 1433 | .arm, .armeb, .thumb, .thumbeb => 15, | ||
| 1434 | .loongarch32, .loongarch64 => 32, | ||
| 1435 | .riscv32, .riscv32be, .riscv64, .riscv64be => 32, | ||
| 1432 | .x86 => 8, | 1436 | .x86 => 8, |
| 1433 | .x86_64 => 16, | 1437 | .x86_64 => 16, |
| 1434 | .arm, .armeb, .thumb, .thumbeb => 15, | ||
| 1435 | .aarch64, .aarch64_be => 32, | ||
| 1436 | else => null, | 1438 | else => null, |
| 1437 | }; | 1439 | }; |
| 1438 | } | 1440 | } |
| 1439 | 1441 | ||
| 1440 | pub fn fpRegNum(arch: std.Target.Cpu.Arch) u16 { | 1442 | pub fn fpRegNum(arch: std.Target.Cpu.Arch) u16 { |
| 1441 | return switch (arch) { | 1443 | return switch (arch) { |
| 1444 | .aarch64, .aarch64_be => 29, | ||
| 1445 | .arm, .armeb, .thumb, .thumbeb => 11, | ||
| 1446 | .loongarch32, .loongarch64 => 22, | ||
| 1447 | .riscv32, .riscv32be, .riscv64, .riscv64be => 8, | ||
| 1442 | .x86 => 5, | 1448 | .x86 => 5, |
| 1443 | .x86_64 => 6, | 1449 | .x86_64 => 6, |
| 1444 | .arm, .armeb, .thumb, .thumbeb => 11, | ||
| 1445 | .aarch64, .aarch64_be => 29, | ||
| 1446 | else => unreachable, | 1450 | else => unreachable, |
| 1447 | }; | 1451 | }; |
| 1448 | } | 1452 | } |
| 1449 | 1453 | ||
| 1450 | pub fn spRegNum(arch: std.Target.Cpu.Arch) u16 { | 1454 | pub fn spRegNum(arch: std.Target.Cpu.Arch) u16 { |
| 1451 | return switch (arch) { | 1455 | return switch (arch) { |
| 1456 | .aarch64, .aarch64_be => 31, | ||
| 1457 | .arm, .armeb, .thumb, .thumbeb => 13, | ||
| 1458 | .loongarch32, .loongarch64 => 3, | ||
| 1459 | .riscv32, .riscv32be, .riscv64, .riscv64be => 2, | ||
| 1452 | .x86 => 4, | 1460 | .x86 => 4, |
| 1453 | .x86_64 => 7, | 1461 | .x86_64 => 7, |
| 1454 | .arm, .armeb, .thumb, .thumbeb => 13, | ||
| 1455 | .aarch64, .aarch64_be => 31, | ||
| 1456 | else => unreachable, | 1462 | else => unreachable, |
| 1457 | }; | 1463 | }; |
| 1458 | } | 1464 | } |
| ... | @@ -1470,10 +1476,6 @@ pub fn supportsUnwinding(target: *const std.Target) bool { | ... | @@ -1470,10 +1476,6 @@ pub fn supportsUnwinding(target: *const std.Target) bool { |
| 1470 | .spirv64, | 1476 | .spirv64, |
| 1471 | => false, | 1477 | => false, |
| 1472 | 1478 | ||
| 1473 | // Enabling this causes relocation errors such as: | ||
| 1474 | // error: invalid relocation type R_RISCV_SUB32 at offset 0x20 | ||
| 1475 | .riscv64, .riscv64be, .riscv32, .riscv32be => false, | ||
| 1476 | |||
| 1477 | // Conservative guess. Feel free to update this logic with any targets | 1479 | // Conservative guess. Feel free to update this logic with any targets |
| 1478 | // that are known to not support Dwarf unwinding. | 1480 | // that are known to not support Dwarf unwinding. |
| 1479 | else => true, | 1481 | else => true, |
lib/std/debug/Dwarf/Unwind/VirtualMachine.zig+12-1| ... | @@ -256,7 +256,18 @@ fn evalInstructions( | ... | @@ -256,7 +256,18 @@ fn evalInstructions( |
| 256 | .offset = cfa.offset_sf * cie.data_alignment_factor, | 256 | .offset = cfa.offset_sf * cie.data_alignment_factor, |
| 257 | } }, | 257 | } }, |
| 258 | .def_cfa_reg => |register| switch (vm.current_row.cfa) { | 258 | .def_cfa_reg => |register| switch (vm.current_row.cfa) { |
| 259 | .none, .expression => return error.InvalidOperation, | 259 | .none => { |
| 260 | // According to the DWARF specification, this is not valid, because this | ||
| 261 | // instruction can only be used to replace the register if the rule is already a | ||
| 262 | // `.reg_off`. However, this is emitted in practice by GNU toolchains for some | ||
| 263 | // targets, and so by convention is interpreted as equivalent to `.def_cfa` with | ||
| 264 | // an offset of 0. | ||
| 265 | vm.current_row.cfa = .{ .reg_off = .{ | ||
| 266 | .register = register, | ||
| 267 | .offset = 0, | ||
| 268 | } }; | ||
| 269 | }, | ||
| 270 | .expression => return error.InvalidOperation, | ||
| 260 | .reg_off => |*ro| ro.register = register, | 271 | .reg_off => |*ro| ro.register = register, |
| 261 | }, | 272 | }, |
| 262 | .def_cfa_offset => |offset| switch (vm.current_row.cfa) { | 273 | .def_cfa_offset => |offset| switch (vm.current_row.cfa) { |
lib/std/debug/SelfInfo/Darwin.zig deleted-993| ... | @@ -1,993 +0,0 @@ | ||
| 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/Elf.zig+29-6| ... | @@ -84,12 +84,35 @@ pub const can_unwind: bool = s: { | ... | @@ -84,12 +84,35 @@ pub const can_unwind: bool = s: { |
| 84 | // Notably, we are yet to support unwinding on ARM. There, unwinding is not done through | 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. | 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) { | 86 | const archs: []const std.Target.Cpu.Arch = switch (builtin.target.os.tag) { |
| 87 | .linux => &.{ .x86, .x86_64, .aarch64, .aarch64_be }, | 87 | .linux => &.{ |
| 88 | .netbsd => &.{ .x86, .x86_64, .aarch64, .aarch64_be }, | 88 | .aarch64, |
| 89 | .freebsd => &.{ .x86_64, .aarch64, .aarch64_be }, | 89 | .aarch64_be, |
| 90 | .openbsd => &.{.x86_64}, | 90 | .loongarch64, |
| 91 | .solaris => &.{ .x86, .x86_64 }, | 91 | .riscv32, |
| 92 | .illumos => &.{ .x86, .x86_64 }, | 92 | .riscv64, |
| 93 | .x86, | ||
| 94 | .x86_64, | ||
| 95 | }, | ||
| 96 | .netbsd => &.{ | ||
| 97 | .aarch64, | ||
| 98 | .aarch64_be, | ||
| 99 | .x86, | ||
| 100 | .x86_64, | ||
| 101 | }, | ||
| 102 | .freebsd => &.{ | ||
| 103 | .x86_64, | ||
| 104 | .aarch64, | ||
| 105 | }, | ||
| 106 | .openbsd => &.{ | ||
| 107 | .x86_64, | ||
| 108 | }, | ||
| 109 | .solaris => &.{ | ||
| 110 | .x86_64, | ||
| 111 | }, | ||
| 112 | .illumos => &.{ | ||
| 113 | .x86, | ||
| 114 | .x86_64, | ||
| 115 | }, | ||
| 93 | else => unreachable, | 116 | else => unreachable, |
| 94 | }; | 117 | }; |
| 95 | for (archs) |a| { | 118 | for (archs) |a| { |
lib/std/debug/SelfInfo/MachO.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 => 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/Windows.zig+1-1| ... | @@ -88,7 +88,7 @@ pub const UnwindContext = struct { | ... | @@ -88,7 +88,7 @@ pub const UnwindContext = struct { |
| 88 | .R15 = ctx.gprs.get(.r15), | 88 | .R15 = ctx.gprs.get(.r15), |
| 89 | .Rip = ctx.gprs.get(.rip), | 89 | .Rip = ctx.gprs.get(.rip), |
| 90 | }), | 90 | }), |
| 91 | .aarch64, .aarch64_be => .{ | 91 | .aarch64 => .{ |
| 92 | .ContextFlags = 0, | 92 | .ContextFlags = 0, |
| 93 | .Cpsr = 0, | 93 | .Cpsr = 0, |
| 94 | .DUMMYUNIONNAME = .{ .X = ctx.x }, | 94 | .DUMMYUNIONNAME = .{ .X = ctx.x }, |
lib/std/debug/cpu_context.zig+219-9| ... | @@ -4,10 +4,12 @@ | ... | @@ -4,10 +4,12 @@ |
| 4 | pub const Native = if (@hasDecl(root, "debug") and @hasDecl(root.debug, "CpuContext")) | 4 | pub const Native = if (@hasDecl(root, "debug") and @hasDecl(root.debug, "CpuContext")) |
| 5 | root.debug.CpuContext | 5 | root.debug.CpuContext |
| 6 | else switch (native_arch) { | 6 | else switch (native_arch) { |
| 7 | .aarch64, .aarch64_be => Aarch64, | ||
| 8 | .arm, .armeb, .thumb, .thumbeb => Arm, | ||
| 9 | .loongarch32, .loongarch64 => LoongArch, | ||
| 10 | .riscv32, .riscv32be, .riscv64, .riscv64be => Riscv, | ||
| 7 | .x86 => X86, | 11 | .x86 => X86, |
| 8 | .x86_64 => X86_64, | 12 | .x86_64 => X86_64, |
| 9 | .arm, .armeb, .thumb, .thumbeb => Arm, | ||
| 10 | .aarch64, .aarch64_be => Aarch64, | ||
| 11 | else => noreturn, | 13 | else => noreturn, |
| 12 | }; | 14 | }; |
| 13 | 15 | ||
| ... | @@ -21,7 +23,7 @@ pub fn fromPosixSignalContext(ctx_ptr: ?*const anyopaque) ?Native { | ... | @@ -21,7 +23,7 @@ pub fn fromPosixSignalContext(ctx_ptr: ?*const anyopaque) ?Native { |
| 21 | const uc: *const signal_ucontext_t = @ptrCast(@alignCast(ctx_ptr)); | 23 | const uc: *const signal_ucontext_t = @ptrCast(@alignCast(ctx_ptr)); |
| 22 | return switch (native_arch) { | 24 | return switch (native_arch) { |
| 23 | .x86 => switch (native_os) { | 25 | .x86 => switch (native_os) { |
| 24 | .linux, .netbsd, .solaris, .illumos => .{ .gprs = .init(.{ | 26 | .linux, .netbsd, .illumos => .{ .gprs = .init(.{ |
| 25 | .eax = uc.mcontext.gregs[std.posix.REG.EAX], | 27 | .eax = uc.mcontext.gregs[std.posix.REG.EAX], |
| 26 | .ecx = uc.mcontext.gregs[std.posix.REG.ECX], | 28 | .ecx = uc.mcontext.gregs[std.posix.REG.ECX], |
| 27 | .edx = uc.mcontext.gregs[std.posix.REG.EDX], | 29 | .edx = uc.mcontext.gregs[std.posix.REG.EDX], |
| ... | @@ -92,7 +94,7 @@ pub fn fromPosixSignalContext(ctx_ptr: ?*const anyopaque) ?Native { | ... | @@ -92,7 +94,7 @@ pub fn fromPosixSignalContext(ctx_ptr: ?*const anyopaque) ?Native { |
| 92 | .r15 = @bitCast(uc.sc_r15), | 94 | .r15 = @bitCast(uc.sc_r15), |
| 93 | .rip = @bitCast(uc.sc_rip), | 95 | .rip = @bitCast(uc.sc_rip), |
| 94 | }) }, | 96 | }) }, |
| 95 | .macos, .ios => .{ .gprs = .init(.{ | 97 | .driverkit, .macos, .ios => .{ .gprs = .init(.{ |
| 96 | .rax = uc.mcontext.ss.rax, | 98 | .rax = uc.mcontext.ss.rax, |
| 97 | .rdx = uc.mcontext.ss.rdx, | 99 | .rdx = uc.mcontext.ss.rdx, |
| 98 | .rcx = uc.mcontext.ss.rcx, | 100 | .rcx = uc.mcontext.ss.rcx, |
| ... | @@ -137,7 +139,7 @@ pub fn fromPosixSignalContext(ctx_ptr: ?*const anyopaque) ?Native { | ... | @@ -137,7 +139,7 @@ pub fn fromPosixSignalContext(ctx_ptr: ?*const anyopaque) ?Native { |
| 137 | else => null, | 139 | else => null, |
| 138 | }, | 140 | }, |
| 139 | .aarch64, .aarch64_be => switch (builtin.os.tag) { | 141 | .aarch64, .aarch64_be => switch (builtin.os.tag) { |
| 140 | .macos, .ios, .tvos, .watchos, .visionos => .{ | 142 | .driverkit, .macos, .ios, .tvos, .watchos, .visionos => .{ |
| 141 | .x = uc.mcontext.ss.regs ++ @as([2]u64, .{ | 143 | .x = uc.mcontext.ss.regs ++ @as([2]u64, .{ |
| 142 | uc.mcontext.ss.fp, // x29 = fp | 144 | uc.mcontext.ss.fp, // x29 = fp |
| 143 | uc.mcontext.ss.lr, // x30 = lr | 145 | uc.mcontext.ss.lr, // x30 = lr |
| ... | @@ -173,6 +175,20 @@ pub fn fromPosixSignalContext(ctx_ptr: ?*const anyopaque) ?Native { | ... | @@ -173,6 +175,20 @@ pub fn fromPosixSignalContext(ctx_ptr: ?*const anyopaque) ?Native { |
| 173 | }, | 175 | }, |
| 174 | else => null, | 176 | else => null, |
| 175 | }, | 177 | }, |
| 178 | .loongarch64 => switch (builtin.os.tag) { | ||
| 179 | .linux => .{ | ||
| 180 | .r = uc.mcontext.regs, // includes r0 (hardwired zero) | ||
| 181 | .pc = uc.mcontext.pc, | ||
| 182 | }, | ||
| 183 | else => null, | ||
| 184 | }, | ||
| 185 | .riscv32, .riscv64 => switch (builtin.os.tag) { | ||
| 186 | .linux => .{ | ||
| 187 | .r = [1]usize{0} ++ uc.mcontext.gregs[1..].*, // r0 position is used for pc; replace with zero | ||
| 188 | .pc = uc.mcontext.gregs[0], | ||
| 189 | }, | ||
| 190 | else => null, | ||
| 191 | }, | ||
| 176 | else => null, | 192 | else => null, |
| 177 | }; | 193 | }; |
| 178 | } | 194 | } |
| ... | @@ -209,7 +225,7 @@ pub fn fromWindowsContext(ctx: *const std.os.windows.CONTEXT) Native { | ... | @@ -209,7 +225,7 @@ pub fn fromWindowsContext(ctx: *const std.os.windows.CONTEXT) Native { |
| 209 | .r15 = ctx.R15, | 225 | .r15 = ctx.R15, |
| 210 | .rip = ctx.Rip, | 226 | .rip = ctx.Rip, |
| 211 | }) }, | 227 | }) }, |
| 212 | .aarch64, .aarch64_be => .{ | 228 | .aarch64 => .{ |
| 213 | .x = ctx.DUMMYUNIONNAME.X[0..31].*, | 229 | .x = ctx.DUMMYUNIONNAME.X[0..31].*, |
| 214 | .sp = ctx.Sp, | 230 | .sp = ctx.Sp, |
| 215 | .pc = ctx.Pc, | 231 | .pc = ctx.Pc, |
| ... | @@ -371,7 +387,6 @@ pub const Arm = struct { | ... | @@ -371,7 +387,6 @@ pub const Arm = struct { |
| 371 | pub fn dwarfRegisterBytes(ctx: *Arm, register_num: u16) DwarfRegisterError![]u8 { | 387 | pub fn dwarfRegisterBytes(ctx: *Arm, register_num: u16) DwarfRegisterError![]u8 { |
| 372 | // DWARF for the Arm(r) Architecture § 4.1 "DWARF register names" | 388 | // DWARF for the Arm(r) Architecture § 4.1 "DWARF register names" |
| 373 | switch (register_num) { | 389 | switch (register_num) { |
| 374 | // The order of `Gpr` intentionally matches DWARF's mappings. | ||
| 375 | 0...15 => return @ptrCast(&ctx.r[register_num]), | 390 | 0...15 => return @ptrCast(&ctx.r[register_num]), |
| 376 | 391 | ||
| 377 | 64...95 => return error.UnsupportedRegister, // S0 - S31 | 392 | 64...95 => return error.UnsupportedRegister, // S0 - S31 |
| ... | @@ -444,7 +459,6 @@ pub const Aarch64 = extern struct { | ... | @@ -444,7 +459,6 @@ pub const Aarch64 = extern struct { |
| 444 | pub fn dwarfRegisterBytes(ctx: *Aarch64, register_num: u16) DwarfRegisterError![]u8 { | 459 | pub fn dwarfRegisterBytes(ctx: *Aarch64, register_num: u16) DwarfRegisterError![]u8 { |
| 445 | // DWARF for the Arm(r) 64-bit Architecture (AArch64) § 4.1 "DWARF register names" | 460 | // DWARF for the Arm(r) 64-bit Architecture (AArch64) § 4.1 "DWARF register names" |
| 446 | switch (register_num) { | 461 | switch (register_num) { |
| 447 | // The order of `Gpr` intentionally matches DWARF's mappings. | ||
| 448 | 0...30 => return @ptrCast(&ctx.x[register_num]), | 462 | 0...30 => return @ptrCast(&ctx.x[register_num]), |
| 449 | 31 => return @ptrCast(&ctx.sp), | 463 | 31 => return @ptrCast(&ctx.sp), |
| 450 | 32 => return @ptrCast(&ctx.pc), | 464 | 32 => return @ptrCast(&ctx.pc), |
| ... | @@ -467,11 +481,207 @@ pub const Aarch64 = extern struct { | ... | @@ -467,11 +481,207 @@ pub const Aarch64 = extern struct { |
| 467 | } | 481 | } |
| 468 | }; | 482 | }; |
| 469 | 483 | ||
| 484 | /// This is an `extern struct` so that inline assembly in `current` can use field offsets. | ||
| 485 | pub const LoongArch = extern struct { | ||
| 486 | /// The numbered general-purpose registers r0 - r31. r0 must be zero. | ||
| 487 | r: [32]usize, | ||
| 488 | pc: usize, | ||
| 489 | |||
| 490 | pub inline fn current() LoongArch { | ||
| 491 | var ctx: LoongArch = undefined; | ||
| 492 | asm volatile (if (@sizeOf(usize) == 8) | ||
| 493 | \\ st.d $zero, $t0, 0 | ||
| 494 | \\ st.d $ra, $t0, 8 | ||
| 495 | \\ st.d $tp, $t0, 16 | ||
| 496 | \\ st.d $sp, $t0, 24 | ||
| 497 | \\ st.d $a0, $t0, 32 | ||
| 498 | \\ st.d $a1, $t0, 40 | ||
| 499 | \\ st.d $a2, $t0, 48 | ||
| 500 | \\ st.d $a3, $t0, 56 | ||
| 501 | \\ st.d $a4, $t0, 64 | ||
| 502 | \\ st.d $a5, $t0, 72 | ||
| 503 | \\ st.d $a6, $t0, 80 | ||
| 504 | \\ st.d $a7, $t0, 88 | ||
| 505 | \\ st.d $t0, $t0, 96 | ||
| 506 | \\ st.d $t1, $t0, 104 | ||
| 507 | \\ st.d $t2, $t0, 112 | ||
| 508 | \\ st.d $t3, $t0, 120 | ||
| 509 | \\ st.d $t4, $t0, 128 | ||
| 510 | \\ st.d $t5, $t0, 136 | ||
| 511 | \\ st.d $t6, $t0, 144 | ||
| 512 | \\ st.d $t7, $t0, 152 | ||
| 513 | \\ st.d $t8, $t0, 160 | ||
| 514 | \\ st.d $r21, $t0, 168 | ||
| 515 | \\ st.d $fp, $t0, 176 | ||
| 516 | \\ st.d $s0, $t0, 184 | ||
| 517 | \\ st.d $s1, $t0, 192 | ||
| 518 | \\ st.d $s2, $t0, 200 | ||
| 519 | \\ st.d $s3, $t0, 208 | ||
| 520 | \\ st.d $s4, $t0, 216 | ||
| 521 | \\ st.d $s5, $t0, 224 | ||
| 522 | \\ st.d $s6, $t0, 232 | ||
| 523 | \\ st.d $s7, $t0, 240 | ||
| 524 | \\ st.d $s8, $t0, 248 | ||
| 525 | \\ bl 1f | ||
| 526 | \\1: | ||
| 527 | \\ st.d $ra, $t0, 256 | ||
| 528 | \\ ld.d $ra, $t0, 8 | ||
| 529 | else | ||
| 530 | \\ st.w $zero, $t0, 0 | ||
| 531 | \\ st.w $ra, $t0, 4 | ||
| 532 | \\ st.w $tp, $t0, 8 | ||
| 533 | \\ st.w $sp, $t0, 12 | ||
| 534 | \\ st.w $a0, $t0, 16 | ||
| 535 | \\ st.w $a1, $t0, 20 | ||
| 536 | \\ st.w $a2, $t0, 24 | ||
| 537 | \\ st.w $a3, $t0, 28 | ||
| 538 | \\ st.w $a4, $t0, 32 | ||
| 539 | \\ st.w $a5, $t0, 36 | ||
| 540 | \\ st.w $a6, $t0, 40 | ||
| 541 | \\ st.w $a7, $t0, 44 | ||
| 542 | \\ st.w $t0, $t0, 48 | ||
| 543 | \\ st.w $t1, $t0, 52 | ||
| 544 | \\ st.w $t2, $t0, 56 | ||
| 545 | \\ st.w $t3, $t0, 60 | ||
| 546 | \\ st.w $t4, $t0, 64 | ||
| 547 | \\ st.w $t5, $t0, 68 | ||
| 548 | \\ st.w $t6, $t0, 72 | ||
| 549 | \\ st.w $t7, $t0, 76 | ||
| 550 | \\ st.w $t8, $t0, 80 | ||
| 551 | \\ st.w $r21, $t0, 84 | ||
| 552 | \\ st.w $fp, $t0, 88 | ||
| 553 | \\ st.w $s0, $t0, 92 | ||
| 554 | \\ st.w $s1, $t0, 96 | ||
| 555 | \\ st.w $s2, $t0, 100 | ||
| 556 | \\ st.w $s3, $t0, 104 | ||
| 557 | \\ st.w $s4, $t0, 108 | ||
| 558 | \\ st.w $s5, $t0, 112 | ||
| 559 | \\ st.w $s6, $t0, 116 | ||
| 560 | \\ st.w $s7, $t0, 120 | ||
| 561 | \\ st.w $s8, $t0, 124 | ||
| 562 | \\ bl 1f | ||
| 563 | \\1: | ||
| 564 | \\ st.w $ra, $t0, 128 | ||
| 565 | \\ ld.w $ra, $t0, 4 | ||
| 566 | : | ||
| 567 | : [gprs] "{$r12}" (&ctx), | ||
| 568 | : .{ .memory = true }); | ||
| 569 | return ctx; | ||
| 570 | } | ||
| 571 | |||
| 572 | pub fn dwarfRegisterBytes(ctx: *LoongArch, register_num: u16) DwarfRegisterError![]u8 { | ||
| 573 | switch (register_num) { | ||
| 574 | 0...31 => return @ptrCast(&ctx.r[register_num]), | ||
| 575 | 32 => return @ptrCast(&ctx.pc), | ||
| 576 | |||
| 577 | else => return error.InvalidRegister, | ||
| 578 | } | ||
| 579 | } | ||
| 580 | }; | ||
| 581 | |||
| 582 | /// This is an `extern struct` so that inline assembly in `current` can use field offsets. | ||
| 583 | pub const Riscv = extern struct { | ||
| 584 | /// The numbered general-purpose registers r0 - r31. r0 must be zero. | ||
| 585 | r: [32]usize, | ||
| 586 | pc: usize, | ||
| 587 | |||
| 588 | pub inline fn current() Riscv { | ||
| 589 | var ctx: Riscv = undefined; | ||
| 590 | asm volatile (if (@sizeOf(usize) == 8) | ||
| 591 | \\ sd zero, 0(t0) | ||
| 592 | \\ sd ra, 8(t0) | ||
| 593 | \\ sd sp, 16(t0) | ||
| 594 | \\ sd gp, 24(t0) | ||
| 595 | \\ sd tp, 32(t0) | ||
| 596 | \\ sd t0, 40(t0) | ||
| 597 | \\ sd t1, 48(t0) | ||
| 598 | \\ sd t2, 56(t0) | ||
| 599 | \\ sd s0, 64(t0) | ||
| 600 | \\ sd s1, 72(t0) | ||
| 601 | \\ sd a0, 80(t0) | ||
| 602 | \\ sd a1, 88(t0) | ||
| 603 | \\ sd a2, 96(t0) | ||
| 604 | \\ sd a3, 104(t0) | ||
| 605 | \\ sd a4, 112(t0) | ||
| 606 | \\ sd a5, 120(t0) | ||
| 607 | \\ sd a6, 128(t0) | ||
| 608 | \\ sd a7, 136(t0) | ||
| 609 | \\ sd s2, 144(t0) | ||
| 610 | \\ sd s3, 152(t0) | ||
| 611 | \\ sd s4, 160(t0) | ||
| 612 | \\ sd s5, 168(t0) | ||
| 613 | \\ sd s6, 176(t0) | ||
| 614 | \\ sd s7, 184(t0) | ||
| 615 | \\ sd s8, 192(t0) | ||
| 616 | \\ sd s9, 200(t0) | ||
| 617 | \\ sd s10, 208(t0) | ||
| 618 | \\ sd s11, 216(t0) | ||
| 619 | \\ sd t3, 224(t0) | ||
| 620 | \\ sd t4, 232(t0) | ||
| 621 | \\ sd t5, 240(t0) | ||
| 622 | \\ sd t6, 248(t0) | ||
| 623 | \\ jal ra, 1f | ||
| 624 | \\1: | ||
| 625 | \\ sd ra, 256(t0) | ||
| 626 | \\ ld ra, 8(t0) | ||
| 627 | else | ||
| 628 | \\ sw zero, 0(t0) | ||
| 629 | \\ sw ra, 4(t0) | ||
| 630 | \\ sw sp, 8(t0) | ||
| 631 | \\ sw gp, 12(t0) | ||
| 632 | \\ sw tp, 16(t0) | ||
| 633 | \\ sw t0, 20(t0) | ||
| 634 | \\ sw t1, 24(t0) | ||
| 635 | \\ sw t2, 28(t0) | ||
| 636 | \\ sw s0, 32(t0) | ||
| 637 | \\ sw s1, 36(t0) | ||
| 638 | \\ sw a0, 40(t0) | ||
| 639 | \\ sw a1, 44(t0) | ||
| 640 | \\ sw a2, 48(t0) | ||
| 641 | \\ sw a3, 52(t0) | ||
| 642 | \\ sw a4, 56(t0) | ||
| 643 | \\ sw a5, 60(t0) | ||
| 644 | \\ sw a6, 64(t0) | ||
| 645 | \\ sw a7, 68(t0) | ||
| 646 | \\ sw s2, 72(t0) | ||
| 647 | \\ sw s3, 76(t0) | ||
| 648 | \\ sw s4, 80(t0) | ||
| 649 | \\ sw s5, 84(t0) | ||
| 650 | \\ sw s6, 88(t0) | ||
| 651 | \\ sw s7, 92(t0) | ||
| 652 | \\ sw s8, 96(t0) | ||
| 653 | \\ sw s9, 100(t0) | ||
| 654 | \\ sw s10, 104(t0) | ||
| 655 | \\ sw s11, 108(t0) | ||
| 656 | \\ sw t3, 112(t0) | ||
| 657 | \\ sw t4, 116(t0) | ||
| 658 | \\ sw t5, 120(t0) | ||
| 659 | \\ sw t6, 124(t0) | ||
| 660 | \\ jal ra, 1f | ||
| 661 | \\1: | ||
| 662 | \\ sw ra, 128(t0) | ||
| 663 | \\ lw ra, 4(t0) | ||
| 664 | : | ||
| 665 | : [gprs] "{t0}" (&ctx), | ||
| 666 | : .{ .memory = true }); | ||
| 667 | return ctx; | ||
| 668 | } | ||
| 669 | |||
| 670 | pub fn dwarfRegisterBytes(ctx: *Riscv, register_num: u16) DwarfRegisterError![]u8 { | ||
| 671 | switch (register_num) { | ||
| 672 | 0...31 => return @ptrCast(&ctx.r[register_num]), | ||
| 673 | 32 => return @ptrCast(&ctx.pc), | ||
| 674 | |||
| 675 | else => return error.InvalidRegister, | ||
| 676 | } | ||
| 677 | } | ||
| 678 | }; | ||
| 679 | |||
| 470 | const signal_ucontext_t = switch (native_os) { | 680 | const signal_ucontext_t = switch (native_os) { |
| 471 | .linux => std.os.linux.ucontext_t, | 681 | .linux => std.os.linux.ucontext_t, |
| 472 | .emscripten => std.os.emscripten.ucontext_t, | 682 | .emscripten => std.os.emscripten.ucontext_t, |
| 473 | .freebsd => std.os.freebsd.ucontext_t, | 683 | .freebsd => std.os.freebsd.ucontext_t, |
| 474 | .macos, .ios, .tvos, .watchos, .visionos => extern struct { | 684 | .driverkit, .macos, .ios, .tvos, .watchos, .visionos => extern struct { |
| 475 | onstack: c_int, | 685 | onstack: c_int, |
| 476 | sigmask: std.c.sigset_t, | 686 | sigmask: std.c.sigset_t, |
| 477 | stack: std.c.stack_t, | 687 | stack: std.c.stack_t, |