Skip to content

Commit

Permalink
dwarf: move macho unwind code from macho -> dwarf
Browse files Browse the repository at this point in the history
dwarf: fixup unchecked .eh_frame CIE offset subtraction
  • Loading branch information
kcbanner committed Jul 21, 2023
1 parent ba68512 commit 5e1b682
Show file tree
Hide file tree
Showing 3 changed files with 363 additions and 359 deletions.
2 changes: 1 addition & 1 deletion lib/std/debug.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
363 changes: 362 additions & 1 deletion lib/std/dwarf.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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,
},
Expand Down Expand Up @@ -2461,6 +2820,8 @@ fn pcRelBase(field_ptr: usize, pc_rel_offset: i64) !usize {
}
}



test {
std.testing.refAllDecls(@This());
}

0 comments on commit 5e1b682

Please sign in to comment.