Commit ba3f38959a
Changed files (5)
lib
std
debug
lib/std/debug/Dwarf/abi.zig
@@ -7,7 +7,7 @@ const Arch = std.Target.Cpu.Arch;
/// Tells whether unwinding for this target is supported by the Dwarf standard.
///
-/// See also `std.debug.SelfInfo.supportsUnwinding` which tells whether the Zig
+/// See also `std.debug.SelfInfo.supports_unwinding` which tells whether the Zig
/// standard library has a working implementation of unwinding for this target.
pub fn supportsUnwinding(target: *const std.Target) bool {
return switch (target.cpu.arch) {
lib/std/debug/SelfInfo/DarwinModule.zig
@@ -0,0 +1,801 @@
+/// The runtime address where __TEXT is loaded.
+text_base: usize,
+load_offset: usize,
+name: []const u8,
+
+pub fn key(m: *const DarwinModule) usize {
+ return m.text_base;
+}
+
+pub fn lookup(cache: *LookupCache, gpa: Allocator, address: usize) !DarwinModule {
+ _ = cache;
+ _ = gpa;
+ const image_count = std.c._dyld_image_count();
+ for (0..image_count) |image_idx| {
+ const header = std.c._dyld_get_image_header(@intCast(image_idx)) orelse continue;
+ const text_base = @intFromPtr(header);
+ if (address < text_base) continue;
+ const load_offset = std.c._dyld_get_image_vmaddr_slide(@intCast(image_idx));
+
+ // Find the __TEXT segment
+ var it: macho.LoadCommandIterator = .{
+ .ncmds = header.ncmds,
+ .buffer = @as([*]u8, @ptrCast(header))[@sizeOf(macho.mach_header_64)..][0..header.sizeofcmds],
+ };
+ const text_segment_cmd = 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 segment_cmd;
+ } else continue;
+
+ const seg_start = load_offset + text_segment_cmd.vmaddr;
+ assert(seg_start == text_base);
+ const seg_end = seg_start + text_segment_cmd.vmsize;
+ if (address < seg_start or address >= seg_end) continue;
+
+ // We've found the matching __TEXT segment. This is the image we need.
+ return .{
+ .text_base = text_base,
+ .load_offset = load_offset,
+ .name = mem.span(std.c._dyld_get_image_name(@intCast(image_idx))),
+ };
+ }
+ return error.MissingDebugInfo;
+}
+fn loadLocationInfo(module: *const DarwinModule, gpa: Allocator, di: *DebugInfo) !void {
+ const mapped_mem = try mapDebugInfoFile(module.name);
+ errdefer posix.munmap(mapped_mem);
+
+ const hdr: *const macho.mach_header_64 = @ptrCast(@alignCast(mapped_mem.ptr));
+ if (hdr.magic != macho.MH_MAGIC_64)
+ return error.InvalidDebugInfo;
+
+ const symtab: macho.symtab_command = symtab: {
+ 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()) {
+ .SYMTAB => break :symtab cmd.cast(macho.symtab_command) orelse return error.InvalidDebugInfo,
+ else => {},
+ };
+ return error.MissingDebugInfo;
+ };
+
+ const syms_ptr: [*]align(1) const macho.nlist_64 = @ptrCast(mapped_mem[symtab.symoff..]);
+ const syms = syms_ptr[0..symtab.nsyms];
+ const strings = mapped_mem[symtab.stroff..][0 .. symtab.strsize - 1 :0];
+
+ var symbols: std.ArrayList(MachoSymbol) = try .initCapacity(gpa, syms.len);
+ defer symbols.deinit(gpa);
+
+ 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) 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,
+ .size = 0,
+ .ofile = ofile,
+ };
+ },
+ else => return error.InvalidDebugInfo,
+ },
+ .fun => switch (state) {
+ .bnsym => {
+ state = .fun_strx;
+ last_sym.strx = sym.n_strx;
+ },
+ .fun_strx => {
+ state = .fun_size;
+ last_sym.size = @intCast(sym.n_value);
+ },
+ else => return error.InvalidDebugInfo,
+ },
+ .ensym => switch (state) {
+ .fun_size => {
+ state = .ensym;
+ symbols.appendAssumeCapacity(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 => return error.MissingDebugInfo,
+ .oso_close => {},
+ else => return error.InvalidDebugInfo,
+ }
+
+ 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);
+
+ di.full = .{
+ .mapped_memory = mapped_mem,
+ .symbols = symbols_slice,
+ .strings = strings,
+ .ofiles = .empty,
+ };
+}
+fn loadUnwindInfo(module: *const DarwinModule, gpa: Allocator, di: *DebugInfo) !void {
+ _ = gpa;
+
+ 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 = 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();
+ } else unreachable;
+
+ var unwind_info: ?[]const u8 = null;
+ var eh_frame: ?[]const u8 = null;
+ for (sections) |sect| {
+ if (mem.eql(u8, sect.sectName(), "__unwind_info")) {
+ const sect_ptr: [*]u8 = @ptrFromInt(@as(usize, @intCast(module.load_offset + sect.addr)));
+ 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(module.load_offset + sect.addr)));
+ eh_frame = sect_ptr[0..@intCast(sect.size)];
+ }
+ }
+ di.unwind = .{
+ .unwind_info = unwind_info,
+ .eh_frame = eh_frame,
+ };
+}
+pub fn getSymbolAtAddress(module: *const DarwinModule, gpa: Allocator, di: *DebugInfo, address: usize) !std.debug.Symbol {
+ if (di.full == null) try module.loadLocationInfo(gpa, di);
+ const vaddr = address - module.load_offset;
+ const symbol = MachoSymbol.find(di.full.?.symbols, vaddr) orelse return .{
+ .name = null,
+ .compile_unit_name = null,
+ .source_location = null,
+ };
+
+ // 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(di.full.?.strings[symbol.strx..], 0);
+ const o_file_path = mem.sliceTo(di.full.?.strings[symbol.ofile..], 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,
+ };
+
+ const o_file: *DebugInfo.OFile = of: {
+ const gop = try di.full.?.ofiles.getOrPut(gpa, o_file_path);
+ if (!gop.found_existing) {
+ gop.value_ptr.* = DebugInfo.loadOFile(gpa, o_file_path) catch |err| {
+ defer _ = di.full.?.ofiles.pop().?;
+ switch (err) {
+ error.MissingDebugInfo,
+ error.InvalidDebugInfo,
+ => return sym_only_result,
+ else => |e| return e,
+ }
+ };
+ }
+ break :of gop.value_ptr;
+ };
+
+ const symbol_ofile_vaddr = o_file.addr_table.get(stab_symbol) orelse return sym_only_result;
+
+ const compile_unit = o_file.dwarf.findCompileUnit(native_endian, symbol_ofile_vaddr) catch |err| switch (err) {
+ error.MissingDebugInfo, error.InvalidDebugInfo => return sym_only_result,
+ else => |e| return e,
+ };
+
+ return .{
+ .name = o_file.dwarf.getSymbolName(symbol_ofile_vaddr) 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 |err| switch (err) {
+ error.MissingDebugInfo, error.InvalidDebugInfo => null,
+ else => return err,
+ },
+ };
+}
+pub fn unwindFrame(module: *const DarwinModule, gpa: Allocator, di: *DebugInfo, context: *UnwindContext) !usize {
+ if (di.unwind == null) try module.loadUnwindInfo(gpa, di);
+ const unwind_info = di.unwind.?.unwind_info orelse return error.MissingUnwindInfo;
+ // MLUGG TODO: inline?
+ return unwindFrameMachO(
+ module.text_base,
+ module.load_offset,
+ context,
+ unwind_info,
+ di.unwind.?.eh_frame,
+ );
+}
+/// 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.
+fn unwindFrameMachO(
+ text_base: usize,
+ load_offset: usize,
+ context: *UnwindContext,
+ unwind_info: []const u8,
+ opt_eh_frame: ?[]const u8,
+) !usize {
+ if (unwind_info.len < @sizeOf(macho.unwind_info_section_header)) return error.InvalidUnwindInfo;
+ 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.InvalidUnwindInfo;
+ 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.MissingUnwindInfo;
+
+ // offset of the PC into the `__TEXT` segment
+ const pc_text_offset = context.pc - 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.MissingUnwindInfo;
+
+ 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.InvalidUnwindInfo;
+ 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.InvalidUnwindInfo;
+ 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.InvalidUnwindInfo;
+ 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.InvalidUnwindInfo;
+ 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.InvalidUnwindInfo;
+
+ 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.InvalidUnwindInfo;
+ 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.InvalidUnwindInfo;
+ 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.InvalidUnwindInfo;
+
+ 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.InvalidUnwindInfo;
+ 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.InvalidUnwindInfo;
+ break :entry .{
+ .function_offset = function_offset,
+ .raw_encoding = local_encodings[local_index],
+ };
+ },
+ else => return error.InvalidUnwindInfo,
+ };
+
+ if (entry.raw_encoding == 0) return error.NoUnwindInfo;
+ const reg_context: Dwarf.abi.RegisterContext = .{ .eh_frame = false, .is_macho = true };
+
+ 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.UnimplementedUnwindEncoding,
+ .RBP_FRAME => ip: {
+ const frame = encoding.value.x86_64.frame;
+
+ const fp = (try regValueNative(context.thread_context, fpRegNum(reg_context), reg_context)).*;
+ 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 regValueNative(context.thread_context, fpRegNum(reg_context), reg_context)).* = new_fp;
+ (try regValueNative(context.thread_context, spRegNum(reg_context), reg_context)).* = new_sp;
+ (try regValueNative(context.thread_context, ip_reg_num, reg_context)).* = 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 regValueNative(context.thread_context, reg_number, reg_context)).* = @as(*const usize, @ptrFromInt(addr)).*;
+ }
+
+ break :ip new_ip;
+ },
+ .STACK_IMMD,
+ .STACK_IND,
+ => ip: {
+ const frameless = encoding.value.x86_64.frameless;
+
+ const sp = (try regValueNative(context.thread_context, spRegNum(reg_context), reg_context)).*;
+ 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 =
+ 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 regValueNative(context.thread_context, reg_number, reg_context)).* = @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 regValueNative(context.thread_context, spRegNum(reg_context), reg_context)).* = new_sp;
+ (try regValueNative(context.thread_context, ip_reg_num, reg_context)).* = new_ip;
+
+ break :ip new_ip;
+ },
+ .DWARF => {
+ const eh_frame = opt_eh_frame orelse return error.MissingEhFrame;
+ const eh_frame_vaddr = @intFromPtr(eh_frame.ptr) - load_offset;
+ return context.unwindFrameDwarf(
+ &.initSection(.eh_frame, eh_frame_vaddr, eh_frame),
+ load_offset,
+ @intCast(encoding.value.x86_64.dwarf),
+ );
+ },
+ },
+ .aarch64, .aarch64_be => switch (encoding.mode.arm64) {
+ .OLD => return error.UnimplementedUnwindEncoding,
+ .FRAMELESS => ip: {
+ const sp = (try regValueNative(context.thread_context, spRegNum(reg_context), reg_context)).*;
+ const new_sp = sp + encoding.value.arm64.frameless.stack_size * 16;
+ const new_ip = (try regValueNative(context.thread_context, 30, reg_context)).*;
+ (try regValueNative(context.thread_context, spRegNum(reg_context), reg_context)).* = new_sp;
+ break :ip new_ip;
+ },
+ .DWARF => {
+ const eh_frame = opt_eh_frame orelse return error.MissingEhFrame;
+ const eh_frame_vaddr = @intFromPtr(eh_frame.ptr) - load_offset;
+ return context.unwindFrameDwarf(
+ &.initSection(.eh_frame, eh_frame_vaddr, eh_frame),
+ load_offset,
+ @intCast(encoding.value.x86_64.dwarf),
+ );
+ },
+ .FRAME => ip: {
+ const frame = encoding.value.arm64.frame;
+
+ const fp = (try regValueNative(context.thread_context, fpRegNum(reg_context), reg_context)).*;
+ 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 regValueNative(context.thread_context, 19 + i, reg_context)).* = @as(*const usize, @ptrFromInt(reg_addr)).*;
+ reg_addr += @sizeOf(usize);
+ (try regValueNative(context.thread_context, 20 + i, reg_context)).* = @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 regBytes(context.thread_context, 64 + 8 + i, context.reg_context));
+ dest.* = @as(*const usize, @ptrFromInt(reg_addr)).*;
+ }
+ reg_addr += @sizeOf(usize);
+ {
+ const dest: *align(1) usize = @ptrCast(try regBytes(context.thread_context, 64 + 9 + i, context.reg_context));
+ 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 regValueNative(context.thread_context, fpRegNum(reg_context), reg_context)).* = new_fp;
+ (try regValueNative(context.thread_context, ip_reg_num, reg_context)).* = new_ip;
+
+ break :ip new_ip;
+ },
+ },
+ else => comptime unreachable, // unimplemented
+ };
+
+ context.pc = UnwindContext.stripInstructionPtrAuthCode(new_ip);
+ if (context.pc > 0) context.pc -= 1;
+ return new_ip;
+}
+/// No cache needed, because `_dyld_get_image_header` etc are already fast.
+pub const LookupCache = struct {
+ pub const init: LookupCache = .{};
+};
+pub const DebugInfo = struct {
+ unwind: ?struct {
+ // Backed by the in-memory sections mapped by the loader
+ unwind_info: ?[]const u8,
+ eh_frame: ?[]const u8,
+ },
+ // MLUGG TODO: awful field name
+ full: ?struct {
+ mapped_memory: []align(std.heap.page_size_min) const u8,
+ symbols: []const MachoSymbol,
+ strings: [:0]const u8,
+ // MLUGG TODO: this could use an adapter to just index straight into `strings`!
+ ofiles: std.StringArrayHashMapUnmanaged(OFile),
+ },
+
+ pub const init: DebugInfo = .{
+ .unwind = null,
+ .full = null,
+ };
+
+ const OFile = struct {
+ dwarf: Dwarf,
+ // MLUGG TODO: this could use an adapter to just index straight into the strtab!
+ addr_table: std.StringArrayHashMapUnmanaged(u64),
+ };
+
+ fn deinit(di: *DebugInfo, gpa: Allocator) void {
+ for (di.full.ofiles.values()) |*ofile| {
+ ofile.dwarf.deinit(gpa);
+ ofile.addr_table.deinit(gpa);
+ }
+ di.full.ofiles.deinit();
+ gpa.free(di.full.symbols);
+ posix.munmap(di.full.mapped_memory);
+ }
+
+ 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 addr_table: std.StringArrayHashMapUnmanaged(u64) = .empty;
+ defer addr_table.deinit(gpa);
+ try addr_table.ensureUnusedCapacity(gpa, @intCast(symtab.len));
+ for (symtab) |sym| {
+ 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 = addr_table.getOrPutAssumeCapacity(sym_name);
+ if (gop.found_existing) return error.InvalidDebugInfo;
+ gop.value_ptr.* = sym.n_value;
+ }
+
+ 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 .{
+ .dwarf = dwarf,
+ .addr_table = addr_table.move(),
+ };
+ }
+};
+
+const MachoSymbol = struct {
+ strx: u32,
+ addr: u64,
+ size: u32,
+ ofile: 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, .size = undefined, .ofile = undefined },
+ .{ .addr = 200, .strx = undefined, .size = undefined, .ofile = undefined },
+ .{ .addr = 300, .strx = undefined, .size = 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;
+}
+
+fn fpRegNum(reg_context: Dwarf.abi.RegisterContext) u8 {
+ return Dwarf.abi.fpRegNum(builtin.target.cpu.arch, reg_context);
+}
+fn spRegNum(reg_context: Dwarf.abi.RegisterContext) u8 {
+ return Dwarf.abi.spRegNum(builtin.target.cpu.arch, reg_context);
+}
+const ip_reg_num = Dwarf.abi.ipRegNum(builtin.target.cpu.arch).?;
+
+/// 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 => |e| return e,
+ };
+ defer file.close();
+
+ const file_len = std.math.cast(usize, try file.getEndPos()) orelse return error.InvalidDebugInfo;
+
+ return posix.mmap(
+ null,
+ file_len,
+ posix.PROT.READ,
+ .{ .TYPE = .SHARED },
+ file.handle,
+ 0,
+ );
+}
+
+const DarwinModule = @This();
+
+const std = @import("../../std.zig");
+const Allocator = std.mem.Allocator;
+const Dwarf = std.debug.Dwarf;
+const assert = std.debug.assert;
+const macho = std.macho;
+const mem = std.mem;
+const posix = std.posix;
+const testing = std.testing;
+const UnwindContext = std.debug.SelfInfo.UnwindContext;
+const regBytes = Dwarf.abi.regBytes;
+const regValueNative = Dwarf.abi.regValueNative;
+
+const builtin = @import("builtin");
+const native_endian = builtin.target.cpu.arch.endian();
lib/std/debug/SelfInfo/ElfModule.zig
@@ -0,0 +1,144 @@
+load_offset: usize,
+name: []const u8,
+build_id: ?[]const u8,
+gnu_eh_frame: ?[]const u8,
+
+/// No cache needed, because `dl_iterate_phdr` is already fast.
+pub const LookupCache = struct {
+ pub const init: LookupCache = .{};
+};
+
+pub const DebugInfo = struct {
+ loaded_elf: ?Dwarf.ElfModule,
+ unwind: ?Dwarf.Unwind,
+ pub const init: DebugInfo = .{
+ .loaded_elf = null,
+ .unwind = null,
+ };
+};
+
+pub fn key(m: ElfModule) usize {
+ return m.load_offset;
+}
+pub fn lookup(cache: *LookupCache, gpa: Allocator, address: usize) !ElfModule {
+ _ = cache;
+ _ = gpa;
+ if (builtin.target.os.tag == .haiku) @panic("TODO implement lookup module for Haiku");
+ const DlIterContext = struct {
+ /// input
+ address: usize,
+ /// output
+ module: ElfModule,
+
+ fn callback(info: *std.posix.dl_phdr_info, size: usize, context: *@This()) !void {
+ _ = size;
+ // The base address is too high
+ if (context.address < info.addr)
+ return;
+
+ const phdrs = info.phdr[0..info.phnum];
+ for (phdrs) |*phdr| {
+ if (phdr.p_type != elf.PT_LOAD) continue;
+
+ // Overflowing addition is used to handle the case of VSDOs having a p_vaddr = 0xffffffffff700000
+ const seg_start = info.addr +% phdr.p_vaddr;
+ const seg_end = seg_start + phdr.p_memsz;
+ if (context.address >= seg_start and context.address < seg_end) {
+ context.module = .{
+ .load_offset = info.addr,
+ // Android libc uses NULL instead of "" to mark the main program
+ .name = mem.sliceTo(info.name, 0) orelse "",
+ .build_id = null,
+ .gnu_eh_frame = null,
+ };
+ break;
+ }
+ } else return;
+
+ for (info.phdr[0..info.phnum]) |phdr| {
+ switch (phdr.p_type) {
+ elf.PT_NOTE => {
+ // Look for .note.gnu.build-id
+ const segment_ptr: [*]const u8 = @ptrFromInt(info.addr + phdr.p_vaddr);
+ var r: std.Io.Reader = .fixed(segment_ptr[0..phdr.p_memsz]);
+ const name_size = r.takeInt(u32, native_endian) catch continue;
+ const desc_size = r.takeInt(u32, native_endian) catch continue;
+ const note_type = r.takeInt(u32, native_endian) catch continue;
+ const name = r.take(name_size) catch continue;
+ if (note_type != elf.NT_GNU_BUILD_ID) continue;
+ if (!mem.eql(u8, name, "GNU\x00")) continue;
+ const desc = r.take(desc_size) catch continue;
+ context.module.build_id = desc;
+ },
+ elf.PT_GNU_EH_FRAME => {
+ const segment_ptr: [*]const u8 = @ptrFromInt(info.addr + phdr.p_vaddr);
+ context.module.gnu_eh_frame = segment_ptr[0..phdr.p_memsz];
+ },
+ else => {},
+ }
+ }
+
+ // Stop the iteration
+ return error.Found;
+ }
+ };
+ var ctx: DlIterContext = .{
+ .address = address,
+ .module = undefined,
+ };
+ std.posix.dl_iterate_phdr(&ctx, error{Found}, DlIterContext.callback) catch |err| switch (err) {
+ error.Found => return ctx.module,
+ };
+ return error.MissingDebugInfo;
+}
+fn loadLocationInfo(module: *const ElfModule, gpa: Allocator, di: *DebugInfo) !void {
+ if (module.name.len > 0) {
+ di.loaded_elf = Dwarf.ElfModule.load(gpa, .{
+ .root_dir = .cwd(),
+ .sub_path = module.name,
+ }, module.build_id, null, null, null) catch |err| switch (err) {
+ error.FileNotFound => return error.MissingDebugInfo,
+ error.Overflow => return error.InvalidDebugInfo,
+ else => |e| return e,
+ };
+ } else {
+ const path = try std.fs.selfExePathAlloc(gpa);
+ defer gpa.free(path);
+ di.loaded_elf = Dwarf.ElfModule.load(gpa, .{
+ .root_dir = .cwd(),
+ .sub_path = path,
+ }, module.build_id, null, null, null) catch |err| switch (err) {
+ error.FileNotFound => return error.MissingDebugInfo,
+ error.Overflow => return error.InvalidDebugInfo,
+ else => |e| return e,
+ };
+ }
+}
+pub fn getSymbolAtAddress(module: *const ElfModule, gpa: Allocator, di: *DebugInfo, address: usize) !std.debug.Symbol {
+ if (di.loaded_elf == null) try module.loadLocationInfo(gpa, di);
+ const vaddr = address - module.load_offset;
+ return di.loaded_elf.?.dwarf.getSymbol(gpa, native_endian, vaddr);
+}
+fn loadUnwindInfo(module: *const ElfModule, gpa: Allocator, di: *DebugInfo) !void {
+ const section_bytes = module.gnu_eh_frame orelse return error.MissingUnwindInfo; // MLUGG TODO: load from file
+ const section_vaddr: u64 = @intFromPtr(section_bytes.ptr) - module.load_offset;
+ const header: Dwarf.Unwind.EhFrameHeader = try .parse(section_vaddr, section_bytes, @sizeOf(usize), native_endian);
+ di.unwind = .initEhFrameHdr(header, section_vaddr, @ptrFromInt(module.load_offset + header.eh_frame_vaddr));
+ try di.unwind.?.prepareLookup(gpa, @sizeOf(usize), native_endian);
+}
+pub fn unwindFrame(module: *const ElfModule, gpa: Allocator, di: *DebugInfo, context: *UnwindContext) !usize {
+ if (di.unwind == null) try module.loadUnwindInfo(gpa, di);
+ return context.unwindFrameDwarf(&di.unwind.?, module.load_offset, null);
+}
+
+const ElfModule = @This();
+
+const std = @import("../../std.zig");
+const Allocator = std.mem.Allocator;
+const Dwarf = std.debug.Dwarf;
+const elf = std.elf;
+const mem = std.mem;
+const UnwindContext = std.debug.SelfInfo.UnwindContext;
+
+const builtin = @import("builtin");
+const native_endian = builtin.target.cpu.arch.endian();
lib/std/debug/SelfInfo/WindowsModule.zig
@@ -0,0 +1,255 @@
+base_address: usize,
+size: usize,
+name: []const u8,
+handle: windows.HMODULE,
+pub fn key(m: WindowsModule) usize {
+ return m.base_address;
+}
+pub fn lookup(cache: *LookupCache, gpa: Allocator, address: usize) !WindowsModule {
+ if (lookupInCache(cache, address)) |m| return m;
+ {
+ // Check a new module hasn't been loaded
+ cache.modules.clearRetainingCapacity();
+
+ const handle = windows.kernel32.CreateToolhelp32Snapshot(windows.TH32CS_SNAPMODULE | windows.TH32CS_SNAPMODULE32, 0);
+ if (handle == windows.INVALID_HANDLE_VALUE) {
+ return windows.unexpectedError(windows.GetLastError());
+ }
+ defer windows.CloseHandle(handle);
+
+ var entry: windows.MODULEENTRY32 = undefined;
+ entry.dwSize = @sizeOf(windows.MODULEENTRY32);
+ if (windows.kernel32.Module32First(handle, &entry) != 0) {
+ try cache.modules.append(gpa, entry);
+ while (windows.kernel32.Module32Next(handle, &entry) != 0) {
+ try cache.modules.append(gpa, entry);
+ }
+ }
+ }
+ if (lookupInCache(cache, address)) |m| return m;
+ return error.MissingDebugInfo;
+}
+pub fn getSymbolAtAddress(module: *const WindowsModule, gpa: Allocator, di: *DebugInfo, address: usize) !std.debug.Symbol {
+ if (!di.loaded) try module.loadLocationInfo(gpa, di);
+ // Translate the runtime address into a virtual address into the module
+ const vaddr = address - module.base_address;
+
+ if (di.pdb != null) {
+ if (try di.getSymbolFromPdb(vaddr)) |symbol| return symbol;
+ }
+
+ if (di.dwarf) |*dwarf| {
+ const dwarf_address = vaddr + di.coff_image_base;
+ return dwarf.getSymbol(gpa, native_endian, dwarf_address);
+ }
+
+ return error.MissingDebugInfo;
+}
+fn lookupInCache(cache: *const LookupCache, address: usize) ?WindowsModule {
+ for (cache.modules.items) |*entry| {
+ const base_address = @intFromPtr(entry.modBaseAddr);
+ if (address >= base_address and address < base_address + entry.modBaseSize) {
+ return .{
+ .base_address = base_address,
+ .size = entry.modBaseSize,
+ .name = std.mem.sliceTo(&entry.szModule, 0),
+ .handle = entry.hModule,
+ };
+ }
+ }
+ return null;
+}
+fn loadLocationInfo(module: *const WindowsModule, gpa: Allocator, di: *DebugInfo) !void {
+ const mapped_ptr: [*]const u8 = @ptrFromInt(module.base_address);
+ const mapped = mapped_ptr[0..module.size];
+ var coff_obj = coff.Coff.init(mapped, true) catch return error.InvalidDebugInfo;
+ // The string table is not mapped into memory by the loader, so if a section name is in the
+ // string table then we have to map the full image file from disk. This can happen when
+ // a binary is produced with -gdwarf, since the section names are longer than 8 bytes.
+ if (coff_obj.strtabRequired()) {
+ var name_buffer: [windows.PATH_MAX_WIDE + 4:0]u16 = undefined;
+ name_buffer[0..4].* = .{ '\\', '?', '?', '\\' }; // openFileAbsoluteW requires the prefix to be present
+ const process_handle = windows.GetCurrentProcess();
+ const len = windows.kernel32.GetModuleFileNameExW(
+ process_handle,
+ module.handle,
+ name_buffer[4..],
+ windows.PATH_MAX_WIDE,
+ );
+ if (len == 0) return error.MissingDebugInfo;
+ const coff_file = fs.openFileAbsoluteW(name_buffer[0 .. len + 4 :0], .{}) catch |err| switch (err) {
+ error.FileNotFound => return error.MissingDebugInfo,
+ else => |e| return e,
+ };
+ errdefer coff_file.close();
+ var section_handle: windows.HANDLE = undefined;
+ const create_section_rc = windows.ntdll.NtCreateSection(
+ §ion_handle,
+ windows.STANDARD_RIGHTS_REQUIRED | windows.SECTION_QUERY | windows.SECTION_MAP_READ,
+ null,
+ null,
+ windows.PAGE_READONLY,
+ // The documentation states that if no AllocationAttribute is specified, then SEC_COMMIT is the default.
+ // In practice, this isn't the case and specifying 0 will result in INVALID_PARAMETER_6.
+ windows.SEC_COMMIT,
+ coff_file.handle,
+ );
+ if (create_section_rc != .SUCCESS) return error.MissingDebugInfo;
+ errdefer windows.CloseHandle(section_handle);
+ var coff_len: usize = 0;
+ var section_view_ptr: [*]const u8 = undefined;
+ const map_section_rc = windows.ntdll.NtMapViewOfSection(
+ section_handle,
+ process_handle,
+ @ptrCast(§ion_view_ptr),
+ null,
+ 0,
+ null,
+ &coff_len,
+ .ViewUnmap,
+ 0,
+ windows.PAGE_READONLY,
+ );
+ if (map_section_rc != .SUCCESS) return error.MissingDebugInfo;
+ errdefer assert(windows.ntdll.NtUnmapViewOfSection(process_handle, @constCast(section_view_ptr)) == .SUCCESS);
+ const section_view = section_view_ptr[0..coff_len];
+ coff_obj = coff.Coff.init(section_view, false) catch return error.InvalidDebugInfo;
+ di.mapped_file = .{
+ .file = coff_file,
+ .section_handle = section_handle,
+ .section_view = section_view,
+ };
+ }
+ di.coff_image_base = coff_obj.getImageBase();
+
+ if (coff_obj.getSectionByName(".debug_info")) |_| {
+ di.dwarf = .{};
+
+ inline for (@typeInfo(Dwarf.Section.Id).@"enum".fields, 0..) |section, i| {
+ di.dwarf.?.sections[i] = if (coff_obj.getSectionByName("." ++ section.name)) |section_header| blk: {
+ break :blk .{
+ .data = try coff_obj.getSectionDataAlloc(section_header, gpa),
+ .owned = true,
+ };
+ } else null;
+ }
+
+ try di.dwarf.?.open(gpa, native_endian);
+ }
+
+ if (try coff_obj.getPdbPath()) |raw_path| pdb: {
+ const path = blk: {
+ if (fs.path.isAbsolute(raw_path)) {
+ break :blk raw_path;
+ } else {
+ const self_dir = try fs.selfExeDirPathAlloc(gpa);
+ defer gpa.free(self_dir);
+ break :blk try fs.path.join(gpa, &.{ self_dir, raw_path });
+ }
+ };
+ defer if (path.ptr != raw_path.ptr) gpa.free(path);
+
+ di.pdb = Pdb.init(gpa, path) catch |err| switch (err) {
+ error.FileNotFound, error.IsDir => break :pdb,
+ else => return err,
+ };
+ try di.pdb.?.parseInfoStream();
+ try di.pdb.?.parseDbiStream();
+
+ if (!mem.eql(u8, &coff_obj.guid, &di.pdb.?.guid) or coff_obj.age != di.pdb.?.age)
+ return error.InvalidDebugInfo;
+
+ di.coff_section_headers = try coff_obj.getSectionHeadersAlloc(gpa);
+ }
+
+ di.loaded = true;
+}
+pub const LookupCache = struct {
+ modules: std.ArrayListUnmanaged(windows.MODULEENTRY32),
+ pub const init: LookupCache = .{ .modules = .empty };
+};
+pub const DebugInfo = struct {
+ loaded: bool,
+
+ coff_image_base: u64,
+ mapped_file: ?struct {
+ file: fs.File,
+ section_handle: windows.HANDLE,
+ section_view: []const u8,
+ fn deinit(mapped: @This()) void {
+ const process_handle = windows.GetCurrentProcess();
+ assert(windows.ntdll.NtUnmapViewOfSection(process_handle, @constCast(mapped.section_view.ptr)) == .SUCCESS);
+ windows.CloseHandle(mapped.section_handle);
+ mapped.file.close();
+ }
+ },
+
+ dwarf: ?Dwarf,
+
+ pdb: ?Pdb,
+ /// Populated iff `pdb != null`; otherwise `&.{}`.
+ coff_section_headers: []coff.SectionHeader,
+
+ pub const init: DebugInfo = .{
+ .loaded = false,
+ .coff_image_base = undefined,
+ .mapped_file = null,
+ .dwarf = null,
+ .pdb = null,
+ .coff_section_headers = &.{},
+ };
+
+ fn deinit(di: *DebugInfo, gpa: Allocator) void {
+ if (di.dwarf) |*dwarf| dwarf.deinit(gpa);
+ if (di.pdb) |*pdb| pdb.deinit();
+ gpa.free(di.coff_section_headers);
+ if (di.mapped_file) |mapped| mapped.deinit();
+ }
+
+ fn getSymbolFromPdb(di: *DebugInfo, relocated_address: usize) !?std.debug.Symbol {
+ var coff_section: *align(1) const coff.SectionHeader = undefined;
+ const mod_index = for (di.pdb.?.sect_contribs) |sect_contrib| {
+ if (sect_contrib.section > di.coff_section_headers.len) continue;
+ // Remember that SectionContribEntry.Section is 1-based.
+ coff_section = &di.coff_section_headers[sect_contrib.section - 1];
+
+ const vaddr_start = coff_section.virtual_address + sect_contrib.offset;
+ const vaddr_end = vaddr_start + sect_contrib.size;
+ if (relocated_address >= vaddr_start and relocated_address < vaddr_end) {
+ break sect_contrib.module_index;
+ }
+ } else {
+ // we have no information to add to the address
+ return null;
+ };
+
+ const module = try di.pdb.?.getModule(mod_index) orelse return error.InvalidDebugInfo;
+
+ return .{
+ .name = di.pdb.?.getSymbolName(
+ module,
+ relocated_address - coff_section.virtual_address,
+ ),
+ .compile_unit_name = fs.path.basename(module.obj_file_name),
+ .source_location = try di.pdb.?.getLineNumberInfo(
+ module,
+ relocated_address - coff_section.virtual_address,
+ ),
+ };
+ }
+};
+
+const WindowsModule = @This();
+
+const std = @import("../../std.zig");
+const Allocator = std.mem.Allocator;
+const Dwarf = std.debug.Dwarf;
+const Pdb = std.debug.Pdb;
+const assert = std.debug.assert;
+const coff = std.coff;
+const fs = std.fs;
+const mem = std.mem;
+const windows = std.os.windows;
+
+const builtin = @import("builtin");
+const native_endian = builtin.target.cpu.arch.endian();
lib/std/debug/SelfInfo.zig
@@ -11,18 +11,8 @@ const native_arch = builtin.cpu.arch;
const std = @import("../std.zig");
const mem = std.mem;
const Allocator = std.mem.Allocator;
-const windows = std.os.windows;
-const macho = std.macho;
-const fs = std.fs;
-const coff = std.coff;
const assert = std.debug.assert;
-const posix = std.posix;
-const elf = std.elf;
const Dwarf = std.debug.Dwarf;
-const Pdb = std.debug.Pdb;
-const File = std.fs.File;
-const math = std.math;
-const testing = std.testing;
const regBytes = Dwarf.abi.regBytes;
const regValueNative = Dwarf.abi.regValueNative;
@@ -31,6 +21,7 @@ const SelfInfo = @This();
modules: std.AutoHashMapUnmanaged(usize, Module.DebugInfo),
lookup_cache: Module.LookupCache,
+/// Indicates whether the `SelfInfo` implementation has support for this target.
pub const target_supported: bool = switch (native_os) {
.linux,
.freebsd,
@@ -45,9 +36,39 @@ pub const target_supported: bool = switch (native_os) {
else => false,
};
+/// Indicates whether unwinding for the host is *implemented* here in the Zig
+/// standard library.
+///
+/// See also `Dwarf.abi.supportsUnwinding` which tells whether Dwarf supports
+/// unwinding on a target *in theory*.
+pub const supports_unwinding: bool = switch (builtin.target.cpu.arch) {
+ .x86 => switch (builtin.target.os.tag) {
+ .linux, .netbsd, .solaris, .illumos => true,
+ else => false,
+ },
+ .x86_64 => switch (builtin.target.os.tag) {
+ .linux, .netbsd, .freebsd, .openbsd, .macos, .ios, .solaris, .illumos => true,
+ else => false,
+ },
+ .arm, .armeb, .thumb, .thumbeb => switch (builtin.target.os.tag) {
+ .linux => true,
+ else => false,
+ },
+ .aarch64, .aarch64_be => switch (builtin.target.os.tag) {
+ .linux, .netbsd, .freebsd, .macos, .ios => true,
+ else => false,
+ },
+ // Unwinding is possible on other targets but this implementation does
+ // not support them...yet!
+ else => false,
+};
+comptime {
+ if (supports_unwinding) assert(Dwarf.abi.supportsUnwinding(&builtin.target));
+}
+
pub const init: SelfInfo = .{
.modules = .empty,
- .lookup_cache = if (Module.LookupCache != void) .init,
+ .lookup_cache = .init,
};
pub fn deinit(self: *SelfInfo) void {
@@ -59,19 +80,14 @@ pub fn deinit(self: *SelfInfo) void {
self.allocator.destroy(mdi);
}
self.modules.deinit(self.allocator);
- if (native_os == .windows) {
- for (self.modules.items) |module| {
- self.allocator.free(module.name);
- if (module.mapped_file) |mapped_file| mapped_file.deinit();
- }
- self.modules.deinit(self.allocator);
- }
}
pub fn unwindFrame(self: *SelfInfo, gpa: Allocator, context: *UnwindContext) !usize {
- comptime assert(target_supported);
+ comptime assert(supports_unwinding);
const module: Module = try .lookup(&self.lookup_cache, gpa, context.pc);
const gop = try self.modules.getOrPut(gpa, module.load_offset);
+ self.modules.lockPointers();
+ defer self.modules.unlockPointers();
if (!gop.found_existing) gop.value_ptr.* = .init;
return module.unwindFrame(gpa, gop.value_ptr, context);
}
@@ -80,417 +96,39 @@ pub fn getSymbolAtAddress(self: *SelfInfo, gpa: Allocator, address: usize) !std.
comptime assert(target_supported);
const module: Module = try .lookup(&self.lookup_cache, gpa, address);
const gop = try self.modules.getOrPut(gpa, module.key());
+ self.modules.lockPointers();
+ defer self.modules.unlockPointers();
if (!gop.found_existing) gop.value_ptr.* = .init;
return module.getSymbolAtAddress(gpa, gop.value_ptr, address);
}
-/// Returns the module name for a given address.
-/// This can be called when getModuleForAddress fails, so implementations should provide
-/// a path that doesn't rely on any side-effects of a prior successful module lookup.
pub fn getModuleNameForAddress(self: *SelfInfo, gpa: Allocator, address: usize) error{ Unexpected, OutOfMemory, MissingDebugInfo }![]const u8 {
comptime assert(target_supported);
const module: Module = try .lookup(&self.lookup_cache, gpa, address);
return module.name;
}
+/// This type contains the target-specific implementation. It must expose the following declarations:
+///
+/// * `LookupCache: type`
+/// * `LookupCache.init: LookupCache`
+/// * `lookup: fn (*LookupCache, Allocator, address: usize) !Module`
+/// * `key: fn (*const Module) usize`
+/// * `DebugInfo: type`
+/// * `DebugInfo.init: DebugInfo`
+/// * `getSymbolAtAddress: fn (*const Module, Allocator, *DebugInfo, address: usize) !std.debug.Symbol`
+///
+/// If unwinding is supported on this target, it must additionally expose the following declarations:
+///
+/// * `unwindFrame: fn (*const Module, Allocator, *DebugInfo, *UnwindContext) !usize`
const Module = switch (native_os) {
else => {}, // Dwarf, // TODO MLUGG: it's this on master but that's definitely broken atm...
- .macos, .ios, .watchos, .tvos, .visionos => struct {
- /// The runtime address where __TEXT is loaded.
- text_base: usize,
- load_offset: usize,
- name: []const u8,
- fn key(m: *const Module) usize {
- return m.text_base;
- }
- fn lookup(cache: *LookupCache, gpa: Allocator, address: usize) !Module {
- _ = cache;
- _ = gpa;
- const image_count = std.c._dyld_image_count();
- for (0..image_count) |image_idx| {
- const header = std.c._dyld_get_image_header(@intCast(image_idx)) orelse continue;
- const text_base = @intFromPtr(header);
- if (address < text_base) continue;
- const load_offset = std.c._dyld_get_image_vmaddr_slide(@intCast(image_idx));
-
- // Find the __TEXT segment
- var it: macho.LoadCommandIterator = .{
- .ncmds = header.ncmds,
- .buffer = @as([*]u8, @ptrCast(header))[@sizeOf(macho.mach_header_64)..][0..header.sizeofcmds],
- };
- const text_segment_cmd = 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 segment_cmd;
- } else continue;
-
- const seg_start = load_offset + text_segment_cmd.vmaddr;
- assert(seg_start == text_base);
- const seg_end = seg_start + text_segment_cmd.vmsize;
- if (address < seg_start or address >= seg_end) continue;
-
- // We've found the matching __TEXT segment. This is the image we need.
- return .{
- .text_base = text_base,
- .load_offset = load_offset,
- .name = mem.span(std.c._dyld_get_image_name(@intCast(image_idx))),
- };
- }
- return error.MissingDebugInfo;
- }
- fn loadLocationInfo(module: *const Module, gpa: Allocator, di: *Module.DebugInfo) !void {
- const mapped_mem = try mapDebugInfoFile(module.name);
- errdefer posix.munmap(mapped_mem);
-
- const hdr: *const macho.mach_header_64 = @ptrCast(@alignCast(mapped_mem.ptr));
- if (hdr.magic != macho.MH_MAGIC_64)
- return error.InvalidDebugInfo;
-
- const symtab: macho.symtab_command = symtab: {
- 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()) {
- .SYMTAB => break :symtab cmd.cast(macho.symtab_command) orelse return error.InvalidDebugInfo,
- else => {},
- };
- return error.MissingDebugInfo;
- };
-
- const syms_ptr: [*]align(1) const macho.nlist_64 = @ptrCast(mapped_mem[symtab.symoff..]);
- const syms = syms_ptr[0..symtab.nsyms];
- const strings = mapped_mem[symtab.stroff..][0 .. symtab.strsize - 1 :0];
-
- var symbols: std.ArrayList(MachoSymbol) = try .initCapacity(gpa, syms.len);
- defer symbols.deinit(gpa);
-
- 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) 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,
- .size = 0,
- .ofile = ofile,
- };
- },
- else => return error.InvalidDebugInfo,
- },
- .fun => switch (state) {
- .bnsym => {
- state = .fun_strx;
- last_sym.strx = sym.n_strx;
- },
- .fun_strx => {
- state = .fun_size;
- last_sym.size = @intCast(sym.n_value);
- },
- else => return error.InvalidDebugInfo,
- },
- .ensym => switch (state) {
- .fun_size => {
- state = .ensym;
- symbols.appendAssumeCapacity(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 => return error.MissingDebugInfo,
- .oso_close => {},
- else => return error.InvalidDebugInfo,
- }
-
- 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);
-
- di.full = .{
- .mapped_memory = mapped_mem,
- .symbols = symbols_slice,
- .strings = strings,
- .ofiles = .empty,
- };
- }
- fn loadUnwindInfo(module: *const Module, gpa: Allocator, di: *Module.DebugInfo) !void {
- _ = gpa;
-
- 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 = 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();
- } else unreachable;
-
- var unwind_info: ?[]const u8 = null;
- var eh_frame: ?[]const u8 = null;
- for (sections) |sect| {
- if (mem.eql(u8, sect.sectName(), "__unwind_info")) {
- const sect_ptr: [*]u8 = @ptrFromInt(@as(usize, @intCast(module.load_offset + sect.addr)));
- 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(module.load_offset + sect.addr)));
- eh_frame = sect_ptr[0..@intCast(sect.size)];
- }
- }
- di.unwind = .{
- .unwind_info = unwind_info,
- .eh_frame = eh_frame,
- };
- }
- fn getSymbolAtAddress(module: *const Module, gpa: Allocator, di: *DebugInfo, address: usize) !std.debug.Symbol {
- if (di.full == null) try module.loadLocationInfo(gpa, di);
- const vaddr = address - module.load_offset;
- const symbol = MachoSymbol.find(di.full.?.symbols, vaddr) orelse return .{
- .name = null,
- .compile_unit_name = null,
- .source_location = null,
- };
-
- // 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(di.full.?.strings[symbol.strx..], 0);
- const o_file_path = mem.sliceTo(di.full.?.strings[symbol.ofile..], 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,
- };
-
- const o_file: *DebugInfo.OFile = of: {
- const gop = try di.full.?.ofiles.getOrPut(gpa, o_file_path);
- if (!gop.found_existing) {
- gop.value_ptr.* = DebugInfo.loadOFile(gpa, o_file_path) catch |err| {
- defer _ = di.full.?.ofiles.pop().?;
- switch (err) {
- error.MissingDebugInfo,
- error.InvalidDebugInfo,
- => return sym_only_result,
- else => |e| return e,
- }
- };
- }
- break :of gop.value_ptr;
- };
-
- const symbol_ofile_vaddr = o_file.addr_table.get(stab_symbol) orelse return sym_only_result;
-
- const compile_unit = o_file.dwarf.findCompileUnit(native_endian, symbol_ofile_vaddr) catch |err| switch (err) {
- error.MissingDebugInfo, error.InvalidDebugInfo => return sym_only_result,
- else => |e| return e,
- };
-
- return .{
- .name = o_file.dwarf.getSymbolName(symbol_ofile_vaddr) 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 |err| switch (err) {
- error.MissingDebugInfo, error.InvalidDebugInfo => null,
- else => return err,
- },
- };
- }
- fn unwindFrame(module: *const Module, gpa: Allocator, di: *DebugInfo, context: *UnwindContext) !usize {
- if (di.unwind == null) try module.loadUnwindInfo(gpa, di);
- const unwind_info = di.unwind.?.unwind_info orelse return error.MissingUnwindInfo;
- // MLUGG TODO: inline?
- return unwindFrameMachO(
- module.text_base,
- module.load_offset,
- context,
- unwind_info,
- di.unwind.?.eh_frame,
- );
- }
- const LookupCache = void;
- const DebugInfo = struct {
- unwind: ?struct {
- // Backed by the in-memory sections mapped by the loader
- unwind_info: ?[]const u8,
- eh_frame: ?[]const u8,
- },
- // MLUGG TODO: awful field name
- full: ?struct {
- mapped_memory: []align(std.heap.page_size_min) const u8,
- symbols: []const MachoSymbol,
- strings: [:0]const u8,
- // MLUGG TODO: this could use an adapter to just index straight into `strings`!
- ofiles: std.StringArrayHashMapUnmanaged(OFile),
- },
-
- const init: DebugInfo = .{
- .unwind = null,
- .full = null,
- };
-
- const OFile = struct {
- dwarf: Dwarf,
- // MLUGG TODO: this could use an adapter to just index straight into the strtab!
- addr_table: std.StringArrayHashMapUnmanaged(u64),
- };
-
- fn deinit(di: *DebugInfo, gpa: Allocator) void {
- for (di.full.ofiles.values()) |*ofile| {
- ofile.dwarf.deinit(gpa);
- ofile.addr_table.deinit(gpa);
- }
- di.full.ofiles.deinit();
- gpa.free(di.full.symbols);
- posix.munmap(di.full.mapped_memory);
- }
-
- 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 addr_table: std.StringArrayHashMapUnmanaged(u64) = .empty;
- defer addr_table.deinit(gpa);
- try addr_table.ensureUnusedCapacity(gpa, @intCast(symtab.len));
- for (symtab) |sym| {
- 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 = addr_table.getOrPutAssumeCapacity(sym_name);
- if (gop.found_existing) return error.InvalidDebugInfo;
- gop.value_ptr.* = sym.n_value;
- }
-
- 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 .{
- .dwarf = dwarf,
- .addr_table = addr_table.move(),
- };
- }
- };
- },
+ .linux, .netbsd, .freebsd, .dragonfly, .openbsd, .haiku, .solaris, .illumos => @import("SelfInfo/ElfModule.zig"),
+ .macos, .ios, .watchos, .tvos, .visionos => @import("SelfInfo/DarwinModule.zig"),
+ .uefi, .windows => @import("SelfInfo/WindowsModule.zig"),
.wasi, .emscripten => struct {
- const LookupCache = void;
- const DebugInfo = struct {
- const init: DebugInfo = .{};
+ const LookupCache = struct {
+ const init: LookupCache = .{};
};
fn lookup(cache: *LookupCache, gpa: Allocator, address: usize) !Module {
_ = cache;
@@ -498,6 +136,9 @@ const Module = switch (native_os) {
_ = address;
@panic("TODO implement lookup module for Wasm");
}
+ const DebugInfo = struct {
+ const init: DebugInfo = .{};
+ };
fn getSymbolAtAddress(module: *const Module, gpa: Allocator, di: *DebugInfo, address: usize) !std.debug.Symbol {
_ = module;
_ = gpa;
@@ -506,430 +147,9 @@ const Module = switch (native_os) {
unreachable;
}
},
- .linux, .netbsd, .freebsd, .dragonfly, .openbsd, .haiku, .solaris, .illumos => struct {
- load_offset: usize,
- name: []const u8,
- build_id: ?[]const u8,
- gnu_eh_frame: ?[]const u8,
- const LookupCache = void;
- const DebugInfo = struct {
- loaded_elf: ?Dwarf.ElfModule,
- unwind: ?Dwarf.Unwind,
- const init: DebugInfo = .{
- .loaded_elf = null,
- .unwind = null,
- };
- };
- fn key(m: Module) usize {
- return m.load_offset;
- }
- fn lookup(cache: *LookupCache, gpa: Allocator, address: usize) !Module {
- _ = cache;
- _ = gpa;
- if (native_os == .haiku) @panic("TODO implement lookup module for Haiku");
- const DlIterContext = struct {
- /// input
- address: usize,
- /// output
- module: Module,
-
- fn callback(info: *posix.dl_phdr_info, size: usize, context: *@This()) !void {
- _ = size;
- // The base address is too high
- if (context.address < info.addr)
- return;
-
- const phdrs = info.phdr[0..info.phnum];
- for (phdrs) |*phdr| {
- if (phdr.p_type != elf.PT_LOAD) continue;
-
- // Overflowing addition is used to handle the case of VSDOs having a p_vaddr = 0xffffffffff700000
- const seg_start = info.addr +% phdr.p_vaddr;
- const seg_end = seg_start + phdr.p_memsz;
- if (context.address >= seg_start and context.address < seg_end) {
- context.module = .{
- .load_offset = info.addr,
- // Android libc uses NULL instead of "" to mark the main program
- .name = mem.sliceTo(info.name, 0) orelse "",
- .build_id = null,
- .gnu_eh_frame = null,
- };
- break;
- }
- } else return;
-
- for (info.phdr[0..info.phnum]) |phdr| {
- switch (phdr.p_type) {
- elf.PT_NOTE => {
- // Look for .note.gnu.build-id
- const segment_ptr: [*]const u8 = @ptrFromInt(info.addr + phdr.p_vaddr);
- var r: std.Io.Reader = .fixed(segment_ptr[0..phdr.p_memsz]);
- const name_size = r.takeInt(u32, native_endian) catch continue;
- const desc_size = r.takeInt(u32, native_endian) catch continue;
- const note_type = r.takeInt(u32, native_endian) catch continue;
- const name = r.take(name_size) catch continue;
- if (note_type != elf.NT_GNU_BUILD_ID) continue;
- if (!mem.eql(u8, name, "GNU\x00")) continue;
- const desc = r.take(desc_size) catch continue;
- context.module.build_id = desc;
- },
- elf.PT_GNU_EH_FRAME => {
- const segment_ptr: [*]const u8 = @ptrFromInt(info.addr + phdr.p_vaddr);
- context.module.gnu_eh_frame = segment_ptr[0..phdr.p_memsz];
- },
- else => {},
- }
- }
-
- // Stop the iteration
- return error.Found;
- }
- };
- var ctx: DlIterContext = .{
- .address = address,
- .module = undefined,
- };
- posix.dl_iterate_phdr(&ctx, error{Found}, DlIterContext.callback) catch |err| switch (err) {
- error.Found => return ctx.module,
- };
- return error.MissingDebugInfo;
- }
- fn loadLocationInfo(module: *const Module, gpa: Allocator, di: *Module.DebugInfo) !void {
- if (module.name.len > 0) {
- di.loaded_elf = Dwarf.ElfModule.load(gpa, .{
- .root_dir = .cwd(),
- .sub_path = module.name,
- }, module.build_id, null, null, null) catch |err| switch (err) {
- error.FileNotFound => return error.MissingDebugInfo,
- error.Overflow => return error.InvalidDebugInfo,
- else => |e| return e,
- };
- } else {
- const path = try std.fs.selfExePathAlloc(gpa);
- defer gpa.free(path);
- di.loaded_elf = Dwarf.ElfModule.load(gpa, .{
- .root_dir = .cwd(),
- .sub_path = path,
- }, module.build_id, null, null, null) catch |err| switch (err) {
- error.FileNotFound => return error.MissingDebugInfo,
- error.Overflow => return error.InvalidDebugInfo,
- else => |e| return e,
- };
- }
- }
- fn getSymbolAtAddress(module: *const Module, gpa: Allocator, di: *DebugInfo, address: usize) !std.debug.Symbol {
- if (di.loaded_elf == null) try module.loadLocationInfo(gpa, di);
- const vaddr = address - module.load_offset;
- return di.loaded_elf.?.dwarf.getSymbol(gpa, native_endian, vaddr);
- }
- fn loadUnwindInfo(module: *const Module, gpa: Allocator, di: *Module.DebugInfo) !void {
- const section_bytes = module.gnu_eh_frame orelse return error.MissingUnwindInfo; // MLUGG TODO: load from file
- const section_vaddr: u64 = @intFromPtr(section_bytes.ptr) - module.load_offset;
- const header: Dwarf.Unwind.EhFrameHeader = try .parse(section_vaddr, section_bytes, @sizeOf(usize), native_endian);
- di.unwind = .initEhFrameHdr(header, section_vaddr, @ptrFromInt(module.load_offset + header.eh_frame_vaddr));
- try di.unwind.?.prepareLookup(gpa, @sizeOf(usize), native_endian);
- }
- fn unwindFrame(module: *const Module, gpa: Allocator, di: *DebugInfo, context: *UnwindContext) !usize {
- if (di.unwind == null) try module.loadUnwindInfo(gpa, di);
- return unwindFrameDwarf(&di.unwind.?, module.load_offset, context, null);
- }
- },
- .uefi, .windows => struct {
- base_address: usize,
- size: usize,
- name: []const u8,
- handle: windows.HMODULE,
- fn key(m: Module) usize {
- return m.base_address;
- }
- fn lookup(cache: *LookupCache, gpa: Allocator, address: usize) !Module {
- if (lookupInCache(cache, address)) |m| return m;
- {
- // Check a new module hasn't been loaded
- cache.modules.clearRetainingCapacity();
-
- const handle = windows.kernel32.CreateToolhelp32Snapshot(windows.TH32CS_SNAPMODULE | windows.TH32CS_SNAPMODULE32, 0);
- if (handle == windows.INVALID_HANDLE_VALUE) {
- return windows.unexpectedError(windows.GetLastError());
- }
- defer windows.CloseHandle(handle);
-
- var entry: windows.MODULEENTRY32 = undefined;
- entry.dwSize = @sizeOf(windows.MODULEENTRY32);
- if (windows.kernel32.Module32First(handle, &entry) != 0) {
- try cache.modules.append(gpa, entry);
- while (windows.kernel32.Module32Next(handle, &entry) != 0) {
- try cache.modules.append(gpa, entry);
- }
- }
- }
- if (lookupInCache(cache, address)) |m| return m;
- return error.MissingDebugInfo;
- }
- fn lookupInCache(cache: *const LookupCache, address: usize) ?Module {
- for (cache.modules.items) |*entry| {
- const base_address = @intFromPtr(entry.modBaseAddr);
- if (address >= base_address and address < base_address + entry.modBaseSize) {
- return .{
- .base_address = base_address,
- .size = entry.modBaseSize,
- .name = std.mem.sliceTo(&entry.szModule, 0),
- .handle = entry.hModule,
- };
- }
- }
- return null;
- }
- fn loadLocationInfo(module: *const Module, gpa: Allocator, di: *DebugInfo) !void {
- const mapped_ptr: [*]const u8 = @ptrFromInt(module.base_address);
- const mapped = mapped_ptr[0..module.size];
- var coff_obj = coff.Coff.init(mapped, true) catch return error.InvalidDebugInfo;
- // The string table is not mapped into memory by the loader, so if a section name is in the
- // string table then we have to map the full image file from disk. This can happen when
- // a binary is produced with -gdwarf, since the section names are longer than 8 bytes.
- if (coff_obj.strtabRequired()) {
- var name_buffer: [windows.PATH_MAX_WIDE + 4:0]u16 = undefined;
- name_buffer[0..4].* = .{ '\\', '?', '?', '\\' }; // openFileAbsoluteW requires the prefix to be present
- const process_handle = windows.GetCurrentProcess();
- const len = windows.kernel32.GetModuleFileNameExW(
- process_handle,
- module.handle,
- name_buffer[4..],
- windows.PATH_MAX_WIDE,
- );
- if (len == 0) return error.MissingDebugInfo;
- const coff_file = fs.openFileAbsoluteW(name_buffer[0 .. len + 4 :0], .{}) catch |err| switch (err) {
- error.FileNotFound => return error.MissingDebugInfo,
- else => |e| return e,
- };
- errdefer coff_file.close();
- var section_handle: windows.HANDLE = undefined;
- const create_section_rc = windows.ntdll.NtCreateSection(
- §ion_handle,
- windows.STANDARD_RIGHTS_REQUIRED | windows.SECTION_QUERY | windows.SECTION_MAP_READ,
- null,
- null,
- windows.PAGE_READONLY,
- // The documentation states that if no AllocationAttribute is specified, then SEC_COMMIT is the default.
- // In practice, this isn't the case and specifying 0 will result in INVALID_PARAMETER_6.
- windows.SEC_COMMIT,
- coff_file.handle,
- );
- if (create_section_rc != .SUCCESS) return error.MissingDebugInfo;
- errdefer windows.CloseHandle(section_handle);
- var coff_len: usize = 0;
- var section_view_ptr: [*]const u8 = undefined;
- const map_section_rc = windows.ntdll.NtMapViewOfSection(
- section_handle,
- process_handle,
- @ptrCast(§ion_view_ptr),
- null,
- 0,
- null,
- &coff_len,
- .ViewUnmap,
- 0,
- windows.PAGE_READONLY,
- );
- if (map_section_rc != .SUCCESS) return error.MissingDebugInfo;
- errdefer assert(windows.ntdll.NtUnmapViewOfSection(process_handle, @constCast(section_view_ptr)) == .SUCCESS);
- const section_view = section_view_ptr[0..coff_len];
- coff_obj = coff.Coff.init(section_view, false) catch return error.InvalidDebugInfo;
- di.mapped_file = .{
- .file = coff_file,
- .section_handle = section_handle,
- .section_view = section_view,
- };
- }
- di.coff_image_base = coff_obj.getImageBase();
-
- if (coff_obj.getSectionByName(".debug_info")) |_| {
- di.dwarf = .{};
-
- inline for (@typeInfo(Dwarf.Section.Id).@"enum".fields, 0..) |section, i| {
- di.dwarf.?.sections[i] = if (coff_obj.getSectionByName("." ++ section.name)) |section_header| blk: {
- break :blk .{
- .data = try coff_obj.getSectionDataAlloc(section_header, gpa),
- .owned = true,
- };
- } else null;
- }
-
- try di.dwarf.?.open(gpa, native_endian);
- }
-
- if (try coff_obj.getPdbPath()) |raw_path| pdb: {
- const path = blk: {
- if (fs.path.isAbsolute(raw_path)) {
- break :blk raw_path;
- } else {
- const self_dir = try fs.selfExeDirPathAlloc(gpa);
- defer gpa.free(self_dir);
- break :blk try fs.path.join(gpa, &.{ self_dir, raw_path });
- }
- };
- defer if (path.ptr != raw_path.ptr) gpa.free(path);
-
- di.pdb = Pdb.init(gpa, path) catch |err| switch (err) {
- error.FileNotFound, error.IsDir => break :pdb,
- else => return err,
- };
- try di.pdb.?.parseInfoStream();
- try di.pdb.?.parseDbiStream();
-
- if (!mem.eql(u8, &coff_obj.guid, &di.pdb.?.guid) or coff_obj.age != di.pdb.?.age)
- return error.InvalidDebugInfo;
-
- di.coff_section_headers = try coff_obj.getSectionHeadersAlloc(gpa);
- }
-
- di.loaded = true;
- }
- const LookupCache = struct {
- modules: std.ArrayListUnmanaged(windows.MODULEENTRY32),
- const init: LookupCache = .{ .modules = .empty };
- };
- const DebugInfo = struct {
- loaded: bool,
-
- coff_image_base: u64,
- mapped_file: ?struct {
- file: File,
- section_handle: windows.HANDLE,
- section_view: []const u8,
- fn deinit(mapped: @This()) void {
- const process_handle = windows.GetCurrentProcess();
- assert(windows.ntdll.NtUnmapViewOfSection(process_handle, @constCast(mapped.section_view.ptr)) == .SUCCESS);
- windows.CloseHandle(mapped.section_handle);
- mapped.file.close();
- }
- },
-
- dwarf: ?Dwarf,
-
- pdb: ?Pdb,
- /// Populated iff `pdb != null`; otherwise `&.{}`.
- coff_section_headers: []coff.SectionHeader,
-
- const init: DebugInfo = .{
- .loaded = false,
- .coff_image_base = undefined,
- .mapped_file = null,
- .dwarf = null,
- .pdb = null,
- .coff_section_headers = &.{},
- };
-
- fn deinit(di: *DebugInfo, gpa: Allocator) void {
- if (di.dwarf) |*dwarf| dwarf.deinit(gpa);
- if (di.pdb) |*pdb| pdb.deinit();
- gpa.free(di.coff_section_headers);
- if (di.mapped_file) |mapped| mapped.deinit();
- }
-
- fn getSymbolFromPdb(di: *DebugInfo, relocated_address: usize) !?std.debug.Symbol {
- var coff_section: *align(1) const coff.SectionHeader = undefined;
- const mod_index = for (di.pdb.?.sect_contribs) |sect_contrib| {
- if (sect_contrib.section > di.coff_section_headers.len) continue;
- // Remember that SectionContribEntry.Section is 1-based.
- coff_section = &di.coff_section_headers[sect_contrib.section - 1];
-
- const vaddr_start = coff_section.virtual_address + sect_contrib.offset;
- const vaddr_end = vaddr_start + sect_contrib.size;
- if (relocated_address >= vaddr_start and relocated_address < vaddr_end) {
- break sect_contrib.module_index;
- }
- } else {
- // we have no information to add to the address
- return null;
- };
-
- const module = try di.pdb.?.getModule(mod_index) orelse return error.InvalidDebugInfo;
-
- return .{
- .name = di.pdb.?.getSymbolName(
- module,
- relocated_address - coff_section.virtual_address,
- ),
- .compile_unit_name = fs.path.basename(module.obj_file_name),
- .source_location = try di.pdb.?.getLineNumberInfo(
- module,
- relocated_address - coff_section.virtual_address,
- ),
- };
- }
- };
-
- fn getSymbolAtAddress(module: *const Module, gpa: Allocator, di: *DebugInfo, address: usize) !std.debug.Symbol {
- if (!di.loaded) try module.loadLocationInfo(gpa, di);
- // Translate the runtime address into a virtual address into the module
- const vaddr = address - module.base_address;
-
- if (di.pdb != null) {
- if (try di.getSymbolFromPdb(vaddr)) |symbol| return symbol;
- }
-
- if (di.dwarf) |*dwarf| {
- const dwarf_address = vaddr + di.coff_image_base;
- return dwarf.getSymbol(gpa, native_endian, dwarf_address);
- }
-
- return error.MissingDebugInfo;
- }
- },
-};
-
-const MachoSymbol = struct {
- strx: u32,
- addr: u64,
- size: u32,
- ofile: 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, .size = undefined, .ofile = undefined },
- .{ .addr = 200, .strx = undefined, .size = undefined, .ofile = undefined },
- .{ .addr = 300, .strx = undefined, .size = 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;
+ _ = Module;
}
pub const UnwindContext = struct {
@@ -944,6 +164,7 @@ pub const UnwindContext = struct {
pub fn init(gpa: Allocator, thread_context: *std.debug.ThreadContext) !UnwindContext {
comptime assert(supports_unwinding);
+ const ip_reg_num = Dwarf.abi.ipRegNum(native_arch).?;
const pc = stripInstructionPtrAuthCode(
(try regValueNative(thread_context, ip_reg_num, null)).*,
);
@@ -970,7 +191,7 @@ pub const UnwindContext = struct {
}
pub fn getFp(self: *const UnwindContext) !usize {
- return (try regValueNative(self.thread_context, fpRegNum(self.reg_context), self.reg_context)).*;
+ return (try regValueNative(self.thread_context, Dwarf.abi.fpRegNum(native_arch, self.reg_context), self.reg_context)).*;
}
/// Resolves the register rule and places the result into `out` (see regBytes)
@@ -1019,7 +240,7 @@ pub const UnwindContext = struct {
.register => |register| {
const src = try regBytes(context.thread_context, register, context.reg_context);
if (src.len != out.len) return error.RegisterSizeMismatch;
- @memcpy(out, try regBytes(context.thread_context, register, context.reg_context));
+ @memcpy(out, src);
},
.expression => |expression| {
context.stack_machine.reset();
@@ -1043,553 +264,171 @@ pub const UnwindContext = struct {
.architectural => return error.UnimplementedRegisterRule,
}
}
-};
-
-/// Some platforms use pointer authentication - the upper bits of instruction pointers contain a signature.
-/// This function clears these signature bits to make the pointer usable.
-pub inline fn stripInstructionPtrAuthCode(ptr: usize) usize {
- if (native_arch.isAARCH64()) {
- // `hint 0x07` maps to `xpaclri` (or `nop` if the hardware doesn't support it)
- // The save / restore is because `xpaclri` operates on x30 (LR)
- return asm (
- \\mov x16, x30
- \\mov x30, x15
- \\hint 0x07
- \\mov x15, x30
- \\mov x30, x16
- : [ret] "={x15}" (-> usize),
- : [ptr] "{x15}" (ptr),
- : .{ .x16 = true });
- }
-
- return ptr;
-}
-
-/// Unwind a stack frame using DWARF unwinding info, updating the register context.
-///
-/// If `.eh_frame_hdr` is available and complete, it will be used to binary search for the FDE.
-/// Otherwise, a linear scan of `.eh_frame` and `.debug_frame` is done to find the FDE. The latter
-/// may require lazily loading the data in those sections.
-///
-/// `explicit_fde_offset` is for cases where the FDE offset is known, such as when __unwind_info
-/// defers unwinding to DWARF. This is an offset into the `.eh_frame` section.
-fn unwindFrameDwarf(
- unwind: *const Dwarf.Unwind,
- load_offset: usize,
- context: *UnwindContext,
- explicit_fde_offset: ?usize,
-) !usize {
- if (!supports_unwinding) return error.UnsupportedCpuArchitecture;
- if (context.pc == 0) return 0;
-
- const pc_vaddr = context.pc - load_offset;
- const fde_offset = explicit_fde_offset orelse try unwind.lookupPc(
- pc_vaddr,
- @sizeOf(usize),
- native_endian,
- ) orelse return error.MissingDebugInfo;
- const format, const cie, const fde = try unwind.getFde(fde_offset, @sizeOf(usize), native_endian);
+ /// Unwind a stack frame using DWARF unwinding info, updating the register context.
+ ///
+ /// If `.eh_frame_hdr` is available and complete, it will be used to binary search for the FDE.
+ /// Otherwise, a linear scan of `.eh_frame` and `.debug_frame` is done to find the FDE. The latter
+ /// may require lazily loading the data in those sections.
+ ///
+ /// `explicit_fde_offset` is for cases where the FDE offset is known, such as when __unwind_info
+ /// defers unwinding to DWARF. This is an offset into the `.eh_frame` section.
+ pub fn unwindFrameDwarf(
+ context: *UnwindContext,
+ unwind: *const Dwarf.Unwind,
+ load_offset: usize,
+ explicit_fde_offset: ?usize,
+ ) !usize {
+ if (!supports_unwinding) return error.UnsupportedCpuArchitecture;
+ if (context.pc == 0) return 0;
+
+ const pc_vaddr = context.pc - load_offset;
+
+ const fde_offset = explicit_fde_offset orelse try unwind.lookupPc(
+ pc_vaddr,
+ @sizeOf(usize),
+ native_endian,
+ ) orelse return error.MissingDebugInfo;
+ const format, const cie, const fde = try unwind.getFde(fde_offset, @sizeOf(usize), native_endian);
+
+ // Check if this FDE *actually* includes the address.
+ if (pc_vaddr < fde.pc_begin or pc_vaddr >= fde.pc_begin + fde.pc_range) return error.MissingDebugInfo;
+
+ // Do not set `compile_unit` because the spec states that CFIs
+ // may not reference other debug sections anyway.
+ var expression_context: Dwarf.expression.Context = .{
+ .format = format,
+ .thread_context = context.thread_context,
+ .reg_context = context.reg_context,
+ .cfa = context.cfa,
+ };
- // Check if this FDE *actually* includes the address.
- if (pc_vaddr < fde.pc_begin or pc_vaddr >= fde.pc_begin + fde.pc_range) return error.MissingDebugInfo;
+ context.vm.reset();
+ context.reg_context.eh_frame = cie.version != 4;
+ context.reg_context.is_macho = native_os.isDarwin();
- // Do not set `compile_unit` because the spec states that CFIs
- // may not reference other debug sections anyway.
- var expression_context: Dwarf.expression.Context = .{
- .format = format,
- .thread_context = context.thread_context,
- .reg_context = context.reg_context,
- .cfa = context.cfa,
- };
+ const row = try context.vm.runTo(context.gpa, context.pc - load_offset, cie, fde, @sizeOf(usize), native_endian);
+ context.cfa = switch (row.cfa.rule) {
+ .val_offset => |offset| blk: {
+ const register = row.cfa.register orelse return error.InvalidCFARule;
+ const value = (try regValueNative(context.thread_context, register, context.reg_context)).*;
+ break :blk try applyOffset(value, offset);
+ },
+ .expression => |expr| blk: {
+ context.stack_machine.reset();
+ const value = try context.stack_machine.run(
+ expr,
+ context.gpa,
+ expression_context,
+ context.cfa,
+ );
- context.vm.reset();
- context.reg_context.eh_frame = cie.version != 4;
- context.reg_context.is_macho = native_os.isDarwin();
+ if (value) |v| {
+ if (v != .generic) return error.InvalidExpressionValue;
+ break :blk v.generic;
+ } else return error.NoExpressionValue;
+ },
+ else => return error.InvalidCFARule,
+ };
- const row = try context.vm.runTo(context.gpa, context.pc - load_offset, cie, fde, @sizeOf(usize), native_endian);
- context.cfa = switch (row.cfa.rule) {
- .val_offset => |offset| blk: {
- const register = row.cfa.register orelse return error.InvalidCFARule;
- const value = mem.readInt(usize, (try regBytes(context.thread_context, register, context.reg_context))[0..@sizeOf(usize)], native_endian);
- break :blk try applyOffset(value, offset);
- },
- .expression => |expr| blk: {
- context.stack_machine.reset();
- const value = try context.stack_machine.run(
- expr,
- context.gpa,
- expression_context,
- context.cfa,
- );
+ expression_context.cfa = context.cfa;
- if (value) |v| {
- if (v != .generic) return error.InvalidExpressionValue;
- break :blk v.generic;
- } else return error.NoExpressionValue;
- },
- else => return error.InvalidCFARule,
- };
+ // Buffering the modifications is done because copying the thread context is not portable,
+ // some implementations (ie. darwin) use internal pointers to the mcontext.
+ var arena: std.heap.ArenaAllocator = .init(context.gpa);
+ defer arena.deinit();
+ const update_arena = arena.allocator();
- expression_context.cfa = context.cfa;
+ const RegisterUpdate = struct {
+ // Backed by thread_context
+ dest: []u8,
+ // Backed by arena
+ src: []const u8,
+ prev: ?*@This(),
+ };
- // Buffering the modifications is done because copying the thread context is not portable,
- // some implementations (ie. darwin) use internal pointers to the mcontext.
- var arena: std.heap.ArenaAllocator = .init(context.gpa);
- defer arena.deinit();
- const update_arena = arena.allocator();
+ var update_tail: ?*RegisterUpdate = null;
+ var has_return_address = true;
+ for (context.vm.rowColumns(row)) |column| {
+ if (column.register) |register| {
+ if (register == cie.return_address_register) {
+ has_return_address = column.rule != .undefined;
+ }
- const RegisterUpdate = struct {
- // Backed by thread_context
- dest: []u8,
- // Backed by arena
- src: []const u8,
- prev: ?*@This(),
- };
+ const dest = try regBytes(context.thread_context, register, context.reg_context);
+ const src = try update_arena.alloc(u8, dest.len);
+ try context.resolveRegisterRule(column, expression_context, src);
- var update_tail: ?*RegisterUpdate = null;
- var has_return_address = true;
- for (context.vm.rowColumns(row)) |column| {
- if (column.register) |register| {
- if (register == cie.return_address_register) {
- has_return_address = column.rule != .undefined;
+ const new_update = try update_arena.create(RegisterUpdate);
+ new_update.* = .{
+ .dest = dest,
+ .src = src,
+ .prev = update_tail,
+ };
+ update_tail = new_update;
}
+ }
- const dest = try regBytes(context.thread_context, register, context.reg_context);
- const src = try update_arena.alloc(u8, dest.len);
- try context.resolveRegisterRule(column, expression_context, src);
+ // On all implemented architectures, the CFA is defined as being the previous frame's SP
+ (try regValueNative(context.thread_context, Dwarf.abi.spRegNum(native_arch, context.reg_context), context.reg_context)).* = context.cfa.?;
- const new_update = try update_arena.create(RegisterUpdate);
- new_update.* = .{
- .dest = dest,
- .src = src,
- .prev = update_tail,
- };
- update_tail = new_update;
+ while (update_tail) |tail| {
+ @memcpy(tail.dest, tail.src);
+ update_tail = tail.prev;
}
- }
- // On all implemented architectures, the CFA is defined as being the previous frame's SP
- (try regValueNative(context.thread_context, spRegNum(context.reg_context), context.reg_context)).* = context.cfa.?;
+ if (has_return_address) {
+ context.pc = stripInstructionPtrAuthCode((try regValueNative(
+ context.thread_context,
+ cie.return_address_register,
+ context.reg_context,
+ )).*);
+ } else {
+ context.pc = 0;
+ }
- while (update_tail) |tail| {
- @memcpy(tail.dest, tail.src);
- update_tail = tail.prev;
+ const ip_reg_num = Dwarf.abi.ipRegNum(native_arch).?;
+ (try regValueNative(context.thread_context, ip_reg_num, context.reg_context)).* = context.pc;
+
+ // The call instruction will have pushed the address of the instruction that follows the call as the return address.
+ // This next instruction may be past the end of the function if the caller was `noreturn` (ie. the last instruction in
+ // the function was the call). If we were to look up an FDE entry using the return address directly, it could end up
+ // either not finding an FDE at all, or using the next FDE in the program, producing incorrect results. To prevent this,
+ // we subtract one so that the next lookup is guaranteed to land inside the
+ //
+ // The exception to this rule is signal frames, where we return execution would be returned to the instruction
+ // that triggered the handler.
+ const return_address = context.pc;
+ if (context.pc > 0 and !cie.is_signal_frame) context.pc -= 1;
+
+ return return_address;
}
-
- if (has_return_address) {
- context.pc = stripInstructionPtrAuthCode(mem.readInt(usize, (try regBytes(
- context.thread_context,
- cie.return_address_register,
- context.reg_context,
- ))[0..@sizeOf(usize)], native_endian));
- } else {
- context.pc = 0;
+ /// Since register rules are applied (usually) during a panic,
+ /// checked addition / subtraction is used so that we can return
+ /// an error and fall back to FP-based unwinding.
+ fn applyOffset(base: usize, offset: i64) !usize {
+ return if (offset >= 0)
+ try std.math.add(usize, base, @as(usize, @intCast(offset)))
+ else
+ try std.math.sub(usize, base, @as(usize, @intCast(-offset)));
}
-
- (try regValueNative(context.thread_context, ip_reg_num, context.reg_context)).* = context.pc;
-
- // The call instruction will have pushed the address of the instruction that follows the call as the return address.
- // This next instruction may be past the end of the function if the caller was `noreturn` (ie. the last instruction in
- // the function was the call). If we were to look up an FDE entry using the return address directly, it could end up
- // either not finding an FDE at all, or using the next FDE in the program, producing incorrect results. To prevent this,
- // we subtract one so that the next lookup is guaranteed to land inside the
- //
- // The exception to this rule is signal frames, where we return execution would be returned to the instruction
- // that triggered the handler.
- const return_address = context.pc;
- if (context.pc > 0 and !cie.is_signal_frame) context.pc -= 1;
-
- return return_address;
-}
-
-fn fpRegNum(reg_context: Dwarf.abi.RegisterContext) u8 {
- return Dwarf.abi.fpRegNum(native_arch, reg_context);
-}
-
-fn spRegNum(reg_context: Dwarf.abi.RegisterContext) u8 {
- return Dwarf.abi.spRegNum(native_arch, reg_context);
-}
-
-const ip_reg_num = Dwarf.abi.ipRegNum(native_arch).?;
-
-/// Tells whether unwinding for the host is implemented.
-pub const supports_unwinding = supportsUnwinding(&builtin.target);
-
-comptime {
- if (supports_unwinding) assert(Dwarf.abi.supportsUnwinding(&builtin.target));
-}
-
-/// Tells whether unwinding for this target is *implemented* here in the Zig
-/// standard library.
-///
-/// See also `Dwarf.abi.supportsUnwinding` which tells whether Dwarf supports
-/// unwinding on that target *in theory*.
-pub fn supportsUnwinding(target: *const std.Target) bool {
- return switch (target.cpu.arch) {
- .x86 => switch (target.os.tag) {
- .linux, .netbsd, .solaris, .illumos => true,
- else => false,
- },
- .x86_64 => switch (target.os.tag) {
- .linux, .netbsd, .freebsd, .openbsd, .macos, .ios, .solaris, .illumos => true,
- else => false,
- },
- .arm, .armeb, .thumb, .thumbeb => switch (target.os.tag) {
- .linux => true,
- else => false,
- },
- .aarch64, .aarch64_be => switch (target.os.tag) {
- .linux, .netbsd, .freebsd, .macos, .ios => true,
- else => false,
- },
- // Unwinding is possible on other targets but this implementation does
- // not support them...yet!
- else => false,
- };
-}
-
-/// Since register rules are applied (usually) during a panic,
-/// checked addition / subtraction is used so that we can return
-/// an error and fall back to FP-based unwinding.
-fn applyOffset(base: usize, offset: i64) !usize {
- return if (offset >= 0)
- try std.math.add(usize, base, @as(usize, @intCast(offset)))
- else
- try std.math.sub(usize, base, @as(usize, @intCast(-offset)));
-}
-
-/// Uses `mmap` to map the file at `opt_path` (or, if `null`, the self executable image) into memory.
-fn mapDebugInfoFile(opt_path: ?[]const u8) ![]align(std.heap.page_size_min) const u8 {
- const open_result = if (opt_path) |path|
- fs.cwd().openFile(path, .{})
- else
- fs.openSelfExe(.{});
- const file = open_result catch |err| switch (err) {
- error.FileNotFound => return error.MissingDebugInfo,
- else => |e| return e,
- };
- defer file.close();
-
- const file_len = math.cast(usize, try file.getEndPos()) orelse return error.InvalidDebugInfo;
-
- return posix.mmap(
- null,
- file_len,
- posix.PROT.READ,
- .{ .TYPE = .SHARED },
- file.handle,
- 0,
- );
-}
-
-/// 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.
-fn unwindFrameMachO(
- text_base: usize,
- load_offset: usize,
- context: *UnwindContext,
- unwind_info: []const u8,
- opt_eh_frame: ?[]const u8,
-) !usize {
- if (unwind_info.len < @sizeOf(macho.unwind_info_section_header)) return error.InvalidUnwindInfo;
- 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.InvalidUnwindInfo;
- 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.MissingUnwindInfo;
-
- // offset of the PC into the `__TEXT` segment
- const pc_text_offset = context.pc - 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;
- }
+ /// Some platforms use pointer authentication - the upper bits of instruction pointers contain a signature.
+ /// This function clears these signature bits to make the pointer usable.
+ pub inline fn stripInstructionPtrAuthCode(ptr: usize) usize {
+ if (native_arch.isAARCH64()) {
+ // `hint 0x07` maps to `xpaclri` (or `nop` if the hardware doesn't support it)
+ // The save / restore is because `xpaclri` operates on x30 (LR)
+ return asm (
+ \\mov x16, x30
+ \\mov x30, x15
+ \\hint 0x07
+ \\mov x15, x30
+ \\mov x30, x16
+ : [ret] "={x15}" (-> usize),
+ : [ptr] "{x15}" (ptr),
+ : .{ .x16 = true });
}
- 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.MissingUnwindInfo;
-
- 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.InvalidUnwindInfo;
- 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.InvalidUnwindInfo;
- 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.InvalidUnwindInfo;
- 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.InvalidUnwindInfo;
- 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.InvalidUnwindInfo;
-
- 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.InvalidUnwindInfo;
- 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.InvalidUnwindInfo;
- 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.InvalidUnwindInfo;
-
- 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.InvalidUnwindInfo;
- 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.InvalidUnwindInfo;
- break :entry .{
- .function_offset = function_offset,
- .raw_encoding = local_encodings[local_index],
- };
- },
- else => return error.InvalidUnwindInfo,
- };
-
- if (entry.raw_encoding == 0) return error.NoUnwindInfo;
- const reg_context: Dwarf.abi.RegisterContext = .{ .eh_frame = false, .is_macho = true };
- 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.UnimplementedUnwindEncoding,
- .RBP_FRAME => ip: {
- const frame = encoding.value.x86_64.frame;
-
- const fp = (try regValueNative(context.thread_context, fpRegNum(reg_context), reg_context)).*;
- 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 regValueNative(context.thread_context, fpRegNum(reg_context), reg_context)).* = new_fp;
- (try regValueNative(context.thread_context, spRegNum(reg_context), reg_context)).* = new_sp;
- (try regValueNative(context.thread_context, ip_reg_num, reg_context)).* = 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 regValueNative(context.thread_context, reg_number, reg_context)).* = @as(*const usize, @ptrFromInt(addr)).*;
- }
-
- break :ip new_ip;
- },
- .STACK_IMMD,
- .STACK_IND,
- => ip: {
- const frameless = encoding.value.x86_64.frameless;
-
- const sp = (try regValueNative(context.thread_context, spRegNum(reg_context), reg_context)).*;
- 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 =
- 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 regValueNative(context.thread_context, reg_number, reg_context)).* = @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 regValueNative(context.thread_context, spRegNum(reg_context), reg_context)).* = new_sp;
- (try regValueNative(context.thread_context, ip_reg_num, reg_context)).* = new_ip;
-
- break :ip new_ip;
- },
- .DWARF => {
- const eh_frame = opt_eh_frame orelse return error.MissingEhFrame;
- const eh_frame_vaddr = @intFromPtr(eh_frame.ptr) - load_offset;
- return unwindFrameDwarf(
- &.initSection(.eh_frame, eh_frame_vaddr, eh_frame),
- load_offset,
- context,
- @intCast(encoding.value.x86_64.dwarf),
- );
- },
- },
- .aarch64, .aarch64_be => switch (encoding.mode.arm64) {
- .OLD => return error.UnimplementedUnwindEncoding,
- .FRAMELESS => ip: {
- const sp = (try regValueNative(context.thread_context, spRegNum(reg_context), reg_context)).*;
- const new_sp = sp + encoding.value.arm64.frameless.stack_size * 16;
- const new_ip = (try regValueNative(context.thread_context, 30, reg_context)).*;
- (try regValueNative(context.thread_context, spRegNum(reg_context), reg_context)).* = new_sp;
- break :ip new_ip;
- },
- .DWARF => {
- const eh_frame = opt_eh_frame orelse return error.MissingEhFrame;
- const eh_frame_vaddr = @intFromPtr(eh_frame.ptr) - load_offset;
- return unwindFrameDwarf(
- &.initSection(.eh_frame, eh_frame_vaddr, eh_frame),
- load_offset,
- context,
- @intCast(encoding.value.x86_64.dwarf),
- );
- },
- .FRAME => ip: {
- const frame = encoding.value.arm64.frame;
-
- const fp = (try regValueNative(context.thread_context, fpRegNum(reg_context), reg_context)).*;
- 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 regValueNative(context.thread_context, 19 + i, reg_context)).* = @as(*const usize, @ptrFromInt(reg_addr)).*;
- reg_addr += @sizeOf(usize);
- (try regValueNative(context.thread_context, 20 + i, reg_context)).* = @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 regBytes(context.thread_context, 64 + 8 + i, context.reg_context));
- dest.* = @as(*const usize, @ptrFromInt(reg_addr)).*;
- }
- reg_addr += @sizeOf(usize);
- {
- const dest: *align(1) usize = @ptrCast(try regBytes(context.thread_context, 64 + 9 + i, context.reg_context));
- 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 regValueNative(context.thread_context, fpRegNum(reg_context), reg_context)).* = new_fp;
- (try regValueNative(context.thread_context, ip_reg_num, reg_context)).* = new_ip;
-
- break :ip new_ip;
- },
- },
- else => comptime unreachable, // unimplemented
- };
-
- context.pc = stripInstructionPtrAuthCode(new_ip);
- if (context.pc > 0) context.pc -= 1;
- return new_ip;
-}
+ return ptr;
+ }
+};