commit bc4da9a90743c11c7c0b3e485f46d365d57d87b7 (tree)
parent 14019a95a4f3519bc03d23f79eb3141b51a9b2c2
Author: Alex Rønne Petersen <alex@alexrp.com>
Date: Thu, 2 Oct 2025 21:38:07 +0200
Merge pull request #25437 from alexrp/std-debug
`std.debug`: LoongArch and RISC-V unwind support + some minor cleanups
Diffstat:
8 files changed, 1272 insertions(+), 1042 deletions(-)
diff --git a/lib/std/debug.zig b/lib/std/debug.zig
@@ -61,28 +61,12 @@ pub const cpu_context = @import("debug/cpu_context.zig");
/// ```
pub const SelfInfo = if (@hasDecl(root, "debug") and @hasDecl(root.debug, "SelfInfo"))
root.debug.SelfInfo
-else switch (native_os) {
- .linux,
- .netbsd,
- .freebsd,
- .dragonfly,
- .openbsd,
- .solaris,
- .illumos,
- => @import("debug/SelfInfo/Elf.zig"),
-
- .macos,
- .ios,
- .watchos,
- .tvos,
- .visionos,
- => @import("debug/SelfInfo/Darwin.zig"),
-
- .uefi,
- .windows,
- => @import("debug/SelfInfo/Windows.zig"),
-
- else => void,
+else switch (std.Target.ObjectFormat.default(native_os, native_arch)) {
+ .coff => if (native_os == .windows) @import("debug/SelfInfo/Windows.zig") else void,
+ .elf => @import("debug/SelfInfo/Elf.zig"),
+ .macho => @import("debug/SelfInfo/MachO.zig"),
+ .goff, .plan9, .spirv, .wasm, .xcoff => void,
+ .c, .hex, .raw => unreachable,
};
pub const SelfInfoError = error{
diff --git a/lib/std/debug/Dwarf.zig b/lib/std/debug/Dwarf.zig
@@ -1429,30 +1429,36 @@ pub fn compactUnwindToDwarfRegNumber(unwind_reg_number: u3) !u16 {
/// Returns `null` for CPU architectures without an instruction pointer register.
pub fn ipRegNum(arch: std.Target.Cpu.Arch) ?u16 {
return switch (arch) {
+ .aarch64, .aarch64_be => 32,
+ .arm, .armeb, .thumb, .thumbeb => 15,
+ .loongarch32, .loongarch64 => 32,
+ .riscv32, .riscv32be, .riscv64, .riscv64be => 32,
.x86 => 8,
.x86_64 => 16,
- .arm, .armeb, .thumb, .thumbeb => 15,
- .aarch64, .aarch64_be => 32,
else => null,
};
}
pub fn fpRegNum(arch: std.Target.Cpu.Arch) u16 {
return switch (arch) {
+ .aarch64, .aarch64_be => 29,
+ .arm, .armeb, .thumb, .thumbeb => 11,
+ .loongarch32, .loongarch64 => 22,
+ .riscv32, .riscv32be, .riscv64, .riscv64be => 8,
.x86 => 5,
.x86_64 => 6,
- .arm, .armeb, .thumb, .thumbeb => 11,
- .aarch64, .aarch64_be => 29,
else => unreachable,
};
}
pub fn spRegNum(arch: std.Target.Cpu.Arch) u16 {
return switch (arch) {
+ .aarch64, .aarch64_be => 31,
+ .arm, .armeb, .thumb, .thumbeb => 13,
+ .loongarch32, .loongarch64 => 3,
+ .riscv32, .riscv32be, .riscv64, .riscv64be => 2,
.x86 => 4,
.x86_64 => 7,
- .arm, .armeb, .thumb, .thumbeb => 13,
- .aarch64, .aarch64_be => 31,
else => unreachable,
};
}
@@ -1470,10 +1476,6 @@ pub fn supportsUnwinding(target: *const std.Target) bool {
.spirv64,
=> false,
- // Enabling this causes relocation errors such as:
- // error: invalid relocation type R_RISCV_SUB32 at offset 0x20
- .riscv64, .riscv64be, .riscv32, .riscv32be => false,
-
// Conservative guess. Feel free to update this logic with any targets
// that are known to not support Dwarf unwinding.
else => true,
diff --git a/lib/std/debug/Dwarf/Unwind/VirtualMachine.zig b/lib/std/debug/Dwarf/Unwind/VirtualMachine.zig
@@ -256,7 +256,18 @@ fn evalInstructions(
.offset = cfa.offset_sf * cie.data_alignment_factor,
} },
.def_cfa_reg => |register| switch (vm.current_row.cfa) {
- .none, .expression => return error.InvalidOperation,
+ .none => {
+ // According to the DWARF specification, this is not valid, because this
+ // instruction can only be used to replace the register if the rule is already a
+ // `.reg_off`. However, this is emitted in practice by GNU toolchains for some
+ // targets, and so by convention is interpreted as equivalent to `.def_cfa` with
+ // an offset of 0.
+ vm.current_row.cfa = .{ .reg_off = .{
+ .register = register,
+ .offset = 0,
+ } };
+ },
+ .expression => return error.InvalidOperation,
.reg_off => |*ro| ro.register = register,
},
.def_cfa_offset => |offset| switch (vm.current_row.cfa) {
diff --git a/lib/std/debug/SelfInfo/Darwin.zig b/lib/std/debug/SelfInfo/Darwin.zig
@@ -1,993 +0,0 @@
-mutex: std.Thread.Mutex,
-/// Accessed through `Module.Adapter`.
-modules: std.ArrayHashMapUnmanaged(Module, void, Module.Context, false),
-ofiles: std.StringArrayHashMapUnmanaged(?OFile),
-
-pub const init: SelfInfo = .{
- .mutex = .{},
- .modules = .empty,
- .ofiles = .empty,
-};
-pub fn deinit(si: *SelfInfo, gpa: Allocator) void {
- for (si.modules.keys()) |*module| {
- unwind: {
- const u = &(module.unwind orelse break :unwind catch break :unwind);
- if (u.dwarf) |*dwarf| dwarf.deinit(gpa);
- }
- loaded: {
- const l = &(module.loaded_macho orelse break :loaded catch break :loaded);
- gpa.free(l.symbols);
- posix.munmap(l.mapped_memory);
- }
- }
- for (si.ofiles.values()) |*opt_ofile| {
- const ofile = &(opt_ofile.* orelse continue);
- ofile.dwarf.deinit(gpa);
- ofile.symbols_by_name.deinit(gpa);
- posix.munmap(ofile.mapped_memory);
- }
- si.modules.deinit(gpa);
- si.ofiles.deinit(gpa);
-}
-
-pub fn getSymbol(si: *SelfInfo, gpa: Allocator, address: usize) Error!std.debug.Symbol {
- const module = try si.findModule(gpa, address);
- defer si.mutex.unlock();
-
- const loaded_macho = try module.getLoadedMachO(gpa);
-
- const vaddr = address - loaded_macho.vaddr_offset;
- const symbol = MachoSymbol.find(loaded_macho.symbols, vaddr) orelse return .unknown;
-
- // offset of `address` from start of `symbol`
- const address_symbol_offset = vaddr - symbol.addr;
-
- // Take the symbol name from the N_FUN STAB entry, we're going to
- // use it if we fail to find the DWARF infos
- const stab_symbol = mem.sliceTo(loaded_macho.strings[symbol.strx..], 0);
-
- // If any information is missing, we can at least return this from now on.
- const sym_only_result: std.debug.Symbol = .{
- .name = stab_symbol,
- .compile_unit_name = null,
- .source_location = null,
- };
-
- if (symbol.ofile == MachoSymbol.unknown_ofile) {
- // We don't have STAB info, so can't track down the object file; all we can do is the symbol name.
- return sym_only_result;
- }
-
- const o_file: *OFile = of: {
- const path = mem.sliceTo(loaded_macho.strings[symbol.ofile..], 0);
- const gop = try si.ofiles.getOrPut(gpa, path);
- if (!gop.found_existing) {
- gop.value_ptr.* = loadOFile(gpa, path) catch null;
- }
- if (gop.value_ptr.*) |*o_file| {
- break :of o_file;
- } else {
- return sym_only_result;
- }
- };
-
- const symbol_index = o_file.symbols_by_name.getKeyAdapted(
- @as([]const u8, stab_symbol),
- @as(OFile.SymbolAdapter, .{ .strtab = o_file.strtab, .symtab = o_file.symtab }),
- ) orelse return sym_only_result;
- const symbol_ofile_vaddr = o_file.symtab[symbol_index].n_value;
-
- const compile_unit = o_file.dwarf.findCompileUnit(native_endian, symbol_ofile_vaddr) catch return sym_only_result;
-
- return .{
- .name = o_file.dwarf.getSymbolName(symbol_ofile_vaddr + address_symbol_offset) orelse stab_symbol,
- .compile_unit_name = compile_unit.die.getAttrString(
- &o_file.dwarf,
- native_endian,
- std.dwarf.AT.name,
- o_file.dwarf.section(.debug_str),
- compile_unit,
- ) catch |err| switch (err) {
- error.MissingDebugInfo, error.InvalidDebugInfo => null,
- },
- .source_location = o_file.dwarf.getLineNumberInfo(
- gpa,
- native_endian,
- compile_unit,
- symbol_ofile_vaddr + address_symbol_offset,
- ) catch null,
- };
-}
-pub fn getModuleName(si: *SelfInfo, gpa: Allocator, address: usize) Error![]const u8 {
- const module = try si.findModule(gpa, address);
- defer si.mutex.unlock();
- return module.name;
-}
-
-pub const can_unwind: bool = true;
-pub const UnwindContext = std.debug.Dwarf.SelfUnwinder;
-/// Unwind a frame using MachO compact unwind info (from `__unwind_info`).
-/// If the compact encoding can't encode a way to unwind a frame, it will
-/// defer unwinding to DWARF, in which case `__eh_frame` will be used if available.
-pub fn unwindFrame(si: *SelfInfo, gpa: Allocator, context: *UnwindContext) Error!usize {
- return unwindFrameInner(si, gpa, context) catch |err| switch (err) {
- error.InvalidDebugInfo,
- error.MissingDebugInfo,
- error.UnsupportedDebugInfo,
- error.ReadFailed,
- error.OutOfMemory,
- error.Unexpected,
- => |e| return e,
- error.UnsupportedRegister,
- error.UnsupportedAddrSize,
- error.UnimplementedUserOpcode,
- => return error.UnsupportedDebugInfo,
- error.Overflow,
- error.EndOfStream,
- error.StreamTooLong,
- error.InvalidOpcode,
- error.InvalidOperation,
- error.InvalidOperand,
- error.InvalidRegister,
- error.IncompatibleRegisterSize,
- => return error.InvalidDebugInfo,
- };
-}
-fn unwindFrameInner(si: *SelfInfo, gpa: Allocator, context: *UnwindContext) !usize {
- const module = try si.findModule(gpa, context.pc);
- defer si.mutex.unlock();
-
- const unwind: *Module.Unwind = try module.getUnwindInfo(gpa);
-
- const ip_reg_num = comptime Dwarf.ipRegNum(builtin.target.cpu.arch).?;
- const fp_reg_num = comptime Dwarf.fpRegNum(builtin.target.cpu.arch);
- const sp_reg_num = comptime Dwarf.spRegNum(builtin.target.cpu.arch);
-
- const unwind_info = unwind.unwind_info orelse return error.MissingDebugInfo;
- if (unwind_info.len < @sizeOf(macho.unwind_info_section_header)) return error.InvalidDebugInfo;
- const header: *align(1) const macho.unwind_info_section_header = @ptrCast(unwind_info);
-
- const index_byte_count = header.indexCount * @sizeOf(macho.unwind_info_section_header_index_entry);
- if (unwind_info.len < header.indexSectionOffset + index_byte_count) return error.InvalidDebugInfo;
- const indices: []align(1) const macho.unwind_info_section_header_index_entry = @ptrCast(unwind_info[header.indexSectionOffset..][0..index_byte_count]);
- if (indices.len == 0) return error.MissingDebugInfo;
-
- // offset of the PC into the `__TEXT` segment
- const pc_text_offset = context.pc - module.text_base;
-
- const start_offset: u32, const first_level_offset: u32 = index: {
- var left: usize = 0;
- var len: usize = indices.len;
- while (len > 1) {
- const mid = left + len / 2;
- if (pc_text_offset < indices[mid].functionOffset) {
- len /= 2;
- } else {
- left = mid;
- len -= len / 2;
- }
- }
- break :index .{ indices[left].secondLevelPagesSectionOffset, indices[left].functionOffset };
- };
- // An offset of 0 is a sentinel indicating a range does not have unwind info.
- if (start_offset == 0) return error.MissingDebugInfo;
-
- const common_encodings_byte_count = header.commonEncodingsArrayCount * @sizeOf(macho.compact_unwind_encoding_t);
- if (unwind_info.len < header.commonEncodingsArraySectionOffset + common_encodings_byte_count) return error.InvalidDebugInfo;
- const common_encodings: []align(1) const macho.compact_unwind_encoding_t = @ptrCast(
- unwind_info[header.commonEncodingsArraySectionOffset..][0..common_encodings_byte_count],
- );
-
- if (unwind_info.len < start_offset + @sizeOf(macho.UNWIND_SECOND_LEVEL)) return error.InvalidDebugInfo;
- const kind: *align(1) const macho.UNWIND_SECOND_LEVEL = @ptrCast(unwind_info[start_offset..]);
-
- const entry: struct {
- function_offset: usize,
- raw_encoding: u32,
- } = switch (kind.*) {
- .REGULAR => entry: {
- if (unwind_info.len < start_offset + @sizeOf(macho.unwind_info_regular_second_level_page_header)) return error.InvalidDebugInfo;
- const page_header: *align(1) const macho.unwind_info_regular_second_level_page_header = @ptrCast(unwind_info[start_offset..]);
-
- const entries_byte_count = page_header.entryCount * @sizeOf(macho.unwind_info_regular_second_level_entry);
- if (unwind_info.len < start_offset + entries_byte_count) return error.InvalidDebugInfo;
- const entries: []align(1) const macho.unwind_info_regular_second_level_entry = @ptrCast(
- unwind_info[start_offset + page_header.entryPageOffset ..][0..entries_byte_count],
- );
- if (entries.len == 0) return error.InvalidDebugInfo;
-
- var left: usize = 0;
- var len: usize = entries.len;
- while (len > 1) {
- const mid = left + len / 2;
- if (pc_text_offset < entries[mid].functionOffset) {
- len /= 2;
- } else {
- left = mid;
- len -= len / 2;
- }
- }
- break :entry .{
- .function_offset = entries[left].functionOffset,
- .raw_encoding = entries[left].encoding,
- };
- },
- .COMPRESSED => entry: {
- if (unwind_info.len < start_offset + @sizeOf(macho.unwind_info_compressed_second_level_page_header)) return error.InvalidDebugInfo;
- const page_header: *align(1) const macho.unwind_info_compressed_second_level_page_header = @ptrCast(unwind_info[start_offset..]);
-
- const entries_byte_count = page_header.entryCount * @sizeOf(macho.UnwindInfoCompressedEntry);
- if (unwind_info.len < start_offset + entries_byte_count) return error.InvalidDebugInfo;
- const entries: []align(1) const macho.UnwindInfoCompressedEntry = @ptrCast(
- unwind_info[start_offset + page_header.entryPageOffset ..][0..entries_byte_count],
- );
- if (entries.len == 0) return error.InvalidDebugInfo;
-
- var left: usize = 0;
- var len: usize = entries.len;
- while (len > 1) {
- const mid = left + len / 2;
- if (pc_text_offset < first_level_offset + entries[mid].funcOffset) {
- len /= 2;
- } else {
- left = mid;
- len -= len / 2;
- }
- }
- const entry = entries[left];
-
- const function_offset = first_level_offset + entry.funcOffset;
- if (entry.encodingIndex < common_encodings.len) {
- break :entry .{
- .function_offset = function_offset,
- .raw_encoding = common_encodings[entry.encodingIndex],
- };
- }
-
- const local_index = entry.encodingIndex - common_encodings.len;
- const local_encodings_byte_count = page_header.encodingsCount * @sizeOf(macho.compact_unwind_encoding_t);
- if (unwind_info.len < start_offset + page_header.encodingsPageOffset + local_encodings_byte_count) return error.InvalidDebugInfo;
- const local_encodings: []align(1) const macho.compact_unwind_encoding_t = @ptrCast(
- unwind_info[start_offset + page_header.encodingsPageOffset ..][0..local_encodings_byte_count],
- );
- if (local_index >= local_encodings.len) return error.InvalidDebugInfo;
- break :entry .{
- .function_offset = function_offset,
- .raw_encoding = local_encodings[local_index],
- };
- },
- else => return error.InvalidDebugInfo,
- };
-
- if (entry.raw_encoding == 0) return error.MissingDebugInfo;
-
- const encoding: macho.CompactUnwindEncoding = @bitCast(entry.raw_encoding);
- const new_ip = switch (builtin.cpu.arch) {
- .x86_64 => switch (encoding.mode.x86_64) {
- .OLD => return error.UnsupportedDebugInfo,
- .RBP_FRAME => ip: {
- const frame = encoding.value.x86_64.frame;
-
- const fp = (try dwarfRegNative(&context.cpu_state, fp_reg_num)).*;
- const new_sp = fp + 2 * @sizeOf(usize);
-
- const ip_ptr = fp + @sizeOf(usize);
- const new_ip = @as(*const usize, @ptrFromInt(ip_ptr)).*;
- const new_fp = @as(*const usize, @ptrFromInt(fp)).*;
-
- (try dwarfRegNative(&context.cpu_state, fp_reg_num)).* = new_fp;
- (try dwarfRegNative(&context.cpu_state, sp_reg_num)).* = new_sp;
- (try dwarfRegNative(&context.cpu_state, ip_reg_num)).* = new_ip;
-
- const regs: [5]u3 = .{
- frame.reg0,
- frame.reg1,
- frame.reg2,
- frame.reg3,
- frame.reg4,
- };
- for (regs, 0..) |reg, i| {
- if (reg == 0) continue;
- const addr = fp - frame.frame_offset * @sizeOf(usize) + i * @sizeOf(usize);
- const reg_number = try Dwarf.compactUnwindToDwarfRegNumber(reg);
- (try dwarfRegNative(&context.cpu_state, reg_number)).* = @as(*const usize, @ptrFromInt(addr)).*;
- }
-
- break :ip new_ip;
- },
- .STACK_IMMD,
- .STACK_IND,
- => ip: {
- const frameless = encoding.value.x86_64.frameless;
-
- const sp = (try dwarfRegNative(&context.cpu_state, sp_reg_num)).*;
- const stack_size: usize = stack_size: {
- if (encoding.mode.x86_64 == .STACK_IMMD) {
- break :stack_size @as(usize, frameless.stack.direct.stack_size) * @sizeOf(usize);
- }
- // In .STACK_IND, the stack size is inferred from the subq instruction at the beginning of the function.
- const sub_offset_addr =
- module.text_base +
- entry.function_offset +
- frameless.stack.indirect.sub_offset;
- // `sub_offset_addr` points to the offset of the literal within the instruction
- const sub_operand = @as(*align(1) const u32, @ptrFromInt(sub_offset_addr)).*;
- break :stack_size sub_operand + @sizeOf(usize) * @as(usize, frameless.stack.indirect.stack_adjust);
- };
-
- // Decode the Lehmer-coded sequence of registers.
- // For a description of the encoding see lib/libc/include/any-macos.13-any/mach-o/compact_unwind_encoding.h
-
- // Decode the variable-based permutation number into its digits. Each digit represents
- // an index into the list of register numbers that weren't yet used in the sequence at
- // the time the digit was added.
- const reg_count = frameless.stack_reg_count;
- const ip_ptr = ip_ptr: {
- var digits: [6]u3 = undefined;
- var accumulator: usize = frameless.stack_reg_permutation;
- var base: usize = 2;
- for (0..reg_count) |i| {
- const div = accumulator / base;
- digits[digits.len - 1 - i] = @intCast(accumulator - base * div);
- accumulator = div;
- base += 1;
- }
-
- var registers: [6]u3 = undefined;
- var used_indices: [6]bool = @splat(false);
- for (digits[digits.len - reg_count ..], 0..) |target_unused_index, i| {
- var unused_count: u8 = 0;
- const unused_index = for (used_indices, 0..) |used, index| {
- if (!used) {
- if (target_unused_index == unused_count) break index;
- unused_count += 1;
- }
- } else unreachable;
- registers[i] = @intCast(unused_index + 1);
- used_indices[unused_index] = true;
- }
-
- var reg_addr = sp + stack_size - @sizeOf(usize) * @as(usize, reg_count + 1);
- for (0..reg_count) |i| {
- const reg_number = try Dwarf.compactUnwindToDwarfRegNumber(registers[i]);
- (try dwarfRegNative(&context.cpu_state, reg_number)).* = @as(*const usize, @ptrFromInt(reg_addr)).*;
- reg_addr += @sizeOf(usize);
- }
-
- break :ip_ptr reg_addr;
- };
-
- const new_ip = @as(*const usize, @ptrFromInt(ip_ptr)).*;
- const new_sp = ip_ptr + @sizeOf(usize);
-
- (try dwarfRegNative(&context.cpu_state, sp_reg_num)).* = new_sp;
- (try dwarfRegNative(&context.cpu_state, ip_reg_num)).* = new_ip;
-
- break :ip new_ip;
- },
- .DWARF => {
- const dwarf = &(unwind.dwarf orelse return error.MissingDebugInfo);
- const rules = try context.computeRules(gpa, dwarf, unwind.vmaddr_slide, encoding.value.x86_64.dwarf);
- return context.next(gpa, &rules);
- },
- },
- .aarch64, .aarch64_be => switch (encoding.mode.arm64) {
- .OLD => return error.UnsupportedDebugInfo,
- .FRAMELESS => ip: {
- const sp = (try dwarfRegNative(&context.cpu_state, sp_reg_num)).*;
- const new_sp = sp + encoding.value.arm64.frameless.stack_size * 16;
- const new_ip = (try dwarfRegNative(&context.cpu_state, 30)).*;
- (try dwarfRegNative(&context.cpu_state, sp_reg_num)).* = new_sp;
- break :ip new_ip;
- },
- .DWARF => {
- const dwarf = &(unwind.dwarf orelse return error.MissingDebugInfo);
- const rules = try context.computeRules(gpa, dwarf, unwind.vmaddr_slide, encoding.value.arm64.dwarf);
- return context.next(gpa, &rules);
- },
- .FRAME => ip: {
- const frame = encoding.value.arm64.frame;
-
- const fp = (try dwarfRegNative(&context.cpu_state, fp_reg_num)).*;
- const ip_ptr = fp + @sizeOf(usize);
-
- var reg_addr = fp - @sizeOf(usize);
- inline for (@typeInfo(@TypeOf(frame.x_reg_pairs)).@"struct".fields, 0..) |field, i| {
- if (@field(frame.x_reg_pairs, field.name) != 0) {
- (try dwarfRegNative(&context.cpu_state, 19 + i)).* = @as(*const usize, @ptrFromInt(reg_addr)).*;
- reg_addr += @sizeOf(usize);
- (try dwarfRegNative(&context.cpu_state, 20 + i)).* = @as(*const usize, @ptrFromInt(reg_addr)).*;
- reg_addr += @sizeOf(usize);
- }
- }
-
- inline for (@typeInfo(@TypeOf(frame.d_reg_pairs)).@"struct".fields, 0..) |field, i| {
- if (@field(frame.d_reg_pairs, field.name) != 0) {
- // Only the lower half of the 128-bit V registers are restored during unwinding
- {
- const dest: *align(1) usize = @ptrCast(try context.cpu_state.dwarfRegisterBytes(64 + 8 + i));
- dest.* = @as(*const usize, @ptrFromInt(reg_addr)).*;
- }
- reg_addr += @sizeOf(usize);
- {
- const dest: *align(1) usize = @ptrCast(try context.cpu_state.dwarfRegisterBytes(64 + 9 + i));
- dest.* = @as(*const usize, @ptrFromInt(reg_addr)).*;
- }
- reg_addr += @sizeOf(usize);
- }
- }
-
- const new_ip = @as(*const usize, @ptrFromInt(ip_ptr)).*;
- const new_fp = @as(*const usize, @ptrFromInt(fp)).*;
-
- (try dwarfRegNative(&context.cpu_state, fp_reg_num)).* = new_fp;
- (try dwarfRegNative(&context.cpu_state, ip_reg_num)).* = new_ip;
-
- break :ip new_ip;
- },
- },
- else => comptime unreachable, // unimplemented
- };
-
- const ret_addr = std.debug.stripInstructionPtrAuthCode(new_ip);
-
- // Like `Dwarf.SelfUnwinder.next`, adjust our next lookup pc in case the `call` was this
- // function's last instruction making `ret_addr` one byte past its end.
- context.pc = ret_addr -| 1;
-
- return ret_addr;
-}
-
-/// Acquires the mutex on success.
-fn findModule(si: *SelfInfo, gpa: Allocator, address: usize) Error!*Module {
- var info: std.c.dl_info = undefined;
- if (std.c.dladdr(@ptrFromInt(address), &info) == 0) {
- return error.MissingDebugInfo;
- }
- si.mutex.lock();
- errdefer si.mutex.unlock();
- const gop = try si.modules.getOrPutAdapted(gpa, @intFromPtr(info.fbase), Module.Adapter{});
- errdefer comptime unreachable;
- if (!gop.found_existing) {
- gop.key_ptr.* = .{
- .text_base = @intFromPtr(info.fbase),
- .name = std.mem.span(info.fname),
- .unwind = null,
- .loaded_macho = null,
- };
- }
- return gop.key_ptr;
-}
-
-const Module = struct {
- text_base: usize,
- name: []const u8,
- unwind: ?(Error!Unwind),
- loaded_macho: ?(Error!LoadedMachO),
-
- const Adapter = struct {
- pub fn hash(_: Adapter, text_base: usize) u32 {
- return @truncate(std.hash.int(text_base));
- }
- pub fn eql(_: Adapter, a_text_base: usize, b_module: Module, b_index: usize) bool {
- _ = b_index;
- return a_text_base == b_module.text_base;
- }
- };
- const Context = struct {
- pub fn hash(_: Context, module: Module) u32 {
- return @truncate(std.hash.int(module.text_base));
- }
- pub fn eql(_: Context, a_module: Module, b_module: Module, b_index: usize) bool {
- _ = b_index;
- return a_module.text_base == b_module.text_base;
- }
- };
-
- const Unwind = struct {
- /// The slide applied to the `__unwind_info` and `__eh_frame` sections.
- /// So, `unwind_info.ptr` is this many bytes higher than the section's vmaddr.
- vmaddr_slide: u64,
- /// Backed by the in-memory section mapped by the loader.
- unwind_info: ?[]const u8,
- /// Backed by the in-memory `__eh_frame` section mapped by the loader.
- dwarf: ?Dwarf.Unwind,
- };
-
- const LoadedMachO = struct {
- mapped_memory: []align(std.heap.page_size_min) const u8,
- symbols: []const MachoSymbol,
- strings: []const u8,
- /// This is not necessarily the same as the vmaddr_slide that dyld would report. This is
- /// because the segments in the file on disk might differ from the ones in memory. Normally
- /// we wouldn't necessarily expect that to work, but /usr/lib/dyld is incredibly annoying:
- /// it exists on disk (necessarily, because the kernel needs to load it!), but is also in
- /// the dyld cache (dyld actually restart itself from cache after loading it), and the two
- /// versions have (very) different segment base addresses. It's sort of like a large slide
- /// has been applied to all addresses in memory. For an optimal experience, we consider the
- /// on-disk vmaddr instead of the in-memory one.
- vaddr_offset: usize,
- };
-
- fn getUnwindInfo(module: *Module, gpa: Allocator) Error!*Unwind {
- if (module.unwind == null) module.unwind = loadUnwindInfo(module, gpa);
- return if (module.unwind.?) |*unwind| unwind else |err| err;
- }
- fn loadUnwindInfo(module: *const Module, gpa: Allocator) Error!Unwind {
- const header: *std.macho.mach_header = @ptrFromInt(module.text_base);
-
- var it: macho.LoadCommandIterator = .{
- .ncmds = header.ncmds,
- .buffer = @as([*]u8, @ptrCast(header))[@sizeOf(macho.mach_header_64)..][0..header.sizeofcmds],
- };
- const sections, const text_vmaddr = while (it.next()) |load_cmd| {
- if (load_cmd.cmd() != .SEGMENT_64) continue;
- const segment_cmd = load_cmd.cast(macho.segment_command_64).?;
- if (!mem.eql(u8, segment_cmd.segName(), "__TEXT")) continue;
- break .{ load_cmd.getSections(), segment_cmd.vmaddr };
- } else unreachable;
-
- const vmaddr_slide = module.text_base - text_vmaddr;
-
- var opt_unwind_info: ?[]const u8 = null;
- var opt_eh_frame: ?[]const u8 = null;
- for (sections) |sect| {
- if (mem.eql(u8, sect.sectName(), "__unwind_info")) {
- const sect_ptr: [*]u8 = @ptrFromInt(@as(usize, @intCast(vmaddr_slide + sect.addr)));
- opt_unwind_info = sect_ptr[0..@intCast(sect.size)];
- } else if (mem.eql(u8, sect.sectName(), "__eh_frame")) {
- const sect_ptr: [*]u8 = @ptrFromInt(@as(usize, @intCast(vmaddr_slide + sect.addr)));
- opt_eh_frame = sect_ptr[0..@intCast(sect.size)];
- }
- }
- const eh_frame = opt_eh_frame orelse return .{
- .vmaddr_slide = vmaddr_slide,
- .unwind_info = opt_unwind_info,
- .dwarf = null,
- };
- var dwarf: Dwarf.Unwind = .initSection(.eh_frame, @intFromPtr(eh_frame.ptr) - vmaddr_slide, eh_frame);
- errdefer dwarf.deinit(gpa);
- // We don't need lookups, so this call is just for scanning CIEs.
- dwarf.prepare(gpa, @sizeOf(usize), native_endian, false, true) catch |err| switch (err) {
- error.ReadFailed => unreachable, // it's all fixed buffers
- error.InvalidDebugInfo,
- error.MissingDebugInfo,
- error.OutOfMemory,
- => |e| return e,
- error.EndOfStream,
- error.Overflow,
- error.StreamTooLong,
- error.InvalidOperand,
- error.InvalidOpcode,
- error.InvalidOperation,
- => return error.InvalidDebugInfo,
- error.UnsupportedAddrSize,
- error.UnsupportedDwarfVersion,
- error.UnimplementedUserOpcode,
- => return error.UnsupportedDebugInfo,
- };
-
- return .{
- .vmaddr_slide = vmaddr_slide,
- .unwind_info = opt_unwind_info,
- .dwarf = dwarf,
- };
- }
-
- fn getLoadedMachO(module: *Module, gpa: Allocator) Error!*LoadedMachO {
- if (module.loaded_macho == null) module.loaded_macho = loadMachO(module, gpa) catch |err| switch (err) {
- error.InvalidDebugInfo, error.MissingDebugInfo, error.OutOfMemory, error.Unexpected => |e| e,
- else => error.ReadFailed,
- };
- return if (module.loaded_macho.?) |*lm| lm else |err| err;
- }
- fn loadMachO(module: *const Module, gpa: Allocator) Error!LoadedMachO {
- const all_mapped_memory = try mapDebugInfoFile(module.name);
- errdefer posix.munmap(all_mapped_memory);
-
- // In most cases, the file we just mapped is a Mach-O binary. However, it could be a "universal
- // binary": a simple file format which contains Mach-O binaries for multiple targets. For
- // instance, `/usr/lib/dyld` is currently distributed as a universal binary containing images
- // for both ARM64 macOS and x86_64 macOS.
- if (all_mapped_memory.len < 4) return error.InvalidDebugInfo;
- const magic = @as(*const u32, @ptrCast(all_mapped_memory.ptr)).*;
- // The contents of a Mach-O file, which may or may not be the whole of `all_mapped_memory`.
- const mapped_macho = switch (magic) {
- macho.MH_MAGIC_64 => all_mapped_memory,
-
- macho.FAT_CIGAM => mapped_macho: {
- // This is the universal binary format (aka a "fat binary"). Annoyingly, the whole thing
- // is big-endian, so we'll be swapping some bytes.
- if (all_mapped_memory.len < @sizeOf(macho.fat_header)) return error.InvalidDebugInfo;
- const hdr: *const macho.fat_header = @ptrCast(all_mapped_memory.ptr);
- const archs_ptr: [*]const macho.fat_arch = @ptrCast(all_mapped_memory.ptr + @sizeOf(macho.fat_header));
- const archs: []const macho.fat_arch = archs_ptr[0..@byteSwap(hdr.nfat_arch)];
- const native_cpu_type = switch (builtin.cpu.arch) {
- .x86_64 => macho.CPU_TYPE_X86_64,
- .aarch64 => macho.CPU_TYPE_ARM64,
- else => comptime unreachable,
- };
- for (archs) |*arch| {
- if (@byteSwap(arch.cputype) != native_cpu_type) continue;
- const offset = @byteSwap(arch.offset);
- const size = @byteSwap(arch.size);
- break :mapped_macho all_mapped_memory[offset..][0..size];
- }
- // Our native architecture was not present in the fat binary.
- return error.MissingDebugInfo;
- },
-
- // Even on modern 64-bit targets, this format doesn't seem to be too extensively used. It
- // will be fairly easy to add support here if necessary; it's very similar to above.
- macho.FAT_CIGAM_64 => return error.UnsupportedDebugInfo,
-
- else => return error.InvalidDebugInfo,
- };
-
- const hdr: *const macho.mach_header_64 = @ptrCast(@alignCast(mapped_macho.ptr));
- if (hdr.magic != macho.MH_MAGIC_64)
- return error.InvalidDebugInfo;
-
- const symtab: macho.symtab_command, const text_vmaddr: u64 = lc_iter: {
- var it: macho.LoadCommandIterator = .{
- .ncmds = hdr.ncmds,
- .buffer = mapped_macho[@sizeOf(macho.mach_header_64)..][0..hdr.sizeofcmds],
- };
- var symtab: ?macho.symtab_command = null;
- var text_vmaddr: ?u64 = null;
- while (it.next()) |cmd| switch (cmd.cmd()) {
- .SYMTAB => symtab = cmd.cast(macho.symtab_command) orelse return error.InvalidDebugInfo,
- .SEGMENT_64 => if (cmd.cast(macho.segment_command_64)) |seg_cmd| {
- if (!mem.eql(u8, seg_cmd.segName(), "__TEXT")) continue;
- text_vmaddr = seg_cmd.vmaddr;
- },
- else => {},
- };
- break :lc_iter .{
- symtab orelse return error.MissingDebugInfo,
- text_vmaddr orelse return error.MissingDebugInfo,
- };
- };
-
- const syms_ptr: [*]align(1) const macho.nlist_64 = @ptrCast(mapped_macho[symtab.symoff..]);
- const syms = syms_ptr[0..symtab.nsyms];
- const strings = mapped_macho[symtab.stroff..][0 .. symtab.strsize - 1];
-
- var symbols: std.ArrayList(MachoSymbol) = try .initCapacity(gpa, syms.len);
- defer symbols.deinit(gpa);
-
- // This map is temporary; it is used only to detect duplicates here. This is
- // necessary because we prefer to use STAB ("symbolic debugging table") symbols,
- // but they might not be present, so we track normal symbols too.
- // Indices match 1-1 with those of `symbols`.
- var symbol_names: std.StringArrayHashMapUnmanaged(void) = .empty;
- defer symbol_names.deinit(gpa);
- try symbol_names.ensureUnusedCapacity(gpa, syms.len);
-
- var ofile: u32 = undefined;
- var last_sym: MachoSymbol = undefined;
- var state: enum {
- init,
- oso_open,
- oso_close,
- bnsym,
- fun_strx,
- fun_size,
- ensym,
- } = .init;
-
- for (syms) |*sym| {
- if (sym.n_type.bits.is_stab == 0) {
- if (sym.n_strx == 0) continue;
- switch (sym.n_type.bits.type) {
- .undf, .pbud, .indr, .abs, _ => continue,
- .sect => {
- const name = std.mem.sliceTo(strings[sym.n_strx..], 0);
- const gop = symbol_names.getOrPutAssumeCapacity(name);
- if (!gop.found_existing) {
- assert(gop.index == symbols.items.len);
- symbols.appendAssumeCapacity(.{
- .strx = sym.n_strx,
- .addr = sym.n_value,
- .ofile = MachoSymbol.unknown_ofile,
- });
- }
- },
- }
- continue;
- }
-
- // TODO handle globals N_GSYM, and statics N_STSYM
- switch (sym.n_type.stab) {
- .oso => switch (state) {
- .init, .oso_close => {
- state = .oso_open;
- ofile = sym.n_strx;
- },
- else => return error.InvalidDebugInfo,
- },
- .bnsym => switch (state) {
- .oso_open, .ensym => {
- state = .bnsym;
- last_sym = .{
- .strx = 0,
- .addr = sym.n_value,
- .ofile = ofile,
- };
- },
- else => return error.InvalidDebugInfo,
- },
- .fun => switch (state) {
- .bnsym => {
- state = .fun_strx;
- last_sym.strx = sym.n_strx;
- },
- .fun_strx => {
- state = .fun_size;
- },
- else => return error.InvalidDebugInfo,
- },
- .ensym => switch (state) {
- .fun_size => {
- state = .ensym;
- if (last_sym.strx != 0) {
- const name = std.mem.sliceTo(strings[last_sym.strx..], 0);
- const gop = symbol_names.getOrPutAssumeCapacity(name);
- if (!gop.found_existing) {
- assert(gop.index == symbols.items.len);
- symbols.appendAssumeCapacity(last_sym);
- } else {
- symbols.items[gop.index] = last_sym;
- }
- }
- },
- else => return error.InvalidDebugInfo,
- },
- .so => switch (state) {
- .init, .oso_close => {},
- .oso_open, .ensym => {
- state = .oso_close;
- },
- else => return error.InvalidDebugInfo,
- },
- else => {},
- }
- }
-
- switch (state) {
- .init => {
- // Missing STAB symtab entries is still okay, unless there were also no normal symbols.
- if (symbols.items.len == 0) return error.MissingDebugInfo;
- },
- .oso_close => {},
- else => return error.InvalidDebugInfo, // corrupted STAB entries in symtab
- }
-
- const symbols_slice = try symbols.toOwnedSlice(gpa);
- errdefer gpa.free(symbols_slice);
-
- // Even though lld emits symbols in ascending order, this debug code
- // should work for programs linked in any valid way.
- // This sort is so that we can binary search later.
- mem.sort(MachoSymbol, symbols_slice, {}, MachoSymbol.addressLessThan);
-
- return .{
- .mapped_memory = all_mapped_memory,
- .symbols = symbols_slice,
- .strings = strings,
- .vaddr_offset = module.text_base - text_vmaddr,
- };
- }
-};
-
-const OFile = struct {
- mapped_memory: []align(std.heap.page_size_min) const u8,
- dwarf: Dwarf,
- strtab: []const u8,
- symtab: []align(1) const macho.nlist_64,
- /// All named symbols in `symtab`. Stored `u32` key is the index into `symtab`. Accessed
- /// through `SymbolAdapter`, so that the symbol name is used as the logical key.
- symbols_by_name: std.ArrayHashMapUnmanaged(u32, void, void, true),
-
- const SymbolAdapter = struct {
- strtab: []const u8,
- symtab: []align(1) const macho.nlist_64,
- pub fn hash(ctx: SymbolAdapter, sym_name: []const u8) u32 {
- _ = ctx;
- return @truncate(std.hash.Wyhash.hash(0, sym_name));
- }
- pub fn eql(ctx: SymbolAdapter, a_sym_name: []const u8, b_sym_index: u32, b_index: usize) bool {
- _ = b_index;
- const b_sym = ctx.symtab[b_sym_index];
- const b_sym_name = std.mem.sliceTo(ctx.strtab[b_sym.n_strx..], 0);
- return mem.eql(u8, a_sym_name, b_sym_name);
- }
- };
-};
-
-const MachoSymbol = struct {
- strx: u32,
- addr: u64,
- /// Value may be `unknown_ofile`.
- ofile: u32,
- const unknown_ofile = std.math.maxInt(u32);
- fn addressLessThan(context: void, lhs: MachoSymbol, rhs: MachoSymbol) bool {
- _ = context;
- return lhs.addr < rhs.addr;
- }
- /// Assumes that `symbols` is sorted in order of ascending `addr`.
- fn find(symbols: []const MachoSymbol, address: usize) ?*const MachoSymbol {
- if (symbols.len == 0) return null; // no potential match
- if (address < symbols[0].addr) return null; // address is before the lowest-address symbol
- var left: usize = 0;
- var len: usize = symbols.len;
- while (len > 1) {
- const mid = left + len / 2;
- if (address < symbols[mid].addr) {
- len /= 2;
- } else {
- left = mid;
- len -= len / 2;
- }
- }
- return &symbols[left];
- }
-
- test find {
- const symbols: []const MachoSymbol = &.{
- .{ .addr = 100, .strx = undefined, .ofile = undefined },
- .{ .addr = 200, .strx = undefined, .ofile = undefined },
- .{ .addr = 300, .strx = undefined, .ofile = undefined },
- };
-
- try testing.expectEqual(null, find(symbols, 0));
- try testing.expectEqual(null, find(symbols, 99));
- try testing.expectEqual(&symbols[0], find(symbols, 100).?);
- try testing.expectEqual(&symbols[0], find(symbols, 150).?);
- try testing.expectEqual(&symbols[0], find(symbols, 199).?);
-
- try testing.expectEqual(&symbols[1], find(symbols, 200).?);
- try testing.expectEqual(&symbols[1], find(symbols, 250).?);
- try testing.expectEqual(&symbols[1], find(symbols, 299).?);
-
- try testing.expectEqual(&symbols[2], find(symbols, 300).?);
- try testing.expectEqual(&symbols[2], find(symbols, 301).?);
- try testing.expectEqual(&symbols[2], find(symbols, 5000).?);
- }
-};
-test {
- _ = MachoSymbol;
-}
-
-/// Uses `mmap` to map the file at `path` into memory.
-fn mapDebugInfoFile(path: []const u8) ![]align(std.heap.page_size_min) const u8 {
- const file = std.fs.cwd().openFile(path, .{}) catch |err| switch (err) {
- error.FileNotFound => return error.MissingDebugInfo,
- else => return error.ReadFailed,
- };
- defer file.close();
-
- const file_end_pos = file.getEndPos() catch |err| switch (err) {
- error.Unexpected => |e| return e,
- else => return error.ReadFailed,
- };
- const file_len = std.math.cast(usize, file_end_pos) orelse return error.InvalidDebugInfo;
-
- return posix.mmap(
- null,
- file_len,
- posix.PROT.READ,
- .{ .TYPE = .SHARED },
- file.handle,
- 0,
- ) catch |err| switch (err) {
- error.Unexpected => |e| return e,
- else => return error.ReadFailed,
- };
-}
-
-fn loadOFile(gpa: Allocator, o_file_path: []const u8) !OFile {
- const mapped_mem = try mapDebugInfoFile(o_file_path);
- errdefer posix.munmap(mapped_mem);
-
- if (mapped_mem.len < @sizeOf(macho.mach_header_64)) return error.InvalidDebugInfo;
- const hdr: *const macho.mach_header_64 = @ptrCast(@alignCast(mapped_mem.ptr));
- if (hdr.magic != std.macho.MH_MAGIC_64) return error.InvalidDebugInfo;
-
- const seg_cmd: macho.LoadCommandIterator.LoadCommand, const symtab_cmd: macho.symtab_command = cmds: {
- var seg_cmd: ?macho.LoadCommandIterator.LoadCommand = null;
- var symtab_cmd: ?macho.symtab_command = null;
- var it: macho.LoadCommandIterator = .{
- .ncmds = hdr.ncmds,
- .buffer = mapped_mem[@sizeOf(macho.mach_header_64)..][0..hdr.sizeofcmds],
- };
- while (it.next()) |cmd| switch (cmd.cmd()) {
- .SEGMENT_64 => seg_cmd = cmd,
- .SYMTAB => symtab_cmd = cmd.cast(macho.symtab_command) orelse return error.InvalidDebugInfo,
- else => {},
- };
- break :cmds .{
- seg_cmd orelse return error.MissingDebugInfo,
- symtab_cmd orelse return error.MissingDebugInfo,
- };
- };
-
- if (mapped_mem.len < symtab_cmd.stroff + symtab_cmd.strsize) return error.InvalidDebugInfo;
- if (mapped_mem[symtab_cmd.stroff + symtab_cmd.strsize - 1] != 0) return error.InvalidDebugInfo;
- const strtab = mapped_mem[symtab_cmd.stroff..][0 .. symtab_cmd.strsize - 1];
-
- const n_sym_bytes = symtab_cmd.nsyms * @sizeOf(macho.nlist_64);
- if (mapped_mem.len < symtab_cmd.symoff + n_sym_bytes) return error.InvalidDebugInfo;
- const symtab: []align(1) const macho.nlist_64 = @ptrCast(mapped_mem[symtab_cmd.symoff..][0..n_sym_bytes]);
-
- // TODO handle tentative (common) symbols
- var symbols_by_name: std.ArrayHashMapUnmanaged(u32, void, void, true) = .empty;
- defer symbols_by_name.deinit(gpa);
- try symbols_by_name.ensureUnusedCapacity(gpa, @intCast(symtab.len));
- for (symtab, 0..) |sym, sym_index| {
- if (sym.n_strx == 0) continue;
- switch (sym.n_type.bits.type) {
- .undf => continue, // includes tentative symbols
- .abs => continue,
- else => {},
- }
- const sym_name = mem.sliceTo(strtab[sym.n_strx..], 0);
- const gop = symbols_by_name.getOrPutAssumeCapacityAdapted(
- @as([]const u8, sym_name),
- @as(OFile.SymbolAdapter, .{ .strtab = strtab, .symtab = symtab }),
- );
- if (gop.found_existing) return error.InvalidDebugInfo;
- gop.key_ptr.* = @intCast(sym_index);
- }
-
- var sections: Dwarf.SectionArray = @splat(null);
- for (seg_cmd.getSections()) |sect| {
- if (!std.mem.eql(u8, "__DWARF", sect.segName())) continue;
-
- const section_index: usize = inline for (@typeInfo(Dwarf.Section.Id).@"enum".fields, 0..) |section, i| {
- if (mem.eql(u8, "__" ++ section.name, sect.sectName())) break i;
- } else continue;
-
- if (mapped_mem.len < sect.offset + sect.size) return error.InvalidDebugInfo;
- const section_bytes = mapped_mem[sect.offset..][0..sect.size];
- sections[section_index] = .{
- .data = section_bytes,
- .owned = false,
- };
- }
-
- const missing_debug_info =
- sections[@intFromEnum(Dwarf.Section.Id.debug_info)] == null or
- sections[@intFromEnum(Dwarf.Section.Id.debug_abbrev)] == null or
- sections[@intFromEnum(Dwarf.Section.Id.debug_str)] == null or
- sections[@intFromEnum(Dwarf.Section.Id.debug_line)] == null;
- if (missing_debug_info) return error.MissingDebugInfo;
-
- var dwarf: Dwarf = .{ .sections = sections };
- errdefer dwarf.deinit(gpa);
- try dwarf.open(gpa, native_endian);
-
- return .{
- .mapped_memory = mapped_mem,
- .dwarf = dwarf,
- .strtab = strtab,
- .symtab = symtab,
- .symbols_by_name = symbols_by_name.move(),
- };
-}
-
-const std = @import("std");
-const Allocator = std.mem.Allocator;
-const Dwarf = std.debug.Dwarf;
-const Error = std.debug.SelfInfoError;
-const assert = std.debug.assert;
-const posix = std.posix;
-const macho = std.macho;
-const mem = std.mem;
-const testing = std.testing;
-const dwarfRegNative = std.debug.Dwarf.SelfUnwinder.regNative;
-
-const builtin = @import("builtin");
-const native_endian = builtin.target.cpu.arch.endian();
-
-const SelfInfo = @This();
diff --git a/lib/std/debug/SelfInfo/Elf.zig b/lib/std/debug/SelfInfo/Elf.zig
@@ -84,12 +84,35 @@ pub const can_unwind: bool = s: {
// Notably, we are yet to support unwinding on ARM. There, unwinding is not done through
// `.eh_frame`, but instead with the `.ARM.exidx` section, which has a different format.
const archs: []const std.Target.Cpu.Arch = switch (builtin.target.os.tag) {
- .linux => &.{ .x86, .x86_64, .aarch64, .aarch64_be },
- .netbsd => &.{ .x86, .x86_64, .aarch64, .aarch64_be },
- .freebsd => &.{ .x86_64, .aarch64, .aarch64_be },
- .openbsd => &.{.x86_64},
- .solaris => &.{ .x86, .x86_64 },
- .illumos => &.{ .x86, .x86_64 },
+ .linux => &.{
+ .aarch64,
+ .aarch64_be,
+ .loongarch64,
+ .riscv32,
+ .riscv64,
+ .x86,
+ .x86_64,
+ },
+ .netbsd => &.{
+ .aarch64,
+ .aarch64_be,
+ .x86,
+ .x86_64,
+ },
+ .freebsd => &.{
+ .x86_64,
+ .aarch64,
+ },
+ .openbsd => &.{
+ .x86_64,
+ },
+ .solaris => &.{
+ .x86_64,
+ },
+ .illumos => &.{
+ .x86,
+ .x86_64,
+ },
else => unreachable,
};
for (archs) |a| {
diff --git a/lib/std/debug/SelfInfo/MachO.zig b/lib/std/debug/SelfInfo/MachO.zig
@@ -0,0 +1,993 @@
+mutex: std.Thread.Mutex,
+/// Accessed through `Module.Adapter`.
+modules: std.ArrayHashMapUnmanaged(Module, void, Module.Context, false),
+ofiles: std.StringArrayHashMapUnmanaged(?OFile),
+
+pub const init: SelfInfo = .{
+ .mutex = .{},
+ .modules = .empty,
+ .ofiles = .empty,
+};
+pub fn deinit(si: *SelfInfo, gpa: Allocator) void {
+ for (si.modules.keys()) |*module| {
+ unwind: {
+ const u = &(module.unwind orelse break :unwind catch break :unwind);
+ if (u.dwarf) |*dwarf| dwarf.deinit(gpa);
+ }
+ loaded: {
+ const l = &(module.loaded_macho orelse break :loaded catch break :loaded);
+ gpa.free(l.symbols);
+ posix.munmap(l.mapped_memory);
+ }
+ }
+ for (si.ofiles.values()) |*opt_ofile| {
+ const ofile = &(opt_ofile.* orelse continue);
+ ofile.dwarf.deinit(gpa);
+ ofile.symbols_by_name.deinit(gpa);
+ posix.munmap(ofile.mapped_memory);
+ }
+ si.modules.deinit(gpa);
+ si.ofiles.deinit(gpa);
+}
+
+pub fn getSymbol(si: *SelfInfo, gpa: Allocator, address: usize) Error!std.debug.Symbol {
+ const module = try si.findModule(gpa, address);
+ defer si.mutex.unlock();
+
+ const loaded_macho = try module.getLoadedMachO(gpa);
+
+ const vaddr = address - loaded_macho.vaddr_offset;
+ const symbol = MachoSymbol.find(loaded_macho.symbols, vaddr) orelse return .unknown;
+
+ // offset of `address` from start of `symbol`
+ const address_symbol_offset = vaddr - symbol.addr;
+
+ // Take the symbol name from the N_FUN STAB entry, we're going to
+ // use it if we fail to find the DWARF infos
+ const stab_symbol = mem.sliceTo(loaded_macho.strings[symbol.strx..], 0);
+
+ // If any information is missing, we can at least return this from now on.
+ const sym_only_result: std.debug.Symbol = .{
+ .name = stab_symbol,
+ .compile_unit_name = null,
+ .source_location = null,
+ };
+
+ if (symbol.ofile == MachoSymbol.unknown_ofile) {
+ // We don't have STAB info, so can't track down the object file; all we can do is the symbol name.
+ return sym_only_result;
+ }
+
+ const o_file: *OFile = of: {
+ const path = mem.sliceTo(loaded_macho.strings[symbol.ofile..], 0);
+ const gop = try si.ofiles.getOrPut(gpa, path);
+ if (!gop.found_existing) {
+ gop.value_ptr.* = loadOFile(gpa, path) catch null;
+ }
+ if (gop.value_ptr.*) |*o_file| {
+ break :of o_file;
+ } else {
+ return sym_only_result;
+ }
+ };
+
+ const symbol_index = o_file.symbols_by_name.getKeyAdapted(
+ @as([]const u8, stab_symbol),
+ @as(OFile.SymbolAdapter, .{ .strtab = o_file.strtab, .symtab = o_file.symtab }),
+ ) orelse return sym_only_result;
+ const symbol_ofile_vaddr = o_file.symtab[symbol_index].n_value;
+
+ const compile_unit = o_file.dwarf.findCompileUnit(native_endian, symbol_ofile_vaddr) catch return sym_only_result;
+
+ return .{
+ .name = o_file.dwarf.getSymbolName(symbol_ofile_vaddr + address_symbol_offset) orelse stab_symbol,
+ .compile_unit_name = compile_unit.die.getAttrString(
+ &o_file.dwarf,
+ native_endian,
+ std.dwarf.AT.name,
+ o_file.dwarf.section(.debug_str),
+ compile_unit,
+ ) catch |err| switch (err) {
+ error.MissingDebugInfo, error.InvalidDebugInfo => null,
+ },
+ .source_location = o_file.dwarf.getLineNumberInfo(
+ gpa,
+ native_endian,
+ compile_unit,
+ symbol_ofile_vaddr + address_symbol_offset,
+ ) catch null,
+ };
+}
+pub fn getModuleName(si: *SelfInfo, gpa: Allocator, address: usize) Error![]const u8 {
+ const module = try si.findModule(gpa, address);
+ defer si.mutex.unlock();
+ return module.name;
+}
+
+pub const can_unwind: bool = true;
+pub const UnwindContext = std.debug.Dwarf.SelfUnwinder;
+/// Unwind a frame using MachO compact unwind info (from `__unwind_info`).
+/// If the compact encoding can't encode a way to unwind a frame, it will
+/// defer unwinding to DWARF, in which case `__eh_frame` will be used if available.
+pub fn unwindFrame(si: *SelfInfo, gpa: Allocator, context: *UnwindContext) Error!usize {
+ return unwindFrameInner(si, gpa, context) catch |err| switch (err) {
+ error.InvalidDebugInfo,
+ error.MissingDebugInfo,
+ error.UnsupportedDebugInfo,
+ error.ReadFailed,
+ error.OutOfMemory,
+ error.Unexpected,
+ => |e| return e,
+ error.UnsupportedRegister,
+ error.UnsupportedAddrSize,
+ error.UnimplementedUserOpcode,
+ => return error.UnsupportedDebugInfo,
+ error.Overflow,
+ error.EndOfStream,
+ error.StreamTooLong,
+ error.InvalidOpcode,
+ error.InvalidOperation,
+ error.InvalidOperand,
+ error.InvalidRegister,
+ error.IncompatibleRegisterSize,
+ => return error.InvalidDebugInfo,
+ };
+}
+fn unwindFrameInner(si: *SelfInfo, gpa: Allocator, context: *UnwindContext) !usize {
+ const module = try si.findModule(gpa, context.pc);
+ defer si.mutex.unlock();
+
+ const unwind: *Module.Unwind = try module.getUnwindInfo(gpa);
+
+ const ip_reg_num = comptime Dwarf.ipRegNum(builtin.target.cpu.arch).?;
+ const fp_reg_num = comptime Dwarf.fpRegNum(builtin.target.cpu.arch);
+ const sp_reg_num = comptime Dwarf.spRegNum(builtin.target.cpu.arch);
+
+ const unwind_info = unwind.unwind_info orelse return error.MissingDebugInfo;
+ if (unwind_info.len < @sizeOf(macho.unwind_info_section_header)) return error.InvalidDebugInfo;
+ const header: *align(1) const macho.unwind_info_section_header = @ptrCast(unwind_info);
+
+ const index_byte_count = header.indexCount * @sizeOf(macho.unwind_info_section_header_index_entry);
+ if (unwind_info.len < header.indexSectionOffset + index_byte_count) return error.InvalidDebugInfo;
+ const indices: []align(1) const macho.unwind_info_section_header_index_entry = @ptrCast(unwind_info[header.indexSectionOffset..][0..index_byte_count]);
+ if (indices.len == 0) return error.MissingDebugInfo;
+
+ // offset of the PC into the `__TEXT` segment
+ const pc_text_offset = context.pc - module.text_base;
+
+ const start_offset: u32, const first_level_offset: u32 = index: {
+ var left: usize = 0;
+ var len: usize = indices.len;
+ while (len > 1) {
+ const mid = left + len / 2;
+ if (pc_text_offset < indices[mid].functionOffset) {
+ len /= 2;
+ } else {
+ left = mid;
+ len -= len / 2;
+ }
+ }
+ break :index .{ indices[left].secondLevelPagesSectionOffset, indices[left].functionOffset };
+ };
+ // An offset of 0 is a sentinel indicating a range does not have unwind info.
+ if (start_offset == 0) return error.MissingDebugInfo;
+
+ const common_encodings_byte_count = header.commonEncodingsArrayCount * @sizeOf(macho.compact_unwind_encoding_t);
+ if (unwind_info.len < header.commonEncodingsArraySectionOffset + common_encodings_byte_count) return error.InvalidDebugInfo;
+ const common_encodings: []align(1) const macho.compact_unwind_encoding_t = @ptrCast(
+ unwind_info[header.commonEncodingsArraySectionOffset..][0..common_encodings_byte_count],
+ );
+
+ if (unwind_info.len < start_offset + @sizeOf(macho.UNWIND_SECOND_LEVEL)) return error.InvalidDebugInfo;
+ const kind: *align(1) const macho.UNWIND_SECOND_LEVEL = @ptrCast(unwind_info[start_offset..]);
+
+ const entry: struct {
+ function_offset: usize,
+ raw_encoding: u32,
+ } = switch (kind.*) {
+ .REGULAR => entry: {
+ if (unwind_info.len < start_offset + @sizeOf(macho.unwind_info_regular_second_level_page_header)) return error.InvalidDebugInfo;
+ const page_header: *align(1) const macho.unwind_info_regular_second_level_page_header = @ptrCast(unwind_info[start_offset..]);
+
+ const entries_byte_count = page_header.entryCount * @sizeOf(macho.unwind_info_regular_second_level_entry);
+ if (unwind_info.len < start_offset + entries_byte_count) return error.InvalidDebugInfo;
+ const entries: []align(1) const macho.unwind_info_regular_second_level_entry = @ptrCast(
+ unwind_info[start_offset + page_header.entryPageOffset ..][0..entries_byte_count],
+ );
+ if (entries.len == 0) return error.InvalidDebugInfo;
+
+ var left: usize = 0;
+ var len: usize = entries.len;
+ while (len > 1) {
+ const mid = left + len / 2;
+ if (pc_text_offset < entries[mid].functionOffset) {
+ len /= 2;
+ } else {
+ left = mid;
+ len -= len / 2;
+ }
+ }
+ break :entry .{
+ .function_offset = entries[left].functionOffset,
+ .raw_encoding = entries[left].encoding,
+ };
+ },
+ .COMPRESSED => entry: {
+ if (unwind_info.len < start_offset + @sizeOf(macho.unwind_info_compressed_second_level_page_header)) return error.InvalidDebugInfo;
+ const page_header: *align(1) const macho.unwind_info_compressed_second_level_page_header = @ptrCast(unwind_info[start_offset..]);
+
+ const entries_byte_count = page_header.entryCount * @sizeOf(macho.UnwindInfoCompressedEntry);
+ if (unwind_info.len < start_offset + entries_byte_count) return error.InvalidDebugInfo;
+ const entries: []align(1) const macho.UnwindInfoCompressedEntry = @ptrCast(
+ unwind_info[start_offset + page_header.entryPageOffset ..][0..entries_byte_count],
+ );
+ if (entries.len == 0) return error.InvalidDebugInfo;
+
+ var left: usize = 0;
+ var len: usize = entries.len;
+ while (len > 1) {
+ const mid = left + len / 2;
+ if (pc_text_offset < first_level_offset + entries[mid].funcOffset) {
+ len /= 2;
+ } else {
+ left = mid;
+ len -= len / 2;
+ }
+ }
+ const entry = entries[left];
+
+ const function_offset = first_level_offset + entry.funcOffset;
+ if (entry.encodingIndex < common_encodings.len) {
+ break :entry .{
+ .function_offset = function_offset,
+ .raw_encoding = common_encodings[entry.encodingIndex],
+ };
+ }
+
+ const local_index = entry.encodingIndex - common_encodings.len;
+ const local_encodings_byte_count = page_header.encodingsCount * @sizeOf(macho.compact_unwind_encoding_t);
+ if (unwind_info.len < start_offset + page_header.encodingsPageOffset + local_encodings_byte_count) return error.InvalidDebugInfo;
+ const local_encodings: []align(1) const macho.compact_unwind_encoding_t = @ptrCast(
+ unwind_info[start_offset + page_header.encodingsPageOffset ..][0..local_encodings_byte_count],
+ );
+ if (local_index >= local_encodings.len) return error.InvalidDebugInfo;
+ break :entry .{
+ .function_offset = function_offset,
+ .raw_encoding = local_encodings[local_index],
+ };
+ },
+ else => return error.InvalidDebugInfo,
+ };
+
+ if (entry.raw_encoding == 0) return error.MissingDebugInfo;
+
+ const encoding: macho.CompactUnwindEncoding = @bitCast(entry.raw_encoding);
+ const new_ip = switch (builtin.cpu.arch) {
+ .x86_64 => switch (encoding.mode.x86_64) {
+ .OLD => return error.UnsupportedDebugInfo,
+ .RBP_FRAME => ip: {
+ const frame = encoding.value.x86_64.frame;
+
+ const fp = (try dwarfRegNative(&context.cpu_state, fp_reg_num)).*;
+ const new_sp = fp + 2 * @sizeOf(usize);
+
+ const ip_ptr = fp + @sizeOf(usize);
+ const new_ip = @as(*const usize, @ptrFromInt(ip_ptr)).*;
+ const new_fp = @as(*const usize, @ptrFromInt(fp)).*;
+
+ (try dwarfRegNative(&context.cpu_state, fp_reg_num)).* = new_fp;
+ (try dwarfRegNative(&context.cpu_state, sp_reg_num)).* = new_sp;
+ (try dwarfRegNative(&context.cpu_state, ip_reg_num)).* = new_ip;
+
+ const regs: [5]u3 = .{
+ frame.reg0,
+ frame.reg1,
+ frame.reg2,
+ frame.reg3,
+ frame.reg4,
+ };
+ for (regs, 0..) |reg, i| {
+ if (reg == 0) continue;
+ const addr = fp - frame.frame_offset * @sizeOf(usize) + i * @sizeOf(usize);
+ const reg_number = try Dwarf.compactUnwindToDwarfRegNumber(reg);
+ (try dwarfRegNative(&context.cpu_state, reg_number)).* = @as(*const usize, @ptrFromInt(addr)).*;
+ }
+
+ break :ip new_ip;
+ },
+ .STACK_IMMD,
+ .STACK_IND,
+ => ip: {
+ const frameless = encoding.value.x86_64.frameless;
+
+ const sp = (try dwarfRegNative(&context.cpu_state, sp_reg_num)).*;
+ const stack_size: usize = stack_size: {
+ if (encoding.mode.x86_64 == .STACK_IMMD) {
+ break :stack_size @as(usize, frameless.stack.direct.stack_size) * @sizeOf(usize);
+ }
+ // In .STACK_IND, the stack size is inferred from the subq instruction at the beginning of the function.
+ const sub_offset_addr =
+ module.text_base +
+ entry.function_offset +
+ frameless.stack.indirect.sub_offset;
+ // `sub_offset_addr` points to the offset of the literal within the instruction
+ const sub_operand = @as(*align(1) const u32, @ptrFromInt(sub_offset_addr)).*;
+ break :stack_size sub_operand + @sizeOf(usize) * @as(usize, frameless.stack.indirect.stack_adjust);
+ };
+
+ // Decode the Lehmer-coded sequence of registers.
+ // For a description of the encoding see lib/libc/include/any-macos.13-any/mach-o/compact_unwind_encoding.h
+
+ // Decode the variable-based permutation number into its digits. Each digit represents
+ // an index into the list of register numbers that weren't yet used in the sequence at
+ // the time the digit was added.
+ const reg_count = frameless.stack_reg_count;
+ const ip_ptr = ip_ptr: {
+ var digits: [6]u3 = undefined;
+ var accumulator: usize = frameless.stack_reg_permutation;
+ var base: usize = 2;
+ for (0..reg_count) |i| {
+ const div = accumulator / base;
+ digits[digits.len - 1 - i] = @intCast(accumulator - base * div);
+ accumulator = div;
+ base += 1;
+ }
+
+ var registers: [6]u3 = undefined;
+ var used_indices: [6]bool = @splat(false);
+ for (digits[digits.len - reg_count ..], 0..) |target_unused_index, i| {
+ var unused_count: u8 = 0;
+ const unused_index = for (used_indices, 0..) |used, index| {
+ if (!used) {
+ if (target_unused_index == unused_count) break index;
+ unused_count += 1;
+ }
+ } else unreachable;
+ registers[i] = @intCast(unused_index + 1);
+ used_indices[unused_index] = true;
+ }
+
+ var reg_addr = sp + stack_size - @sizeOf(usize) * @as(usize, reg_count + 1);
+ for (0..reg_count) |i| {
+ const reg_number = try Dwarf.compactUnwindToDwarfRegNumber(registers[i]);
+ (try dwarfRegNative(&context.cpu_state, reg_number)).* = @as(*const usize, @ptrFromInt(reg_addr)).*;
+ reg_addr += @sizeOf(usize);
+ }
+
+ break :ip_ptr reg_addr;
+ };
+
+ const new_ip = @as(*const usize, @ptrFromInt(ip_ptr)).*;
+ const new_sp = ip_ptr + @sizeOf(usize);
+
+ (try dwarfRegNative(&context.cpu_state, sp_reg_num)).* = new_sp;
+ (try dwarfRegNative(&context.cpu_state, ip_reg_num)).* = new_ip;
+
+ break :ip new_ip;
+ },
+ .DWARF => {
+ const dwarf = &(unwind.dwarf orelse return error.MissingDebugInfo);
+ const rules = try context.computeRules(gpa, dwarf, unwind.vmaddr_slide, encoding.value.x86_64.dwarf);
+ return context.next(gpa, &rules);
+ },
+ },
+ .aarch64 => switch (encoding.mode.arm64) {
+ .OLD => return error.UnsupportedDebugInfo,
+ .FRAMELESS => ip: {
+ const sp = (try dwarfRegNative(&context.cpu_state, sp_reg_num)).*;
+ const new_sp = sp + encoding.value.arm64.frameless.stack_size * 16;
+ const new_ip = (try dwarfRegNative(&context.cpu_state, 30)).*;
+ (try dwarfRegNative(&context.cpu_state, sp_reg_num)).* = new_sp;
+ break :ip new_ip;
+ },
+ .DWARF => {
+ const dwarf = &(unwind.dwarf orelse return error.MissingDebugInfo);
+ const rules = try context.computeRules(gpa, dwarf, unwind.vmaddr_slide, encoding.value.arm64.dwarf);
+ return context.next(gpa, &rules);
+ },
+ .FRAME => ip: {
+ const frame = encoding.value.arm64.frame;
+
+ const fp = (try dwarfRegNative(&context.cpu_state, fp_reg_num)).*;
+ const ip_ptr = fp + @sizeOf(usize);
+
+ var reg_addr = fp - @sizeOf(usize);
+ inline for (@typeInfo(@TypeOf(frame.x_reg_pairs)).@"struct".fields, 0..) |field, i| {
+ if (@field(frame.x_reg_pairs, field.name) != 0) {
+ (try dwarfRegNative(&context.cpu_state, 19 + i)).* = @as(*const usize, @ptrFromInt(reg_addr)).*;
+ reg_addr += @sizeOf(usize);
+ (try dwarfRegNative(&context.cpu_state, 20 + i)).* = @as(*const usize, @ptrFromInt(reg_addr)).*;
+ reg_addr += @sizeOf(usize);
+ }
+ }
+
+ inline for (@typeInfo(@TypeOf(frame.d_reg_pairs)).@"struct".fields, 0..) |field, i| {
+ if (@field(frame.d_reg_pairs, field.name) != 0) {
+ // Only the lower half of the 128-bit V registers are restored during unwinding
+ {
+ const dest: *align(1) usize = @ptrCast(try context.cpu_state.dwarfRegisterBytes(64 + 8 + i));
+ dest.* = @as(*const usize, @ptrFromInt(reg_addr)).*;
+ }
+ reg_addr += @sizeOf(usize);
+ {
+ const dest: *align(1) usize = @ptrCast(try context.cpu_state.dwarfRegisterBytes(64 + 9 + i));
+ dest.* = @as(*const usize, @ptrFromInt(reg_addr)).*;
+ }
+ reg_addr += @sizeOf(usize);
+ }
+ }
+
+ const new_ip = @as(*const usize, @ptrFromInt(ip_ptr)).*;
+ const new_fp = @as(*const usize, @ptrFromInt(fp)).*;
+
+ (try dwarfRegNative(&context.cpu_state, fp_reg_num)).* = new_fp;
+ (try dwarfRegNative(&context.cpu_state, ip_reg_num)).* = new_ip;
+
+ break :ip new_ip;
+ },
+ },
+ else => comptime unreachable, // unimplemented
+ };
+
+ const ret_addr = std.debug.stripInstructionPtrAuthCode(new_ip);
+
+ // Like `Dwarf.SelfUnwinder.next`, adjust our next lookup pc in case the `call` was this
+ // function's last instruction making `ret_addr` one byte past its end.
+ context.pc = ret_addr -| 1;
+
+ return ret_addr;
+}
+
+/// Acquires the mutex on success.
+fn findModule(si: *SelfInfo, gpa: Allocator, address: usize) Error!*Module {
+ var info: std.c.dl_info = undefined;
+ if (std.c.dladdr(@ptrFromInt(address), &info) == 0) {
+ return error.MissingDebugInfo;
+ }
+ si.mutex.lock();
+ errdefer si.mutex.unlock();
+ const gop = try si.modules.getOrPutAdapted(gpa, @intFromPtr(info.fbase), Module.Adapter{});
+ errdefer comptime unreachable;
+ if (!gop.found_existing) {
+ gop.key_ptr.* = .{
+ .text_base = @intFromPtr(info.fbase),
+ .name = std.mem.span(info.fname),
+ .unwind = null,
+ .loaded_macho = null,
+ };
+ }
+ return gop.key_ptr;
+}
+
+const Module = struct {
+ text_base: usize,
+ name: []const u8,
+ unwind: ?(Error!Unwind),
+ loaded_macho: ?(Error!LoadedMachO),
+
+ const Adapter = struct {
+ pub fn hash(_: Adapter, text_base: usize) u32 {
+ return @truncate(std.hash.int(text_base));
+ }
+ pub fn eql(_: Adapter, a_text_base: usize, b_module: Module, b_index: usize) bool {
+ _ = b_index;
+ return a_text_base == b_module.text_base;
+ }
+ };
+ const Context = struct {
+ pub fn hash(_: Context, module: Module) u32 {
+ return @truncate(std.hash.int(module.text_base));
+ }
+ pub fn eql(_: Context, a_module: Module, b_module: Module, b_index: usize) bool {
+ _ = b_index;
+ return a_module.text_base == b_module.text_base;
+ }
+ };
+
+ const Unwind = struct {
+ /// The slide applied to the `__unwind_info` and `__eh_frame` sections.
+ /// So, `unwind_info.ptr` is this many bytes higher than the section's vmaddr.
+ vmaddr_slide: u64,
+ /// Backed by the in-memory section mapped by the loader.
+ unwind_info: ?[]const u8,
+ /// Backed by the in-memory `__eh_frame` section mapped by the loader.
+ dwarf: ?Dwarf.Unwind,
+ };
+
+ const LoadedMachO = struct {
+ mapped_memory: []align(std.heap.page_size_min) const u8,
+ symbols: []const MachoSymbol,
+ strings: []const u8,
+ /// This is not necessarily the same as the vmaddr_slide that dyld would report. This is
+ /// because the segments in the file on disk might differ from the ones in memory. Normally
+ /// we wouldn't necessarily expect that to work, but /usr/lib/dyld is incredibly annoying:
+ /// it exists on disk (necessarily, because the kernel needs to load it!), but is also in
+ /// the dyld cache (dyld actually restart itself from cache after loading it), and the two
+ /// versions have (very) different segment base addresses. It's sort of like a large slide
+ /// has been applied to all addresses in memory. For an optimal experience, we consider the
+ /// on-disk vmaddr instead of the in-memory one.
+ vaddr_offset: usize,
+ };
+
+ fn getUnwindInfo(module: *Module, gpa: Allocator) Error!*Unwind {
+ if (module.unwind == null) module.unwind = loadUnwindInfo(module, gpa);
+ return if (module.unwind.?) |*unwind| unwind else |err| err;
+ }
+ fn loadUnwindInfo(module: *const Module, gpa: Allocator) Error!Unwind {
+ const header: *std.macho.mach_header = @ptrFromInt(module.text_base);
+
+ var it: macho.LoadCommandIterator = .{
+ .ncmds = header.ncmds,
+ .buffer = @as([*]u8, @ptrCast(header))[@sizeOf(macho.mach_header_64)..][0..header.sizeofcmds],
+ };
+ const sections, const text_vmaddr = while (it.next()) |load_cmd| {
+ if (load_cmd.cmd() != .SEGMENT_64) continue;
+ const segment_cmd = load_cmd.cast(macho.segment_command_64).?;
+ if (!mem.eql(u8, segment_cmd.segName(), "__TEXT")) continue;
+ break .{ load_cmd.getSections(), segment_cmd.vmaddr };
+ } else unreachable;
+
+ const vmaddr_slide = module.text_base - text_vmaddr;
+
+ var opt_unwind_info: ?[]const u8 = null;
+ var opt_eh_frame: ?[]const u8 = null;
+ for (sections) |sect| {
+ if (mem.eql(u8, sect.sectName(), "__unwind_info")) {
+ const sect_ptr: [*]u8 = @ptrFromInt(@as(usize, @intCast(vmaddr_slide + sect.addr)));
+ opt_unwind_info = sect_ptr[0..@intCast(sect.size)];
+ } else if (mem.eql(u8, sect.sectName(), "__eh_frame")) {
+ const sect_ptr: [*]u8 = @ptrFromInt(@as(usize, @intCast(vmaddr_slide + sect.addr)));
+ opt_eh_frame = sect_ptr[0..@intCast(sect.size)];
+ }
+ }
+ const eh_frame = opt_eh_frame orelse return .{
+ .vmaddr_slide = vmaddr_slide,
+ .unwind_info = opt_unwind_info,
+ .dwarf = null,
+ };
+ var dwarf: Dwarf.Unwind = .initSection(.eh_frame, @intFromPtr(eh_frame.ptr) - vmaddr_slide, eh_frame);
+ errdefer dwarf.deinit(gpa);
+ // We don't need lookups, so this call is just for scanning CIEs.
+ dwarf.prepare(gpa, @sizeOf(usize), native_endian, false, true) catch |err| switch (err) {
+ error.ReadFailed => unreachable, // it's all fixed buffers
+ error.InvalidDebugInfo,
+ error.MissingDebugInfo,
+ error.OutOfMemory,
+ => |e| return e,
+ error.EndOfStream,
+ error.Overflow,
+ error.StreamTooLong,
+ error.InvalidOperand,
+ error.InvalidOpcode,
+ error.InvalidOperation,
+ => return error.InvalidDebugInfo,
+ error.UnsupportedAddrSize,
+ error.UnsupportedDwarfVersion,
+ error.UnimplementedUserOpcode,
+ => return error.UnsupportedDebugInfo,
+ };
+
+ return .{
+ .vmaddr_slide = vmaddr_slide,
+ .unwind_info = opt_unwind_info,
+ .dwarf = dwarf,
+ };
+ }
+
+ fn getLoadedMachO(module: *Module, gpa: Allocator) Error!*LoadedMachO {
+ if (module.loaded_macho == null) module.loaded_macho = loadMachO(module, gpa) catch |err| switch (err) {
+ error.InvalidDebugInfo, error.MissingDebugInfo, error.OutOfMemory, error.Unexpected => |e| e,
+ else => error.ReadFailed,
+ };
+ return if (module.loaded_macho.?) |*lm| lm else |err| err;
+ }
+ fn loadMachO(module: *const Module, gpa: Allocator) Error!LoadedMachO {
+ const all_mapped_memory = try mapDebugInfoFile(module.name);
+ errdefer posix.munmap(all_mapped_memory);
+
+ // In most cases, the file we just mapped is a Mach-O binary. However, it could be a "universal
+ // binary": a simple file format which contains Mach-O binaries for multiple targets. For
+ // instance, `/usr/lib/dyld` is currently distributed as a universal binary containing images
+ // for both ARM64 macOS and x86_64 macOS.
+ if (all_mapped_memory.len < 4) return error.InvalidDebugInfo;
+ const magic = @as(*const u32, @ptrCast(all_mapped_memory.ptr)).*;
+ // The contents of a Mach-O file, which may or may not be the whole of `all_mapped_memory`.
+ const mapped_macho = switch (magic) {
+ macho.MH_MAGIC_64 => all_mapped_memory,
+
+ macho.FAT_CIGAM => mapped_macho: {
+ // This is the universal binary format (aka a "fat binary"). Annoyingly, the whole thing
+ // is big-endian, so we'll be swapping some bytes.
+ if (all_mapped_memory.len < @sizeOf(macho.fat_header)) return error.InvalidDebugInfo;
+ const hdr: *const macho.fat_header = @ptrCast(all_mapped_memory.ptr);
+ const archs_ptr: [*]const macho.fat_arch = @ptrCast(all_mapped_memory.ptr + @sizeOf(macho.fat_header));
+ const archs: []const macho.fat_arch = archs_ptr[0..@byteSwap(hdr.nfat_arch)];
+ const native_cpu_type = switch (builtin.cpu.arch) {
+ .x86_64 => macho.CPU_TYPE_X86_64,
+ .aarch64 => macho.CPU_TYPE_ARM64,
+ else => comptime unreachable,
+ };
+ for (archs) |*arch| {
+ if (@byteSwap(arch.cputype) != native_cpu_type) continue;
+ const offset = @byteSwap(arch.offset);
+ const size = @byteSwap(arch.size);
+ break :mapped_macho all_mapped_memory[offset..][0..size];
+ }
+ // Our native architecture was not present in the fat binary.
+ return error.MissingDebugInfo;
+ },
+
+ // Even on modern 64-bit targets, this format doesn't seem to be too extensively used. It
+ // will be fairly easy to add support here if necessary; it's very similar to above.
+ macho.FAT_CIGAM_64 => return error.UnsupportedDebugInfo,
+
+ else => return error.InvalidDebugInfo,
+ };
+
+ const hdr: *const macho.mach_header_64 = @ptrCast(@alignCast(mapped_macho.ptr));
+ if (hdr.magic != macho.MH_MAGIC_64)
+ return error.InvalidDebugInfo;
+
+ const symtab: macho.symtab_command, const text_vmaddr: u64 = lc_iter: {
+ var it: macho.LoadCommandIterator = .{
+ .ncmds = hdr.ncmds,
+ .buffer = mapped_macho[@sizeOf(macho.mach_header_64)..][0..hdr.sizeofcmds],
+ };
+ var symtab: ?macho.symtab_command = null;
+ var text_vmaddr: ?u64 = null;
+ while (it.next()) |cmd| switch (cmd.cmd()) {
+ .SYMTAB => symtab = cmd.cast(macho.symtab_command) orelse return error.InvalidDebugInfo,
+ .SEGMENT_64 => if (cmd.cast(macho.segment_command_64)) |seg_cmd| {
+ if (!mem.eql(u8, seg_cmd.segName(), "__TEXT")) continue;
+ text_vmaddr = seg_cmd.vmaddr;
+ },
+ else => {},
+ };
+ break :lc_iter .{
+ symtab orelse return error.MissingDebugInfo,
+ text_vmaddr orelse return error.MissingDebugInfo,
+ };
+ };
+
+ const syms_ptr: [*]align(1) const macho.nlist_64 = @ptrCast(mapped_macho[symtab.symoff..]);
+ const syms = syms_ptr[0..symtab.nsyms];
+ const strings = mapped_macho[symtab.stroff..][0 .. symtab.strsize - 1];
+
+ var symbols: std.ArrayList(MachoSymbol) = try .initCapacity(gpa, syms.len);
+ defer symbols.deinit(gpa);
+
+ // This map is temporary; it is used only to detect duplicates here. This is
+ // necessary because we prefer to use STAB ("symbolic debugging table") symbols,
+ // but they might not be present, so we track normal symbols too.
+ // Indices match 1-1 with those of `symbols`.
+ var symbol_names: std.StringArrayHashMapUnmanaged(void) = .empty;
+ defer symbol_names.deinit(gpa);
+ try symbol_names.ensureUnusedCapacity(gpa, syms.len);
+
+ var ofile: u32 = undefined;
+ var last_sym: MachoSymbol = undefined;
+ var state: enum {
+ init,
+ oso_open,
+ oso_close,
+ bnsym,
+ fun_strx,
+ fun_size,
+ ensym,
+ } = .init;
+
+ for (syms) |*sym| {
+ if (sym.n_type.bits.is_stab == 0) {
+ if (sym.n_strx == 0) continue;
+ switch (sym.n_type.bits.type) {
+ .undf, .pbud, .indr, .abs, _ => continue,
+ .sect => {
+ const name = std.mem.sliceTo(strings[sym.n_strx..], 0);
+ const gop = symbol_names.getOrPutAssumeCapacity(name);
+ if (!gop.found_existing) {
+ assert(gop.index == symbols.items.len);
+ symbols.appendAssumeCapacity(.{
+ .strx = sym.n_strx,
+ .addr = sym.n_value,
+ .ofile = MachoSymbol.unknown_ofile,
+ });
+ }
+ },
+ }
+ continue;
+ }
+
+ // TODO handle globals N_GSYM, and statics N_STSYM
+ switch (sym.n_type.stab) {
+ .oso => switch (state) {
+ .init, .oso_close => {
+ state = .oso_open;
+ ofile = sym.n_strx;
+ },
+ else => return error.InvalidDebugInfo,
+ },
+ .bnsym => switch (state) {
+ .oso_open, .ensym => {
+ state = .bnsym;
+ last_sym = .{
+ .strx = 0,
+ .addr = sym.n_value,
+ .ofile = ofile,
+ };
+ },
+ else => return error.InvalidDebugInfo,
+ },
+ .fun => switch (state) {
+ .bnsym => {
+ state = .fun_strx;
+ last_sym.strx = sym.n_strx;
+ },
+ .fun_strx => {
+ state = .fun_size;
+ },
+ else => return error.InvalidDebugInfo,
+ },
+ .ensym => switch (state) {
+ .fun_size => {
+ state = .ensym;
+ if (last_sym.strx != 0) {
+ const name = std.mem.sliceTo(strings[last_sym.strx..], 0);
+ const gop = symbol_names.getOrPutAssumeCapacity(name);
+ if (!gop.found_existing) {
+ assert(gop.index == symbols.items.len);
+ symbols.appendAssumeCapacity(last_sym);
+ } else {
+ symbols.items[gop.index] = last_sym;
+ }
+ }
+ },
+ else => return error.InvalidDebugInfo,
+ },
+ .so => switch (state) {
+ .init, .oso_close => {},
+ .oso_open, .ensym => {
+ state = .oso_close;
+ },
+ else => return error.InvalidDebugInfo,
+ },
+ else => {},
+ }
+ }
+
+ switch (state) {
+ .init => {
+ // Missing STAB symtab entries is still okay, unless there were also no normal symbols.
+ if (symbols.items.len == 0) return error.MissingDebugInfo;
+ },
+ .oso_close => {},
+ else => return error.InvalidDebugInfo, // corrupted STAB entries in symtab
+ }
+
+ const symbols_slice = try symbols.toOwnedSlice(gpa);
+ errdefer gpa.free(symbols_slice);
+
+ // Even though lld emits symbols in ascending order, this debug code
+ // should work for programs linked in any valid way.
+ // This sort is so that we can binary search later.
+ mem.sort(MachoSymbol, symbols_slice, {}, MachoSymbol.addressLessThan);
+
+ return .{
+ .mapped_memory = all_mapped_memory,
+ .symbols = symbols_slice,
+ .strings = strings,
+ .vaddr_offset = module.text_base - text_vmaddr,
+ };
+ }
+};
+
+const OFile = struct {
+ mapped_memory: []align(std.heap.page_size_min) const u8,
+ dwarf: Dwarf,
+ strtab: []const u8,
+ symtab: []align(1) const macho.nlist_64,
+ /// All named symbols in `symtab`. Stored `u32` key is the index into `symtab`. Accessed
+ /// through `SymbolAdapter`, so that the symbol name is used as the logical key.
+ symbols_by_name: std.ArrayHashMapUnmanaged(u32, void, void, true),
+
+ const SymbolAdapter = struct {
+ strtab: []const u8,
+ symtab: []align(1) const macho.nlist_64,
+ pub fn hash(ctx: SymbolAdapter, sym_name: []const u8) u32 {
+ _ = ctx;
+ return @truncate(std.hash.Wyhash.hash(0, sym_name));
+ }
+ pub fn eql(ctx: SymbolAdapter, a_sym_name: []const u8, b_sym_index: u32, b_index: usize) bool {
+ _ = b_index;
+ const b_sym = ctx.symtab[b_sym_index];
+ const b_sym_name = std.mem.sliceTo(ctx.strtab[b_sym.n_strx..], 0);
+ return mem.eql(u8, a_sym_name, b_sym_name);
+ }
+ };
+};
+
+const MachoSymbol = struct {
+ strx: u32,
+ addr: u64,
+ /// Value may be `unknown_ofile`.
+ ofile: u32,
+ const unknown_ofile = std.math.maxInt(u32);
+ fn addressLessThan(context: void, lhs: MachoSymbol, rhs: MachoSymbol) bool {
+ _ = context;
+ return lhs.addr < rhs.addr;
+ }
+ /// Assumes that `symbols` is sorted in order of ascending `addr`.
+ fn find(symbols: []const MachoSymbol, address: usize) ?*const MachoSymbol {
+ if (symbols.len == 0) return null; // no potential match
+ if (address < symbols[0].addr) return null; // address is before the lowest-address symbol
+ var left: usize = 0;
+ var len: usize = symbols.len;
+ while (len > 1) {
+ const mid = left + len / 2;
+ if (address < symbols[mid].addr) {
+ len /= 2;
+ } else {
+ left = mid;
+ len -= len / 2;
+ }
+ }
+ return &symbols[left];
+ }
+
+ test find {
+ const symbols: []const MachoSymbol = &.{
+ .{ .addr = 100, .strx = undefined, .ofile = undefined },
+ .{ .addr = 200, .strx = undefined, .ofile = undefined },
+ .{ .addr = 300, .strx = undefined, .ofile = undefined },
+ };
+
+ try testing.expectEqual(null, find(symbols, 0));
+ try testing.expectEqual(null, find(symbols, 99));
+ try testing.expectEqual(&symbols[0], find(symbols, 100).?);
+ try testing.expectEqual(&symbols[0], find(symbols, 150).?);
+ try testing.expectEqual(&symbols[0], find(symbols, 199).?);
+
+ try testing.expectEqual(&symbols[1], find(symbols, 200).?);
+ try testing.expectEqual(&symbols[1], find(symbols, 250).?);
+ try testing.expectEqual(&symbols[1], find(symbols, 299).?);
+
+ try testing.expectEqual(&symbols[2], find(symbols, 300).?);
+ try testing.expectEqual(&symbols[2], find(symbols, 301).?);
+ try testing.expectEqual(&symbols[2], find(symbols, 5000).?);
+ }
+};
+test {
+ _ = MachoSymbol;
+}
+
+/// Uses `mmap` to map the file at `path` into memory.
+fn mapDebugInfoFile(path: []const u8) ![]align(std.heap.page_size_min) const u8 {
+ const file = std.fs.cwd().openFile(path, .{}) catch |err| switch (err) {
+ error.FileNotFound => return error.MissingDebugInfo,
+ else => return error.ReadFailed,
+ };
+ defer file.close();
+
+ const file_end_pos = file.getEndPos() catch |err| switch (err) {
+ error.Unexpected => |e| return e,
+ else => return error.ReadFailed,
+ };
+ const file_len = std.math.cast(usize, file_end_pos) orelse return error.InvalidDebugInfo;
+
+ return posix.mmap(
+ null,
+ file_len,
+ posix.PROT.READ,
+ .{ .TYPE = .SHARED },
+ file.handle,
+ 0,
+ ) catch |err| switch (err) {
+ error.Unexpected => |e| return e,
+ else => return error.ReadFailed,
+ };
+}
+
+fn loadOFile(gpa: Allocator, o_file_path: []const u8) !OFile {
+ const mapped_mem = try mapDebugInfoFile(o_file_path);
+ errdefer posix.munmap(mapped_mem);
+
+ if (mapped_mem.len < @sizeOf(macho.mach_header_64)) return error.InvalidDebugInfo;
+ const hdr: *const macho.mach_header_64 = @ptrCast(@alignCast(mapped_mem.ptr));
+ if (hdr.magic != std.macho.MH_MAGIC_64) return error.InvalidDebugInfo;
+
+ const seg_cmd: macho.LoadCommandIterator.LoadCommand, const symtab_cmd: macho.symtab_command = cmds: {
+ var seg_cmd: ?macho.LoadCommandIterator.LoadCommand = null;
+ var symtab_cmd: ?macho.symtab_command = null;
+ var it: macho.LoadCommandIterator = .{
+ .ncmds = hdr.ncmds,
+ .buffer = mapped_mem[@sizeOf(macho.mach_header_64)..][0..hdr.sizeofcmds],
+ };
+ while (it.next()) |cmd| switch (cmd.cmd()) {
+ .SEGMENT_64 => seg_cmd = cmd,
+ .SYMTAB => symtab_cmd = cmd.cast(macho.symtab_command) orelse return error.InvalidDebugInfo,
+ else => {},
+ };
+ break :cmds .{
+ seg_cmd orelse return error.MissingDebugInfo,
+ symtab_cmd orelse return error.MissingDebugInfo,
+ };
+ };
+
+ if (mapped_mem.len < symtab_cmd.stroff + symtab_cmd.strsize) return error.InvalidDebugInfo;
+ if (mapped_mem[symtab_cmd.stroff + symtab_cmd.strsize - 1] != 0) return error.InvalidDebugInfo;
+ const strtab = mapped_mem[symtab_cmd.stroff..][0 .. symtab_cmd.strsize - 1];
+
+ const n_sym_bytes = symtab_cmd.nsyms * @sizeOf(macho.nlist_64);
+ if (mapped_mem.len < symtab_cmd.symoff + n_sym_bytes) return error.InvalidDebugInfo;
+ const symtab: []align(1) const macho.nlist_64 = @ptrCast(mapped_mem[symtab_cmd.symoff..][0..n_sym_bytes]);
+
+ // TODO handle tentative (common) symbols
+ var symbols_by_name: std.ArrayHashMapUnmanaged(u32, void, void, true) = .empty;
+ defer symbols_by_name.deinit(gpa);
+ try symbols_by_name.ensureUnusedCapacity(gpa, @intCast(symtab.len));
+ for (symtab, 0..) |sym, sym_index| {
+ if (sym.n_strx == 0) continue;
+ switch (sym.n_type.bits.type) {
+ .undf => continue, // includes tentative symbols
+ .abs => continue,
+ else => {},
+ }
+ const sym_name = mem.sliceTo(strtab[sym.n_strx..], 0);
+ const gop = symbols_by_name.getOrPutAssumeCapacityAdapted(
+ @as([]const u8, sym_name),
+ @as(OFile.SymbolAdapter, .{ .strtab = strtab, .symtab = symtab }),
+ );
+ if (gop.found_existing) return error.InvalidDebugInfo;
+ gop.key_ptr.* = @intCast(sym_index);
+ }
+
+ var sections: Dwarf.SectionArray = @splat(null);
+ for (seg_cmd.getSections()) |sect| {
+ if (!std.mem.eql(u8, "__DWARF", sect.segName())) continue;
+
+ const section_index: usize = inline for (@typeInfo(Dwarf.Section.Id).@"enum".fields, 0..) |section, i| {
+ if (mem.eql(u8, "__" ++ section.name, sect.sectName())) break i;
+ } else continue;
+
+ if (mapped_mem.len < sect.offset + sect.size) return error.InvalidDebugInfo;
+ const section_bytes = mapped_mem[sect.offset..][0..sect.size];
+ sections[section_index] = .{
+ .data = section_bytes,
+ .owned = false,
+ };
+ }
+
+ const missing_debug_info =
+ sections[@intFromEnum(Dwarf.Section.Id.debug_info)] == null or
+ sections[@intFromEnum(Dwarf.Section.Id.debug_abbrev)] == null or
+ sections[@intFromEnum(Dwarf.Section.Id.debug_str)] == null or
+ sections[@intFromEnum(Dwarf.Section.Id.debug_line)] == null;
+ if (missing_debug_info) return error.MissingDebugInfo;
+
+ var dwarf: Dwarf = .{ .sections = sections };
+ errdefer dwarf.deinit(gpa);
+ try dwarf.open(gpa, native_endian);
+
+ return .{
+ .mapped_memory = mapped_mem,
+ .dwarf = dwarf,
+ .strtab = strtab,
+ .symtab = symtab,
+ .symbols_by_name = symbols_by_name.move(),
+ };
+}
+
+const std = @import("std");
+const Allocator = std.mem.Allocator;
+const Dwarf = std.debug.Dwarf;
+const Error = std.debug.SelfInfoError;
+const assert = std.debug.assert;
+const posix = std.posix;
+const macho = std.macho;
+const mem = std.mem;
+const testing = std.testing;
+const dwarfRegNative = std.debug.Dwarf.SelfUnwinder.regNative;
+
+const builtin = @import("builtin");
+const native_endian = builtin.target.cpu.arch.endian();
+
+const SelfInfo = @This();
diff --git a/lib/std/debug/SelfInfo/Windows.zig b/lib/std/debug/SelfInfo/Windows.zig
@@ -88,7 +88,7 @@ pub const UnwindContext = struct {
.R15 = ctx.gprs.get(.r15),
.Rip = ctx.gprs.get(.rip),
}),
- .aarch64, .aarch64_be => .{
+ .aarch64 => .{
.ContextFlags = 0,
.Cpsr = 0,
.DUMMYUNIONNAME = .{ .X = ctx.x },
diff --git a/lib/std/debug/cpu_context.zig b/lib/std/debug/cpu_context.zig
@@ -4,10 +4,12 @@
pub const Native = if (@hasDecl(root, "debug") and @hasDecl(root.debug, "CpuContext"))
root.debug.CpuContext
else switch (native_arch) {
+ .aarch64, .aarch64_be => Aarch64,
+ .arm, .armeb, .thumb, .thumbeb => Arm,
+ .loongarch32, .loongarch64 => LoongArch,
+ .riscv32, .riscv32be, .riscv64, .riscv64be => Riscv,
.x86 => X86,
.x86_64 => X86_64,
- .arm, .armeb, .thumb, .thumbeb => Arm,
- .aarch64, .aarch64_be => Aarch64,
else => noreturn,
};
@@ -21,7 +23,7 @@ pub fn fromPosixSignalContext(ctx_ptr: ?*const anyopaque) ?Native {
const uc: *const signal_ucontext_t = @ptrCast(@alignCast(ctx_ptr));
return switch (native_arch) {
.x86 => switch (native_os) {
- .linux, .netbsd, .solaris, .illumos => .{ .gprs = .init(.{
+ .linux, .netbsd, .illumos => .{ .gprs = .init(.{
.eax = uc.mcontext.gregs[std.posix.REG.EAX],
.ecx = uc.mcontext.gregs[std.posix.REG.ECX],
.edx = uc.mcontext.gregs[std.posix.REG.EDX],
@@ -92,7 +94,7 @@ pub fn fromPosixSignalContext(ctx_ptr: ?*const anyopaque) ?Native {
.r15 = @bitCast(uc.sc_r15),
.rip = @bitCast(uc.sc_rip),
}) },
- .macos, .ios => .{ .gprs = .init(.{
+ .driverkit, .macos, .ios => .{ .gprs = .init(.{
.rax = uc.mcontext.ss.rax,
.rdx = uc.mcontext.ss.rdx,
.rcx = uc.mcontext.ss.rcx,
@@ -137,7 +139,7 @@ pub fn fromPosixSignalContext(ctx_ptr: ?*const anyopaque) ?Native {
else => null,
},
.aarch64, .aarch64_be => switch (builtin.os.tag) {
- .macos, .ios, .tvos, .watchos, .visionos => .{
+ .driverkit, .macos, .ios, .tvos, .watchos, .visionos => .{
.x = uc.mcontext.ss.regs ++ @as([2]u64, .{
uc.mcontext.ss.fp, // x29 = fp
uc.mcontext.ss.lr, // x30 = lr
@@ -173,6 +175,20 @@ pub fn fromPosixSignalContext(ctx_ptr: ?*const anyopaque) ?Native {
},
else => null,
},
+ .loongarch64 => switch (builtin.os.tag) {
+ .linux => .{
+ .r = uc.mcontext.regs, // includes r0 (hardwired zero)
+ .pc = uc.mcontext.pc,
+ },
+ else => null,
+ },
+ .riscv32, .riscv64 => switch (builtin.os.tag) {
+ .linux => .{
+ .r = [1]usize{0} ++ uc.mcontext.gregs[1..].*, // r0 position is used for pc; replace with zero
+ .pc = uc.mcontext.gregs[0],
+ },
+ else => null,
+ },
else => null,
};
}
@@ -209,7 +225,7 @@ pub fn fromWindowsContext(ctx: *const std.os.windows.CONTEXT) Native {
.r15 = ctx.R15,
.rip = ctx.Rip,
}) },
- .aarch64, .aarch64_be => .{
+ .aarch64 => .{
.x = ctx.DUMMYUNIONNAME.X[0..31].*,
.sp = ctx.Sp,
.pc = ctx.Pc,
@@ -371,7 +387,6 @@ pub const Arm = struct {
pub fn dwarfRegisterBytes(ctx: *Arm, register_num: u16) DwarfRegisterError![]u8 {
// DWARF for the Arm(r) Architecture § 4.1 "DWARF register names"
switch (register_num) {
- // The order of `Gpr` intentionally matches DWARF's mappings.
0...15 => return @ptrCast(&ctx.r[register_num]),
64...95 => return error.UnsupportedRegister, // S0 - S31
@@ -444,7 +459,6 @@ pub const Aarch64 = extern struct {
pub fn dwarfRegisterBytes(ctx: *Aarch64, register_num: u16) DwarfRegisterError![]u8 {
// DWARF for the Arm(r) 64-bit Architecture (AArch64) § 4.1 "DWARF register names"
switch (register_num) {
- // The order of `Gpr` intentionally matches DWARF's mappings.
0...30 => return @ptrCast(&ctx.x[register_num]),
31 => return @ptrCast(&ctx.sp),
32 => return @ptrCast(&ctx.pc),
@@ -467,11 +481,207 @@ pub const Aarch64 = extern struct {
}
};
+/// This is an `extern struct` so that inline assembly in `current` can use field offsets.
+pub const LoongArch = extern struct {
+ /// The numbered general-purpose registers r0 - r31. r0 must be zero.
+ r: [32]usize,
+ pc: usize,
+
+ pub inline fn current() LoongArch {
+ var ctx: LoongArch = undefined;
+ asm volatile (if (@sizeOf(usize) == 8)
+ \\ st.d $zero, $t0, 0
+ \\ st.d $ra, $t0, 8
+ \\ st.d $tp, $t0, 16
+ \\ st.d $sp, $t0, 24
+ \\ st.d $a0, $t0, 32
+ \\ st.d $a1, $t0, 40
+ \\ st.d $a2, $t0, 48
+ \\ st.d $a3, $t0, 56
+ \\ st.d $a4, $t0, 64
+ \\ st.d $a5, $t0, 72
+ \\ st.d $a6, $t0, 80
+ \\ st.d $a7, $t0, 88
+ \\ st.d $t0, $t0, 96
+ \\ st.d $t1, $t0, 104
+ \\ st.d $t2, $t0, 112
+ \\ st.d $t3, $t0, 120
+ \\ st.d $t4, $t0, 128
+ \\ st.d $t5, $t0, 136
+ \\ st.d $t6, $t0, 144
+ \\ st.d $t7, $t0, 152
+ \\ st.d $t8, $t0, 160
+ \\ st.d $r21, $t0, 168
+ \\ st.d $fp, $t0, 176
+ \\ st.d $s0, $t0, 184
+ \\ st.d $s1, $t0, 192
+ \\ st.d $s2, $t0, 200
+ \\ st.d $s3, $t0, 208
+ \\ st.d $s4, $t0, 216
+ \\ st.d $s5, $t0, 224
+ \\ st.d $s6, $t0, 232
+ \\ st.d $s7, $t0, 240
+ \\ st.d $s8, $t0, 248
+ \\ bl 1f
+ \\1:
+ \\ st.d $ra, $t0, 256
+ \\ ld.d $ra, $t0, 8
+ else
+ \\ st.w $zero, $t0, 0
+ \\ st.w $ra, $t0, 4
+ \\ st.w $tp, $t0, 8
+ \\ st.w $sp, $t0, 12
+ \\ st.w $a0, $t0, 16
+ \\ st.w $a1, $t0, 20
+ \\ st.w $a2, $t0, 24
+ \\ st.w $a3, $t0, 28
+ \\ st.w $a4, $t0, 32
+ \\ st.w $a5, $t0, 36
+ \\ st.w $a6, $t0, 40
+ \\ st.w $a7, $t0, 44
+ \\ st.w $t0, $t0, 48
+ \\ st.w $t1, $t0, 52
+ \\ st.w $t2, $t0, 56
+ \\ st.w $t3, $t0, 60
+ \\ st.w $t4, $t0, 64
+ \\ st.w $t5, $t0, 68
+ \\ st.w $t6, $t0, 72
+ \\ st.w $t7, $t0, 76
+ \\ st.w $t8, $t0, 80
+ \\ st.w $r21, $t0, 84
+ \\ st.w $fp, $t0, 88
+ \\ st.w $s0, $t0, 92
+ \\ st.w $s1, $t0, 96
+ \\ st.w $s2, $t0, 100
+ \\ st.w $s3, $t0, 104
+ \\ st.w $s4, $t0, 108
+ \\ st.w $s5, $t0, 112
+ \\ st.w $s6, $t0, 116
+ \\ st.w $s7, $t0, 120
+ \\ st.w $s8, $t0, 124
+ \\ bl 1f
+ \\1:
+ \\ st.w $ra, $t0, 128
+ \\ ld.w $ra, $t0, 4
+ :
+ : [gprs] "{$r12}" (&ctx),
+ : .{ .memory = true });
+ return ctx;
+ }
+
+ pub fn dwarfRegisterBytes(ctx: *LoongArch, register_num: u16) DwarfRegisterError![]u8 {
+ switch (register_num) {
+ 0...31 => return @ptrCast(&ctx.r[register_num]),
+ 32 => return @ptrCast(&ctx.pc),
+
+ else => return error.InvalidRegister,
+ }
+ }
+};
+
+/// This is an `extern struct` so that inline assembly in `current` can use field offsets.
+pub const Riscv = extern struct {
+ /// The numbered general-purpose registers r0 - r31. r0 must be zero.
+ r: [32]usize,
+ pc: usize,
+
+ pub inline fn current() Riscv {
+ var ctx: Riscv = undefined;
+ asm volatile (if (@sizeOf(usize) == 8)
+ \\ sd zero, 0(t0)
+ \\ sd ra, 8(t0)
+ \\ sd sp, 16(t0)
+ \\ sd gp, 24(t0)
+ \\ sd tp, 32(t0)
+ \\ sd t0, 40(t0)
+ \\ sd t1, 48(t0)
+ \\ sd t2, 56(t0)
+ \\ sd s0, 64(t0)
+ \\ sd s1, 72(t0)
+ \\ sd a0, 80(t0)
+ \\ sd a1, 88(t0)
+ \\ sd a2, 96(t0)
+ \\ sd a3, 104(t0)
+ \\ sd a4, 112(t0)
+ \\ sd a5, 120(t0)
+ \\ sd a6, 128(t0)
+ \\ sd a7, 136(t0)
+ \\ sd s2, 144(t0)
+ \\ sd s3, 152(t0)
+ \\ sd s4, 160(t0)
+ \\ sd s5, 168(t0)
+ \\ sd s6, 176(t0)
+ \\ sd s7, 184(t0)
+ \\ sd s8, 192(t0)
+ \\ sd s9, 200(t0)
+ \\ sd s10, 208(t0)
+ \\ sd s11, 216(t0)
+ \\ sd t3, 224(t0)
+ \\ sd t4, 232(t0)
+ \\ sd t5, 240(t0)
+ \\ sd t6, 248(t0)
+ \\ jal ra, 1f
+ \\1:
+ \\ sd ra, 256(t0)
+ \\ ld ra, 8(t0)
+ else
+ \\ sw zero, 0(t0)
+ \\ sw ra, 4(t0)
+ \\ sw sp, 8(t0)
+ \\ sw gp, 12(t0)
+ \\ sw tp, 16(t0)
+ \\ sw t0, 20(t0)
+ \\ sw t1, 24(t0)
+ \\ sw t2, 28(t0)
+ \\ sw s0, 32(t0)
+ \\ sw s1, 36(t0)
+ \\ sw a0, 40(t0)
+ \\ sw a1, 44(t0)
+ \\ sw a2, 48(t0)
+ \\ sw a3, 52(t0)
+ \\ sw a4, 56(t0)
+ \\ sw a5, 60(t0)
+ \\ sw a6, 64(t0)
+ \\ sw a7, 68(t0)
+ \\ sw s2, 72(t0)
+ \\ sw s3, 76(t0)
+ \\ sw s4, 80(t0)
+ \\ sw s5, 84(t0)
+ \\ sw s6, 88(t0)
+ \\ sw s7, 92(t0)
+ \\ sw s8, 96(t0)
+ \\ sw s9, 100(t0)
+ \\ sw s10, 104(t0)
+ \\ sw s11, 108(t0)
+ \\ sw t3, 112(t0)
+ \\ sw t4, 116(t0)
+ \\ sw t5, 120(t0)
+ \\ sw t6, 124(t0)
+ \\ jal ra, 1f
+ \\1:
+ \\ sw ra, 128(t0)
+ \\ lw ra, 4(t0)
+ :
+ : [gprs] "{t0}" (&ctx),
+ : .{ .memory = true });
+ return ctx;
+ }
+
+ pub fn dwarfRegisterBytes(ctx: *Riscv, register_num: u16) DwarfRegisterError![]u8 {
+ switch (register_num) {
+ 0...31 => return @ptrCast(&ctx.r[register_num]),
+ 32 => return @ptrCast(&ctx.pc),
+
+ else => return error.InvalidRegister,
+ }
+ }
+};
+
const signal_ucontext_t = switch (native_os) {
.linux => std.os.linux.ucontext_t,
.emscripten => std.os.emscripten.ucontext_t,
.freebsd => std.os.freebsd.ucontext_t,
- .macos, .ios, .tvos, .watchos, .visionos => extern struct {
+ .driverkit, .macos, .ios, .tvos, .watchos, .visionos => extern struct {
onstack: c_int,
sigmask: std.c.sigset_t,
stack: std.c.stack_t,