diff options
| author | mlugg <mlugg@mlugg.co.uk> | 2025-09-01 16:50:39 +0100 |
|---|---|---|
| committer | mlugg <mlugg@mlugg.co.uk> | 2025-09-30 13:44:49 +0100 |
| commit | b750e7cf9e2a1225b20ef7fdf53df9ef97cf8065 (patch) | |
| tree | d4760dd9e1279db621fbf4318264f951117a2172 /lib/std/debug/SelfInfo.zig | |
| parent | b706949736fe67e104a14ac1dcaac8b7eb1cc33f (diff) | |
| download | zig-b750e7cf9e2a1225b20ef7fdf53df9ef97cf8065.tar.gz zig-b750e7cf9e2a1225b20ef7fdf53df9ef97cf8065.zip | |
change one million things
Diffstat (limited to 'lib/std/debug/SelfInfo.zig')
| -rw-r--r-- | lib/std/debug/SelfInfo.zig | 2845 |
1 files changed, 1094 insertions, 1751 deletions
diff --git a/lib/std/debug/SelfInfo.zig b/lib/std/debug/SelfInfo.zig index 4a29eb0fa7..daa5cf12d2 100644 --- a/lib/std/debug/SelfInfo.zig +++ b/lib/std/debug/SelfInfo.zig @@ -13,7 +13,6 @@ const windows = std.os.windows; const macho = std.macho; const fs = std.fs; const coff = std.coff; -const pdb = std.pdb; const assert = std.debug.assert; const posix = std.posix; const elf = std.elf; @@ -22,86 +21,37 @@ const Pdb = std.debug.Pdb; const File = std.fs.File; const math = std.math; const testing = std.testing; -const StackIterator = std.debug.StackIterator; const regBytes = Dwarf.abi.regBytes; const regValueNative = Dwarf.abi.regValueNative; const SelfInfo = @This(); -const root = @import("root"); - -allocator: Allocator, -address_map: std.AutoHashMapUnmanaged(usize, Module), -modules: if (native_os == .windows) std.ArrayListUnmanaged(WindowsModule) else void, - -pub const OpenError = error{ - MissingDebugInfo, - UnsupportedOperatingSystem, -} || @typeInfo(@typeInfo(@TypeOf(SelfInfo.init)).@"fn".return_type.?).error_union.error_set; - -pub fn open(allocator: Allocator) OpenError!SelfInfo { - if (builtin.strip_debug_info) - return error.MissingDebugInfo; - switch (native_os) { - .linux, - .freebsd, - .netbsd, - .dragonfly, - .openbsd, - .macos, - .solaris, - .illumos, - .windows, - => return try SelfInfo.init(allocator), - else => return error.UnsupportedOperatingSystem, - } -} - -pub fn init(allocator: Allocator) !SelfInfo { - var debug_info: SelfInfo = .{ - .allocator = allocator, - .address_map = .empty, - .modules = if (native_os == .windows) .{} else {}, - }; - - if (native_os == .windows) { - errdefer debug_info.modules.deinit(allocator); - - const handle = windows.kernel32.CreateToolhelp32Snapshot(windows.TH32CS_SNAPMODULE | windows.TH32CS_SNAPMODULE32, 0); - if (handle == windows.INVALID_HANDLE_VALUE) { - switch (windows.GetLastError()) { - else => |err| return windows.unexpectedError(err), - } - } - defer windows.CloseHandle(handle); - - var module_entry: windows.MODULEENTRY32 = undefined; - module_entry.dwSize = @sizeOf(windows.MODULEENTRY32); - if (windows.kernel32.Module32First(handle, &module_entry) == 0) { - return error.MissingDebugInfo; - } - - var module_valid = true; - while (module_valid) { - const module_info = try debug_info.modules.addOne(allocator); - const name = allocator.dupe(u8, mem.sliceTo(&module_entry.szModule, 0)) catch &.{}; - errdefer allocator.free(name); - - module_info.* = .{ - .base_address = @intFromPtr(module_entry.modBaseAddr), - .size = module_entry.modBaseSize, - .name = name, - .handle = module_entry.hModule, - }; - - module_valid = windows.kernel32.Module32Next(handle, &module_entry) == 1; - } - } +/// MLUGG TODO: what if this field had a less stupid name... +address_map: std.AutoHashMapUnmanaged(usize, Module.DebugInfo), + +module_cache: if (native_os == .windows) std.ArrayListUnmanaged(windows.MODULEENTRY32) else void, + +pub const target_supported: bool = switch (native_os) { + .linux, + .freebsd, + .netbsd, + .dragonfly, + .openbsd, + .macos, + .solaris, + .illumos, + .windows, + => true, + else => false, +}; - return debug_info; -} +pub const init: SelfInfo = .{ + .address_map = .empty, + .module_cache = if (native_os == .windows) .empty, +}; pub fn deinit(self: *SelfInfo) void { + // MLUGG TODO: that's amusing, this function is straight-up unused. i... wonder if it even should be used anywhere? perhaps not... so perhaps it should not even exist...???? var it = self.address_map.iterator(); while (it.next()) |entry| { const mdi = entry.value_ptr.*; @@ -118,49 +68,91 @@ pub fn deinit(self: *SelfInfo) void { } } -fn lookupModuleForAddress(self: *SelfInfo, address: usize) !Module.Lookup { +fn lookupModuleForAddress(self: *SelfInfo, gpa: Allocator, address: usize) !Module { if (builtin.target.os.tag.isDarwin()) { return self.lookupModuleDyld(address); } else if (native_os == .windows) { - return self.lookupModuleWin32(address); + return self.lookupModuleWin32(gpa, address); } else if (native_os == .haiku) { - return self.lookupModuleHaiku(address); + @panic("TODO implement lookup module for Haiku"); } else if (builtin.target.cpu.arch.isWasm()) { - return self.lookupModuleWasm(address); + @panic("TODO implement lookup module for Wasm"); } else { return self.lookupModuleDl(address); } } -fn loadModuleDebugInfo(self: *SelfInfo, lookup: *const Module.Lookup, module: *Module) !void { +fn loadModuleDebugInfo(gpa: Allocator, module: *const Module, di: *Module.DebugInfo) !void { + // MLUGG TODO: this should totally just go into the `Module` impl or something, right? lol + if (builtin.target.os.tag.isDarwin()) { + try loadMachODebugInfo(gpa, module, di); + } else if (native_os == .windows) { + // MLUGG TODO: deal with 'already loaded' properly + try readCoffDebugInfo(gpa, module, di); + } else if (native_os == .haiku) { + unreachable; + } else if (builtin.target.cpu.arch.isWasm()) { + unreachable; + } else { + if (di.mapped_memory != null) return; // already loaded + const filename: ?[]const u8 = if (module.name.len > 0) module.name else null; + const mapped_mem = mapFileOrSelfExe(filename) catch |err| switch (err) { + error.FileNotFound => return error.MissingDebugInfo, + error.FileTooBig => return error.InvalidDebugInfo, + else => |e| return e, + }; + errdefer posix.munmap(mapped_mem); + try di.load(gpa, mapped_mem, module.build_id, null, null, null, filename); + assert(di.mapped_memory != null); + } +} + +fn loadModuleUnwindInfo(gpa: Allocator, module: *const Module, di: *Module.DebugInfo) !void { if (builtin.target.os.tag.isDarwin()) { - @compileError("TODO"); + // MLUGG TODO HACKHACK + try loadMachODebugInfo(gpa, module, di); } else if (native_os == .windows) { - @compileError("TODO"); + comptime unreachable; // not supported } else if (native_os == .haiku) { - @compileError("TODO"); + comptime unreachable; // not supported } else if (builtin.target.cpu.arch.isWasm()) { - @compileError("TODO"); + comptime unreachable; // not supported } else { - if (module.mapped_memory == null) { - var sections: Dwarf.SectionArray = @splat(null); - try readElfDebugInfo(module, self.allocator, if (lookup.name.len > 0) lookup.name else null, lookup.build_id, §ions); - assert(module.mapped_memory != null); + eh_frame: { + if (di.unwind.eh_frame != null) break :eh_frame; // already loaded + const eh_frame_hdr_bytes = module.gnu_eh_frame orelse break :eh_frame; + const eh_frame_hdr: Dwarf.Unwind.EhFrameHeader = try .parse( + @intFromPtr(eh_frame_hdr_bytes.ptr) - module.load_offset, + eh_frame_hdr_bytes, + @sizeOf(usize), + native_endian, + ); + const eh_frame_addr = module.load_offset + @as(usize, @intCast(eh_frame_hdr.eh_frame_vaddr)); + try di.unwind.scanEhFrame( + gpa, + eh_frame_hdr, + @ptrFromInt(eh_frame_addr), + null, + @sizeOf(usize), + native_endian, + ); } } } -pub fn unwindFrame(self: *SelfInfo, context: *UnwindContext) !usize { - const lookup = try self.lookupModuleForAddress(context.pc); - const gop = try self.address_map.getOrPut(self.allocator, lookup.base_address); - if (!gop.found_existing) gop.value_ptr.* = .init(&lookup); +pub fn unwindFrame(self: *SelfInfo, gpa: Allocator, context: *UnwindContext) !usize { + comptime assert(target_supported); + const module = try self.lookupModuleForAddress(gpa, context.pc); + const gop = try self.address_map.getOrPut(gpa, module.load_offset); + if (!gop.found_existing) gop.value_ptr.* = .init; + try loadModuleUnwindInfo(gpa, &module, gop.value_ptr); if (native_os.isDarwin()) { // __unwind_info is a requirement for unwinding on Darwin. It may fall back to DWARF, but unwinding // via DWARF before attempting to use the compact unwind info will produce incorrect results. if (gop.value_ptr.unwind_info) |unwind_info| { if (unwindFrameMachO( - self.allocator, - lookup.base_address, + module.text_base, + module.load_offset, context, unwind_info, gop.value_ptr.eh_frame, @@ -169,292 +161,42 @@ pub fn unwindFrame(self: *SelfInfo, context: *UnwindContext) !usize { } else |err| { if (err != error.RequiresDWARFUnwind) return err; } - } else return error.MissingUnwindInfo; + } + return error.MissingUnwindInfo; + } + if (try gop.value_ptr.getDwarfUnwindForAddress(gpa, context.pc)) |unwind| { + return unwindFrameDwarf(unwind, module.load_offset, context, null); } - if (try gop.value_ptr.getDwarfUnwindForAddress(self.allocator, context.pc)) |unwind| { - return unwindFrameDwarf(self.allocator, unwind, lookup.base_address, context, null); - } else return error.MissingDebugInfo; + return error.MissingDebugInfo; } -pub fn getSymbolAtAddress(self: *SelfInfo, address: usize) !std.debug.Symbol { - const lookup = try self.lookupModuleForAddress(address); - const gop = try self.address_map.getOrPut(self.allocator, lookup.base_address); - if (!gop.found_existing) gop.value_ptr.* = .init(&lookup); - try self.loadModuleDebugInfo(&lookup, gop.value_ptr); - return gop.value_ptr.getSymbolAtAddress(self.allocator, native_endian, lookup.base_address, address); +pub fn getSymbolAtAddress(self: *SelfInfo, gpa: Allocator, address: usize) !std.debug.Symbol { + comptime assert(target_supported); + const module = try self.lookupModuleForAddress(gpa, address); + const gop = try self.address_map.getOrPut(gpa, module.key()); + if (!gop.found_existing) gop.value_ptr.* = .init; + try loadModuleDebugInfo(gpa, &module, gop.value_ptr); + 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, address: usize) ?[]const u8 { - return if (self.lookupModuleForAddress(address)) |lookup| lookup.name else |err| switch (err) { - error.MissingDebugInfo => null, - }; -} - -fn lookupModuleDyld(self: *SelfInfo, address: usize) !*Module { - const image_count = std.c._dyld_image_count(); - - var i: u32 = 0; - while (i < image_count) : (i += 1) { - const header = std.c._dyld_get_image_header(i) orelse continue; - const base_address = @intFromPtr(header); - if (address < base_address) continue; - const vmaddr_slide = std.c._dyld_get_image_vmaddr_slide(i); - - var it = macho.LoadCommandIterator{ - .ncmds = header.ncmds, - .buffer = @alignCast(@as( - [*]u8, - @ptrFromInt(@intFromPtr(header) + @sizeOf(macho.mach_header_64)), - )[0..header.sizeofcmds]), - }; - - var unwind_info: ?[]const u8 = null; - var eh_frame: ?[]const u8 = null; - while (it.next()) |cmd| switch (cmd.cmd()) { - .SEGMENT_64 => { - const segment_cmd = cmd.cast(macho.segment_command_64).?; - if (!mem.eql(u8, "__TEXT", segment_cmd.segName())) continue; - - const seg_start = segment_cmd.vmaddr + vmaddr_slide; - const seg_end = seg_start + segment_cmd.vmsize; - if (address >= seg_start and address < seg_end) { - if (self.address_map.get(base_address)) |obj_di| { - return obj_di; - } - - for (cmd.getSections()) |sect| { - const sect_addr: usize = @intCast(sect.addr); - const sect_size: usize = @intCast(sect.size); - if (mem.eql(u8, "__unwind_info", sect.sectName())) { - unwind_info = @as([*]const u8, @ptrFromInt(sect_addr + vmaddr_slide))[0..sect_size]; - } else if (mem.eql(u8, "__eh_frame", sect.sectName())) { - eh_frame = @as([*]const u8, @ptrFromInt(sect_addr + vmaddr_slide))[0..sect_size]; - } - } - - const obj_di = try self.allocator.create(Module); - errdefer self.allocator.destroy(obj_di); - - const macho_path = mem.sliceTo(std.c._dyld_get_image_name(i), 0); - const macho_file = fs.cwd().openFile(macho_path, .{}) catch |err| switch (err) { - error.FileNotFound => return error.MissingDebugInfo, - else => return err, - }; - obj_di.* = try readMachODebugInfo(self.allocator, macho_file); - obj_di.base_address = base_address; - obj_di.vmaddr_slide = vmaddr_slide; - obj_di.unwind_info = unwind_info; - obj_di.eh_frame = eh_frame; - - try self.address_map.putNoClobber(base_address, obj_di); - - return obj_di; - } - }, - else => {}, - }; - } - - return error.MissingDebugInfo; -} - -fn lookupModuleNameDyld(self: *SelfInfo, address: usize) ?[]const u8 { - _ = self; - const image_count = std.c._dyld_image_count(); - - var i: u32 = 0; - while (i < image_count) : (i += 1) { - const header = std.c._dyld_get_image_header(i) orelse continue; - const base_address = @intFromPtr(header); - if (address < base_address) continue; - const vmaddr_slide = std.c._dyld_get_image_vmaddr_slide(i); - - var it = macho.LoadCommandIterator{ - .ncmds = header.ncmds, - .buffer = @alignCast(@as( - [*]u8, - @ptrFromInt(@intFromPtr(header) + @sizeOf(macho.mach_header_64)), - )[0..header.sizeofcmds]), - }; - - while (it.next()) |cmd| switch (cmd.cmd()) { - .SEGMENT_64 => { - const segment_cmd = cmd.cast(macho.segment_command_64).?; - if (!mem.eql(u8, "__TEXT", segment_cmd.segName())) continue; - - const original_address = address - vmaddr_slide; - const seg_start = segment_cmd.vmaddr; - const seg_end = seg_start + segment_cmd.vmsize; - if (original_address >= seg_start and original_address < seg_end) { - return fs.path.basename(mem.sliceTo(std.c._dyld_get_image_name(i), 0)); - } - }, - else => {}, - }; - } - - return null; -} - -fn lookupModuleWin32(self: *SelfInfo, address: usize) !*Module { - for (self.modules.items) |*module| { - if (address >= module.base_address and address < module.base_address + module.size) { - if (self.address_map.get(module.base_address)) |obj_di| { - return obj_di; - } - - const obj_di = try self.allocator.create(Module); - errdefer self.allocator.destroy(obj_di); - - const mapped_module = @as([*]const u8, @ptrFromInt(module.base_address))[0..module.size]; - var coff_obj = try coff.Coff.init(mapped_module, true); - - // 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; - // openFileAbsoluteW requires the prefix to be present - @memcpy(name_buffer[0..4], &[_]u16{ '\\', '?', '?', '\\' }); - - const process_handle = windows.GetCurrentProcess(); - const len = windows.kernel32.GetModuleFileNameExW( - process_handle, - module.handle, - @ptrCast(&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 => return err, - }; - 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 base_ptr: usize = 0; - const map_section_rc = windows.ntdll.NtMapViewOfSection( - section_handle, - process_handle, - @ptrCast(&base_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, @ptrFromInt(base_ptr)) == .SUCCESS); - - const section_view = @as([*]const u8, @ptrFromInt(base_ptr))[0..coff_len]; - coff_obj = try coff.Coff.init(section_view, false); - - module.mapped_file = .{ - .file = coff_file, - .section_handle = section_handle, - .section_view = section_view, - }; - } - errdefer if (module.mapped_file) |mapped_file| mapped_file.deinit(); - - obj_di.* = try readCoffDebugInfo(self.allocator, &coff_obj); - obj_di.base_address = module.base_address; - - try self.address_map.putNoClobber(module.base_address, obj_di); - return obj_di; - } - } - - return error.MissingDebugInfo; -} - -fn lookupModuleNameWin32(self: *SelfInfo, address: usize) ?[]const u8 { - for (self.modules.items) |module| { - if (address >= module.base_address and address < module.base_address + module.size) { - return module.name; - } - } - return null; -} - -fn lookupModuleNameDl(self: *SelfInfo, address: usize) ?[]const u8 { - _ = self; - - var ctx: struct { - // Input - address: usize, - // Output - name: []const u8 = "", - } = .{ .address = address }; - const CtxTy = @TypeOf(ctx); - - if (posix.dl_iterate_phdr(&ctx, error{Found}, struct { - fn callback(info: *posix.dl_phdr_info, size: usize, context: *CtxTy) !void { - _ = size; - if (context.address < info.addr) return; - const phdrs = info.phdr[0..info.phnum]; - for (phdrs) |*phdr| { - if (phdr.p_type != elf.PT_LOAD) continue; - - 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.name = mem.sliceTo(info.name, 0) orelse ""; - break; - } - } else return; - - return error.Found; - } - }.callback)) { - return null; - } else |err| switch (err) { - error.Found => return fs.path.basename(ctx.name), - } - - return null; +pub fn getModuleNameForAddress(self: *SelfInfo, gpa: Allocator, address: usize) error{ Unexpected, OutOfMemory, MissingDebugInfo }![]const u8 { + comptime assert(target_supported); + const module = try self.lookupModuleForAddress(gpa, address); + return module.name; } -fn lookupModuleDl(self: *SelfInfo, address: usize) !Module.Lookup { - var ctx: struct { - // Input +fn lookupModuleDl(self: *SelfInfo, address: usize) !Module { + _ = self; // MLUGG + const DlIterContext = struct { + /// input address: usize, - // Output - lookup: Module.Lookup, - } = .{ - .address = address, - .lookup = .{ - .base_address = undefined, - .name = undefined, - .build_id = null, - .gnu_eh_frame = null, - }, - }; - const CtxTy = @TypeOf(ctx); + /// output + module: Module, - posix.dl_iterate_phdr(&ctx, error{Found}, struct { - fn callback(info: *posix.dl_phdr_info, size: usize, context: *CtxTy) !void { + fn callback(info: *posix.dl_phdr_info, size: usize, context: *@This()) !void { _ = size; // The base address is too high if (context.address < info.addr) @@ -468,10 +210,13 @@ fn lookupModuleDl(self: *SelfInfo, address: usize) !Module.Lookup { 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) { - // Android libc uses NULL instead of an empty string to mark the - // main program - context.lookup.name = mem.sliceTo(info.name, 0) orelse ""; - context.lookup.base_address = info.addr; + 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; @@ -480,17 +225,20 @@ fn lookupModuleDl(self: *SelfInfo, address: usize) !Module.Lookup { switch (phdr.p_type) { elf.PT_NOTE => { // Look for .note.gnu.build-id - const note_bytes = @as([*]const u8, @ptrFromInt(info.addr + phdr.p_vaddr))[0..phdr.p_memsz]; - const name_size = mem.readInt(u32, note_bytes[0..4], native_endian); - if (name_size != 4) continue; - const desc_size = mem.readInt(u32, note_bytes[4..8], native_endian); - const note_type = mem.readInt(u32, note_bytes[8..12], native_endian); + 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, "GNU\x00", note_bytes[12..16])) continue; - context.lookup.build_id = note_bytes[16..][0..desc_size]; + 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 => { - context.lookup.gnu_eh_frame = @as([*]const u8, @ptrFromInt(info.addr + phdr.p_vaddr))[0..phdr.p_memsz]; + const segment_ptr: [*]const u8 = @ptrFromInt(info.addr + phdr.p_vaddr); + context.module.gnu_eh_frame = segment_ptr[0..phdr.p_memsz]; }, else => {}, } @@ -499,425 +247,558 @@ fn lookupModuleDl(self: *SelfInfo, address: usize) !Module.Lookup { // Stop the iteration return error.Found; } - }.callback) catch |err| switch (err) { - error.Found => return ctx.lookup, }; - if (true) return error.MissingDebugInfo; - - if (self.address_map.get(ctx.lookup.base_address)) |obj_di| { - return obj_di; - } + 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; +} - var sections: Dwarf.SectionArray = @splat(null); - if (ctx.lookup.gnu_eh_frame) |eh_frame_hdr| { - // This is a special case - pointer offsets inside .eh_frame_hdr - // are encoded relative to its base address, so we must use the - // version that is already memory mapped, and not the one that - // will be mapped separately from the ELF file. - sections[@intFromEnum(Dwarf.Unwind.Section.Id.eh_frame_hdr)] = .{ - .data = eh_frame_hdr, - .owned = false, +fn lookupModuleDyld(self: *SelfInfo, address: usize) !Module { + _ = self; // MLUGG + 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, const text_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 .{ segment_cmd, load_cmd.getSections() }; + } 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, but we must look + // for unwind info in it before returning. + + var result: Module = .{ + .text_base = text_base, + .load_offset = load_offset, + .name = mem.span(std.c._dyld_get_image_name(@intCast(image_idx))), + .unwind_info = null, + .eh_frame = null, }; + for (text_sections) |sect| { + if (mem.eql(u8, sect.sectName(), "__unwind_info")) { + const sect_ptr: [*]u8 = @ptrFromInt(@as(usize, @intCast(load_offset + sect.addr))); + result.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(load_offset + sect.addr))); + result.eh_frame = sect_ptr[0..@intCast(sect.size)]; + } + } + return result; } - - const obj_di = try self.allocator.create(Module); - errdefer self.allocator.destroy(obj_di); - obj_di.* = try readElfDebugInfo(self.allocator, if (ctx.lookup.name.len > 0) ctx.lookup.name else null, ctx.lookup.build_id, §ions); - obj_di.base_address = ctx.lookup.base_address; - - // Missing unwind info isn't treated as a failure, as the unwinder will fall back to FP-based unwinding - obj_di.dwarf.scanAllUnwindInfo(self.allocator, ctx.lookup.base_address) catch {}; - - try self.address_map.putNoClobber(self.allocator, ctx.lookup.base_address, obj_di); - - return obj_di; + return error.MissingDebugInfo; } -fn lookupModuleHaiku(self: *SelfInfo, address: usize) !*Module { - _ = self; - _ = address; - @panic("TODO implement lookup module for Haiku"); -} +fn lookupModuleWin32(self: *SelfInfo, gpa: Allocator, address: usize) !Module { + if (self.lookupModuleWin32Cache(address)) |m| return m; -fn lookupModuleWasm(self: *SelfInfo, address: usize) !*Module { - _ = self; - _ = address; - @panic("TODO implement lookup module for Wasm"); -} + { + // Check a new module hasn't been loaded + self.module_cache.clearRetainingCapacity(); -pub const Module = switch (native_os) { - .macos, .ios, .watchos, .tvos, .visionos => struct { - base_address: usize, - vmaddr_slide: usize, - mapped_memory: []align(std.heap.page_size_min) const u8, - symbols: []const MachoSymbol, - strings: [:0]const u8, - ofiles: OFileTable, - - // Backed by the in-memory sections mapped by the loader - unwind_info: ?[]const u8 = null, - eh_frame: ?[]const u8 = null, - - const OFileTable = std.StringHashMap(OFileInfo); - const OFileInfo = struct { - di: Dwarf, - addr_table: std.StringHashMap(u64), - }; + 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); - pub fn deinit(self: *@This(), allocator: Allocator) void { - var it = self.ofiles.iterator(); - while (it.next()) |entry| { - const ofile = entry.value_ptr; - ofile.di.deinit(allocator); - ofile.addr_table.deinit(); + var entry: windows.MODULEENTRY32 = undefined; + entry.dwSize = @sizeOf(windows.MODULEENTRY32); + if (windows.kernel32.Module32First(handle, &entry) != 0) { + try self.module_cache.append(gpa, entry); + while (windows.kernel32.Module32Next(handle, &entry) != 0) { + try self.module_cache.append(gpa, entry); } - self.ofiles.deinit(); - allocator.free(self.symbols); - posix.munmap(self.mapped_memory); } + } - fn loadOFile(self: *@This(), allocator: Allocator, o_file_path: []const u8) !*OFileInfo { - const o_file = try fs.cwd().openFile(o_file_path, .{}); - const mapped_mem = try mapWholeFile(o_file); - - const hdr: *const macho.mach_header_64 = @ptrCast(@alignCast(mapped_mem.ptr)); - if (hdr.magic != std.macho.MH_MAGIC_64) - return error.InvalidDebugInfo; - - var segcmd: ?macho.LoadCommandIterator.LoadCommand = null; - var symtabcmd: ?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 => segcmd = cmd, - .SYMTAB => symtabcmd = cmd.cast(macho.symtab_command).?, - else => {}, + if (self.lookupModuleWin32Cache(address)) |m| return m; + return error.MissingDebugInfo; +} +fn lookupModuleWin32Cache(self: *SelfInfo, address: usize) ?Module { + for (self.module_cache.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; +} - if (segcmd == null or symtabcmd == null) return error.MissingDebugInfo; - - // Parse symbols - const strtab = @as( - [*]const u8, - @ptrCast(&mapped_mem[symtabcmd.?.stroff]), - )[0 .. symtabcmd.?.strsize - 1 :0]; - const symtab = @as( - [*]const macho.nlist_64, - @ptrCast(@alignCast(&mapped_mem[symtabcmd.?.symoff])), - )[0..symtabcmd.?.nsyms]; - - // TODO handle tentative (common) symbols - var addr_table = std.StringHashMap(u64).init(allocator); - try addr_table.ensureTotalCapacity(@as(u32, @intCast(symtab.len))); - for (symtab) |sym| { - if (sym.n_strx == 0) continue; - if (sym.undf() or sym.tentative() or sym.abs()) continue; - const sym_name = mem.sliceTo(strtab[sym.n_strx..], 0); - // TODO is it possible to have a symbol collision? - addr_table.putAssumeCapacityNoClobber(sym_name, sym.n_value); - } +fn readCoffDebugInfo(gpa: Allocator, module: *const Module, di: *Module.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(); - var sections: Dwarf.SectionArray = Dwarf.null_section_array; - if (self.eh_frame) |eh_frame| sections[@intFromEnum(Dwarf.Section.Id.eh_frame)] = .{ - .data = eh_frame, - .owned = false, - }; + if (coff_obj.getSectionByName(".debug_info")) |_| { + di.dwarf = .{}; - for (segcmd.?.getSections()) |sect| { - if (!std.mem.eql(u8, "__DWARF", sect.segName())) continue; + 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), + .virtual_address = section_header.virtual_address, + .owned = true, + }; + } else null; + } - var section_index: ?usize = null; - inline for (@typeInfo(Dwarf.Section.Id).@"enum".fields, 0..) |section, i| { - if (mem.eql(u8, "__" ++ section.name, sect.sectName())) section_index = i; - } - if (section_index == null) continue; + try di.dwarf.?.open(gpa, native_endian); + } - const section_bytes = try Dwarf.chopSlice(mapped_mem, sect.offset, sect.size); - sections[section_index.?] = .{ - .data = section_bytes, - .virtual_address = @intCast(sect.addr), - .owned = false, - }; + 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); - 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 di: Dwarf = .{ - .endian = .little, - .sections = sections, - .is_macho = true, - }; + 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(); - try Dwarf.open(&di, allocator); - const info = OFileInfo{ - .di = di, - .addr_table = addr_table, - }; + if (!mem.eql(u8, &coff_obj.guid, &di.pdb.?.guid) or coff_obj.age != di.pdb.?.age) + return error.InvalidDebugInfo; - // Add the debug info to the cache - const result = try self.ofiles.getOrPut(o_file_path); - assert(!result.found_existing); - result.value_ptr.* = info; + di.coff_section_headers = try coff_obj.getSectionHeadersAlloc(gpa); + } +} - return result.value_ptr; +const Module = switch (native_os) { + else => "MLUGG TODO", // 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, + unwind_info: ?[]const u8, + eh_frame: ?[]const u8, + fn key(m: *const Module) usize { + return m.text_base; } + fn getSymbolAtAddress(module: *const Module, gpa: Allocator, di: *DebugInfo, address: usize) !std.debug.Symbol { + const vaddr = address - module.load_offset; + const symbol = MachoSymbol.find(di.symbols, vaddr) orelse return .{}; // MLUGG TODO null? - pub fn getSymbolAtAddress(self: *@This(), allocator: Allocator, address: usize) !std.debug.Symbol { - const result = try self.getOFileInfoForAddress(allocator, address); - if (result.symbol == null) return .{}; + // 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(self.strings[result.symbol.?.strx..], 0); - if (result.o_file_info == null) return .{ .name = stab_symbol }; - - // Translate again the address, this time into an address inside the - // .o file - const relocated_address_o = result.o_file_info.?.addr_table.get(stab_symbol) orelse return .{ - .name = "???", + const stab_symbol = mem.sliceTo(di.strings[symbol.strx..], 0); + const o_file_path = mem.sliceTo(di.strings[symbol.ofile..], 0); + + const o_file: *DebugInfo.OFile = of: { + const gop = try di.ofiles.getOrPut(gpa, o_file_path); + if (!gop.found_existing) { + gop.value_ptr.* = DebugInfo.loadOFile(gpa, o_file_path) catch |err| { + defer _ = di.ofiles.pop().?; + switch (err) { + error.FileNotFound, + error.MissingDebugInfo, + error.InvalidDebugInfo, + => return .{ .name = stab_symbol }, + else => |e| return e, + } + }; + } + break :of gop.value_ptr; }; - const addr_off = result.relocated_address - result.symbol.?.addr; - const o_file_di = &result.o_file_info.?.di; - if (o_file_di.findCompileUnit(relocated_address_o)) |compile_unit| { - return .{ - .name = o_file_di.getSymbolName(relocated_address_o) orelse "???", - .compile_unit_name = compile_unit.die.getAttrString( - o_file_di, - std.dwarf.AT.name, - o_file_di.section(.debug_str), - compile_unit.*, - ) catch |err| switch (err) { - error.MissingDebugInfo, error.InvalidDebugInfo => "???", - }, - .source_location = o_file_di.getLineNumberInfo( - allocator, - compile_unit, - relocated_address_o + addr_off, - ) catch |err| switch (err) { - error.MissingDebugInfo, error.InvalidDebugInfo => null, - else => return err, - }, - }; - } else |err| switch (err) { - error.MissingDebugInfo, error.InvalidDebugInfo => { - return .{ .name = stab_symbol }; - }, - else => return err, - } - } + const symbol_ofile_vaddr = o_file.addr_table.get(stab_symbol) orelse return .{ .name = stab_symbol }; - pub fn getOFileInfoForAddress(self: *@This(), allocator: Allocator, address: usize) !struct { - relocated_address: usize, - symbol: ?*const MachoSymbol = null, - o_file_info: ?*OFileInfo = null, - } { - // Translate the VA into an address into this object - const relocated_address = address - self.vmaddr_slide; - - // Find the .o file where this symbol is defined - const symbol = machoSearchSymbols(self.symbols, relocated_address) orelse return .{ - .relocated_address = relocated_address, + const compile_unit = o_file.dwarf.findCompileUnit(native_endian, symbol_ofile_vaddr) catch |err| switch (err) { + error.MissingDebugInfo, error.InvalidDebugInfo => return .{ .name = stab_symbol }, + else => |e| return e, }; - // Check if its debug infos are already in the cache - const o_file_path = mem.sliceTo(self.strings[symbol.ofile..], 0); - const o_file_info = self.ofiles.getPtr(o_file_path) orelse - (self.loadOFile(allocator, o_file_path) catch |err| switch (err) { - error.FileNotFound, - error.MissingDebugInfo, - error.InvalidDebugInfo, - => return .{ - .relocated_address = relocated_address, - .symbol = symbol, - }, - else => return err, - }); - return .{ - .relocated_address = relocated_address, - .symbol = symbol, - .o_file_info = o_file_info, + .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 => "???", + }, + .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, + }, }; } + const DebugInfo = struct { + // MLUGG TODO: these are duplicated state. i actually reckon they should be removed from Module, and loadMachODebugInfo should be the one discovering them! + 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), + + // Backed by the in-memory sections mapped by the loader + unwind_info: ?[]const u8, + eh_frame: ?[]const u8, + + // MLUGG TODO HACKHACK: this is awful + const init: DebugInfo = undefined; + + const OFile = struct { + dwarf: Dwarf, + // MLUGG TODO: this could use an adapter to just index straight into the strtab! + addr_table: std.StringArrayHashMapUnmanaged(u64), + }; - pub fn getDwarfInfoForAddress(self: *@This(), allocator: Allocator, address: usize) !?*Dwarf { - return if ((try self.getOFileInfoForAddress(allocator, address)).o_file_info) |o_file_info| &o_file_info.di else null; - } - }, - .uefi, .windows => struct { - base_address: usize, - pdb: ?Pdb, - dwarf: ?Dwarf, - coff_image_base: u64, + fn deinit(di: *DebugInfo, gpa: Allocator) void { + for (di.ofiles.values()) |*ofile| { + ofile.dwarf.deinit(gpa); + ofile.addr_table.deinit(gpa); + } + di.ofiles.deinit(); + gpa.free(di.symbols); + posix.munmap(di.mapped_memory); + } - /// Only used if pdb is non-null - coff_section_headers: []coff.SectionHeader, + fn loadOFile(gpa: Allocator, o_file_path: []const u8) !OFile { + const mapped_mem = try mapFileOrSelfExe(o_file_path); + errdefer posix.munmap(mapped_mem); - pub fn deinit(self: *@This(), gpa: Allocator) void { - if (self.dwarf) |*dwarf| { - dwarf.deinit(gpa); - } + 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; - if (self.pdb) |*p| { - gpa.free(p.file_reader.interface.buffer); - gpa.destroy(p.file_reader); - p.deinit(); - gpa.free(self.coff_section_headers); - } + 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, + }; + }; - self.* = undefined; - } + 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 + // MLUGG TODO: does initCapacity actually make sense? + 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; + } - fn getSymbolFromPdb(self: *@This(), relocated_address: usize) !?std.debug.Symbol { - var coff_section: *align(1) const coff.SectionHeader = undefined; - const mod_index = for (self.pdb.?.sect_contribs) |sect_contrib| { - if (sect_contrib.section > self.coff_section_headers.len) continue; - // Remember that SectionContribEntry.Section is 1-based. - coff_section = &self.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; + 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; + + const section_bytes = try Dwarf.chopSlice(mapped_mem, sect.offset, sect.size); + sections[section_index] = .{ + .data = section_bytes, + .virtual_address = @intCast(sect.addr), + .owned = false, + }; } - } else { - // we have no information to add to the address - return null; - }; - const module = (try self.pdb.?.getModule(mod_index)) orelse - return error.InvalidDebugInfo; - const obj_basename = fs.path.basename(module.obj_file_name); - - const symbol_name = self.pdb.?.getSymbolName( - module, - relocated_address - coff_section.virtual_address, - ) orelse "???"; - const opt_line_info = try self.pdb.?.getLineNumberInfo( - module, - relocated_address - coff_section.virtual_address, - ); + 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; - return .{ - .name = symbol_name, - .compile_unit_name = obj_basename, - .source_location = opt_line_info, - }; + var dwarf: Dwarf = .{ .sections = sections }; + errdefer dwarf.deinit(gpa); + try dwarf.open(gpa, native_endian); + + return .{ + .dwarf = dwarf, + .addr_table = addr_table.move(), + }; + } + }; + }, + .wasi, .emscripten => struct { + const DebugInfo = struct { + const init: DebugInfo = .{}; + fn getSymbolAtAddress(di: *DebugInfo, gpa: Allocator, base_address: usize, address: usize) !std.debug.Symbol { + _ = di; + _ = gpa; + _ = base_address; + _ = address; + 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, + fn key(m: Module) usize { + return m.load_offset; // MLUGG TODO: is this technically valid? idk + } + const DebugInfo = Dwarf.ElfModule; + fn getSymbolAtAddress(mod: *const Module, gpa: Allocator, di: *DebugInfo, address: usize) !std.debug.Symbol { + return di.getSymbolAtAddress(gpa, native_endian, mod.load_offset, address); } + }, + .uefi, .windows => struct { + base_address: usize, + size: usize, + name: []const u8, + handle: windows.HMODULE, + fn key(m: Module) usize { + return m.base_address; + } + const DebugInfo = struct { + 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(); + } + }, - pub fn getSymbolAtAddress(self: *@This(), allocator: Allocator, address: usize) !std.debug.Symbol { - // Translate the VA into an address into this object - const relocated_address = address - self.base_address; + dwarf: ?Dwarf, - if (self.pdb != null) { - if (try self.getSymbolFromPdb(relocated_address)) |symbol| return symbol; - } + pdb: ?Pdb, + /// Populated iff `pdb != null`; otherwise `&.{}`. + coff_section_headers: []coff.SectionHeader, + + const init: DebugInfo = .{ + .coff_image_base = undefined, + .mapped_file = null, + .dwarf = null, + .pdb = null, + .coff_section_headers = &.{}, + }; - if (self.dwarf) |*dwarf| { - const dwarf_address = relocated_address + self.coff_image_base; - return dwarf.getSymbol(allocator, dwarf_address); + 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(); } - return .{}; - } + 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; + }; - pub fn getDwarfInfoForAddress(self: *@This(), allocator: Allocator, address: usize) !?*Dwarf { - _ = allocator; - _ = address; + const module = (try di.pdb.?.getModule(mod_index)) orelse + return error.InvalidDebugInfo; + const obj_basename = fs.path.basename(module.obj_file_name); + + const symbol_name = di.pdb.?.getSymbolName( + module, + relocated_address - coff_section.virtual_address, + ) orelse "???"; + const opt_line_info = try di.pdb.?.getLineNumberInfo( + module, + relocated_address - coff_section.virtual_address, + ); - return switch (self.debug_data) { - .dwarf => |*dwarf| dwarf, - else => null, - }; - } - }, - .linux, .netbsd, .freebsd, .dragonfly, .openbsd, .haiku, .solaris, .illumos => Dwarf.ElfModule, - .wasi, .emscripten => struct { - pub fn deinit(self: *@This(), allocator: Allocator) void { - _ = self; - _ = allocator; - } + return .{ + .name = symbol_name, + .compile_unit_name = obj_basename, + .source_location = opt_line_info, + }; + } + }; - pub fn getSymbolAtAddress(self: *@This(), allocator: Allocator, address: usize) !std.debug.Symbol { - _ = self; - _ = allocator; - _ = address; - return .{}; - } + fn getSymbolAtAddress(mod: *const Module, gpa: Allocator, di: *DebugInfo, address: usize) !std.debug.Symbol { + // Translate the runtime address into a virtual address into the module + const vaddr = address - mod.base_address; - pub fn getDwarfInfoForAddress(self: *@This(), allocator: Allocator, address: usize) !?*Dwarf { - _ = self; - _ = allocator; - _ = address; - return null; - } - }, - else => Dwarf, -}; + if (di.pdb != null) { + if (try di.getSymbolFromPdb(vaddr)) |symbol| return symbol; + } -/// How is this different than `Module` when the host is Windows? -/// Why are both stored in the `SelfInfo` struct? -/// Boy, it sure would be nice if someone added documentation comments for this -/// struct explaining it. -pub const WindowsModule = struct { - base_address: usize, - size: u32, - name: []const u8, - handle: windows.HMODULE, - - // Set when the image file needed to be mapped from disk - mapped_file: ?struct { - file: File, - section_handle: windows.HANDLE, - section_view: []const u8, - - pub fn deinit(self: @This()) void { - const process_handle = windows.GetCurrentProcess(); - assert(windows.ntdll.NtUnmapViewOfSection(process_handle, @ptrCast(@constCast(self.section_view.ptr))) == .SUCCESS); - windows.CloseHandle(self.section_handle); - self.file.close(); + if (di.dwarf) |*dwarf| { + const dwarf_address = vaddr + di.coff_image_base; + return dwarf.getSymbol(gpa, native_endian, dwarf_address); + } + + return error.MissingDebugInfo; } - } = null, + }, }; -/// This takes ownership of macho_file: users of this function should not close -/// it themselves, even on error. -/// TODO it's weird to take ownership even on error, rework this code. -fn readMachODebugInfo(allocator: Allocator, macho_file: File) !Module { - const mapped_mem = try mapWholeFile(macho_file); +fn loadMachODebugInfo(gpa: Allocator, module: *const Module, di: *Module.DebugInfo) !void { + const mapped_mem = mapFileOrSelfExe(module.name) catch |err| switch (err) { + error.FileNotFound => return error.MissingDebugInfo, + error.FileTooBig => return error.InvalidDebugInfo, + else => |e| return e, + }; + 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; - var it = macho.LoadCommandIterator{ - .ncmds = hdr.ncmds, - .buffer = mapped_mem[@sizeOf(macho.mach_header_64)..][0..hdr.sizeofcmds], + 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 symtab = while (it.next()) |cmd| switch (cmd.cmd()) { - .SYMTAB => break cmd.cast(macho.symtab_command).?, - else => {}, - } else return error.MissingDebugInfo; - - const syms = @as( - [*]const macho.nlist_64, - @ptrCast(@alignCast(&mapped_mem[symtab.symoff])), - )[0..symtab.nsyms]; + + 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]; - const symbols_buf = try allocator.alloc(MachoSymbol, syms.len); + // MLUGG TODO: does it really make sense to initCapacity here? how many of syms are omitted? + var symbols: std.ArrayList(MachoSymbol) = try .initCapacity(gpa, syms.len); + defer symbols.deinit(gpa); var ofile: u32 = undefined; var last_sym: MachoSymbol = undefined; - var symbol_index: usize = 0; var state: enum { init, oso_open, @@ -929,64 +810,53 @@ fn readMachODebugInfo(allocator: Allocator, macho_file: File) !Module { } = .init; for (syms) |*sym| { - if (!sym.stab()) continue; + if (sym.n_type.bits.is_stab == 0) continue; // TODO handle globals N_GSYM, and statics N_STSYM - switch (sym.n_type) { - macho.N_OSO => { - switch (state) { - .init, .oso_close => { - state = .oso_open; - ofile = sym.n_strx; - }, - else => return error.InvalidDebugInfo, - } + switch (sym.n_type.stab) { + .oso => switch (state) { + .init, .oso_close => { + state = .oso_open; + ofile = sym.n_strx; + }, + else => return error.InvalidDebugInfo, }, - macho.N_BNSYM => { - switch (state) { - .oso_open, .ensym => { - state = .bnsym; - last_sym = .{ - .strx = 0, - .addr = sym.n_value, - .size = 0, - .ofile = ofile, - }; - }, - 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, }, - macho.N_FUN => { - switch (state) { - .bnsym => { - state = .fun_strx; - last_sym.strx = sym.n_strx; - }, - .fun_strx => { - state = .fun_size; - last_sym.size = @as(u32, @intCast(sym.n_value)); - }, - 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, }, - macho.N_ENSYM => { - switch (state) { - .fun_size => { - state = .ensym; - symbols_buf[symbol_index] = last_sym; - symbol_index += 1; - }, - else => return error.InvalidDebugInfo, - } + .ensym => switch (state) { + .fun_size => { + state = .ensym; + symbols.appendAssumeCapacity(last_sym); + }, + else => return error.InvalidDebugInfo, }, - macho.N_SO => { - switch (state) { - .init, .oso_close => {}, - .oso_open, .ensym => { - state = .oso_close; - }, - else => return error.InvalidDebugInfo, - } + .so => switch (state) { + .init, .oso_close => {}, + .oso_open, .ensym => { + state = .oso_close; + }, + else => return error.InvalidDebugInfo, }, else => {}, } @@ -998,542 +868,98 @@ fn readMachODebugInfo(allocator: Allocator, macho_file: File) !Module { else => return error.InvalidDebugInfo, } - const symbols = try allocator.realloc(symbols_buf, symbol_index); + 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, {}, MachoSymbol.addressLessThan); + mem.sort(MachoSymbol, symbols_slice, {}, MachoSymbol.addressLessThan); - return .{ - .base_address = undefined, - .vmaddr_slide = undefined, + di.* = .{ + .unwind_info = module.unwind_info, + .eh_frame = module.eh_frame, .mapped_memory = mapped_mem, - .ofiles = Module.OFileTable.init(allocator), - .symbols = symbols, + .symbols = symbols_slice, .strings = strings, + .ofiles = .empty, }; } -fn readCoffDebugInfo(allocator: Allocator, coff_obj: *coff.Coff) !Module { - var di: Module = .{ - .base_address = undefined, - .coff_image_base = coff_obj.getImageBase(), - .coff_section_headers = undefined, - }; - - if (coff_obj.getSectionByName(".debug_info")) |_| { - // This coff file has embedded DWARF debug info - var sections: Dwarf.SectionArray = Dwarf.null_section_array; - errdefer for (sections) |section| if (section) |s| if (s.owned) allocator.free(s.data); - - inline for (@typeInfo(Dwarf.Section.Id).@"enum".fields, 0..) |section, i| { - sections[i] = if (coff_obj.getSectionByName("." ++ section.name)) |section_header| blk: { - break :blk .{ - .data = try coff_obj.getSectionDataAlloc(section_header, allocator), - .virtual_address = section_header.virtual_address, - .owned = true, - }; - } else null; - } - - var dwarf: Dwarf = .{ - .endian = native_endian, - .sections = sections, - .is_macho = false, - }; - - try Dwarf.open(&dwarf, allocator); - di.dwarf = dwarf; - } - - const raw_path = try coff_obj.getPdbPath() orelse return di; - const path = blk: { - if (fs.path.isAbsolute(raw_path)) { - break :blk raw_path; - } else { - const self_dir = try fs.selfExeDirPathAlloc(allocator); - defer allocator.free(self_dir); - break :blk try fs.path.join(allocator, &.{ self_dir, raw_path }); - } - }; - defer if (path.ptr != raw_path.ptr) allocator.free(path); - - di.pdb = Pdb.init(allocator, path) catch |err| switch (err) { - error.FileNotFound, error.IsDir => { - if (di.dwarf == null) return error.MissingDebugInfo; - return di; - }, - 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; - - // Only used by the pdb path - di.coff_section_headers = try coff_obj.getSectionHeadersAlloc(allocator); - errdefer allocator.free(di.coff_section_headers); - - return di; -} - -/// Reads debug info from an ELF file, or the current binary if none in specified. -/// If the required sections aren't present but a reference to external debug info is, -/// then this this function will recurse to attempt to load the debug sections from -/// an external file. -pub fn readElfDebugInfo( - em: *Dwarf.ElfModule, - allocator: Allocator, - elf_filename: ?[]const u8, - build_id: ?[]const u8, - parent_sections: *Dwarf.SectionArray, -) !void { - const elf_file = (if (elf_filename) |filename| blk: { - break :blk fs.cwd().openFile(filename, .{}); - } else fs.openSelfExe(.{})) catch |err| switch (err) { - error.FileNotFound => return error.MissingDebugInfo, - else => return err, - }; - - const mapped_mem = try mapWholeFile(elf_file); - return em.load( - allocator, - mapped_mem, - build_id, - null, - parent_sections, - null, - elf_filename, - ); -} - const MachoSymbol = struct { strx: u32, addr: u64, size: u32, ofile: u32, - - /// Returns the address from the macho file - fn address(self: MachoSymbol) u64 { - return self.addr; - } - fn addressLessThan(context: void, lhs: MachoSymbol, rhs: MachoSymbol) bool { _ = context; return lhs.addr < rhs.addr; } -}; - -/// Takes ownership of file, even on error. -/// TODO it's weird to take ownership even on error, rework this code. -fn mapWholeFile(file: File) ![]align(std.heap.page_size_min) const u8 { - defer file.close(); - - const file_len = math.cast(usize, try file.getEndPos()) orelse math.maxInt(usize); - const mapped_mem = try posix.mmap( - null, - file_len, - posix.PROT.READ, - .{ .TYPE = .SHARED }, - file.handle, - 0, - ); - errdefer posix.munmap(mapped_mem); - - return mapped_mem; -} - -fn machoSearchSymbols(symbols: []const MachoSymbol, address: usize) ?*const MachoSymbol { - var min: usize = 0; - var max: usize = symbols.len - 1; - while (min < max) { - const mid = min + (max - min) / 2; - const curr = &symbols[mid]; - const next = &symbols[mid + 1]; - if (address >= next.address()) { - min = mid + 1; - } else if (address < curr.address()) { - max = mid; - } else { - return curr; - } - } - - const max_sym = &symbols[symbols.len - 1]; - if (address >= max_sym.address()) - return max_sym; - - return null; -} - -test machoSearchSymbols { - const symbols = [_]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, machoSearchSymbols(&symbols, 0)); - try testing.expectEqual(null, machoSearchSymbols(&symbols, 99)); - try testing.expectEqual(&symbols[0], machoSearchSymbols(&symbols, 100).?); - try testing.expectEqual(&symbols[0], machoSearchSymbols(&symbols, 150).?); - try testing.expectEqual(&symbols[0], machoSearchSymbols(&symbols, 199).?); - - try testing.expectEqual(&symbols[1], machoSearchSymbols(&symbols, 200).?); - try testing.expectEqual(&symbols[1], machoSearchSymbols(&symbols, 250).?); - try testing.expectEqual(&symbols[1], machoSearchSymbols(&symbols, 299).?); - - try testing.expectEqual(&symbols[2], machoSearchSymbols(&symbols, 300).?); - try testing.expectEqual(&symbols[2], machoSearchSymbols(&symbols, 301).?); - try testing.expectEqual(&symbols[2], machoSearchSymbols(&symbols, 5000).?); -} - -/// 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( - allocator: Allocator, - base_address: usize, - context: *UnwindContext, - unwind_info: []const u8, - eh_frame: ?[]const u8, -) !usize { - const header = std.mem.bytesAsValue( - macho.unwind_info_section_header, - unwind_info[0..@sizeOf(macho.unwind_info_section_header)], - ); - const indices = std.mem.bytesAsSlice( - macho.unwind_info_section_header_index_entry, - unwind_info[header.indexSectionOffset..][0 .. header.indexCount * @sizeOf(macho.unwind_info_section_header_index_entry)], - ); - if (indices.len == 0) return error.MissingUnwindInfo; - - const mapped_pc = context.pc - base_address; - const second_level_index = blk: { + /// 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 = indices.len; - + var len: usize = symbols.len; while (len > 1) { const mid = left + len / 2; - const offset = indices[mid].functionOffset; - if (mapped_pc < offset) { + if (address < symbols[mid].addr) { len /= 2; } else { left = mid; - if (mapped_pc == offset) break; len -= len / 2; } } + return &symbols[left]; + } - // Last index is a sentinel containing the highest address as its functionOffset - if (indices[left].secondLevelPagesSectionOffset == 0) return error.MissingUnwindInfo; - break :blk &indices[left]; - }; - - const common_encodings = std.mem.bytesAsSlice( - macho.compact_unwind_encoding_t, - unwind_info[header.commonEncodingsArraySectionOffset..][0 .. header.commonEncodingsArrayCount * @sizeOf(macho.compact_unwind_encoding_t)], - ); - - const start_offset = second_level_index.secondLevelPagesSectionOffset; - const kind = std.mem.bytesAsValue( - macho.UNWIND_SECOND_LEVEL, - unwind_info[start_offset..][0..@sizeOf(macho.UNWIND_SECOND_LEVEL)], - ); - - const entry: struct { - function_offset: usize, - raw_encoding: u32, - } = switch (kind.*) { - .REGULAR => blk: { - const page_header = std.mem.bytesAsValue( - macho.unwind_info_regular_second_level_page_header, - unwind_info[start_offset..][0..@sizeOf(macho.unwind_info_regular_second_level_page_header)], - ); - - const entries = std.mem.bytesAsSlice( - macho.unwind_info_regular_second_level_entry, - unwind_info[start_offset + page_header.entryPageOffset ..][0 .. page_header.entryCount * @sizeOf(macho.unwind_info_regular_second_level_entry)], - ); - if (entries.len == 0) return error.InvalidUnwindInfo; - - var left: usize = 0; - var len: usize = entries.len; - while (len > 1) { - const mid = left + len / 2; - const offset = entries[mid].functionOffset; - if (mapped_pc < offset) { - len /= 2; - } else { - left = mid; - if (mapped_pc == offset) break; - len -= len / 2; - } - } - - break :blk .{ - .function_offset = entries[left].functionOffset, - .raw_encoding = entries[left].encoding, - }; - }, - .COMPRESSED => blk: { - const page_header = std.mem.bytesAsValue( - macho.unwind_info_compressed_second_level_page_header, - unwind_info[start_offset..][0..@sizeOf(macho.unwind_info_compressed_second_level_page_header)], - ); - - const entries = std.mem.bytesAsSlice( - macho.UnwindInfoCompressedEntry, - unwind_info[start_offset + page_header.entryPageOffset ..][0 .. page_header.entryCount * @sizeOf(macho.UnwindInfoCompressedEntry)], - ); - if (entries.len == 0) return error.InvalidUnwindInfo; - - var left: usize = 0; - var len: usize = entries.len; - while (len > 1) { - const mid = left + len / 2; - const offset = second_level_index.functionOffset + entries[mid].funcOffset; - if (mapped_pc < offset) { - len /= 2; - } else { - left = mid; - if (mapped_pc == offset) break; - len -= len / 2; - } - } - - const entry = entries[left]; - const function_offset = second_level_index.functionOffset + entry.funcOffset; - if (entry.encodingIndex < header.commonEncodingsArrayCount) { - if (entry.encodingIndex >= common_encodings.len) return error.InvalidUnwindInfo; - break :blk .{ - .function_offset = function_offset, - .raw_encoding = common_encodings[entry.encodingIndex], - }; - } else { - const local_index = try math.sub( - u8, - entry.encodingIndex, - math.cast(u8, header.commonEncodingsArrayCount) orelse return error.InvalidUnwindInfo, - ); - const local_encodings = std.mem.bytesAsSlice( - macho.compact_unwind_encoding_t, - unwind_info[start_offset + page_header.encodingsPageOffset ..][0 .. page_header.encodingsCount * @sizeOf(macho.compact_unwind_encoding_t)], - ); - if (local_index >= local_encodings.len) return error.InvalidUnwindInfo; - break :blk .{ - .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 => blk: { - const regs: [5]u3 = .{ - encoding.value.x86_64.frame.reg0, - encoding.value.x86_64.frame.reg1, - encoding.value.x86_64.frame.reg2, - encoding.value.x86_64.frame.reg3, - encoding.value.x86_64.frame.reg4, - }; - - const frame_offset = encoding.value.x86_64.frame.frame_offset * @sizeOf(usize); - var max_reg: usize = 0; - inline for (regs, 0..) |reg, i| { - if (reg > 0) max_reg = i; - } - - 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; - - for (regs, 0..) |reg, i| { - if (reg == 0) continue; - const addr = fp - frame_offset + 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 :blk new_ip; - }, - .STACK_IMMD, - .STACK_IND, - => blk: { - const sp = (try regValueNative(context.thread_context, spRegNum(reg_context), reg_context)).*; - const stack_size = if (encoding.mode.x86_64 == .STACK_IMMD) - @as(usize, encoding.value.x86_64.frameless.stack.direct.stack_size) * @sizeOf(usize) - else stack_size: { - // In .STACK_IND, the stack size is inferred from the subq instruction at the beginning of the function. - const sub_offset_addr = - base_address + - entry.function_offset + - encoding.value.x86_64.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, encoding.value.x86_64.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 = encoding.value.x86_64.frameless.stack_reg_count; - const ip_ptr = if (reg_count > 0) reg_blk: { - var digits: [6]u3 = undefined; - var accumulator: usize = encoding.value.x86_64.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; - } - - const reg_numbers = [_]u3{ 1, 2, 3, 4, 5, 6 }; - var registers: [reg_numbers.len]u3 = undefined; - var used_indices = [_]bool{false} ** reg_numbers.len; - 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] = reg_numbers[unused_index]; - 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 :reg_blk reg_addr; - } else sp + stack_size - @sizeOf(usize); - - 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 :blk new_ip; - }, - .DWARF => { - return unwindFrameMachODwarf(allocator, base_address, context, eh_frame orelse return error.MissingEhFrame, @intCast(encoding.value.x86_64.dwarf)); - }, - }, - .aarch64, .aarch64_be => switch (encoding.mode.arm64) { - .OLD => return error.UnimplementedUnwindEncoding, - .FRAMELESS => blk: { - 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 :blk new_ip; - }, - .DWARF => { - return unwindFrameMachODwarf(allocator, base_address, context, eh_frame orelse return error.MissingEhFrame, @intCast(encoding.value.arm64.dwarf)); - }, - .FRAME => blk: { - 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(encoding.value.arm64.frame.x_reg_pairs)).@"struct".fields, 0..) |field, i| { - if (@field(encoding.value.arm64.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(encoding.value.arm64.frame.d_reg_pairs)).@"struct".fields, 0..) |field, i| { - if (@field(encoding.value.arm64.frame.d_reg_pairs, field.name) != 0) { - // Only the lower half of the 128-bit V registers are restored during unwinding - @memcpy( - try regBytes(context.thread_context, 64 + 8 + i, context.reg_context), - std.mem.asBytes(@as(*const usize, @ptrFromInt(reg_addr))), - ); - reg_addr += @sizeOf(usize); - @memcpy( - try regBytes(context.thread_context, 64 + 9 + i, context.reg_context), - std.mem.asBytes(@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)).*; + 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 regValueNative(context.thread_context, fpRegNum(reg_context), reg_context)).* = new_fp; - (try regValueNative(context.thread_context, ip_reg_num, reg_context)).* = new_ip; + 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).?); - break :blk new_ip; - }, - }, - else => return error.UnimplementedArch, - }; + try testing.expectEqual(&symbols[1], find(symbols, 200).?); + try testing.expectEqual(&symbols[1], find(symbols, 250).?); + try testing.expectEqual(&symbols[1], find(symbols, 299).?); - context.pc = stripInstructionPtrAuthCode(new_ip); - if (context.pc > 0) context.pc -= 1; - return new_ip; + 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; } pub const UnwindContext = struct { - allocator: Allocator, + gpa: Allocator, cfa: ?usize, pc: usize, thread_context: *std.debug.ThreadContext, reg_context: Dwarf.abi.RegisterContext, - vm: VirtualMachine, + vm: Dwarf.Unwind.VirtualMachine, stack_machine: Dwarf.expression.StackMachine(.{ .call_frame_context = true }), - pub fn init( - allocator: Allocator, - thread_context: *std.debug.ThreadContext, - ) !UnwindContext { + pub fn init(gpa: Allocator, thread_context: *std.debug.ThreadContext) !UnwindContext { comptime assert(supports_unwinding); const pc = stripInstructionPtrAuthCode( (try regValueNative(thread_context, ip_reg_num, null)).*, ); - const context_copy = try allocator.create(std.debug.ThreadContext); + const context_copy = try gpa.create(std.debug.ThreadContext); std.debug.copyContext(thread_context, context_copy); return .{ - .allocator = allocator, + .gpa = gpa, .cfa = null, .pc = pc, .thread_context = context_copy, @@ -1544,15 +970,86 @@ pub const UnwindContext = struct { } pub fn deinit(self: *UnwindContext) void { - self.vm.deinit(self.allocator); - self.stack_machine.deinit(self.allocator); - self.allocator.destroy(self.thread_context); + self.vm.deinit(self.gpa); + self.stack_machine.deinit(self.gpa); + self.gpa.destroy(self.thread_context); self.* = undefined; } pub fn getFp(self: *const UnwindContext) !usize { return (try regValueNative(self.thread_context, fpRegNum(self.reg_context), self.reg_context)).*; } + + /// Resolves the register rule and places the result into `out` (see regBytes) + pub fn resolveRegisterRule( + context: *UnwindContext, + col: Dwarf.Unwind.VirtualMachine.Column, + expression_context: std.debug.Dwarf.expression.Context, + out: []u8, + ) !void { + switch (col.rule) { + .default => { + const register = col.register orelse return error.InvalidRegister; + // The default type is usually undefined, but can be overriden by ABI authors. + // See the doc comment on `Dwarf.Unwind.VirtualMachine.RegisterRule.default`. + if (builtin.cpu.arch.isAARCH64() and register >= 19 and register <= 18) { + // Callee-saved registers are initialized as if they had the .same_value rule + const src = try regBytes(context.thread_context, register, context.reg_context); + if (src.len != out.len) return error.RegisterSizeMismatch; + @memcpy(out, src); + return; + } + @memset(out, undefined); + }, + .undefined => { + @memset(out, undefined); + }, + .same_value => { + // TODO: This copy could be eliminated if callers always copy the state then call this function to update it + const register = col.register orelse return error.InvalidRegister; + const src = try regBytes(context.thread_context, register, context.reg_context); + if (src.len != out.len) return error.RegisterSizeMismatch; + @memcpy(out, src); + }, + .offset => |offset| { + if (context.cfa) |cfa| { + const addr = try applyOffset(cfa, offset); + const ptr: *const usize = @ptrFromInt(addr); + mem.writeInt(usize, out[0..@sizeOf(usize)], ptr.*, native_endian); + } else return error.InvalidCFA; + }, + .val_offset => |offset| { + if (context.cfa) |cfa| { + mem.writeInt(usize, out[0..@sizeOf(usize)], try applyOffset(cfa, offset), native_endian); + } else return error.InvalidCFA; + }, + .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)); + }, + .expression => |expression| { + context.stack_machine.reset(); + const value = try context.stack_machine.run(expression, context.gpa, expression_context, context.cfa.?); + const addr = if (value) |v| blk: { + if (v != .generic) return error.InvalidExpressionValue; + break :blk v.generic; + } else return error.NoExpressionValue; + + const ptr: *usize = @ptrFromInt(addr); + mem.writeInt(usize, out[0..@sizeOf(usize)], ptr.*, native_endian); + }, + .val_expression => |expression| { + context.stack_machine.reset(); + const value = try context.stack_machine.run(expression, context.gpa, expression_context, context.cfa.?); + if (value) |v| { + if (v != .generic) return error.InvalidExpressionValue; + mem.writeInt(usize, out[0..@sizeOf(usize)], v.generic, native_endian); + } else return error.NoExpressionValue; + }, + .architectural => return error.UnimplementedRegisterRule, + } + } }; /// Some platforms use pointer authentication - the upper bits of instruction pointers contain a signature. @@ -1584,113 +1081,30 @@ pub inline fn stripInstructionPtrAuthCode(ptr: usize) usize { /// `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( - allocator: Allocator, - unwind: *Dwarf.Unwind, - base_address: usize, + 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; - // Find the FDE and CIE - const cie, const fde = if (explicit_fde_offset) |fde_offset| blk: { - const frame_section = unwind.section(.eh_frame) orelse return error.MissingFDE; - if (fde_offset >= frame_section.len) return error.MissingFDE; - - var fbr: std.Io.Reader = .fixed(frame_section); - fbr.seek = fde_offset; - - const fde_entry_header = try Dwarf.Unwind.EntryHeader.read(&fbr, .eh_frame, native_endian); - if (fde_entry_header.type != .fde) return error.MissingFDE; - - const cie_offset = fde_entry_header.type.fde; - fbr.seek = @intCast(cie_offset); - - const cie_entry_header = try Dwarf.Unwind.EntryHeader.read(&fbr, .eh_frame, native_endian); - if (cie_entry_header.type != .cie) return Dwarf.bad(); - - const cie = try Dwarf.Unwind.CommonInformationEntry.parse( - cie_entry_header.entry_bytes, - 0, - true, - cie_entry_header.format, - .eh_frame, - cie_entry_header.length_offset, - @sizeOf(usize), - native_endian, - ); - const fde = try Dwarf.Unwind.FrameDescriptionEntry.parse( - fde_entry_header.entry_bytes, - 0, - true, - cie, - @sizeOf(usize), - native_endian, - ); - - break :blk .{ cie, fde }; - } else blk: { - // `.eh_frame_hdr` may be incomplete. We'll try it first, but if the lookup fails, we fall - // back to loading `.eh_frame`/`.debug_frame` and using those from that point on. - - if (unwind.eh_frame_hdr) |header| hdr: { - const eh_frame_len = if (unwind.section(.eh_frame)) |eh_frame| eh_frame.len else { - try unwind.scanCieFdeInfo(allocator, native_endian, base_address); - unwind.eh_frame_hdr = null; - break :hdr; - }; - - var cie: Dwarf.Unwind.CommonInformationEntry = undefined; - var fde: Dwarf.Unwind.FrameDescriptionEntry = undefined; - - header.findEntry( - eh_frame_len, - @intFromPtr(unwind.section(.eh_frame_hdr).?.ptr), - context.pc, - &cie, - &fde, - native_endian, - ) catch |err| switch (err) { - error.MissingDebugInfo => { - // `.eh_frame_hdr` appears to be incomplete, so go ahead and populate `cie_map` - // and `fde_list`, and fall back to the binary search logic below. - try unwind.scanCieFdeInfo(allocator, native_endian, base_address); - - // Since `.eh_frame_hdr` is incomplete, we're very likely to get more lookup - // failures using it, and we've just built a complete, sorted list of FDEs - // anyway, so just stop using `.eh_frame_hdr` altogether. - unwind.eh_frame_hdr = null; - - break :hdr; - }, - else => return err, - }; - - break :blk .{ cie, fde }; - } - - const index = std.sort.binarySearch(Dwarf.Unwind.FrameDescriptionEntry, unwind.fde_list.items, context.pc, struct { - pub fn compareFn(pc: usize, item: Dwarf.Unwind.FrameDescriptionEntry) std.math.Order { - if (pc < item.pc_begin) return .lt; - - const range_end = item.pc_begin + item.pc_range; - if (pc < range_end) return .eq; + const pc_vaddr = context.pc - load_offset; - return .gt; - } - }.compareFn); - - const fde = if (index) |i| unwind.fde_list.items[i] else return error.MissingFDE; - const cie = unwind.cie_map.get(fde.cie_length_offset) orelse return error.MissingCIE; + const fde_offset = explicit_fde_offset orelse try unwind.findFdeOffset( + pc_vaddr, + @sizeOf(usize), + native_endian, + ) orelse return error.MissingDebugInfo; + const format, const cie, const fde = try unwind.loadFde(fde_offset, @sizeOf(usize), native_endian); - break :blk .{ cie, fde }; - }; + // 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 = cie.format, + .format = format, .thread_context = context.thread_context, .reg_context = context.reg_context, .cfa = context.cfa, @@ -1700,7 +1114,7 @@ fn unwindFrameDwarf( context.reg_context.eh_frame = cie.version != 4; context.reg_context.is_macho = native_os.isDarwin(); - const row = try context.vm.runToNative(context.allocator, context.pc, cie, fde); + 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; @@ -1711,7 +1125,7 @@ fn unwindFrameDwarf( context.stack_machine.reset(); const value = try context.stack_machine.run( expr, - context.allocator, + context.gpa, expression_context, context.cfa, ); @@ -1728,9 +1142,9 @@ fn unwindFrameDwarf( // 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.allocator); + var arena: std.heap.ArenaAllocator = .init(context.gpa); defer arena.deinit(); - const update_allocator = arena.allocator(); + const update_arena = arena.allocator(); const RegisterUpdate = struct { // Backed by thread_context @@ -1749,17 +1163,16 @@ fn unwindFrameDwarf( } const dest = try regBytes(context.thread_context, register, context.reg_context); - const src = try update_allocator.alloc(u8, dest.len); + const src = try update_arena.alloc(u8, dest.len); + try context.resolveRegisterRule(column, expression_context, src); - const prev = update_tail; - update_tail = try update_allocator.create(RegisterUpdate); - update_tail.?.* = .{ + const new_update = try update_arena.create(RegisterUpdate); + new_update.* = .{ .dest = dest, .src = src, - .prev = prev, + .prev = update_tail, }; - - try column.resolveValue(context, expression_context, src); + update_tail = new_update; } } @@ -1792,7 +1205,7 @@ fn unwindFrameDwarf( // 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.isSignalFrame()) context.pc -= 1; + if (context.pc > 0 and !cie.is_signal_frame) context.pc -= 1; return return_address; } @@ -1843,415 +1256,345 @@ pub fn supportsUnwinding(target: *const std.Target) bool { }; } -fn unwindFrameMachODwarf( - allocator: Allocator, - base_address: usize, - context: *UnwindContext, - eh_frame: []const u8, - fde_offset: usize, -) !usize { - var di: Dwarf = .{ - .endian = native_endian, - .is_macho = true, - }; - defer di.deinit(context.allocator); +/// 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))); +} - di.sections[@intFromEnum(Dwarf.Section.Id.eh_frame)] = .{ - .data = eh_frame, - .owned = false, - }; +/// Uses `mmap` to map the file at `opt_path` (or, if `null`, the self executable image) into memory. +fn mapFileOrSelfExe(opt_path: ?[]const u8) ![]align(std.heap.page_size_min) const u8 { + const file = if (opt_path) |path| + try fs.cwd().openFile(path, .{}) + else + try fs.openSelfExe(.{}); + defer file.close(); + + const file_len = math.cast(usize, try file.getEndPos()) orelse return error.FileTooBig; - return unwindFrameDwarf(allocator, &di, base_address, context, fde_offset); + return posix.mmap( + null, + file_len, + posix.PROT.READ, + .{ .TYPE = .SHARED }, + file.handle, + 0, + ); } -/// This is a virtual machine that runs DWARF call frame instructions. -pub const VirtualMachine = struct { - /// See section 6.4.1 of the DWARF5 specification for details on each - const RegisterRule = union(enum) { - // The spec says that the default rule for each column is the undefined rule. - // However, it also allows ABI / compiler authors to specify alternate defaults, so - // there is a distinction made here. - default: void, - undefined: void, - same_value: void, - // offset(N) - offset: i64, - // val_offset(N) - val_offset: i64, - // register(R) - register: u8, - // expression(E) - expression: []const u8, - // val_expression(E) - val_expression: []const u8, - // Augmenter-defined rule - architectural: void, - }; +/// 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, + 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); - /// Each row contains unwinding rules for a set of registers. - pub const Row = struct { - /// Offset from `FrameDescriptionEntry.pc_begin` - offset: u64 = 0, - /// Special-case column that defines the CFA (Canonical Frame Address) rule. - /// The register field of this column defines the register that CFA is derived from. - cfa: Column = .{}, - /// The register fields in these columns define the register the rule applies to. - columns: ColumnRange = .{}, - /// Indicates that the next write to any column in this row needs to copy - /// the backing column storage first, as it may be referenced by previous rows. - copy_on_write: bool = false, - }; + 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; - pub const Column = struct { - register: ?u8 = null, - rule: RegisterRule = .{ .default = {} }, - - /// Resolves the register rule and places the result into `out` (see regBytes) - pub fn resolveValue( - self: Column, - context: *SelfInfo.UnwindContext, - expression_context: std.debug.Dwarf.expression.Context, - out: []u8, - ) !void { - switch (self.rule) { - .default => { - const register = self.register orelse return error.InvalidRegister; - try getRegDefaultValue(register, context, out); - }, - .undefined => { - @memset(out, undefined); - }, - .same_value => { - // TODO: This copy could be eliminated if callers always copy the state then call this function to update it - const register = self.register orelse return error.InvalidRegister; - const src = try regBytes(context.thread_context, register, context.reg_context); - if (src.len != out.len) return error.RegisterSizeMismatch; - @memcpy(out, src); - }, - .offset => |offset| { - if (context.cfa) |cfa| { - const addr = try applyOffset(cfa, offset); - const ptr: *const usize = @ptrFromInt(addr); - mem.writeInt(usize, out[0..@sizeOf(usize)], ptr.*, native_endian); - } else return error.InvalidCFA; - }, - .val_offset => |offset| { - if (context.cfa) |cfa| { - mem.writeInt(usize, out[0..@sizeOf(usize)], try applyOffset(cfa, offset), native_endian); - } else return error.InvalidCFA; - }, - .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)); - }, - .expression => |expression| { - context.stack_machine.reset(); - const value = try context.stack_machine.run(expression, context.allocator, expression_context, context.cfa.?); - const addr = if (value) |v| blk: { - if (v != .generic) return error.InvalidExpressionValue; - break :blk v.generic; - } else return error.NoExpressionValue; - - const ptr: *usize = @ptrFromInt(addr); - mem.writeInt(usize, out[0..@sizeOf(usize)], ptr.*, native_endian); - }, - .val_expression => |expression| { - context.stack_machine.reset(); - const value = try context.stack_machine.run(expression, context.allocator, expression_context, context.cfa.?); - if (value) |v| { - if (v != .generic) return error.InvalidExpressionValue; - mem.writeInt(usize, out[0..@sizeOf(usize)], v.generic, native_endian); - } else return error.NoExpressionValue; - }, - .architectural => return error.UnimplementedRegisterRule, + // MLUGG TODO HACKHACK -- Unwind needs a slight refactor to make this work well + const opt_dwarf_unwind: ?Dwarf.Unwind = if (eh_frame) |eh_frame_data| .{ + .debug_frame = null, + .eh_frame = .{ + .header = .{ + .vaddr = undefined, + .eh_frame_vaddr = @intFromPtr(eh_frame_data.ptr) - load_offset, + .search_table = null, + }, + .eh_frame_data = eh_frame_data, + .sorted_fdes = null, + }, + } else null; + + // 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 ColumnRange = struct { - /// Index into `columns` of the first column in this row. - start: usize = undefined, - len: u8 = 0, - }; + 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], + ); - columns: std.ArrayListUnmanaged(Column) = .empty, - stack: std.ArrayListUnmanaged(ColumnRange) = .empty, - current_row: Row = .{}, + 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..]); - /// The result of executing the CIE's initial_instructions - cie_row: ?Row = null, + 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; - pub fn deinit(self: *VirtualMachine, allocator: std.mem.Allocator) void { - self.stack.deinit(allocator); - self.columns.deinit(allocator); - self.* = undefined; - } + 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; - pub fn reset(self: *VirtualMachine) void { - self.stack.clearRetainingCapacity(); - self.columns.clearRetainingCapacity(); - self.current_row = .{}; - self.cie_row = null; - } + 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]; - /// Return a slice backed by the row's non-CFA columns - pub fn rowColumns(self: VirtualMachine, row: Row) []Column { - if (row.columns.len == 0) return &.{}; - return self.columns.items[row.columns.start..][0..row.columns.len]; - } + 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], + }; + } - /// Either retrieves or adds a column for `register` (non-CFA) in the current row. - fn getOrAddColumn(self: *VirtualMachine, allocator: std.mem.Allocator, register: u8) !*Column { - for (self.rowColumns(self.current_row)) |*c| { - if (c.register == register) return c; - } + 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 (self.current_row.columns.len == 0) { - self.current_row.columns.start = self.columns.items.len; - } - self.current_row.columns.len += 1; + if (entry.raw_encoding == 0) return error.NoUnwindInfo; + const reg_context: Dwarf.abi.RegisterContext = .{ .eh_frame = false, .is_macho = true }; - const column = try self.columns.addOne(allocator); - column.* = .{ - .register = register, - }; + 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; - return column; - } + const fp = (try regValueNative(context.thread_context, fpRegNum(reg_context), reg_context)).*; + const new_sp = fp + 2 * @sizeOf(usize); - /// Runs the CIE instructions, then the FDE instructions. Execution halts - /// once the row that corresponds to `pc` is known, and the row is returned. - pub fn runTo( - self: *VirtualMachine, - allocator: std.mem.Allocator, - pc: u64, - cie: std.debug.Dwarf.Unwind.CommonInformationEntry, - fde: std.debug.Dwarf.Unwind.FrameDescriptionEntry, - addr_size_bytes: u8, - endian: std.builtin.Endian, - ) !Row { - assert(self.cie_row == null); - if (pc < fde.pc_begin or pc >= fde.pc_begin + fde.pc_range) return error.AddressOutOfRange; - - var prev_row: Row = self.current_row; - - var cie_stream: std.Io.Reader = .fixed(cie.initial_instructions); - var fde_stream: std.Io.Reader = .fixed(fde.instructions); - const streams = [_]*std.Io.Reader{ &cie_stream, &fde_stream }; - - for (&streams, 0..) |stream, i| { - while (stream.seek < stream.buffer.len) { - const instruction = try std.debug.Dwarf.call_frame.Instruction.read(stream, addr_size_bytes, endian); - prev_row = try self.step(allocator, cie, i == 0, instruction); - if (pc < fde.pc_begin + self.current_row.offset) return prev_row; - } - } + const ip_ptr = fp + @sizeOf(usize); + const new_ip = @as(*const usize, @ptrFromInt(ip_ptr)).*; + const new_fp = @as(*const usize, @ptrFromInt(fp)).*; - return self.current_row; - } + (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; - pub fn runToNative( - self: *VirtualMachine, - allocator: std.mem.Allocator, - pc: u64, - cie: std.debug.Dwarf.Unwind.CommonInformationEntry, - fde: std.debug.Dwarf.Unwind.FrameDescriptionEntry, - ) !Row { - return self.runTo(allocator, pc, cie, fde, @sizeOf(usize), native_endian); - } + 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)).*; + } - fn resolveCopyOnWrite(self: *VirtualMachine, allocator: std.mem.Allocator) !void { - if (!self.current_row.copy_on_write) return; + break :ip new_ip; + }, + .STACK_IMMD, + .STACK_IND, + => ip: { + const frameless = encoding.value.x86_64.frameless; - const new_start = self.columns.items.len; - if (self.current_row.columns.len > 0) { - try self.columns.ensureUnusedCapacity(allocator, self.current_row.columns.len); - self.columns.appendSliceAssumeCapacity(self.rowColumns(self.current_row)); - self.current_row.columns.start = new_start; - } - } + 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); + }; - /// Executes a single instruction. - /// If this instruction is from the CIE, `is_initial` should be set. - /// Returns the value of `current_row` before executing this instruction. - pub fn step( - self: *VirtualMachine, - allocator: std.mem.Allocator, - cie: std.debug.Dwarf.Unwind.CommonInformationEntry, - is_initial: bool, - instruction: Dwarf.call_frame.Instruction, - ) !Row { - // CIE instructions must be run before FDE instructions - assert(!is_initial or self.cie_row == null); - if (!is_initial and self.cie_row == null) { - self.cie_row = self.current_row; - self.current_row.copy_on_write = true; - } + // 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 - const prev_row = self.current_row; - switch (instruction) { - .set_loc => |i| { - if (i.address <= self.current_row.offset) return error.InvalidOperation; - // TODO: Check cie.segment_selector_size != 0 for DWARFV4 - self.current_row.offset = i.address; - }, - inline .advance_loc, - .advance_loc1, - .advance_loc2, - .advance_loc4, - => |i| { - self.current_row.offset += i.delta * cie.code_alignment_factor; - self.current_row.copy_on_write = true; - }, - inline .offset, - .offset_extended, - .offset_extended_sf, - => |i| { - try self.resolveCopyOnWrite(allocator); - const column = try self.getOrAddColumn(allocator, i.register); - column.rule = .{ .offset = @as(i64, @intCast(i.offset)) * cie.data_alignment_factor }; - }, - inline .restore, - .restore_extended, - => |i| { - try self.resolveCopyOnWrite(allocator); - if (self.cie_row) |cie_row| { - const column = try self.getOrAddColumn(allocator, i.register); - column.rule = for (self.rowColumns(cie_row)) |cie_column| { - if (cie_column.register == i.register) break cie_column.rule; - } else .{ .default = {} }; - } else return error.InvalidOperation; - }, - .nop => {}, - .undefined => |i| { - try self.resolveCopyOnWrite(allocator); - const column = try self.getOrAddColumn(allocator, i.register); - column.rule = .{ .undefined = {} }; - }, - .same_value => |i| { - try self.resolveCopyOnWrite(allocator); - const column = try self.getOrAddColumn(allocator, i.register); - column.rule = .{ .same_value = {} }; - }, - .register => |i| { - try self.resolveCopyOnWrite(allocator); - const column = try self.getOrAddColumn(allocator, i.register); - column.rule = .{ .register = i.target_register }; - }, - .remember_state => { - try self.stack.append(allocator, self.current_row.columns); - self.current_row.copy_on_write = true; - }, - .restore_state => { - const restored_columns = self.stack.pop() orelse return error.InvalidOperation; - self.columns.shrinkRetainingCapacity(self.columns.items.len - self.current_row.columns.len); - try self.columns.ensureUnusedCapacity(allocator, restored_columns.len); - - self.current_row.columns.start = self.columns.items.len; - self.current_row.columns.len = restored_columns.len; - self.columns.appendSliceAssumeCapacity(self.columns.items[restored_columns.start..][0..restored_columns.len]); - }, - .def_cfa => |i| { - try self.resolveCopyOnWrite(allocator); - self.current_row.cfa = .{ - .register = i.register, - .rule = .{ .val_offset = @intCast(i.offset) }, - }; - }, - .def_cfa_sf => |i| { - try self.resolveCopyOnWrite(allocator); - self.current_row.cfa = .{ - .register = i.register, - .rule = .{ .val_offset = i.offset * cie.data_alignment_factor }, - }; - }, - .def_cfa_register => |i| { - try self.resolveCopyOnWrite(allocator); - if (self.current_row.cfa.register == null or self.current_row.cfa.rule != .val_offset) return error.InvalidOperation; - self.current_row.cfa.register = i.register; - }, - .def_cfa_offset => |i| { - try self.resolveCopyOnWrite(allocator); - if (self.current_row.cfa.register == null or self.current_row.cfa.rule != .val_offset) return error.InvalidOperation; - self.current_row.cfa.rule = .{ - .val_offset = @intCast(i.offset), - }; - }, - .def_cfa_offset_sf => |i| { - try self.resolveCopyOnWrite(allocator); - if (self.current_row.cfa.register == null or self.current_row.cfa.rule != .val_offset) return error.InvalidOperation; - self.current_row.cfa.rule = .{ - .val_offset = i.offset * cie.data_alignment_factor, - }; - }, - .def_cfa_expression => |i| { - try self.resolveCopyOnWrite(allocator); - self.current_row.cfa.register = undefined; - self.current_row.cfa.rule = .{ - .expression = i.block, - }; - }, - .expression => |i| { - try self.resolveCopyOnWrite(allocator); - const column = try self.getOrAddColumn(allocator, i.register); - column.rule = .{ - .expression = i.block, + // 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; }, - .val_offset => |i| { - try self.resolveCopyOnWrite(allocator); - const column = try self.getOrAddColumn(allocator, i.register); - column.rule = .{ - .val_offset = @as(i64, @intCast(i.offset)) * cie.data_alignment_factor, - }; + .DWARF => { + const dwarf_unwind = &(opt_dwarf_unwind orelse return error.MissingEhFrame); + return unwindFrameDwarf(dwarf_unwind, load_offset, context, @intCast(encoding.value.x86_64.dwarf)); }, - .val_offset_sf => |i| { - try self.resolveCopyOnWrite(allocator); - const column = try self.getOrAddColumn(allocator, i.register); - column.rule = .{ - .val_offset = i.offset * cie.data_alignment_factor, - }; + }, + .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; }, - .val_expression => |i| { - try self.resolveCopyOnWrite(allocator); - const column = try self.getOrAddColumn(allocator, i.register); - column.rule = .{ - .val_expression = i.block, - }; + .DWARF => { + const dwarf_unwind = &(opt_dwarf_unwind orelse return error.MissingEhFrame); + return unwindFrameDwarf(dwarf_unwind, load_offset, context, @intCast(encoding.value.arm64.dwarf)); }, - } + .FRAME => ip: { + const frame = encoding.value.arm64.frame; - return prev_row; - } -}; + const fp = (try regValueNative(context.thread_context, fpRegNum(reg_context), reg_context)).*; + const ip_ptr = fp + @sizeOf(usize); -/// Returns the ABI-defined default value this register has in the unwinding table -/// before running any of the CIE instructions. The DWARF spec defines these as having -/// the .undefined rule by default, but allows ABI authors to override that. -fn getRegDefaultValue(reg_number: u8, context: *UnwindContext, out: []u8) !void { - switch (builtin.cpu.arch) { - .aarch64, .aarch64_be => { - // Callee-saved registers are initialized as if they had the .same_value rule - if (reg_number >= 19 and reg_number <= 28) { - const src = try regBytes(context.thread_context, reg_number, context.reg_context); - if (src.len != out.len) return error.RegisterSizeMismatch; - @memcpy(out, src); - return; - } - }, - else => {}, - } + 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); + } + } - @memset(out, undefined); -} + 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); + } + } -/// 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))); + 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; } |
