mirror of
https://github.com/ziglang/zig.git
synced 2024-11-15 08:33:06 +00:00
dwarf: move macho unwind code from macho -> dwarf
dwarf: fixup unchecked .eh_frame CIE offset subtraction
This commit is contained in:
parent
8e6a62ba10
commit
b1d86db7b4
@ -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;
|
||||
|
@ -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,
|
||||
},
|
||||
|
@ -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);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user