authorgravatar for kcbanner@gmail.comCasey Banner <kcbanner@gmail.com> 2023-05-15 01:52:53-04:00
committergravatar for kcbanner@gmail.comCasey Banner <kcbanner@gmail.com> 2023-07-20 22:58:13-04:00
logb449d98a935a20429874d8eb379d9cc0e49c5fcd
tree8d483a4e3e8ccdfcc90ad9e0c7f70c642f9bff38
parent69399fbb82ea74fce4fb6bbfec5ab2cbfa435c1a

- rework StackIterator to optionally use debug_info to unwind the stack

- add abi routines for getting register values - unwding is working!

4 files changed, 513 insertions(+), 122 deletions(-)

lib/std/debug.zig+187-80
......@@ -135,8 +135,9 @@ pub fn dumpCurrentStackTrace(start_addr: ?usize) void {
135135
136136/// Tries to print the stack trace starting from the supplied base pointer to stderr,
137137/// unbuffered, and ignores any error returned.
138/// `context` is either *const os.ucontext_t on posix, or the result of CONTEXT.getRegs() on Windows.
138139/// TODO multithreaded awareness
139pub fn dumpStackTraceFromBase(bp: usize, ip: usize) void {
140pub fn dumpStackTraceFromBase(context: anytype) void {
140141 nosuspend {
141142 if (comptime builtin.target.isWasm()) {
142143 if (native_os == .wasi) {
......@@ -156,12 +157,15 @@ pub fn dumpStackTraceFromBase(bp: usize, ip: usize) void {
156157 };
157158 const tty_config = io.tty.detectConfig(io.getStdErr());
158159 if (native_os == .windows) {
159 writeCurrentStackTraceWindows(stderr, debug_info, tty_config, ip) catch return;
160 writeCurrentStackTraceWindows(stderr, debug_info, tty_config, context.ip) catch return;
160161 return;
161162 }
162163
163 printSourceAtAddress(debug_info, stderr, ip, tty_config) catch return;
164 var it = StackIterator.init(null, bp);
164 var it = StackIterator.initWithContext(null, debug_info, context) catch return;
165
166 // TODO: Should `it.dwarf_context.pc` be `it.getIp()`? (but then the non-dwarf case has to store ip)
167 printSourceAtAddress(debug_info, stderr, it.dwarf_context.pc, tty_config) catch return;
168
165169 while (it.next()) |return_address| {
166170 // On arm64 macOS, the address of the last frame is 0x0 rather than 0x1 as on x86_64 macOS,
167171 // therefore, we do a check for `return_address == 0` before subtracting 1 from it to avoid
......@@ -206,6 +210,7 @@ pub fn captureStackTrace(first_address: ?usize, stack_trace: *std.builtin.StackT
206210 }
207211 stack_trace.index = slice.len;
208212 } else {
213 // TODO: This should use the dwarf unwinder if it's available
209214 var it = StackIterator.init(first_address, null);
210215 for (stack_trace.instruction_addresses, 0..) |*addr, i| {
211216 addr.* = it.next() orelse {
......@@ -405,6 +410,11 @@ pub const StackIterator = struct {
405410 // Last known value of the frame pointer register.
406411 fp: usize,
407412
413 // When DebugInfo and a register context is available, this iterator can unwind
414 // stacks with frames that don't use a frame pointer (ie. -fomit-frame-pointer).
415 debug_info: ?*DebugInfo,
416 dwarf_context: if (@hasDecl(os, "ucontext_t")) DW.UnwindContext else void = undefined,
417
408418 pub fn init(first_address: ?usize, fp: ?usize) StackIterator {
409419 if (native_arch == .sparc64) {
410420 // Flush all the register windows on stack.
......@@ -416,9 +426,17 @@ pub const StackIterator = struct {
416426 return StackIterator{
417427 .first_address = first_address,
418428 .fp = fp orelse @frameAddress(),
429 .debug_info = null,
419430 };
420431 }
421432
433 pub fn initWithContext(first_address: ?usize, debug_info: *DebugInfo, context: *const os.ucontext_t) !StackIterator {
434 var iterator = init(first_address, null);
435 iterator.debug_info = debug_info;
436 iterator.dwarf_context = try DW.UnwindContext.init(context);
437 return iterator;
438 }
439
422440 // Offset of the saved BP wrt the frame pointer.
423441 const fp_offset = if (native_arch.isRISCV())
424442 // On RISC-V the frame pointer points to the top of the saved register
......@@ -500,7 +518,28 @@ pub const StackIterator = struct {
500518 }
501519 }
502520
521 fn next_dwarf(self: *StackIterator) !void {
522 const module = try self.debug_info.?.getModuleForAddress(self.dwarf_context.pc);
523 if (module.getDwarfInfo()) |di| {
524 try di.unwindFrame(self.debug_info.?.allocator, &self.dwarf_context, module.base_address);
525 } else return error.MissingDebugInfo;
526 }
527
503528 fn next_internal(self: *StackIterator) ?usize {
529 if (self.debug_info != null) {
530 if (self.next_dwarf()) |_| {
531 return self.dwarf_context.pc;
532 } else |err| {
533 // Fall back to fp unwinding on the first failure,
534 // as the register context won't be updated
535 self.fp = self.dwarf_context.getFp() catch 0;
536 self.debug_info = null;
537
538 // TODO: Remove
539 print("\ndwarf unwind error {}, placing fp at 0x{x}\n\n", .{err, self.fp});
540 }
541 }
542
504543 const fp = if (comptime native_arch.isSPARC())
505544 // On SPARC the offset is positive. (!)
506545 math.add(usize, self.fp, fp_offset) catch return null
......@@ -540,6 +579,8 @@ pub fn writeCurrentStackTrace(
540579 if (native_os == .windows) {
541580 return writeCurrentStackTraceWindows(out_stream, debug_info, tty_config, start_addr);
542581 }
582
583 // TODO: Capture a context and use initWithContext
543584 var it = StackIterator.init(start_addr, null);
544585 while (it.next()) |return_address| {
545586 // On arm64 macOS, the address of the last frame is 0x0 rather than 0x1 as on x86_64 macOS,
......@@ -800,12 +841,14 @@ fn readCoffDebugInfo(allocator: mem.Allocator, coff_bytes: []const u8) !ModuleDe
800841 // This coff file has embedded DWARF debug info
801842 _ = sec;
802843
803 const num_sections = std.enums.directEnumArrayLen(DW.DwarfSection, 0);
804 var sections: [num_sections]?[]const u8 = [_]?[]const u8{null} ** num_sections;
805 errdefer for (sections) |section| if (section) |s| allocator.free(s);
844 var sections: DW.DwarfInfo.SectionArray = DW.DwarfInfo.null_section_array;
845 errdefer for (sections) |section| if (section) |s| if (s.owned) allocator.free(s.data);
806846
807847 inline for (@typeInfo(DW.DwarfSection).Enum.fields, 0..) |section, i| {
808 sections[i] = try coff_obj.getSectionDataAlloc("." ++ section.name, allocator);
848 sections[i] = .{
849 .data = try coff_obj.getSectionDataAlloc("." ++ section.name, allocator),
850 .owned = true,
851 };
809852 }
810853
811854 var dwarf = DW.DwarfInfo{
......@@ -813,7 +856,7 @@ fn readCoffDebugInfo(allocator: mem.Allocator, coff_bytes: []const u8) !ModuleDe
813856 .sections = sections,
814857 };
815858
816 try DW.openDwarfDebugInfo(&dwarf, allocator);
859 try DW.openDwarfDebugInfo(&dwarf, allocator, coff_bytes);
817860 di.debug_data = PdbOrDwarf{ .dwarf = dwarf };
818861 return di;
819862 }
......@@ -854,6 +897,8 @@ pub fn readElfDebugInfo(
854897 elf_filename: ?[]const u8,
855898 build_id: ?[]const u8,
856899 expected_crc: ?u32,
900 parent_sections: *DW.DwarfInfo.SectionArray,
901 parent_mapped_mem: ?[]align(mem.page_size) const u8,
857902) !ModuleDebugInfo {
858903 nosuspend {
859904
......@@ -891,10 +936,20 @@ pub fn readElfDebugInfo(
891936 @ptrCast(@alignCast(&mapped_mem[shoff])),
892937 )[0..hdr.e_shnum];
893938
894 const num_sections = std.enums.directEnumArrayLen(DW.DwarfSection, 0);
895 var sections: [num_sections]?[]const u8 = [_]?[]const u8{null} ** num_sections;
896 var owned_sections: [num_sections][]const u8 = [_][]const u8{&.{}} ** num_sections;
897 errdefer for (owned_sections) |section| allocator.free(section);
939 var sections: DW.DwarfInfo.SectionArray = DW.DwarfInfo.null_section_array;
940
941 // Take ownership over any owned sections from the parent scope
942 for (parent_sections, &sections) |*parent, *section| {
943 if (parent.*) |*p| {
944 section.* = p.*;
945 p.owned = false;
946 }
947 }
948
949 errdefer for (sections) |section| if (section) |s| if (s.owned) allocator.free(s.data);
950
951 // TODO: This function should take a ptr to GNU_EH_FRAME (which is .eh_frame_hdr) from the ELF headers
952 // and prefil sections[.eh_frame_hdr]
898953
899954 var separate_debug_filename: ?[]const u8 = null;
900955 var separate_debug_crc: ?u32 = null;
......@@ -920,7 +975,7 @@ pub fn readElfDebugInfo(
920975 if (section_index == null) continue;
921976
922977 const section_bytes = try chopSlice(mapped_mem, shdr.sh_offset, shdr.sh_size);
923 if ((shdr.sh_flags & elf.SHF_COMPRESSED) > 0) {
978 sections[section_index.?] = if ((shdr.sh_flags & elf.SHF_COMPRESSED) > 0) blk: {
924979 var section_stream = io.fixedBufferStream(section_bytes);
925980 var section_reader = section_stream.reader();
926981 const chdr = section_reader.readStruct(elf.Chdr) catch continue;
......@@ -937,11 +992,14 @@ pub fn readElfDebugInfo(
937992 const read = zlib_stream.reader().readAll(decompressed_section) catch continue;
938993 assert(read == decompressed_section.len);
939994
940 sections[section_index.?] = decompressed_section;
941 owned_sections[section_index.?] = decompressed_section;
942 } else {
943 sections[section_index.?] = section_bytes;
944 }
995 break :blk .{
996 .data = decompressed_section,
997 .owned = true,
998 };
999 } else .{
1000 .data = section_bytes,
1001 .owned = false,
1002 };
9451003 }
9461004
9471005 const missing_debug_info =
......@@ -953,6 +1011,12 @@ pub fn readElfDebugInfo(
9531011 // Attempt to load debug info from an external file
9541012 // See: https://sourceware.org/gdb/onlinedocs/gdb/Separate-Debug-Files.html
9551013 if (missing_debug_info) {
1014
1015 // Only allow one level of debug info nesting
1016 if (parent_mapped_mem) |_| {
1017 return error.MissingDebugInfo;
1018 }
1019
9561020 const global_debug_directories = [_][]const u8{
9571021 "/usr/lib/debug",
9581022 };
......@@ -977,8 +1041,9 @@ pub fn readElfDebugInfo(
9771041 // TODO: joinBuf would be ideal (with a fs.MAX_PATH_BYTES buffer)
9781042 const path = try fs.path.join(allocator, &.{ global_directory, ".build-id", &id_prefix_buf, filename });
9791043 defer allocator.free(path);
1044 // TODO: Remove
9801045 std.debug.print(" Loading external debug info from {s}\n", .{path});
981 return readElfDebugInfo(allocator, path, null, separate_debug_crc) catch continue;
1046 return readElfDebugInfo(allocator, path, null, separate_debug_crc, &sections, mapped_mem) catch continue;
9821047 }
9831048 }
9841049
......@@ -987,14 +1052,14 @@ pub fn readElfDebugInfo(
9871052 if (elf_filename != null and mem.eql(u8, elf_filename.?, separate_filename)) return error.MissingDebugInfo;
9881053
9891054 // <cwd>/<gnu_debuglink>
990 if (readElfDebugInfo(allocator, separate_filename, null, separate_debug_crc)) |debug_info| return debug_info else |_| {}
1055 if (readElfDebugInfo(allocator, separate_filename, null, separate_debug_crc, &sections, mapped_mem)) |debug_info| return debug_info else |_| {}
9911056
9921057 // <cwd>/.debug/<gnu_debuglink>
9931058 {
9941059 const path = try fs.path.join(allocator, &.{ ".debug", separate_filename });
9951060 defer allocator.free(path);
9961061
997 if (readElfDebugInfo(allocator, path, null, separate_debug_crc)) |debug_info| return debug_info else |_| {}
1062 if (readElfDebugInfo(allocator, path, null, separate_debug_crc, &sections, mapped_mem)) |debug_info| return debug_info else |_| {}
9981063 }
9991064
10001065 var cwd_buf: [fs.MAX_PATH_BYTES]u8 = undefined;
......@@ -1004,7 +1069,7 @@ pub fn readElfDebugInfo(
10041069 for (global_debug_directories) |global_directory| {
10051070 const path = try fs.path.join(allocator, &.{ global_directory, cwd_path, separate_filename });
10061071 defer allocator.free(path);
1007 if (readElfDebugInfo(allocator, path, null, separate_debug_crc)) |debug_info| return debug_info else |_| {}
1072 if (readElfDebugInfo(allocator, path, null, separate_debug_crc, &sections, mapped_mem)) |debug_info| return debug_info else |_| {}
10081073 }
10091074 }
10101075
......@@ -1016,13 +1081,13 @@ pub fn readElfDebugInfo(
10161081 .sections = sections,
10171082 };
10181083
1019 try DW.openDwarfDebugInfo(&di, allocator);
1084 try DW.openDwarfDebugInfo(&di, allocator, parent_mapped_mem orelse mapped_mem);
10201085
10211086 return ModuleDebugInfo{
10221087 .base_address = undefined,
10231088 .dwarf = di,
1024 .mapped_memory = mapped_mem,
1025 .owned_sections = owned_sections,
1089 .mapped_memory = parent_mapped_mem orelse mapped_mem,
1090 .external_mapped_memory = if (parent_mapped_mem != null) mapped_mem else null,
10261091 };
10271092 }
10281093}
......@@ -1426,7 +1491,8 @@ pub const DebugInfo = struct {
14261491 for (phdrs) |*phdr| {
14271492 if (phdr.p_type != elf.PT_LOAD) continue;
14281493
1429 const seg_start = info.dlpi_addr + phdr.p_vaddr;
1494 // Overflowing addition is used to handle the case of VSDOs having a p_vaddr = 0xffffffffff700000
1495 const seg_start = info.dlpi_addr +% phdr.p_vaddr;
14301496 const seg_end = seg_start + phdr.p_memsz;
14311497 if (context.address >= seg_start and context.address < seg_end) {
14321498 // Android libc uses NULL instead of an empty string to mark the
......@@ -1437,6 +1503,8 @@ pub const DebugInfo = struct {
14371503 }
14381504 } else return;
14391505
1506 // TODO: Look for the GNU_EH_FRAME section and pass it to readElfDebugInfo
1507
14401508 for (info.dlpi_phdr[0..info.dlpi_phnum]) |phdr| {
14411509 if (phdr.p_type != elf.PT_NOTE) continue;
14421510
......@@ -1447,7 +1515,7 @@ pub const DebugInfo = struct {
14471515 const note_type = mem.readIntSliceNative(u32, note_bytes[8..12]);
14481516 if (note_type != elf.NT_GNU_BUILD_ID) continue;
14491517 if (!mem.eql(u8, "GNU\x00", note_bytes[12..16])) continue;
1450 context.build_id = note_bytes[16 .. 16 + desc_size];
1518 context.build_id = note_bytes[16..][0..desc_size];
14511519 }
14521520
14531521 // Stop the iteration
......@@ -1466,7 +1534,10 @@ pub const DebugInfo = struct {
14661534 const obj_di = try self.allocator.create(ModuleDebugInfo);
14671535 errdefer self.allocator.destroy(obj_di);
14681536
1469 obj_di.* = try readElfDebugInfo(self.allocator, if (ctx.name.len > 0) ctx.name else null, ctx.build_id, null);
1537 var sections: DW.DwarfInfo.SectionArray = DW.DwarfInfo.null_section_array;
1538 // TODO: If GNU_EH_FRAME was found, set it in sections
1539
1540 obj_di.* = try readElfDebugInfo(self.allocator, if (ctx.name.len > 0) ctx.name else null, ctx.build_id, null, &sections, null);
14701541 obj_di.base_address = ctx.base_address;
14711542
14721543 try self.address_map.putNoClobber(ctx.base_address, obj_di);
......@@ -1491,6 +1562,7 @@ pub const ModuleDebugInfo = switch (native_os) {
14911562 .macos, .ios, .watchos, .tvos => struct {
14921563 base_address: usize,
14931564 mapped_memory: []align(mem.page_size) const u8,
1565 external_mapped_memory: ?[]align(mem.page_size) const u8,
14941566 symbols: []const MachoSymbol,
14951567 strings: [:0]const u8,
14961568 ofiles: OFileTable,
......@@ -1511,6 +1583,7 @@ pub const ModuleDebugInfo = switch (native_os) {
15111583 self.ofiles.deinit();
15121584 allocator.free(self.symbols);
15131585 os.munmap(self.mapped_memory);
1586 if (self.external_mapped_memory) |m| os.munmap(m);
15141587 }
15151588
15161589 fn loadOFile(self: *@This(), allocator: mem.Allocator, o_file_path: []const u8) !OFileInfo {
......@@ -1723,6 +1796,12 @@ pub const ModuleDebugInfo = switch (native_os) {
17231796 unreachable;
17241797 }
17251798 }
1799
1800 pub fn getDwarfInfo(self: *@This()) ?*const DW.DwarfInfo {
1801 // TODO: Implement
1802 _ = self;
1803 return null;
1804 }
17261805 },
17271806 .uefi, .windows => struct {
17281807 base_address: usize,
......@@ -1803,19 +1882,24 @@ pub const ModuleDebugInfo = switch (native_os) {
18031882 .line_info = opt_line_info,
18041883 };
18051884 }
1885
1886 pub fn getDwarfInfo(self: *@This()) ?*const DW.DwarfInfo {
1887 return switch (self.debug_data) {
1888 .dwarf => |*dwarf| dwarf,
1889 else => null,
1890 };
1891 }
18061892 },
18071893 .linux, .netbsd, .freebsd, .dragonfly, .openbsd, .haiku, .solaris => struct {
18081894 base_address: usize,
18091895 dwarf: DW.DwarfInfo,
18101896 mapped_memory: []align(mem.page_size) const u8,
1811 owned_sections: [num_sections][]const u8 = [_][]const u8{&.{}} ** num_sections,
1812
1813 const num_sections = 14;
1897 external_mapped_memory: ?[]align(mem.page_size) const u8,
18141898
18151899 fn deinit(self: *@This(), allocator: mem.Allocator) void {
18161900 self.dwarf.deinit(allocator);
1817 for (self.owned_sections) |section| allocator.free(section);
18181901 os.munmap(self.mapped_memory);
1902 if (self.external_mapped_memory) |m| os.munmap(m);
18191903 }
18201904
18211905 pub fn getSymbolAtAddress(self: *@This(), allocator: mem.Allocator, address: usize) !SymbolInfo {
......@@ -1823,6 +1907,10 @@ pub const ModuleDebugInfo = switch (native_os) {
18231907 const relocated_address = address - self.base_address;
18241908 return getSymbolFromDwarf(allocator, relocated_address, &self.dwarf);
18251909 }
1910
1911 pub fn getDwarfInfo(self: *@This()) ?*const DW.DwarfInfo {
1912 return &self.dwarf;
1913 }
18261914 },
18271915 .wasi => struct {
18281916 fn deinit(self: *@This(), allocator: mem.Allocator) void {
......@@ -1836,6 +1924,11 @@ pub const ModuleDebugInfo = switch (native_os) {
18361924 _ = address;
18371925 return SymbolInfo{};
18381926 }
1927
1928 pub fn getDwarfInfo(self: *@This()) ?*const DW.DwarfInfo {
1929 _ = self;
1930 return null;
1931 }
18391932 },
18401933 else => DW.DwarfInfo,
18411934};
......@@ -1992,55 +2085,69 @@ fn dumpSegfaultInfoPosix(sig: i32, addr: usize, ctx_ptr: ?*const anyopaque) void
19922085 } catch os.abort();
19932086
19942087 switch (native_arch) {
1995 .x86 => {
1996 const ctx: *const os.ucontext_t = @ptrCast(@alignCast(ctx_ptr));
1997 const ip = @as(usize, @intCast(ctx.mcontext.gregs[os.REG.EIP]));
1998 const bp = @as(usize, @intCast(ctx.mcontext.gregs[os.REG.EBP]));
1999 dumpStackTraceFromBase(bp, ip);
2000 },
2001 .x86_64 => {
2002 const ctx: *const os.ucontext_t = @ptrCast(@alignCast(ctx_ptr));
2003 const ip = switch (native_os) {
2004 .linux, .netbsd, .solaris => @as(usize, @intCast(ctx.mcontext.gregs[os.REG.RIP])),
2005 .freebsd => @as(usize, @intCast(ctx.mcontext.rip)),
2006 .openbsd => @as(usize, @intCast(ctx.sc_rip)),
2007 .macos => @as(usize, @intCast(ctx.mcontext.ss.rip)),
2008 else => unreachable,
2009 };
2010 const bp = switch (native_os) {
2011 .linux, .netbsd, .solaris => @as(usize, @intCast(ctx.mcontext.gregs[os.REG.RBP])),
2012 .openbsd => @as(usize, @intCast(ctx.sc_rbp)),
2013 .freebsd => @as(usize, @intCast(ctx.mcontext.rbp)),
2014 .macos => @as(usize, @intCast(ctx.mcontext.ss.rbp)),
2015 else => unreachable,
2016 };
2017 dumpStackTraceFromBase(bp, ip);
2018 },
2019 .arm => {
2020 const ctx: *const os.ucontext_t = @ptrCast(@alignCast(ctx_ptr));
2021 const ip = @as(usize, @intCast(ctx.mcontext.arm_pc));
2022 const bp = @as(usize, @intCast(ctx.mcontext.arm_fp));
2023 dumpStackTraceFromBase(bp, ip);
2024 },
2025 .aarch64 => {
2026 const ctx: *const os.ucontext_t = @ptrCast(@alignCast(ctx_ptr));
2027 const ip = switch (native_os) {
2028 .macos => @as(usize, @intCast(ctx.mcontext.ss.pc)),
2029 .netbsd => @as(usize, @intCast(ctx.mcontext.gregs[os.REG.PC])),
2030 .freebsd => @as(usize, @intCast(ctx.mcontext.gpregs.elr)),
2031 else => @as(usize, @intCast(ctx.mcontext.pc)),
2032 };
2033 // x29 is the ABI-designated frame pointer
2034 const bp = switch (native_os) {
2035 .macos => @as(usize, @intCast(ctx.mcontext.ss.fp)),
2036 .netbsd => @as(usize, @intCast(ctx.mcontext.gregs[os.REG.FP])),
2037 .freebsd => @as(usize, @intCast(ctx.mcontext.gpregs.x[os.REG.FP])),
2038 else => @as(usize, @intCast(ctx.mcontext.regs[29])),
2039 };
2040 dumpStackTraceFromBase(bp, ip);
2088 .x86,
2089 .x86_64,
2090 .arm,
2091 .aarch64,
2092 => {
2093 const ctx = @ptrCast(*const os.ucontext_t, @alignCast(@alignOf(os.ucontext_t), ctx_ptr));
2094 dumpStackTraceFromBase(ctx);
20412095 },
20422096 else => {},
20432097 }
2098
2099 // TODO: Move this logic to dwarf.abi.regBytes
2100
2101 // switch (native_arch) {
2102 // .x86 => {
2103 // const ctx = @ptrCast(*const os.ucontext_t, @alignCast(@alignOf(os.ucontext_t), ctx_ptr));
2104 // const ip = @intCast(usize, ctx.mcontext.gregs[os.REG.EIP]) ;
2105 // const bp = @intCast(usize, ctx.mcontext.gregs[os.REG.EBP]);
2106 // dumpStackTraceFromBase(bp, ip);
2107 // },
2108 // .x86_64 => {
2109 // const ctx = @ptrCast(*const os.ucontext_t, @alignCast(@alignOf(os.ucontext_t), ctx_ptr));
2110 // const ip = switch (native_os) {
2111 // .linux, .netbsd, .solaris => @intCast(usize, ctx.mcontext.gregs[os.REG.RIP]),
2112 // .freebsd => @intCast(usize, ctx.mcontext.rip),
2113 // .openbsd => @intCast(usize, ctx.sc_rip),
2114 // .macos => @intCast(usize, ctx.mcontext.ss.rip),
2115 // else => unreachable,
2116 // };
2117 // const bp = switch (native_os) {
2118 // .linux, .netbsd, .solaris => @intCast(usize, ctx.mcontext.gregs[os.REG.RBP]),
2119 // .openbsd => @intCast(usize, ctx.sc_rbp),
2120 // .freebsd => @intCast(usize, ctx.mcontext.rbp),
2121 // .macos => @intCast(usize, ctx.mcontext.ss.rbp),
2122 // else => unreachable,
2123 // };
2124 // dumpStackTraceFromBase(bp, ip);
2125 // },
2126 // .arm => {
2127 // const ctx = @ptrCast(*const os.ucontext_t, @alignCast(@alignOf(os.ucontext_t), ctx_ptr));
2128 // const ip = @intCast(usize, ctx.mcontext.arm_pc);
2129 // const bp = @intCast(usize, ctx.mcontext.arm_fp);
2130 // dumpStackTraceFromBase(bp, ip);
2131 // },
2132 // .aarch64 => {
2133 // const ctx = @ptrCast(*const os.ucontext_t, @alignCast(@alignOf(os.ucontext_t), ctx_ptr));
2134 // const ip = switch (native_os) {
2135 // .macos => @intCast(usize, ctx.mcontext.ss.pc),
2136 // .netbsd => @intCast(usize, ctx.mcontext.gregs[os.REG.PC]),
2137 // .freebsd => @intCast(usize, ctx.mcontext.gpregs.elr),
2138 // else => @intCast(usize, ctx.mcontext.pc),
2139 // };
2140 // // x29 is the ABI-designated frame pointer
2141 // const bp = switch (native_os) {
2142 // .macos => @intCast(usize, ctx.mcontext.ss.fp),
2143 // .netbsd => @intCast(usize, ctx.mcontext.gregs[os.REG.FP]),
2144 // .freebsd => @intCast(usize, ctx.mcontext.gpregs.x[os.REG.FP]),
2145 // else => @intCast(usize, ctx.mcontext.regs[29]),
2146 // };
2147 // dumpStackTraceFromBase(bp, ip);
2148 // },
2149 // else => {},
2150 // }
20442151}
20452152
20462153fn handleSegfaultWindows(info: *windows.EXCEPTION_POINTERS) callconv(windows.WINAPI) c_long {
......@@ -2105,7 +2212,7 @@ fn dumpSegfaultInfoWindows(info: *windows.EXCEPTION_POINTERS, msg: u8, label: ?[
21052212 else => unreachable,
21062213 } catch os.abort();
21072214
2108 dumpStackTraceFromBase(regs.bp, regs.ip);
2215 dumpStackTraceFromBase(regs);
21092216}
21102217
21112218pub fn dumpStackPointerAddr(prefix: []const u8) void {
lib/std/dwarf.zig+130-17
......@@ -3,6 +3,7 @@ const std = @import("std.zig");
33const debug = std.debug;
44const fs = std.fs;
55const io = std.io;
6const os = std.os;
67const mem = std.mem;
78const math = std.math;
89const leb = @import("leb128.zig");
......@@ -664,10 +665,17 @@ pub const DwarfSection = enum {
664665};
665666
666667pub const DwarfInfo = struct {
667 endian: std.builtin.Endian,
668 pub const Section = struct {
669 data: []const u8,
670 owned: bool,
671 };
672
673 const num_sections = std.enums.directEnumArrayLen(DwarfSection, 0);
674 pub const SectionArray = [num_sections]?Section;
675 pub const null_section_array = [_]?Section{null} ** num_sections;
668676
669 // No section memory is owned by the DwarfInfo
670 sections: [std.enums.directEnumArrayLen(DwarfSection, 0)]?[]const u8,
677 endian: std.builtin.Endian,
678 sections: SectionArray,
671679
672680 // Filled later by the initializer
673681 abbrev_table_list: std.ArrayListUnmanaged(AbbrevTableHeader) = .{},
......@@ -679,10 +687,13 @@ pub const DwarfInfo = struct {
679687 fde_list: std.ArrayListUnmanaged(FrameDescriptionEntry) = .{},
680688
681689 pub fn section(di: DwarfInfo, dwarf_section: DwarfSection) ?[]const u8 {
682 return di.sections[@enumToInt(dwarf_section)];
690 return if (di.sections[@enumToInt(dwarf_section)]) |s| s.data else null;
683691 }
684692
685693 pub fn deinit(di: *DwarfInfo, allocator: mem.Allocator) void {
694 for (di.sections) |s| {
695 if (s.owned) allocator.free(s.data);
696 }
686697 for (di.abbrev_table_list.items) |*abbrev| {
687698 abbrev.deinit();
688699 }
......@@ -696,6 +707,8 @@ pub const DwarfInfo = struct {
696707 func.deinit(allocator);
697708 }
698709 di.func_list.deinit(allocator);
710 di.cie_map.deinit(allocator);
711 di.fde_list.deinit(allocator);
699712 }
700713
701714 pub fn getSymbolName(di: *DwarfInfo, address: u64) ?[]const u8 {
......@@ -1443,7 +1456,6 @@ pub const DwarfInfo = struct {
14431456 return getStringGeneric(di.section(.debug_line_str), offset);
14441457 }
14451458
1446
14471459 fn readDebugAddr(di: DwarfInfo, compile_unit: CompileUnit, index: u64) !u64 {
14481460 const debug_addr = di.section(.debug_addr) orelse return badDwarf();
14491461
......@@ -1470,12 +1482,13 @@ pub const DwarfInfo = struct {
14701482 };
14711483 }
14721484
1473 pub fn scanAllUnwindInfo(di: *DwarfInfo, allocator: mem.Allocator) !void {
1485 pub fn scanAllUnwindInfo(di: *DwarfInfo, allocator: mem.Allocator, binary_mem: []const u8) !void {
14741486 var has_eh_frame_hdr = false;
1475 if (di.section(.eh_frame)) |eh_frame_hdr| {
1487 if (di.section(.eh_frame_hdr)) |eh_frame_hdr| {
14761488 has_eh_frame_hdr = true;
14771489
1478 // TODO: Parse this section
1490 // TODO: Parse this section to get the lookup table, and skip loading the entire section
1491
14791492 _ = eh_frame_hdr;
14801493 }
14811494
......@@ -1494,16 +1507,14 @@ pub const DwarfInfo = struct {
14941507 }
14951508
14961509 const id_len = @as(u8, if (is_64) 8 else 4);
1510 const id = if (is_64) try reader.readInt(u64, di.endian) else try reader.readInt(u32, di.endian);
14971511 const entry_bytes = eh_frame[stream.pos..][0 .. length - id_len];
1498 const id = try reader.readInt(u32, di.endian);
1499
1500 // TODO: Get section_offset here (pass in from headers)
15011512
15021513 if (id == 0) {
15031514 const cie = try CommonInformationEntry.parse(
15041515 entry_bytes,
15051516 @ptrToInt(eh_frame.ptr),
1506 0,
1517 @ptrToInt(eh_frame.ptr) - @ptrToInt(binary_mem.ptr),
15071518 true,
15081519 length_offset,
15091520 @sizeOf(usize),
......@@ -1511,12 +1522,12 @@ pub const DwarfInfo = struct {
15111522 );
15121523 try di.cie_map.put(allocator, length_offset, cie);
15131524 } else {
1514 const cie_offset = stream.pos - 4 - id;
1525 const cie_offset = stream.pos - id_len - id;
15151526 const cie = di.cie_map.get(cie_offset) orelse return badDwarf();
15161527 const fde = try FrameDescriptionEntry.parse(
15171528 entry_bytes,
15181529 @ptrToInt(eh_frame.ptr),
1519 0,
1530 @ptrToInt(eh_frame.ptr) - @ptrToInt(binary_mem.ptr),
15201531 true,
15211532 cie,
15221533 @sizeOf(usize),
......@@ -1524,6 +1535,8 @@ pub const DwarfInfo = struct {
15241535 );
15251536 try di.fde_list.append(allocator, fde);
15261537 }
1538
1539 stream.pos += entry_bytes.len;
15271540 }
15281541
15291542 // TODO: Avoiding sorting if has_eh_frame_hdr exists
......@@ -1536,16 +1549,116 @@ pub const DwarfInfo = struct {
15361549 }
15371550 }
15381551
1552 pub fn unwindFrame(di: *const DwarfInfo, allocator: mem.Allocator, context: *UnwindContext, module_base_address: usize) !void {
1553 if (context.pc == 0) return;
1554
1555 // TODO: Handle signal frame (ie. use_prev_instr in libunwind)
1556 // TOOD: Use eh_frame_hdr to accelerate the search if available
1557 //const eh_frame_hdr = di.section(.eh_frame_hdr) orelse return error.MissingDebugInfo;
1558
1559 // Find the FDE
1560 const unmapped_pc = context.pc - module_base_address;
1561 const index = std.sort.binarySearch(FrameDescriptionEntry, unmapped_pc, di.fde_list.items, {}, struct {
1562 pub fn compareFn(_: void, pc: usize, mid_item: FrameDescriptionEntry) std.math.Order {
1563 if (pc < mid_item.pc_begin) {
1564 return .lt;
1565 } else {
1566 const range_end = mid_item.pc_begin + mid_item.pc_range;
1567 if (pc < range_end) {
1568 return .eq;
1569 }
1570
1571 return .gt;
1572 }
1573 }
1574 }.compareFn);
1575
1576 const fde = if (index) |i| &di.fde_list.items[i] else return error.MissingFDE;
1577 const cie = di.cie_map.getPtr(fde.cie_length_offset) orelse return error.MissingCIE;
1578
1579 // const prev_cfa = context.cfa;
1580 // const prev_pc = context.pc;
1581
1582 // TODO: Cache this on self so we can re-use the allocations?
1583 var vm = call_frame.VirtualMachine{};
1584 defer vm.deinit(allocator);
1585
1586 const row = try vm.runToNative(allocator, unmapped_pc, cie.*, fde.*);
1587 context.cfa = switch (row.cfa.rule) {
1588 .val_offset => |offset| blk: {
1589 const register = row.cfa.register orelse return error.InvalidCFARule;
1590 const value = mem.readIntSliceNative(usize, try abi.regBytes(&context.ucontext, register));
1591
1592 // TODO: Check isValidMemory?
1593 break :blk try call_frame.applyOffset(value, offset);
1594 },
1595 .expression => |expression| {
1596
1597 // TODO: Evaluate expression
1598 _ = expression;
1599 return error.UnimplementedTODO;
1600
1601 },
1602 else => return error.InvalidCFARule,
1603 };
1604
1605 // Update the context with the unwound values
1606 // TODO: Need old cfa and pc?
1607
1608 var next_ucontext = context.ucontext;
1609
1610 var has_next_ip = false;
1611 for (vm.rowColumns(row)) |column| {
1612 if (column.register) |register| {
1613 const dest = try abi.regBytes(&next_ucontext, register);
1614 if (register == cie.return_address_register) {
1615 has_next_ip = column.rule != .undefined;
1616 }
1617
1618 try column.resolveValue(context.*, dest);
1619 }
1620 }
1621
1622 context.ucontext = next_ucontext;
1623
1624 if (has_next_ip) {
1625 context.pc = mem.readIntSliceNative(usize, try abi.regBytes(&context.ucontext, @enumToInt(abi.Register.ip)));
1626 } else {
1627 context.pc = 0;
1628 }
1629
1630 mem.writeIntSliceNative(usize, try abi.regBytes(&context.ucontext, @enumToInt(abi.Register.sp)), context.cfa.?);
1631 }
1632};
1633
1634pub const UnwindContext = struct {
1635 cfa: ?usize,
1636 pc: usize,
1637 ucontext: os.ucontext_t,
1638
1639 pub fn init(ucontext: *const os.ucontext_t) !UnwindContext {
1640 const pc = mem.readIntSliceNative(usize, try abi.regBytes(ucontext, @enumToInt(abi.Register.ip)));
1641 return .{
1642 .cfa = null,
1643 .pc = pc,
1644 .ucontext = ucontext.*,
1645 };
1646 }
1647
1648 pub fn getFp(self: *const UnwindContext) !usize {
1649 return mem.readIntSliceNative(usize, try abi.regBytes(&self.ucontext, @enumToInt(abi.Register.fp)));
1650 }
15391651};
15401652
15411653/// Initialize DWARF info. The caller has the responsibility to initialize most
1542/// the DwarfInfo fields before calling.
1543pub fn openDwarfDebugInfo(di: *DwarfInfo, allocator: mem.Allocator) !void {
1654/// the DwarfInfo fields before calling. `binary_mem` is the raw bytes of the
1655/// main binary file (not the secondary debug info file).
1656pub fn openDwarfDebugInfo(di: *DwarfInfo, allocator: mem.Allocator, binary_mem: []const u8) !void {
15441657 try di.scanAllFunctions(allocator);
15451658 try di.scanAllCompileUnits(allocator);
15461659
15471660 // Unwind info is not required
1548 di.scanAllUnwindInfo(allocator) catch {};
1661 di.scanAllUnwindInfo(allocator, binary_mem) catch {};
15491662}
15501663
15511664/// This function is to make it handy to comment out the return and make it
lib/std/dwarf/abi.zig+106
......@@ -1,4 +1,110 @@
1const builtin = @import("builtin");
12const std = @import("../std.zig");
3const os = std.os;
4const mem = std.mem;
5
6/// Maps register names to their DWARF register number.
7/// `bp`, `ip`, and `sp` are provided as aliases.
8pub const Register = switch (builtin.cpu.arch) {
9 .x86 => {
10
11 //pub const ip = Register.eip;
12 //pub const sp = Register.
13 },
14 .x86_64 => enum(u8) {
15 rax,
16 rdx,
17 rcx,
18 rbx,
19 rsi,
20 rdi,
21 rbp,
22 rsp,
23 r8,
24 r9,
25 r10,
26 r11,
27 r12,
28 r13,
29 r14,
30 r15,
31 rip,
32 xmm0,
33 xmm1,
34 xmm2,
35 xmm3,
36 xmm4,
37 xmm5,
38 xmm6,
39 xmm7,
40 xmm8,
41 xmm9,
42 xmm10,
43 xmm11,
44 xmm12,
45 xmm13,
46 xmm14,
47 xmm15,
48
49 pub const fp = Register.rbp;
50 pub const ip = Register.rip;
51 pub const sp = Register.rsp;
52 },
53 else => enum {},
54};
55
56fn RegBytesReturnType(comptime ContextPtrType: type) type {
57 const info = @typeInfo(ContextPtrType);
58 if (info != .Pointer or info.Pointer.child != os.ucontext_t) {
59 @compileError("Expected a pointer to ucontext_t, got " ++ @typeName(@TypeOf(ContextPtrType)));
60 }
61
62 return if (info.Pointer.is_const) return []const u8 else []u8;
63}
64
65/// Returns a slice containing the backing storage for `reg_number`
66pub fn regBytes(ucontext_ptr: anytype, reg_number: u8) !RegBytesReturnType(@TypeOf(ucontext_ptr)) {
67 var m = &ucontext_ptr.mcontext;
68
69 return switch (builtin.cpu.arch) {
70 .x86_64 => switch (builtin.os.tag) {
71 .linux, .netbsd, .solaris => switch (reg_number) {
72 0 => mem.asBytes(&m.gregs[os.REG.RAX]),
73 1 => mem.asBytes(&m.gregs[os.REG.RDX]),
74 2 => mem.asBytes(&m.gregs[os.REG.RCX]),
75 3 => mem.asBytes(&m.gregs[os.REG.RBX]),
76 4 => mem.asBytes(&m.gregs[os.REG.RSI]),
77 5 => mem.asBytes(&m.gregs[os.REG.RDI]),
78 6 => mem.asBytes(&m.gregs[os.REG.RBP]),
79 7 => mem.asBytes(&m.gregs[os.REG.RSP]),
80 8 => mem.asBytes(&m.gregs[os.REG.R8]),
81 9 => mem.asBytes(&m.gregs[os.REG.R9]),
82 10 => mem.asBytes(&m.gregs[os.REG.R10]),
83 11 => mem.asBytes(&m.gregs[os.REG.R11]),
84 12 => mem.asBytes(&m.gregs[os.REG.R12]),
85 13 => mem.asBytes(&m.gregs[os.REG.R13]),
86 14 => mem.asBytes(&m.gregs[os.REG.R14]),
87 15 => mem.asBytes(&m.gregs[os.REG.R15]),
88 16 => mem.asBytes(&m.gregs[os.REG.RIP]),
89 17...32 => |i| mem.asBytes(&m.fpregs.xmm[i - 17]),
90 else => error.InvalidRegister,
91 },
92 //.freebsd => @intCast(usize, ctx.mcontext.rip),
93 //.openbsd => @intCast(usize, ctx.sc_rip),
94 //.macos => @intCast(usize, ctx.mcontext.ss.rip),
95 else => error.UnimplementedOs,
96 },
97 else => error.UnimplementedArch,
98 };
99}
100
101/// Returns the ABI-defined default value this register has in the unwinding table
102/// before running any of the CIE instructions.
103pub fn getRegDefaultValue(reg_number: u8, out: []u8) void {
104 // TODO: Implement any ABI-specific rules for the default value for registers
105 _ = reg_number;
106 @memset(out, undefined);
107}
2108
3109fn writeUnknownReg(writer: anytype, reg_number: u8) !void {
4110 try writer.print("reg{}", .{reg_number});
lib/std/dwarf/call_frame.zig+90-25
......@@ -1,5 +1,6 @@
11const builtin = @import("builtin");
22const std = @import("../std.zig");
3const mem = std.mem;
34const debug = std.debug;
45const leb = @import("../leb128.zig");
56const abi = @import("abi.zig");
......@@ -216,10 +217,19 @@ pub const Instruction = union(Opcode) {
216217 }
217218};
218219
220/// Since register rules are applied (usually) during a panic,
221/// checked addition / subtraction is used so that we can return
222/// an error and fall back to FP-based unwinding.
223pub fn applyOffset(base: usize, offset: i64) !usize {
224 return if (offset >= 0)
225 try std.math.add(usize, base, @intCast(usize, offset))
226 else
227 try std.math.sub(usize, base, @intCast(usize, -offset));
228}
229
219230/// This is a virtual machine that runs DWARF call frame instructions.
220/// See section 6.4.1 of the DWARF5 specification.
221231pub const VirtualMachine = struct {
222
232 /// See section 6.4.1 of the DWARF5 specification for details on each
223233 const RegisterRule = union(enum) {
224234 // The spec says that the default rule for each column is the undefined rule.
225235 // However, it also allows ABI / compiler authors to specify alternate defaults, so
......@@ -254,20 +264,63 @@ pub const VirtualMachine = struct {
254264 offset: u64 = 0,
255265
256266 /// Special-case column that defines the CFA (Canonical Frame Address) rule.
257 /// The register field of this column defines the register that CFA is derived
258 /// from, while other columns define register rules in terms of the CFA.
267 /// The register field of this column defines the register that CFA is derived from.
259268 cfa: Column = .{},
269
270 /// The register fields in these columns define the register the rule applies to.
260271 columns: ColumnRange = .{},
261272
262273 /// Indicates that the next write to any column in this row needs to copy
263 /// the backing column storage first.
274 /// the backing column storage first, as it may be referenced by previous rows.
264275 copy_on_write: bool = false,
265276 };
266277
267278 pub const Column = struct {
268 /// Register can only null in the case of the CFA column
269279 register: ?u8 = null,
270280 rule: RegisterRule = .{ .default = {} },
281
282 /// Resolves the register rule and places the result into `out` (see dwarf.abi.regBytes)
283 pub fn resolveValue(self: Column, context: dwarf.UnwindContext, out: []u8) !void {
284 switch (self.rule) {
285 .default => {
286 const register = self.register orelse return error.InvalidRegister;
287 abi.getRegDefaultValue(register, out);
288 },
289 .undefined => {
290 @memset(out, undefined);
291 },
292 .same_value => {},
293 .offset => |offset| {
294 if (context.cfa) |cfa| {
295 const ptr = @intToPtr(*const usize, try applyOffset(cfa, offset));
296
297 // TODO: context.isValidMemory(ptr)
298 mem.writeIntSliceNative(usize, out, ptr.*);
299 } else return error.InvalidCFA;
300 },
301 .val_offset => |offset| {
302 if (context.cfa) |cfa| {
303 mem.writeIntSliceNative(usize, out, try applyOffset(cfa, offset));
304 } else return error.InvalidCFA;
305 },
306 .register => |register| {
307 const src = try abi.regBytes(&context.ucontext, register);
308 if (src.len != out.len) return error.RegisterTypeMismatch;
309 @memcpy(out, try abi.regBytes(&context.ucontext, register));
310 },
311 .expression => |expression| {
312 // TODO
313 _ = expression;
314 unreachable;
315 },
316 .val_expression => |expression| {
317 // TODO
318 _ = expression;
319 unreachable;
320 },
321 .architectural => return error.UnimplementedRule,
322 }
323 }
271324 };
272325
273326 const ColumnRange = struct {
......@@ -294,7 +347,7 @@ pub const VirtualMachine = struct {
294347 return self.columns.items[row.columns.start..][0..row.columns.len];
295348 }
296349
297 /// Either retrieves or adds a column for `register` (non-CFA) in the current row
350 /// Either retrieves or adds a column for `register` (non-CFA) in the current row.
298351 fn getOrAddColumn(self: *VirtualMachine, allocator: std.mem.Allocator, register: u8) !*Column {
299352 for (self.rowColumns(self.current_row)) |*c| {
300353 if (c.register == register) return c;
......@@ -315,7 +368,7 @@ pub const VirtualMachine = struct {
315368
316369 /// Runs the CIE instructions, then the FDE instructions. Execution halts
317370 /// once the row that corresponds to `pc` is known, and it is returned.
318 pub fn unwindTo(
371 pub fn runTo(
319372 self: *VirtualMachine,
320373 allocator: std.mem.Allocator,
321374 pc: u64,
......@@ -328,12 +381,15 @@ pub const VirtualMachine = struct {
328381 if (pc < fde.pc_begin or pc >= fde.pc_begin + fde.pc_range) return error.AddressOutOfRange;
329382
330383 var prev_row: Row = self.current_row;
331 const streams = .{
332 std.io.fixedBufferStream(cie.initial_instructions),
333 std.io.fixedBufferStream(fde.instructions),
384
385 var cie_stream = std.io.fixedBufferStream(cie.initial_instructions);
386 var fde_stream = std.io.fixedBufferStream(fde.instructions);
387 var streams = [_]*std.io.FixedBufferStream([]const u8){
388 &cie_stream,
389 &fde_stream,
334390 };
335391
336 outer: for (streams, 0..) |*stream, i| {
392 outer: for (&streams, 0..) |stream, i| {
337393 while (stream.pos < stream.buffer.len) {
338394 const instruction = try dwarf.call_frame.Instruction.read(stream, addr_size_bytes, endian);
339395 prev_row = try self.step(allocator, cie, i == 0, instruction);
......@@ -346,14 +402,14 @@ pub const VirtualMachine = struct {
346402 return prev_row;
347403 }
348404
349 pub fn unwindToNative(
405 pub fn runToNative(
350406 self: *VirtualMachine,
351407 allocator: std.mem.Allocator,
352408 pc: u64,
353409 cie: dwarf.CommonInformationEntry,
354410 fde: dwarf.FrameDescriptionEntry,
355 ) void {
356 self.stepTo(allocator, pc, cie, fde, @sizeOf(usize), builtin.target.cpu.arch.endian());
411 ) !Row {
412 return self.runTo(allocator, pc, cie, fde, @sizeOf(usize), builtin.target.cpu.arch.endian());
357413 }
358414
359415 fn resolveCopyOnWrite(self: *VirtualMachine, allocator: std.mem.Allocator) !void {
......@@ -451,30 +507,30 @@ pub const VirtualMachine = struct {
451507 try self.resolveCopyOnWrite(allocator);
452508 self.current_row.cfa = .{
453509 .register = i.operands.register,
454 .rule = .{ .offset = @intCast(i64, i.operands.offset) },
510 .rule = .{ .val_offset = @intCast(i64, i.operands.offset) },
455511 };
456512 },
457513 .def_cfa_sf => |i| {
458514 try self.resolveCopyOnWrite(allocator);
459515 self.current_row.cfa = .{
460516 .register = i.operands.register,
461 .rule = .{ .offset = i.operands.offset * cie.data_alignment_factor },
517 .rule = .{ .val_offset = i.operands.offset * cie.data_alignment_factor },
462518 };
463519 },
464520 .def_cfa_register => |i| {
465521 try self.resolveCopyOnWrite(allocator);
466 if (self.current_row.cfa.register == null or self.current_row.cfa.rule != .offset) return error.InvalidOperation;
522 if (self.current_row.cfa.register == null or self.current_row.cfa.rule != .val_offset) return error.InvalidOperation;
467523 self.current_row.cfa.register = i.operands.register;
468524 },
469525 .def_cfa_offset => |i| {
470526 try self.resolveCopyOnWrite(allocator);
471 if (self.current_row.cfa.register == null or self.current_row.cfa.rule != .offset) return error.InvalidOperation;
472 self.current_row.cfa.rule = .{ .offset = @intCast(i64, i.operands.offset) };
527 if (self.current_row.cfa.register == null or self.current_row.cfa.rule != .val_offset) return error.InvalidOperation;
528 self.current_row.cfa.rule = .{ .val_offset = @intCast(i64, i.operands.offset) };
473529 },
474530 .def_cfa_offset_sf => |i| {
475531 try self.resolveCopyOnWrite(allocator);
476 if (self.current_row.cfa.register == null or self.current_row.cfa.rule != .offset) return error.InvalidOperation;
477 self.current_row.cfa.rule = .{ .offset = i.operands.offset * cie.data_alignment_factor };
532 if (self.current_row.cfa.register == null or self.current_row.cfa.rule != .val_offset) return error.InvalidOperation;
533 self.current_row.cfa.rule = .{ .val_offset = i.operands.offset * cie.data_alignment_factor };
478534 },
479535 .def_cfa_expression => |i| {
480536 try self.resolveCopyOnWrite(allocator);
......@@ -490,9 +546,18 @@ pub const VirtualMachine = struct {
490546 .expression = i.operands.block,
491547 };
492548 },
493 .val_offset => {},
494 .val_offset_sf => {},
495 .val_expression => {},
549 .val_offset => {
550 // TODO: Implement
551 unreachable;
552 },
553 .val_offset_sf => {
554 // TODO: Implement
555 unreachable;
556 },
557 .val_expression => {
558 // TODO: Implement
559 unreachable;
560 },
496561 }
497562
498563 return prev_row;