Commit b1d86db7b4

kcbanner <kcbanner@gmail.com>
2023-07-21 04:57:21
dwarf: move macho unwind code from macho -> dwarf dwarf: fixup unchecked .eh_frame CIE offset subtraction
1 parent 8e6a62b
Changed files (3)
lib/std/debug.zig
@@ -648,7 +648,7 @@ pub const StackIterator = struct {
                 // __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 (module.unwind_info) |unwind_info| {
-                    if (macho.unwindFrame(&unwind_state.dwarf_context, unwind_info, module.eh_frame, module.base_address)) |return_address| {
+                    if (DW.unwindFrameMachO(&unwind_state.dwarf_context, unwind_info, module.eh_frame, module.base_address)) |return_address| {
                         return return_address;
                     } else |err| {
                         if (err != error.RequiresDWARFUnwind) return err;
lib/std/dwarf.zig
@@ -1841,6 +1841,365 @@ pub const DwarfInfo = struct {
     }
 };
 
+/// Returns the DWARF register number for an x86_64 register number found in compact unwind info
+fn compactUnwindToDwarfRegNumber(unwind_reg_number: u3) !u8 {
+    return switch (unwind_reg_number) {
+        1 => 3, // RBX
+        2 => 12, // R12
+        3 => 13, // R13
+        4 => 14, // R14
+        5 => 15, // R15
+        6 => 6, // RBP
+        else => error.InvalidUnwindRegisterNumber,
+    };
+}
+
+const macho = std.macho;
+
+/// Unwind a frame using MachO compact unwind info (from __unwind_info).
+/// If the compact encoding can't encode a way to unwind a frame, it will
+/// defer unwinding to DWARF, in which case `.eh_frame` will be used if available.
+pub fn unwindFrameMachO(context: *UnwindContext, unwind_info: []const u8, eh_frame: ?[]const u8, module_base_address: usize) !usize {
+    const header = mem.bytesAsValue(
+        macho.unwind_info_section_header,
+        unwind_info[0..@sizeOf(macho.unwind_info_section_header)],
+    );
+    const indices = 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 - module_base_address;
+    const second_level_index = blk: {
+        var left: usize = 0;
+        var len: usize = indices.len;
+
+        while (len > 1) {
+            const mid = left + len / 2;
+            const offset = indices[mid].functionOffset;
+            if (mapped_pc < offset) {
+                len /= 2;
+            } else {
+                left = mid;
+                if (mapped_pc == offset) break;
+                len -= len / 2;
+            }
+        }
+
+        // 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 = 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 = 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 = 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 = 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 = 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 = 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 std.math.sub(
+                    u8,
+                    entry.encodingIndex,
+                    std.math.cast(u8, header.commonEncodingsArrayCount) orelse return error.InvalidUnwindInfo,
+                );
+                const local_encodings = 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 = 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 abi.regValueNative(usize, context.thread_context, abi.fpRegNum(reg_context), reg_context)).*;
+                const new_sp = fp + 2 * @sizeOf(usize);
+
+                // Verify the stack range we're about to read register values from
+                if (!context.isValidMemory(new_sp) or !context.isValidMemory(fp - frame_offset + max_reg * @sizeOf(usize))) return error.InvalidUnwindInfo;
+
+                const ip_ptr = fp + @sizeOf(usize);
+                const new_ip = @as(*const usize, @ptrFromInt(ip_ptr)).*;
+                const new_fp = @as(*const usize, @ptrFromInt(fp)).*;
+
+                (try abi.regValueNative(usize, context.thread_context, abi.fpRegNum(reg_context), reg_context)).* = new_fp;
+                (try abi.regValueNative(usize, context.thread_context, abi.spRegNum(reg_context), reg_context)).* = new_sp;
+                (try abi.regValueNative(usize, context.thread_context, abi.ipRegNum(), 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 compactUnwindToDwarfRegNumber(reg);
+                    (try abi.regValueNative(usize, context.thread_context, reg_number, reg_context)).* = @as(*const usize, @ptrFromInt(addr)).*;
+                }
+
+                break :blk new_ip;
+            },
+            .STACK_IMMD,
+            .STACK_IND,
+            => blk: {
+                const sp = (try abi.regValueNative(usize, context.thread_context, abi.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 =
+                        module_base_address +
+                        entry.function_offset +
+                        encoding.value.x86_64.frameless.stack.indirect.sub_offset;
+                    if (!context.isValidMemory(sub_offset_addr)) return error.InvalidUnwindInfo;
+
+                    // `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);
+                    if (!context.isValidMemory(reg_addr)) return error.InvalidUnwindInfo;
+                    for (0..reg_count) |i| {
+                        const reg_number = try compactUnwindToDwarfRegNumber(registers[i]);
+                        (try abi.regValueNative(usize, 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);
+                if (!context.isValidMemory(new_sp)) return error.InvalidUnwindInfo;
+
+                (try abi.regValueNative(usize, context.thread_context, abi.spRegNum(reg_context), reg_context)).* = new_sp;
+                (try abi.regValueNative(usize, context.thread_context, abi.ipRegNum(), reg_context)).* = new_ip;
+
+                break :blk new_ip;
+            },
+            .DWARF => {
+                return unwindFrameMachODwarf(context, eh_frame orelse return error.MissingEhFrame, @intCast(encoding.value.x86_64.dwarf));
+            },
+        },
+        .aarch64 => switch (encoding.mode.arm64) {
+            .OLD => return error.UnimplementedUnwindEncoding,
+            .FRAMELESS => blk: {
+                const sp = (try abi.regValueNative(usize, context.thread_context, abi.spRegNum(reg_context), reg_context)).*;
+                const new_sp = sp + encoding.value.arm64.frameless.stack_size * 16;
+                const new_ip = (try abi.regValueNative(usize, context.thread_context, 30, reg_context)).*;
+                if (!context.isValidMemory(new_sp)) return error.InvalidUnwindInfo;
+                (try abi.regValueNative(usize, context.thread_context, abi.spRegNum(reg_context), reg_context)).* = new_sp;
+                break :blk new_ip;
+            },
+            .DWARF => {
+                return unwindFrameMachODwarf(context, eh_frame orelse return error.MissingEhFrame, @intCast(encoding.value.arm64.dwarf));
+            },
+            .FRAME => blk: {
+                const fp = (try abi.regValueNative(usize, context.thread_context, abi.fpRegNum(reg_context), reg_context)).*;
+                const new_sp = fp + 16;
+                const ip_ptr = fp + @sizeOf(usize);
+
+                const num_restored_pairs: usize =
+                    @popCount(@as(u5, @bitCast(encoding.value.arm64.frame.x_reg_pairs))) +
+                    @popCount(@as(u4, @bitCast(encoding.value.arm64.frame.d_reg_pairs)));
+                const min_reg_addr = fp - num_restored_pairs * 2 * @sizeOf(usize);
+
+                if (!context.isValidMemory(new_sp) or !context.isValidMemory(min_reg_addr)) return error.InvalidUnwindInfo;
+
+                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 abi.regValueNative(usize, context.thread_context, 19 + i, reg_context)).* = @as(*const usize, @ptrFromInt(reg_addr)).*;
+                        reg_addr += @sizeOf(usize);
+                        (try abi.regValueNative(usize, 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 abi.regBytes(context.thread_context, 64 + 8 + i, context.reg_context),
+                            mem.asBytes(@as(*const usize, @ptrFromInt(reg_addr))),
+                        );
+                        reg_addr += @sizeOf(usize);
+                        @memcpy(
+                            try abi.regBytes(context.thread_context, 64 + 9 + i, context.reg_context),
+                            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)).*;
+
+                (try abi.regValueNative(usize, context.thread_context, abi.fpRegNum(reg_context), reg_context)).* = new_fp;
+                (try abi.regValueNative(usize, context.thread_context, abi.ipRegNum(), reg_context)).* = new_ip;
+
+                break :blk new_ip;
+            },
+        },
+        else => return error.UnimplementedArch,
+    };
+
+    context.pc = abi.stripInstructionPtrAuthCode(new_ip);
+    if (context.pc > 0) context.pc -= 1;
+    return new_ip;
+}
+
+fn unwindFrameMachODwarf(context: *UnwindContext, eh_frame: []const u8, fde_offset: usize) !usize {
+    var di = DwarfInfo{
+        .endian = builtin.cpu.arch.endian(),
+        .is_macho = true,
+    };
+    defer di.deinit(context.allocator);
+
+    di.sections[@intFromEnum(DwarfSection.eh_frame)] = .{
+        .data = eh_frame,
+        .owned = false,
+    };
+
+    return di.unwindFrame(context, fde_offset);
+}
+
 pub const UnwindContext = struct {
     allocator: mem.Allocator,
     cfa: ?usize,
@@ -2166,7 +2525,7 @@ pub const EntryHeader = struct {
             .is_64 = is_64,
             .type = if (id == cie_id) .{ .cie = {} } else .{
                 .fde = switch (dwarf_section) {
-                    .eh_frame => stream.pos - id_len - id,
+                    .eh_frame => try std.math.sub(u64, stream.pos - id_len, id),
                     .debug_frame => id,
                     else => unreachable,
                 },
lib/std/macho.zig
@@ -2125,360 +2125,3 @@ pub const CompactUnwindEncoding = packed struct(u32) {
     has_lsda: u1,
     start: u1,
 };
-
-/// Returns the DWARF register number for an x86_64 register number found in compact unwind info
-fn dwarfRegNumber(unwind_reg_number: u3) !u8 {
-    return switch (unwind_reg_number) {
-        1 => 3, // RBX
-        2 => 12, // R12
-        3 => 13, // R13
-        4 => 14, // R14
-        5 => 15, // R15
-        6 => 6, // RBP
-        else => error.InvalidUnwindRegisterNumber,
-    };
-}
-
-const dwarf = std.dwarf;
-const abi = dwarf.abi;
-
-pub fn unwindFrame(context: *dwarf.UnwindContext, unwind_info: []const u8, eh_frame: ?[]const u8, module_base_address: usize) !usize {
-    const header = mem.bytesAsValue(
-        unwind_info_section_header,
-        unwind_info[0..@sizeOf(unwind_info_section_header)],
-    );
-    const indices = mem.bytesAsSlice(
-        unwind_info_section_header_index_entry,
-        unwind_info[header.indexSectionOffset..][0 .. header.indexCount * @sizeOf(unwind_info_section_header_index_entry)],
-    );
-    if (indices.len == 0) return error.MissingUnwindInfo;
-
-    const mapped_pc = context.pc - module_base_address;
-    const second_level_index = blk: {
-        var left: usize = 0;
-        var len: usize = indices.len;
-
-        while (len > 1) {
-            const mid = left + len / 2;
-            const offset = indices[mid].functionOffset;
-            if (mapped_pc < offset) {
-                len /= 2;
-            } else {
-                left = mid;
-                if (mapped_pc == offset) break;
-                len -= len / 2;
-            }
-        }
-
-        // 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 = mem.bytesAsSlice(
-        compact_unwind_encoding_t,
-        unwind_info[header.commonEncodingsArraySectionOffset..][0 .. header.commonEncodingsArrayCount * @sizeOf(compact_unwind_encoding_t)],
-    );
-
-    const start_offset = second_level_index.secondLevelPagesSectionOffset;
-    const kind = mem.bytesAsValue(
-        UNWIND_SECOND_LEVEL,
-        unwind_info[start_offset..][0..@sizeOf(UNWIND_SECOND_LEVEL)],
-    );
-
-    const entry: struct {
-        function_offset: usize,
-        raw_encoding: u32,
-    } = switch (kind.*) {
-        .REGULAR => blk: {
-            const page_header = mem.bytesAsValue(
-                unwind_info_regular_second_level_page_header,
-                unwind_info[start_offset..][0..@sizeOf(unwind_info_regular_second_level_page_header)],
-            );
-
-            const entries = mem.bytesAsSlice(
-                unwind_info_regular_second_level_entry,
-                unwind_info[start_offset + page_header.entryPageOffset ..][0 .. page_header.entryCount * @sizeOf(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 = mem.bytesAsValue(
-                unwind_info_compressed_second_level_page_header,
-                unwind_info[start_offset..][0..@sizeOf(unwind_info_compressed_second_level_page_header)],
-            );
-
-            const entries = mem.bytesAsSlice(
-                UnwindInfoCompressedEntry,
-                unwind_info[start_offset + page_header.entryPageOffset ..][0 .. page_header.entryCount * @sizeOf(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 std.math.sub(
-                    u8,
-                    entry.encodingIndex,
-                    std.math.cast(u8, header.commonEncodingsArrayCount) orelse return error.InvalidUnwindInfo,
-                );
-                const local_encodings = mem.bytesAsSlice(
-                    compact_unwind_encoding_t,
-                    unwind_info[start_offset + page_header.encodingsPageOffset ..][0 .. page_header.encodingsCount * @sizeOf(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: 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 abi.regValueNative(usize, context.thread_context, abi.fpRegNum(reg_context), reg_context)).*;
-                const new_sp = fp + 2 * @sizeOf(usize);
-
-                // Verify the stack range we're about to read register values from
-                if (!context.isValidMemory(new_sp) or !context.isValidMemory(fp - frame_offset + max_reg * @sizeOf(usize))) return error.InvalidUnwindInfo;
-
-                const ip_ptr = fp + @sizeOf(usize);
-                const new_ip = @as(*const usize, @ptrFromInt(ip_ptr)).*;
-                const new_fp = @as(*const usize, @ptrFromInt(fp)).*;
-
-                (try abi.regValueNative(usize, context.thread_context, abi.fpRegNum(reg_context), reg_context)).* = new_fp;
-                (try abi.regValueNative(usize, context.thread_context, abi.spRegNum(reg_context), reg_context)).* = new_sp;
-                (try abi.regValueNative(usize, context.thread_context, abi.ipRegNum(), 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 dwarfRegNumber(reg);
-                    (try abi.regValueNative(usize, context.thread_context, reg_number, reg_context)).* = @as(*const usize, @ptrFromInt(addr)).*;
-                }
-
-                break :blk new_ip;
-            },
-            .STACK_IMMD,
-            .STACK_IND,
-            => blk: {
-                const sp = (try abi.regValueNative(usize, context.thread_context, abi.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 =
-                        module_base_address +
-                        entry.function_offset +
-                        encoding.value.x86_64.frameless.stack.indirect.sub_offset;
-                    if (!context.isValidMemory(sub_offset_addr)) return error.InvalidUnwindInfo;
-
-                    // `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);
-                    if (!context.isValidMemory(reg_addr)) return error.InvalidUnwindInfo;
-                    for (0..reg_count) |i| {
-                        const reg_number = try dwarfRegNumber(registers[i]);
-                        (try abi.regValueNative(usize, 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);
-                if (!context.isValidMemory(new_sp)) return error.InvalidUnwindInfo;
-
-                (try abi.regValueNative(usize, context.thread_context, abi.spRegNum(reg_context), reg_context)).* = new_sp;
-                (try abi.regValueNative(usize, context.thread_context, abi.ipRegNum(), reg_context)).* = new_ip;
-
-                break :blk new_ip;
-            },
-            .DWARF => {
-                return unwindFrameDwarf(context, eh_frame orelse return error.MissingEhFrame, @intCast(encoding.value.x86_64.dwarf));
-            },
-        },
-        .aarch64 => switch (encoding.mode.arm64) {
-            .OLD => return error.UnimplementedUnwindEncoding,
-            .FRAMELESS => blk: {
-                const sp = (try abi.regValueNative(usize, context.thread_context, abi.spRegNum(reg_context), reg_context)).*;
-                const new_sp = sp + encoding.value.arm64.frameless.stack_size * 16;
-                const new_ip = (try abi.regValueNative(usize, context.thread_context, 30, reg_context)).*;
-                if (!context.isValidMemory(new_sp)) return error.InvalidUnwindInfo;
-                (try abi.regValueNative(usize, context.thread_context, abi.spRegNum(reg_context), reg_context)).* = new_sp;
-                break :blk new_ip;
-            },
-            .DWARF => {
-                return unwindFrameDwarf(context, eh_frame orelse return error.MissingEhFrame, @intCast(encoding.value.arm64.dwarf));
-            },
-            .FRAME => blk: {
-                const fp = (try abi.regValueNative(usize, context.thread_context, abi.fpRegNum(reg_context), reg_context)).*;
-                const new_sp = fp + 16;
-                const ip_ptr = fp + @sizeOf(usize);
-
-                const num_restored_pairs: usize =
-                    @popCount(@as(u5, @bitCast(encoding.value.arm64.frame.x_reg_pairs))) +
-                    @popCount(@as(u4, @bitCast(encoding.value.arm64.frame.d_reg_pairs)));
-                const min_reg_addr = fp - num_restored_pairs * 2 * @sizeOf(usize);
-
-                if (!context.isValidMemory(new_sp) or !context.isValidMemory(min_reg_addr)) return error.InvalidUnwindInfo;
-
-                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 abi.regValueNative(usize, context.thread_context, 19 + i, reg_context)).* = @as(*const usize, @ptrFromInt(reg_addr)).*;
-                        reg_addr += @sizeOf(usize);
-                        (try abi.regValueNative(usize, 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 abi.regBytes(context.thread_context, 64 + 8 + i, context.reg_context),
-                            mem.asBytes(@as(*const usize, @ptrFromInt(reg_addr))),
-                        );
-                        reg_addr += @sizeOf(usize);
-                        @memcpy(
-                            try abi.regBytes(context.thread_context, 64 + 9 + i, context.reg_context),
-                            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)).*;
-
-                (try abi.regValueNative(usize, context.thread_context, abi.fpRegNum(reg_context), reg_context)).* = new_fp;
-                (try abi.regValueNative(usize, context.thread_context, abi.ipRegNum(), reg_context)).* = new_ip;
-
-                break :blk new_ip;
-            },
-        },
-        else => return error.UnimplementedArch,
-    };
-
-    context.pc = dwarf.abi.stripInstructionPtrAuthCode(new_ip);
-    if (context.pc > 0) context.pc -= 1;
-    return new_ip;
-}
-
-fn unwindFrameDwarf(context: *dwarf.UnwindContext, eh_frame: []const u8, fde_offset: usize) !usize {
-    var di = dwarf.DwarfInfo{
-        .endian = builtin.cpu.arch.endian(),
-        .is_macho = true,
-    };
-    defer di.deinit(context.allocator);
-
-    di.sections[@intFromEnum(dwarf.DwarfSection.eh_frame)] = .{
-        .data = eh_frame,
-        .owned = false,
-    };
-
-    return di.unwindFrame(context, fde_offset);
-}