| author | |
| committer | |
| log | 771410cbf231e1a2b20e25f9d70c13d4ffeace9c |
| tree | d9c08d535aca1250739c91c5f82f2ce9e411570e |
| parent | e1fb662f600a7e134661d3c46d12e7ead83dd799 |
| signature |
3 files changed, 994 insertions(+), 994 deletions(-)
lib/std/debug.zig+1-1| ... | @@ -64,7 +64,7 @@ pub const SelfInfo = if (@hasDecl(root, "debug") and @hasDecl(root.debug, "SelfI | ... | @@ -64,7 +64,7 @@ pub const SelfInfo = if (@hasDecl(root, "debug") and @hasDecl(root.debug, "SelfI |
| 64 | else switch (std.Target.ObjectFormat.default(native_os, native_arch)) { | 64 | else switch (std.Target.ObjectFormat.default(native_os, native_arch)) { |
| 65 | .coff => if (native_os == .windows) @import("debug/SelfInfo/Windows.zig") else void, | 65 | .coff => if (native_os == .windows) @import("debug/SelfInfo/Windows.zig") else void, |
| 66 | .elf => @import("debug/SelfInfo/Elf.zig"), | 66 | .elf => @import("debug/SelfInfo/Elf.zig"), |
| 67 | .macho => @import("debug/SelfInfo/Darwin.zig"), | 67 | .macho => @import("debug/SelfInfo/MachO.zig"), |
| 68 | .goff, .plan9, .spirv, .wasm, .xcoff => void, | 68 | .goff, .plan9, .spirv, .wasm, .xcoff => void, |
| 69 | .c, .hex, .raw => unreachable, | 69 | .c, .hex, .raw => unreachable, |
| 70 | }; | 70 | }; |
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/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, .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(); | ||