feature. See also
. The project being documented here (as the example) is the Zig library itself.
MachO.unwindFrameInner
fn unwindFrameInner(si: *SelfInfo, io: Io, context: *UnwindContext) !usize
File
Code
fn unwindFrameInner(si: *SelfInfo, io: Io, context: *UnwindContext) !usize {
const gpa = std.debug.getDebugInfoAllocator();
const module = try si.findModule(gpa, io, context.pc);
defer si.mutex.unlock(io);
const unwind: *Module.Unwind = try module.getUnwindInfo(gpa);
const ip_reg_num = comptime Dwarf.ipRegNum(builtin.target.cpu.arch).?;
const fp_reg_num = comptime Dwarf.fpRegNum(builtin.target.cpu.arch);
const sp_reg_num = comptime Dwarf.spRegNum(builtin.target.cpu.arch);
const unwind_info = unwind.unwind_info orelse return error.MissingDebugInfo;
if (unwind_info.len < @sizeOf(macho.unwind_info_section_header)) return error.InvalidDebugInfo;
const header: *align(1) const macho.unwind_info_section_header = @ptrCast(unwind_info);
const index_byte_count = header.indexCount * @sizeOf(macho.unwind_info_section_header_index_entry);
if (unwind_info.len < header.indexSectionOffset + index_byte_count) return error.InvalidDebugInfo;
const indices: []align(1) const macho.unwind_info_section_header_index_entry = @ptrCast(unwind_info[header.indexSectionOffset..][0..index_byte_count]);
if (indices.len == 0) return error.MissingDebugInfo;
const pc_text_offset = context.pc - module.text_base;
const start_offset: u32, const first_level_offset: u32 = index: {
var left: usize = 0;
var len: usize = indices.len;
while (len > 1) {
const mid = left + len / 2;
if (pc_text_offset < indices[mid].functionOffset) {
len /= 2;
} else {
left = mid;
len -= len / 2;
}
}
break :index .{ indices[left].secondLevelPagesSectionOffset, indices[left].functionOffset };
};
if (start_offset == 0) return error.MissingDebugInfo;
const common_encodings_byte_count = header.commonEncodingsArrayCount * @sizeOf(macho.compact_unwind_encoding_t);
if (unwind_info.len < header.commonEncodingsArraySectionOffset + common_encodings_byte_count) return error.InvalidDebugInfo;
const common_encodings: []align(1) const macho.compact_unwind_encoding_t = @ptrCast(
unwind_info[header.commonEncodingsArraySectionOffset..][0..common_encodings_byte_count],
);
if (unwind_info.len < start_offset + @sizeOf(macho.UNWIND_SECOND_LEVEL)) return error.InvalidDebugInfo;
const kind: *align(1) const macho.UNWIND_SECOND_LEVEL = @ptrCast(unwind_info[start_offset..]);
const entry: struct {
function_offset: usize,
raw_encoding: u32,
} = switch (kind.*) {
.REGULAR => entry: {
if (unwind_info.len < start_offset + @sizeOf(macho.unwind_info_regular_second_level_page_header)) return error.InvalidDebugInfo;
const page_header: *align(1) const macho.unwind_info_regular_second_level_page_header = @ptrCast(unwind_info[start_offset..]);
const entries_byte_count = page_header.entryCount * @sizeOf(macho.unwind_info_regular_second_level_entry);
if (unwind_info.len < start_offset + entries_byte_count) return error.InvalidDebugInfo;
const entries: []align(1) const macho.unwind_info_regular_second_level_entry = @ptrCast(
unwind_info[start_offset + page_header.entryPageOffset ..][0..entries_byte_count],
);
if (entries.len == 0) return error.InvalidDebugInfo;
var left: usize = 0;
var len: usize = entries.len;
while (len > 1) {
const mid = left + len / 2;
if (pc_text_offset < entries[mid].functionOffset) {
len /= 2;
} else {
left = mid;
len -= len / 2;
}
}
break :entry .{
.function_offset = entries[left].functionOffset,
.raw_encoding = entries[left].encoding,
};
},
.COMPRESSED => entry: {
if (unwind_info.len < start_offset + @sizeOf(macho.unwind_info_compressed_second_level_page_header)) return error.InvalidDebugInfo;
const page_header: *align(1) const macho.unwind_info_compressed_second_level_page_header = @ptrCast(unwind_info[start_offset..]);
const entries_byte_count = page_header.entryCount * @sizeOf(macho.UnwindInfoCompressedEntry);
if (unwind_info.len < start_offset + entries_byte_count) return error.InvalidDebugInfo;
const entries: []align(1) const macho.UnwindInfoCompressedEntry = @ptrCast(
unwind_info[start_offset + page_header.entryPageOffset ..][0..entries_byte_count],
);
if (entries.len == 0) return error.InvalidDebugInfo;
var left: usize = 0;
var len: usize = entries.len;
while (len > 1) {
const mid = left + len / 2;
if (pc_text_offset < first_level_offset + entries[mid].funcOffset) {
len /= 2;
} else {
left = mid;
len -= len / 2;
}
}
const entry = entries[left];
const function_offset = first_level_offset + entry.funcOffset;
if (entry.encodingIndex < common_encodings.len) {
break :entry .{
.function_offset = function_offset,
.raw_encoding = common_encodings[entry.encodingIndex],
};
}
const local_index = entry.encodingIndex - common_encodings.len;
const local_encodings_byte_count = page_header.encodingsCount * @sizeOf(macho.compact_unwind_encoding_t);
if (unwind_info.len < start_offset + page_header.encodingsPageOffset + local_encodings_byte_count) return error.InvalidDebugInfo;
const local_encodings: []align(1) const macho.compact_unwind_encoding_t = @ptrCast(
unwind_info[start_offset + page_header.encodingsPageOffset ..][0..local_encodings_byte_count],
);
if (local_index >= local_encodings.len) return error.InvalidDebugInfo;
break :entry .{
.function_offset = function_offset,
.raw_encoding = local_encodings[local_index],
};
},
else => return error.InvalidDebugInfo,
};
if (entry.raw_encoding == 0) return error.MissingDebugInfo;
const encoding: macho.CompactUnwindEncoding = @bitCast(entry.raw_encoding);
const new_ip = switch (builtin.cpu.arch) {
.x86_64 => switch (encoding.mode.x86_64) {
.OLD => return error.UnsupportedDebugInfo,
.RBP_FRAME => ip: {
const frame = encoding.value.x86_64.frame;
const fp = (try dwarfRegNative(&context.cpu_state, fp_reg_num)).*;
const new_sp = fp + 2 * @sizeOf(usize);
const ip_ptr = fp + @sizeOf(usize);
const new_ip = @as(*const usize, @ptrFromInt(ip_ptr)).*;
const new_fp = @as(*const usize, @ptrFromInt(fp)).*;
(try dwarfRegNative(&context.cpu_state, fp_reg_num)).* = new_fp;
(try dwarfRegNative(&context.cpu_state, sp_reg_num)).* = new_sp;
(try dwarfRegNative(&context.cpu_state, ip_reg_num)).* = new_ip;
const regs: [5]u3 = .{
frame.reg0,
frame.reg1,
frame.reg2,
frame.reg3,
frame.reg4,
};
for (regs, 0..) |reg, i| {
if (reg == 0) continue;
const addr = fp - frame.frame_offset * @sizeOf(usize) + i * @sizeOf(usize);
const reg_number = try Dwarf.compactUnwindToDwarfRegNumber(reg);
(try dwarfRegNative(&context.cpu_state, reg_number)).* = @as(*const usize, @ptrFromInt(addr)).*;
}
break :ip new_ip;
},
.STACK_IMMD,
.STACK_IND,
=> ip: {
const frameless = encoding.value.x86_64.frameless;
const sp = (try dwarfRegNative(&context.cpu_state, sp_reg_num)).*;
const stack_size: usize = stack_size: {
if (encoding.mode.x86_64 == .STACK_IMMD) {
break :stack_size @as(usize, frameless.stack.direct.stack_size) * @sizeOf(usize);
}
const sub_offset_addr =
module.text_base +
entry.function_offset +
frameless.stack.indirect.sub_offset;
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);
};
// For a description of the encoding see lib/libc/include/any-macos.13-any/mach-o/compact_unwind_encoding.h
// Decode the variable-based permutation number into its digits. Each digit represents
// an index into the list of register numbers that weren't yet used in the sequence at
// the time the digit was added.
const reg_count = frameless.stack_reg_count;
const ip_ptr = ip_ptr: {
var digits: [6]u3 = undefined;
var accumulator: usize = frameless.stack_reg_permutation;
var base: usize = 2;
for (0..reg_count) |i| {
const div = accumulator / base;
digits[digits.len - 1 - i] = @intCast(accumulator - base * div);
accumulator = div;
base += 1;
}
var registers: [6]u3 = undefined;
var used_indices: [6]bool = @splat(false);
for (digits[digits.len - reg_count ..], 0..) |target_unused_index, i| {
var unused_count: u8 = 0;
const unused_index = for (used_indices, 0..) |used, index| {
if (!used) {
if (target_unused_index == unused_count) break index;
unused_count += 1;
}
} else unreachable;
registers[i] = @intCast(unused_index + 1);
used_indices[unused_index] = true;
}
var reg_addr = sp + stack_size - @sizeOf(usize) * @as(usize, reg_count + 1);
for (0..reg_count) |i| {
const reg_number = try Dwarf.compactUnwindToDwarfRegNumber(registers[i]);
(try dwarfRegNative(&context.cpu_state, reg_number)).* = @as(*const usize, @ptrFromInt(reg_addr)).*;
reg_addr += @sizeOf(usize);
}
break :ip_ptr reg_addr;
};
const new_ip = @as(*const usize, @ptrFromInt(ip_ptr)).*;
const new_sp = ip_ptr + @sizeOf(usize);
(try dwarfRegNative(&context.cpu_state, sp_reg_num)).* = new_sp;
(try dwarfRegNative(&context.cpu_state, ip_reg_num)).* = new_ip;
break :ip new_ip;
},
.DWARF => {
const dwarf = &(unwind.dwarf orelse return error.MissingDebugInfo);
const rules = try context.computeRules(gpa, dwarf, unwind.vmaddr_slide, encoding.value.x86_64.dwarf);
return context.next(gpa, &rules);
},
},
.aarch64 => switch (encoding.mode.arm64) {
.OLD => return error.UnsupportedDebugInfo,
.FRAMELESS => ip: {
const sp = (try dwarfRegNative(&context.cpu_state, sp_reg_num)).*;
const new_sp = sp + encoding.value.arm64.frameless.stack_size * 16;
const new_ip = (try dwarfRegNative(&context.cpu_state, 30)).*;
(try dwarfRegNative(&context.cpu_state, sp_reg_num)).* = new_sp;
break :ip new_ip;
},
.DWARF => {
const dwarf = &(unwind.dwarf orelse return error.MissingDebugInfo);
const rules = try context.computeRules(gpa, dwarf, unwind.vmaddr_slide, encoding.value.arm64.dwarf);
return context.next(gpa, &rules);
},
.FRAME => ip: {
const frame = encoding.value.arm64.frame;
const fp = (try dwarfRegNative(&context.cpu_state, fp_reg_num)).*;
const ip_ptr = fp + @sizeOf(usize);
var reg_addr = fp - @sizeOf(usize);
inline for (@typeInfo(@TypeOf(frame.x_reg_pairs)).@"struct".field_names, 0..) |field_name, i| {
if (@field(frame.x_reg_pairs, field_name) != 0) {
(try dwarfRegNative(&context.cpu_state, 19 + i)).* = @as(*const usize, @ptrFromInt(reg_addr)).*;
reg_addr += @sizeOf(usize);
(try dwarfRegNative(&context.cpu_state, 20 + i)).* = @as(*const usize, @ptrFromInt(reg_addr)).*;
reg_addr += @sizeOf(usize);
}
}
// vector registers in the AArch64 `cpu_context` anyway, so there's no reason to
// fail a legitimate unwind just because we're asked to restore the registers here.
// If some weird/broken unwind info tells us to read them later, we will fail then.
reg_addr += 16 * @as(usize, @popCount(@as(u4, @bitCast(frame.d_reg_pairs))));
const new_ip = @as(*const usize, @ptrFromInt(ip_ptr)).*;
const new_fp = @as(*const usize, @ptrFromInt(fp)).*;
(try dwarfRegNative(&context.cpu_state, fp_reg_num)).* = new_fp;
(try dwarfRegNative(&context.cpu_state, ip_reg_num)).* = new_ip;
break :ip new_ip;
},
},
else => comptime unreachable,
};
const ret_addr = std.debug.stripInstructionPtrAuthCode(new_ip);
// function's last instruction making `ret_addr` one byte past its end.
context.pc = ret_addr -| 1;
return ret_addr;
}