Commit b750e7cf9e
Changed files (10)
lib
std
debug
dwarf
src
link
Elf
lib/std/debug/Dwarf/Unwind/VirtualMachine.zig
@@ -0,0 +1,298 @@
+//! Virtual machine that evaluates DWARF call frame instructions
+
+/// See section 6.4.1 of the DWARF5 specification for details on each
+pub const RegisterRule = union(enum) {
+ /// The spec says that the default rule for each column is the undefined rule.
+ /// However, it also allows ABI / compiler authors to specify alternate defaults, so
+ /// there is a distinction made here.
+ default: void,
+ undefined: void,
+ same_value: void,
+ /// offset(N)
+ offset: i64,
+ /// val_offset(N)
+ val_offset: i64,
+ /// register(R)
+ register: u8,
+ /// expression(E)
+ expression: []const u8,
+ /// val_expression(E)
+ val_expression: []const u8,
+ /// Augmenter-defined rule
+ architectural: void,
+};
+
+/// Each row contains unwinding rules for a set of registers.
+pub const Row = struct {
+ /// Offset from `FrameDescriptionEntry.pc_begin`
+ offset: u64 = 0,
+ /// Special-case column that defines the CFA (Canonical Frame Address) rule.
+ /// The register field of this column defines the register that CFA is derived from.
+ cfa: Column = .{},
+ /// The register fields in these columns define the register the rule applies to.
+ columns: ColumnRange = .{},
+ /// Indicates that the next write to any column in this row needs to copy
+ /// the backing column storage first, as it may be referenced by previous rows.
+ copy_on_write: bool = false,
+};
+
+pub const Column = struct {
+ register: ?u8 = null,
+ rule: RegisterRule = .{ .default = {} },
+};
+
+const ColumnRange = struct {
+ /// Index into `columns` of the first column in this row.
+ start: usize = undefined,
+ len: u8 = 0,
+};
+
+columns: std.ArrayList(Column) = .empty,
+stack: std.ArrayList(ColumnRange) = .empty,
+current_row: Row = .{},
+
+/// The result of executing the CIE's initial_instructions
+cie_row: ?Row = null,
+
+pub fn deinit(self: *VirtualMachine, gpa: Allocator) void {
+ self.stack.deinit(gpa);
+ self.columns.deinit(gpa);
+ self.* = undefined;
+}
+
+pub fn reset(self: *VirtualMachine) void {
+ self.stack.clearRetainingCapacity();
+ self.columns.clearRetainingCapacity();
+ self.current_row = .{};
+ self.cie_row = null;
+}
+
+/// Return a slice backed by the row's non-CFA columns
+pub fn rowColumns(self: VirtualMachine, row: Row) []Column {
+ if (row.columns.len == 0) return &.{};
+ return self.columns.items[row.columns.start..][0..row.columns.len];
+}
+
+/// Either retrieves or adds a column for `register` (non-CFA) in the current row.
+fn getOrAddColumn(self: *VirtualMachine, gpa: Allocator, register: u8) !*Column {
+ for (self.rowColumns(self.current_row)) |*c| {
+ if (c.register == register) return c;
+ }
+
+ if (self.current_row.columns.len == 0) {
+ self.current_row.columns.start = self.columns.items.len;
+ }
+ self.current_row.columns.len += 1;
+
+ const column = try self.columns.addOne(gpa);
+ column.* = .{
+ .register = register,
+ };
+
+ return column;
+}
+
+/// Runs the CIE instructions, then the FDE instructions. Execution halts
+/// once the row that corresponds to `pc` is known, and the row is returned.
+pub fn runTo(
+ self: *VirtualMachine,
+ gpa: Allocator,
+ pc: u64,
+ cie: Dwarf.Unwind.CommonInformationEntry,
+ fde: Dwarf.Unwind.FrameDescriptionEntry,
+ addr_size_bytes: u8,
+ endian: std.builtin.Endian,
+) !Row {
+ assert(self.cie_row == null);
+ assert(pc >= fde.pc_begin);
+ assert(pc < fde.pc_begin + fde.pc_range);
+
+ var prev_row: Row = self.current_row;
+
+ const instruction_slices: [2][]const u8 = .{
+ cie.initial_instructions,
+ fde.instructions,
+ };
+ for (instruction_slices, [2]bool{ true, false }) |slice, is_cie_stream| {
+ var stream: std.Io.Reader = .fixed(slice);
+ while (stream.seek < slice.len) {
+ const instruction: Dwarf.call_frame.Instruction = try .read(&stream, addr_size_bytes, endian);
+ prev_row = try self.step(gpa, cie, is_cie_stream, instruction);
+ if (pc < fde.pc_begin + self.current_row.offset) return prev_row;
+ }
+ }
+
+ return self.current_row;
+}
+
+fn resolveCopyOnWrite(self: *VirtualMachine, gpa: Allocator) !void {
+ if (!self.current_row.copy_on_write) return;
+
+ const new_start = self.columns.items.len;
+ if (self.current_row.columns.len > 0) {
+ try self.columns.ensureUnusedCapacity(gpa, self.current_row.columns.len);
+ self.columns.appendSliceAssumeCapacity(self.rowColumns(self.current_row));
+ self.current_row.columns.start = new_start;
+ }
+}
+
+/// Executes a single instruction.
+/// If this instruction is from the CIE, `is_initial` should be set.
+/// Returns the value of `current_row` before executing this instruction.
+pub fn step(
+ self: *VirtualMachine,
+ gpa: Allocator,
+ cie: Dwarf.Unwind.CommonInformationEntry,
+ is_initial: bool,
+ instruction: Dwarf.call_frame.Instruction,
+) !Row {
+ // CIE instructions must be run before FDE instructions
+ assert(!is_initial or self.cie_row == null);
+ if (!is_initial and self.cie_row == null) {
+ self.cie_row = self.current_row;
+ self.current_row.copy_on_write = true;
+ }
+
+ const prev_row = self.current_row;
+ switch (instruction) {
+ .set_loc => |i| {
+ if (i.address <= self.current_row.offset) return error.InvalidOperation;
+ if (cie.segment_selector_size != 0) return error.InvalidOperation; // unsupported
+ // TODO: Check cie.segment_selector_size != 0 for DWARFV4
+ self.current_row.offset = i.address;
+ },
+ inline .advance_loc,
+ .advance_loc1,
+ .advance_loc2,
+ .advance_loc4,
+ => |i| {
+ self.current_row.offset += i.delta * cie.code_alignment_factor;
+ self.current_row.copy_on_write = true;
+ },
+ inline .offset,
+ .offset_extended,
+ .offset_extended_sf,
+ => |i| {
+ try self.resolveCopyOnWrite(gpa);
+ const column = try self.getOrAddColumn(gpa, i.register);
+ column.rule = .{ .offset = @as(i64, @intCast(i.offset)) * cie.data_alignment_factor };
+ },
+ inline .restore,
+ .restore_extended,
+ => |i| {
+ try self.resolveCopyOnWrite(gpa);
+ if (self.cie_row) |cie_row| {
+ const column = try self.getOrAddColumn(gpa, i.register);
+ column.rule = for (self.rowColumns(cie_row)) |cie_column| {
+ if (cie_column.register == i.register) break cie_column.rule;
+ } else .{ .default = {} };
+ } else return error.InvalidOperation;
+ },
+ .nop => {},
+ .undefined => |i| {
+ try self.resolveCopyOnWrite(gpa);
+ const column = try self.getOrAddColumn(gpa, i.register);
+ column.rule = .{ .undefined = {} };
+ },
+ .same_value => |i| {
+ try self.resolveCopyOnWrite(gpa);
+ const column = try self.getOrAddColumn(gpa, i.register);
+ column.rule = .{ .same_value = {} };
+ },
+ .register => |i| {
+ try self.resolveCopyOnWrite(gpa);
+ const column = try self.getOrAddColumn(gpa, i.register);
+ column.rule = .{ .register = i.target_register };
+ },
+ .remember_state => {
+ try self.stack.append(gpa, self.current_row.columns);
+ self.current_row.copy_on_write = true;
+ },
+ .restore_state => {
+ const restored_columns = self.stack.pop() orelse return error.InvalidOperation;
+ self.columns.shrinkRetainingCapacity(self.columns.items.len - self.current_row.columns.len);
+ try self.columns.ensureUnusedCapacity(gpa, restored_columns.len);
+
+ self.current_row.columns.start = self.columns.items.len;
+ self.current_row.columns.len = restored_columns.len;
+ self.columns.appendSliceAssumeCapacity(self.columns.items[restored_columns.start..][0..restored_columns.len]);
+ },
+ .def_cfa => |i| {
+ try self.resolveCopyOnWrite(gpa);
+ self.current_row.cfa = .{
+ .register = i.register,
+ .rule = .{ .val_offset = @intCast(i.offset) },
+ };
+ },
+ .def_cfa_sf => |i| {
+ try self.resolveCopyOnWrite(gpa);
+ self.current_row.cfa = .{
+ .register = i.register,
+ .rule = .{ .val_offset = i.offset * cie.data_alignment_factor },
+ };
+ },
+ .def_cfa_register => |i| {
+ try self.resolveCopyOnWrite(gpa);
+ if (self.current_row.cfa.register == null or self.current_row.cfa.rule != .val_offset) return error.InvalidOperation;
+ self.current_row.cfa.register = i.register;
+ },
+ .def_cfa_offset => |i| {
+ try self.resolveCopyOnWrite(gpa);
+ if (self.current_row.cfa.register == null or self.current_row.cfa.rule != .val_offset) return error.InvalidOperation;
+ self.current_row.cfa.rule = .{
+ .val_offset = @intCast(i.offset),
+ };
+ },
+ .def_cfa_offset_sf => |i| {
+ try self.resolveCopyOnWrite(gpa);
+ if (self.current_row.cfa.register == null or self.current_row.cfa.rule != .val_offset) return error.InvalidOperation;
+ self.current_row.cfa.rule = .{
+ .val_offset = i.offset * cie.data_alignment_factor,
+ };
+ },
+ .def_cfa_expression => |i| {
+ try self.resolveCopyOnWrite(gpa);
+ self.current_row.cfa.register = undefined;
+ self.current_row.cfa.rule = .{
+ .expression = i.block,
+ };
+ },
+ .expression => |i| {
+ try self.resolveCopyOnWrite(gpa);
+ const column = try self.getOrAddColumn(gpa, i.register);
+ column.rule = .{
+ .expression = i.block,
+ };
+ },
+ .val_offset => |i| {
+ try self.resolveCopyOnWrite(gpa);
+ const column = try self.getOrAddColumn(gpa, i.register);
+ column.rule = .{
+ .val_offset = @as(i64, @intCast(i.offset)) * cie.data_alignment_factor,
+ };
+ },
+ .val_offset_sf => |i| {
+ try self.resolveCopyOnWrite(gpa);
+ const column = try self.getOrAddColumn(gpa, i.register);
+ column.rule = .{
+ .val_offset = i.offset * cie.data_alignment_factor,
+ };
+ },
+ .val_expression => |i| {
+ try self.resolveCopyOnWrite(gpa);
+ const column = try self.getOrAddColumn(gpa, i.register);
+ column.rule = .{
+ .val_expression = i.block,
+ };
+ },
+ }
+
+ return prev_row;
+}
+
+const std = @import("../../../std.zig");
+const assert = std.debug.assert;
+const Allocator = std.mem.Allocator;
+const Dwarf = std.debug.Dwarf;
+
+const VirtualMachine = @This();
lib/std/debug/Dwarf/call_frame.zig
@@ -1,12 +1,5 @@
-const builtin = @import("builtin");
const std = @import("../../std.zig");
-const mem = std.mem;
-const debug = std.debug;
-const leb = std.leb;
-const DW = std.dwarf;
-const abi = std.debug.Dwarf.abi;
-const assert = std.debug.assert;
-const native_endian = builtin.cpu.arch.endian();
+const Reader = std.Io.Reader;
/// TODO merge with std.dwarf.CFA
const Opcode = enum(u8) {
@@ -51,9 +44,13 @@ const Opcode = enum(u8) {
pub const hi_user = 0x3f;
};
-fn readBlock(reader: *std.Io.Reader) ![]const u8 {
+/// The returned slice points into `reader.buffer`.
+fn readBlock(reader: *Reader) ![]const u8 {
const block_len = try reader.takeLeb128(usize);
- return reader.take(block_len);
+ return reader.take(block_len) catch |err| switch (err) {
+ error.EndOfStream => return error.InvalidOperand,
+ error.ReadFailed => |e| return e,
+ };
}
pub const Instruction = union(Opcode) {
@@ -140,8 +137,9 @@ pub const Instruction = union(Opcode) {
block: []const u8,
},
+ /// `reader` must be a `Reader.fixed` so that regions of its buffer are never invalidated.
pub fn read(
- reader: *std.Io.Reader,
+ reader: *Reader,
addr_size_bytes: u8,
endian: std.builtin.Endian,
) !Instruction {
@@ -173,16 +171,14 @@ pub const Instruction = union(Opcode) {
.restore,
=> unreachable,
.nop => .{ .nop = {} },
- .set_loc => .{
- .set_loc = .{
- .address = switch (addr_size_bytes) {
- 2 => try reader.takeInt(u16, endian),
- 4 => try reader.takeInt(u32, endian),
- 8 => try reader.takeInt(u64, endian),
- else => return error.InvalidAddrSize,
- },
+ .set_loc => .{ .set_loc = .{
+ .address = switch (addr_size_bytes) {
+ 2 => try reader.takeInt(u16, endian),
+ 4 => try reader.takeInt(u32, endian),
+ 8 => try reader.takeInt(u64, endian),
+ else => return error.UnsupportedAddrSize,
},
- },
+ } },
.advance_loc1 => .{
.advance_loc1 = .{ .delta = try reader.takeByte() },
},
lib/std/debug/Dwarf/Unwind.zig
@@ -1,632 +1,622 @@
-sections: SectionArray = @splat(null),
+pub const VirtualMachine = @import("Unwind/VirtualMachine.zig");
-/// Starts out non-`null` if the `.eh_frame_hdr` section is present. May become `null` later if we
-/// find that `.eh_frame_hdr` is incomplete.
-eh_frame_hdr: ?ExceptionFrameHeader = null,
-/// These lookup tables are only used if `eh_frame_hdr` is null
-cie_map: std.AutoArrayHashMapUnmanaged(u64, CommonInformationEntry) = .empty,
-/// Sorted by start_pc
-fde_list: std.ArrayList(FrameDescriptionEntry) = .empty,
-
-pub const Section = struct {
+/// The contents of the `.debug_frame` section as specified by DWARF. This might be a more reliable
+/// stack unwind mechanism in some cases, or it may be present when `.eh_frame` is not, but fetching
+/// the data requires loading the binary, so it is not a viable approach for fast stack trace
+/// capturing within a process.
+debug_frame: ?struct {
data: []const u8,
-
- pub const Id = enum {
- debug_frame,
- eh_frame,
- eh_frame_hdr,
- };
+ /// Offsets into `data` of FDEs, sorted by ascending `pc_begin`.
+ sorted_fdes: []SortedFdeEntry,
+},
+
+/// Data associated with the `.eh_frame` and `.eh_frame_hdr` sections as defined by LSB Core. The
+/// format of `.eh_frame` is an extension of that of DWARF's `.debug_frame` -- in fact it is almost
+/// identical, though subtly different in a few places.
+eh_frame: ?struct {
+ header: EhFrameHeader,
+ /// Though this is a slice, it may be longer than the `.eh_frame` section. When unwinding
+ /// through the runtime-loaded `.eh_frame_hdr` data, we are not told the size of the `.eh_frame`
+ /// section, so construct a slice referring to all of the rest of memory. The end of the section
+ /// must be detected through `EntryHeader.terminator`.
+ eh_frame_data: []const u8,
+ /// Offsets into `eh_frame_data` of FDEs, sorted by ascending `pc_begin`.
+ /// Populated only if `header` does not already contain a lookup table.
+ sorted_fdes: ?[]SortedFdeEntry,
+},
+
+const SortedFdeEntry = struct {
+ /// This FDE's value of `pc_begin`.
+ pc_begin: u64,
+ /// Offset into the section of the corresponding FDE, including the entry header.
+ fde_offset: u64,
};
-const num_sections = std.enums.directEnumArrayLen(Section.Id, 0);
-pub const SectionArray = [num_sections]?Section;
-
-pub fn section(unwind: Unwind, dwarf_section: Section.Id) ?[]const u8 {
- return if (unwind.sections[@intFromEnum(dwarf_section)]) |s| s.data else null;
-}
+const Section = enum { debug_frame, eh_frame };
/// This represents the decoded .eh_frame_hdr header
-pub const ExceptionFrameHeader = struct {
- eh_frame_ptr: usize,
- table_enc: u8,
- fde_count: usize,
- entries: []const u8,
-
- pub fn entrySize(table_enc: u8) !u8 {
- return switch (table_enc & EH.PE.type_mask) {
- EH.PE.udata2,
- EH.PE.sdata2,
- => 4,
- EH.PE.udata4,
- EH.PE.sdata4,
- => 8,
- EH.PE.udata8,
- EH.PE.sdata8,
- => 16,
- // This is a binary search table, so all entries must be the same length
- else => return bad(),
+pub const EhFrameHeader = struct {
+ vaddr: u64,
+ eh_frame_vaddr: u64,
+ search_table: ?struct {
+ /// The byte offset of the search table into the `.eh_frame_hdr` section.
+ offset: u8,
+ encoding: EH.PE,
+ fde_count: usize,
+ entries: []const u8,
+ },
+
+ pub fn entrySize(table_enc: EH.PE, addr_size_bytes: u8) !u8 {
+ return switch (table_enc.type) {
+ .absptr => 2 * addr_size_bytes,
+ .udata2, .sdata2 => 4,
+ .udata4, .sdata4 => 8,
+ .udata8, .sdata8 => 16,
+ .uleb128, .sleb128 => return bad(), // this is a binary search table; all entries must be the same size
+ _ => return bad(),
};
}
- pub fn findEntry(
- self: ExceptionFrameHeader,
- eh_frame_len: usize,
- eh_frame_hdr_ptr: usize,
- pc: usize,
- cie: *CommonInformationEntry,
- fde: *FrameDescriptionEntry,
+ pub fn parse(
+ eh_frame_hdr_vaddr: u64,
+ eh_frame_hdr_bytes: []const u8,
+ addr_size_bytes: u8,
endian: Endian,
- ) !void {
- const entry_size = try entrySize(self.table_enc);
+ ) !EhFrameHeader {
+ var r: Reader = .fixed(eh_frame_hdr_bytes);
- var left: usize = 0;
- var len: usize = self.fde_count;
- var fbr: Reader = .fixed(self.entries);
+ const version = try r.takeByte();
+ if (version != 1) return bad();
- while (len > 1) {
- const mid = left + len / 2;
+ const eh_frame_ptr_enc: EH.PE = @bitCast(try r.takeByte());
+ const fde_count_enc: EH.PE = @bitCast(try r.takeByte());
+ const table_enc: EH.PE = @bitCast(try r.takeByte());
- fbr.seek = mid * entry_size;
- const pc_begin = try readEhPointer(&fbr, self.table_enc, @sizeOf(usize), .{
- .pc_rel_base = @intFromPtr(&self.entries[fbr.seek]),
- .follow_indirect = true,
- .data_rel_base = eh_frame_hdr_ptr,
- }, endian) orelse return bad();
+ const eh_frame_ptr = try readEhPointer(&r, eh_frame_ptr_enc, addr_size_bytes, .{
+ .pc_rel_base = eh_frame_hdr_vaddr + r.seek,
+ }, endian);
+ return .{
+ .vaddr = eh_frame_hdr_vaddr,
+ .eh_frame_vaddr = eh_frame_ptr,
+ .search_table = table: {
+ if (fde_count_enc == EH.PE.omit) break :table null;
+ if (table_enc == EH.PE.omit) break :table null;
+ const fde_count = try readEhPointer(&r, fde_count_enc, addr_size_bytes, .{
+ .pc_rel_base = eh_frame_hdr_vaddr + r.seek,
+ }, endian);
+ const entry_size = try entrySize(table_enc, addr_size_bytes);
+ const bytes_offset = r.seek;
+ const bytes_len = cast(usize, fde_count * entry_size) orelse return error.EndOfStream;
+ const bytes = try r.take(bytes_len);
+ break :table .{
+ .encoding = table_enc,
+ .fde_count = @intCast(fde_count),
+ .entries = bytes,
+ .offset = @intCast(bytes_offset),
+ };
+ },
+ };
+ }
+
+ /// Asserts that `eh_frame_hdr.search_table != null`.
+ fn findEntry(
+ eh_frame_hdr: *const EhFrameHeader,
+ pc: u64,
+ addr_size_bytes: u8,
+ endian: Endian,
+ ) !?u64 {
+ const table = &eh_frame_hdr.search_table.?;
+ const table_vaddr = eh_frame_hdr.vaddr + table.offset;
+ const entry_size = try EhFrameHeader.entrySize(table.encoding, addr_size_bytes);
+ var left: usize = 0;
+ var len: usize = table.fde_count;
+ while (len > 1) {
+ const mid = left + len / 2;
+ var entry_reader: Reader = .fixed(table.entries[mid * entry_size ..][0..entry_size]);
+ const pc_begin = try readEhPointer(&entry_reader, table.encoding, addr_size_bytes, .{
+ .pc_rel_base = table_vaddr + left * entry_size,
+ .data_rel_base = eh_frame_hdr.vaddr,
+ }, endian);
if (pc < pc_begin) {
len /= 2;
} else {
left = mid;
- if (pc == pc_begin) break;
len -= len / 2;
}
}
-
- if (len == 0) return missing();
- fbr.seek = left * entry_size;
-
- // Read past the pc_begin field of the entry
- _ = try readEhPointer(&fbr, self.table_enc, @sizeOf(usize), .{
- .pc_rel_base = @intFromPtr(&self.entries[fbr.seek]),
- .follow_indirect = true,
- .data_rel_base = eh_frame_hdr_ptr,
- }, endian) orelse return bad();
-
- const fde_ptr = cast(usize, try readEhPointer(&fbr, self.table_enc, @sizeOf(usize), .{
- .pc_rel_base = @intFromPtr(&self.entries[fbr.seek]),
- .follow_indirect = true,
- .data_rel_base = eh_frame_hdr_ptr,
- }, endian) orelse return bad()) orelse return bad();
-
- if (fde_ptr < self.eh_frame_ptr) return bad();
-
- const eh_frame = @as([*]const u8, @ptrFromInt(self.eh_frame_ptr))[0..eh_frame_len];
-
- const fde_offset = fde_ptr - self.eh_frame_ptr;
- var eh_frame_fbr: Reader = .fixed(eh_frame);
- eh_frame_fbr.seek = fde_offset;
-
- const fde_entry_header = try EntryHeader.read(&eh_frame_fbr, .eh_frame, endian);
- if (fde_entry_header.type != .fde) return bad();
-
- // CIEs always come before FDEs (the offset is a subtraction), so we can assume this memory is readable
- const cie_offset = fde_entry_header.type.fde;
- eh_frame_fbr.seek = @intCast(cie_offset);
- const cie_entry_header = try EntryHeader.read(&eh_frame_fbr, .eh_frame, endian);
- if (cie_entry_header.type != .cie) return bad();
-
- cie.* = try CommonInformationEntry.parse(
- cie_entry_header.entry_bytes,
- 0,
- true,
- cie_entry_header.format,
- .eh_frame,
- cie_entry_header.length_offset,
- @sizeOf(usize),
- endian,
- );
-
- fde.* = try FrameDescriptionEntry.parse(
- fde_entry_header.entry_bytes,
- 0,
- true,
- cie.*,
- @sizeOf(usize),
- endian,
- );
-
- if (pc < fde.pc_begin or pc >= fde.pc_begin + fde.pc_range) return missing();
+ if (len == 0) return null;
+ var entry_reader: Reader = .fixed(table.entries[left * entry_size ..][0..entry_size]);
+ // Skip past `pc_begin`; we're now interested in the fde offset
+ _ = try readEhPointerAbs(&entry_reader, table.encoding.type, addr_size_bytes, endian);
+ const fde_ptr = try readEhPointer(&entry_reader, table.encoding, addr_size_bytes, .{
+ .pc_rel_base = table_vaddr + left * entry_size,
+ .data_rel_base = eh_frame_hdr.vaddr,
+ }, endian);
+ return std.math.sub(u64, fde_ptr, eh_frame_hdr.eh_frame_vaddr) catch bad(); // offset into .eh_frame
}
};
-pub const EntryHeader = struct {
- /// Offset of the length field in the backing buffer
- length_offset: usize,
- format: Format,
- type: union(enum) {
- cie,
- /// Value is the offset of the corresponding CIE
- fde: u64,
- terminator,
+pub const EntryHeader = union(enum) {
+ cie: struct {
+ format: Format,
+ /// Remaining bytes in the CIE. These are parseable by `CommonInformationEntry.parse`.
+ bytes_len: u64,
+ },
+ fde: struct {
+ format: Format,
+ /// Offset into the section of the corresponding CIE, *including* its entry header.
+ cie_offset: u64,
+ /// Remaining bytes in the FDE. These are parseable by `FrameDescriptionEntry.parse`.
+ bytes_len: u64,
},
- /// The entry's contents, not including the ID field
- entry_bytes: []const u8,
+ /// The `.eh_frame` format includes terminators which indicate that the last CIE/FDE has been
+ /// reached. However, `.debug_frame` does not include such a terminator, so the caller must
+ /// keep track of how many section bytes remain when parsing all entries in `.debug_frame`.
+ terminator,
- /// The length of the entry including the ID field, but not the length field itself
- pub fn entryLength(self: EntryHeader) usize {
- return self.entry_bytes.len + @as(u8, if (self.format == .@"64") 8 else 4);
- }
+ pub fn read(r: *Reader, header_section_offset: u64, section: Section, endian: Endian) !EntryHeader {
+ const unit_header = try Dwarf.readUnitHeader(r, endian);
+ if (unit_header.unit_length == 0) return .terminator;
- /// Reads a header for either an FDE or a CIE, then advances the fbr to the
- /// position after the trailing structure.
- ///
- /// `fbr` must be backed by either the .eh_frame or .debug_frame sections.
- ///
- /// TODO that's a bad API, don't do that. this function should neither require
- /// a fixed reader nor depend on seeking.
- pub fn read(fbr: *Reader, dwarf_section: Section.Id, endian: Endian) !EntryHeader {
- assert(dwarf_section == .eh_frame or dwarf_section == .debug_frame);
-
- const length_offset = fbr.seek;
- const unit_header = try Dwarf.readUnitHeader(fbr, endian);
- const unit_length = cast(usize, unit_header.unit_length) orelse return bad();
- if (unit_length == 0) return .{
- .length_offset = length_offset,
- .format = unit_header.format,
- .type = .terminator,
- .entry_bytes = &.{},
- };
- const start_offset = fbr.seek;
- const end_offset = start_offset + unit_length;
- defer fbr.seek = end_offset;
-
- const id = try Dwarf.readAddress(fbr, unit_header.format, endian);
- const entry_bytes = fbr.buffer[fbr.seek..end_offset];
- const cie_id: u64 = switch (dwarf_section) {
- .eh_frame => CommonInformationEntry.eh_id,
+ // TODO MLUGG: seriously, just... check the formats of everything in BOTH LSB Core and DWARF. this is a fucking *mess*. maybe add spec references.
+
+ // Next is a value which will disambiguate CIEs and FDEs. Annoyingly, LSB Core makes this
+ // value always 4-byte, whereas DWARF makes it depend on the `dwarf.Format`.
+ const cie_ptr_or_id_size: u8 = switch (section) {
+ .eh_frame => 4,
.debug_frame => switch (unit_header.format) {
- .@"32" => CommonInformationEntry.dwarf32_id,
- .@"64" => CommonInformationEntry.dwarf64_id,
+ .@"32" => 4,
+ .@"64" => 8,
},
+ };
+ const cie_ptr_or_id = switch (cie_ptr_or_id_size) {
+ 4 => try r.takeInt(u32, endian),
+ 8 => try r.takeInt(u64, endian),
else => unreachable,
};
+ const remaining_bytes = unit_header.unit_length - cie_ptr_or_id_size;
- return .{
- .length_offset = length_offset,
- .format = unit_header.format,
- .type = if (id == cie_id) .cie else .{ .fde = switch (dwarf_section) {
- .eh_frame => try std.math.sub(u64, start_offset, id),
- .debug_frame => id,
- else => unreachable,
- } },
- .entry_bytes = entry_bytes,
+ // If this entry is a CIE, then `cie_ptr_or_id` will have this value, which is different
+ // between the DWARF `.debug_frame` section and the LSB Core `.eh_frame` section.
+ const cie_id: u64 = switch (section) {
+ .eh_frame => 0,
+ .debug_frame => switch (unit_header.format) {
+ .@"32" => maxInt(u32),
+ .@"64" => maxInt(u64),
+ },
};
+ if (cie_ptr_or_id == cie_id) {
+ return .{ .cie = .{
+ .format = unit_header.format,
+ .bytes_len = remaining_bytes,
+ } };
+ }
+
+ // This is an FDE -- `cie_ptr_or_id` points to the associated CIE. Unfortunately, the format
+ // of that pointer again differs between `.debug_frame` and `.eh_frame`.
+ const cie_offset = switch (section) {
+ .eh_frame => try std.math.sub(u64, header_section_offset + unit_header.header_length, cie_ptr_or_id),
+ .debug_frame => cie_ptr_or_id,
+ };
+ return .{ .fde = .{
+ .format = unit_header.format,
+ .cie_offset = cie_offset,
+ .bytes_len = remaining_bytes,
+ } };
}
};
pub const CommonInformationEntry = struct {
- // Used in .eh_frame
- pub const eh_id = 0;
-
- // Used in .debug_frame (DWARF32)
- pub const dwarf32_id = maxInt(u32);
-
- // Used in .debug_frame (DWARF64)
- pub const dwarf64_id = maxInt(u64);
-
- // Offset of the length field of this entry in the eh_frame section.
- // This is the key that FDEs use to reference CIEs.
- length_offset: u64,
version: u8,
- address_size: u8,
- format: Format,
- // Only present in version 4
- segment_selector_size: ?u8,
+ /// In version 4, CIEs can specify the address size used in the CIE and associated FDEs.
+ /// This value must be used *only* to parse associated FDEs in `FrameDescriptionEntry.parse`.
+ addr_size_bytes: u8,
+
+ /// Always 0 for versions which do not specify this (currently all versions other than 4).
+ segment_selector_size: u8,
code_alignment_factor: u32,
data_alignment_factor: i32,
return_address_register: u8,
- aug_str: []const u8,
- aug_data: []const u8,
- lsda_pointer_enc: u8,
- personality_enc: ?u8,
- personality_routine_pointer: ?u64,
- fde_pointer_enc: u8,
- initial_instructions: []const u8,
+ fde_pointer_enc: EH.PE,
+ is_signal_frame: bool,
- pub fn isSignalFrame(self: CommonInformationEntry) bool {
- for (self.aug_str) |c| if (c == 'S') return true;
- return false;
- }
+ augmentation_kind: AugmentationKind,
- pub fn addressesSignedWithBKey(self: CommonInformationEntry) bool {
- for (self.aug_str) |c| if (c == 'B') return true;
- return false;
- }
+ initial_instructions: []const u8,
- pub fn mteTaggedFrame(self: CommonInformationEntry) bool {
- for (self.aug_str) |c| if (c == 'G') return true;
- return false;
- }
+ pub const AugmentationKind = enum { none, gcc_eh, lsb_z };
/// This function expects to read the CIE starting with the version field.
- /// The returned struct references memory backed by cie_bytes.
- ///
- /// See the FrameDescriptionEntry.parse documentation for the description
- /// of `pc_rel_offset` and `is_runtime`.
+ /// The returned struct references memory backed by `cie_bytes`.
///
/// `length_offset` specifies the offset of this CIE's length field in the
/// .eh_frame / .debug_frame section.
pub fn parse(
cie_bytes: []const u8,
- pc_rel_offset: i64,
- is_runtime: bool,
- format: Format,
- dwarf_section: Section.Id,
- length_offset: u64,
- addr_size_bytes: u8,
- endian: Endian,
+ section: Section,
+ default_addr_size_bytes: u8,
) !CommonInformationEntry {
- if (addr_size_bytes > 8) return error.UnsupportedAddrSize;
+ // We only read the data through this reader.
+ var r: Reader = .fixed(cie_bytes);
- var fbr: Reader = .fixed(cie_bytes);
-
- const version = try fbr.takeByte();
- switch (dwarf_section) {
+ const version = try r.takeByte();
+ switch (section) {
.eh_frame => if (version != 1 and version != 3) return error.UnsupportedDwarfVersion,
.debug_frame => if (version != 4) return error.UnsupportedDwarfVersion,
- else => return error.UnsupportedDwarfSection,
}
- var has_eh_data = false;
- var has_aug_data = false;
-
- var aug_str_len: usize = 0;
- const aug_str_start = fbr.seek;
- var aug_byte = try fbr.takeByte();
- while (aug_byte != 0) : (aug_byte = try fbr.takeByte()) {
- switch (aug_byte) {
- 'z' => {
- if (aug_str_len != 0) return bad();
- has_aug_data = true;
- },
- 'e' => {
- if (has_aug_data or aug_str_len != 0) return bad();
- if (try fbr.takeByte() != 'h') return bad();
- has_eh_data = true;
- },
- else => if (has_eh_data) return bad(),
- }
-
- aug_str_len += 1;
- }
+ const aug_str = try r.takeSentinel(0);
+ const aug_kind: AugmentationKind = aug: {
+ if (aug_str.len == 0) break :aug .none;
+ if (aug_str[0] == 'z') break :aug .lsb_z;
+ if (std.mem.eql(u8, aug_str, "eh")) break :aug .gcc_eh;
+ // We can't finish parsing the CIE if we don't know what its augmentation means.
+ return bad();
+ };
- if (has_eh_data) {
- // legacy data created by older versions of gcc - unsupported here
- for (0..addr_size_bytes) |_| _ = try fbr.takeByte();
+ switch (aug_kind) {
+ .none => {}, // no extra data
+ .lsb_z => {}, // no extra data yet, but there is a bit later
+ .gcc_eh => try r.discardAll(default_addr_size_bytes), // unsupported data
}
- const address_size = if (version == 4) try fbr.takeByte() else addr_size_bytes;
- const segment_selector_size = if (version == 4) try fbr.takeByte() else null;
-
- const code_alignment_factor = try fbr.takeLeb128(u32);
- const data_alignment_factor = try fbr.takeLeb128(i32);
- const return_address_register = if (version == 1) try fbr.takeByte() else try fbr.takeLeb128(u8);
-
- var lsda_pointer_enc: u8 = EH.PE.omit;
- var personality_enc: ?u8 = null;
- var personality_routine_pointer: ?u64 = null;
- var fde_pointer_enc: u8 = EH.PE.absptr;
-
- var aug_data: []const u8 = &[_]u8{};
- const aug_str = if (has_aug_data) blk: {
- const aug_data_len = try fbr.takeLeb128(usize);
- const aug_data_start = fbr.seek;
- aug_data = cie_bytes[aug_data_start..][0..aug_data_len];
-
- const aug_str = cie_bytes[aug_str_start..][0..aug_str_len];
- for (aug_str[1..]) |byte| {
- switch (byte) {
- 'L' => {
- lsda_pointer_enc = try fbr.takeByte();
- },
- 'P' => {
- personality_enc = try fbr.takeByte();
- personality_routine_pointer = try readEhPointer(&fbr, personality_enc.?, addr_size_bytes, .{
- .pc_rel_base = try pcRelBase(@intFromPtr(&cie_bytes[fbr.seek]), pc_rel_offset),
- .follow_indirect = is_runtime,
- }, endian);
- },
- 'R' => {
- fde_pointer_enc = try fbr.takeByte();
- },
- 'S', 'B', 'G' => {},
- else => return bad(),
- }
- }
-
- // aug_data_len can include padding so the CIE ends on an address boundary
- fbr.seek = aug_data_start + aug_data_len;
- break :blk aug_str;
- } else &[_]u8{};
+ const addr_size_bytes = if (version == 4) try r.takeByte() else default_addr_size_bytes;
+ const segment_selector_size: u8 = if (version == 4) try r.takeByte() else 0;
+ const code_alignment_factor = try r.takeLeb128(u32);
+ const data_alignment_factor = try r.takeLeb128(i32);
+ const return_address_register = if (version == 1) try r.takeByte() else try r.takeLeb128(u8);
+
+ // This is where LSB's augmentation might add some data.
+ const fde_pointer_enc: EH.PE, const is_signal_frame: bool = aug: {
+ const default_fde_pointer_enc: EH.PE = .{ .type = .absptr, .rel = .abs };
+ if (aug_kind != .lsb_z) break :aug .{ default_fde_pointer_enc, false };
+ const aug_data_len = try r.takeLeb128(u32);
+ var aug_data: Reader = .fixed(try r.take(aug_data_len));
+ var fde_pointer_enc: EH.PE = default_fde_pointer_enc;
+ var is_signal_frame = false;
+ for (aug_str[1..]) |byte| switch (byte) {
+ 'L' => _ = try aug_data.takeByte(), // we ignore the LSDA pointer
+ 'P' => {
+ const enc: EH.PE = @bitCast(try aug_data.takeByte());
+ const endian: Endian = .little; // irrelevant because we're discarding the value anyway
+ _ = try readEhPointerAbs(&r, enc.type, addr_size_bytes, endian); // we ignore the personality routine; endianness is irrelevant since we're discarding
+ },
+ 'R' => fde_pointer_enc = @bitCast(try aug_data.takeByte()),
+ 'S' => is_signal_frame = true,
+ 'B', 'G' => {},
+ else => return bad(),
+ };
+ break :aug .{ fde_pointer_enc, is_signal_frame };
+ };
- const initial_instructions = cie_bytes[fbr.seek..];
return .{
- .length_offset = length_offset,
.version = version,
- .address_size = address_size,
- .format = format,
+ .addr_size_bytes = addr_size_bytes,
.segment_selector_size = segment_selector_size,
.code_alignment_factor = code_alignment_factor,
.data_alignment_factor = data_alignment_factor,
.return_address_register = return_address_register,
- .aug_str = aug_str,
- .aug_data = aug_data,
- .lsda_pointer_enc = lsda_pointer_enc,
- .personality_enc = personality_enc,
- .personality_routine_pointer = personality_routine_pointer,
.fde_pointer_enc = fde_pointer_enc,
- .initial_instructions = initial_instructions,
+ .is_signal_frame = is_signal_frame,
+ .augmentation_kind = aug_kind,
+ .initial_instructions = r.buffered(),
};
}
};
pub const FrameDescriptionEntry = struct {
- // Offset into eh_frame where the CIE for this FDE is stored
- cie_length_offset: u64,
-
pc_begin: u64,
pc_range: u64,
- lsda_pointer: ?u64,
- aug_data: []const u8,
instructions: []const u8,
/// This function expects to read the FDE starting at the PC Begin field.
/// The returned struct references memory backed by `fde_bytes`.
- ///
- /// `pc_rel_offset` specifies an offset to be applied to pc_rel_base values
- /// used when decoding pointers. This should be set to zero if fde_bytes is
- /// backed by the memory of a .eh_frame / .debug_frame section in the running executable.
- /// Otherwise, it should be the relative offset to translate addresses from
- /// where the section is currently stored in memory, to where it *would* be
- /// stored at runtime: section base addr - backing data base ptr.
- ///
- /// Similarly, `is_runtime` specifies this function is being called on a runtime
- /// section, and so indirect pointers can be followed.
pub fn parse(
+ /// The virtual address of the FDE we're parsing, *excluding* its entry header (i.e. the
+ /// address is after the header). If `fde_bytes` is backed by the memory of a loaded
+ /// module's `.eh_frame` section, this will equal `fde_bytes.ptr`.
+ fde_vaddr: u64,
fde_bytes: []const u8,
- pc_rel_offset: i64,
- is_runtime: bool,
cie: CommonInformationEntry,
- addr_size_bytes: u8,
endian: Endian,
) !FrameDescriptionEntry {
- if (addr_size_bytes > 8) return error.InvalidAddrSize;
-
- var fbr: Reader = .fixed(fde_bytes);
-
- const pc_begin = try readEhPointer(&fbr, cie.fde_pointer_enc, addr_size_bytes, .{
- .pc_rel_base = try pcRelBase(@intFromPtr(&fde_bytes[fbr.seek]), pc_rel_offset),
- .follow_indirect = is_runtime,
- }, endian) orelse return bad();
-
- const pc_range = try readEhPointer(&fbr, cie.fde_pointer_enc, addr_size_bytes, .{
- .pc_rel_base = 0,
- .follow_indirect = false,
- }, endian) orelse return bad();
-
- var aug_data: []const u8 = &[_]u8{};
- const lsda_pointer = if (cie.aug_str.len > 0) blk: {
- const aug_data_len = try fbr.takeLeb128(usize);
- const aug_data_start = fbr.seek;
- aug_data = fde_bytes[aug_data_start..][0..aug_data_len];
-
- const lsda_pointer = if (cie.lsda_pointer_enc != EH.PE.omit)
- try readEhPointer(&fbr, cie.lsda_pointer_enc, addr_size_bytes, .{
- .pc_rel_base = try pcRelBase(@intFromPtr(&fde_bytes[fbr.seek]), pc_rel_offset),
- .follow_indirect = is_runtime,
- }, endian)
- else
- null;
-
- fbr.seek = aug_data_start + aug_data_len;
- break :blk lsda_pointer;
- } else null;
-
- const instructions = fde_bytes[fbr.seek..];
+ if (cie.segment_selector_size != 0) return error.UnsupportedAddrSize;
+
+ var r: Reader = .fixed(fde_bytes);
+
+ const pc_begin = try readEhPointer(&r, cie.fde_pointer_enc, cie.addr_size_bytes, .{
+ .pc_rel_base = fde_vaddr,
+ }, endian);
+
+ // I swear I'm not kidding when I say that PC Range is encoded with `cie.fde_pointer_enc`, but ignoring `rel`.
+ const pc_range = switch (try readEhPointerAbs(&r, cie.fde_pointer_enc.type, cie.addr_size_bytes, endian)) {
+ .unsigned => |x| x,
+ .signed => |x| cast(u64, x) orelse return bad(),
+ };
+
+ switch (cie.augmentation_kind) {
+ .none, .gcc_eh => {},
+ .lsb_z => {
+ // There is augmentation data, but it's irrelevant to us -- it
+ // only contains the LSDA pointer, which we don't care about.
+ const aug_data_len = try r.takeLeb128(u64);
+ _ = try r.discardAll(aug_data_len);
+ },
+ }
+
return .{
- .cie_length_offset = cie.length_offset,
.pc_begin = pc_begin,
.pc_range = pc_range,
- .lsda_pointer = lsda_pointer,
- .aug_data = aug_data,
- .instructions = instructions,
+ .instructions = r.buffered(),
};
}
};
-/// If `.eh_frame_hdr` is present, then only the header needs to be parsed. Otherwise, `.eh_frame`
-/// and `.debug_frame` are scanned and a sorted list of FDEs is built for binary searching during
-/// unwinding. Even if `.eh_frame_hdr` is used, we may find during unwinding that it's incomplete,
-/// in which case we build the sorted list of FDEs at that point.
-///
-/// See also `scanCieFdeInfo`.
-pub fn scanAllUnwindInfo(di: *Dwarf, allocator: Allocator, base_address: usize) !void {
- const endian = di.endian;
-
- if (di.section(.eh_frame_hdr)) |eh_frame_hdr| blk: {
- var fbr: Reader = .fixed(eh_frame_hdr);
-
- const version = try fbr.takeByte();
- if (version != 1) break :blk;
-
- const eh_frame_ptr_enc = try fbr.takeByte();
- if (eh_frame_ptr_enc == EH.PE.omit) break :blk;
- const fde_count_enc = try fbr.takeByte();
- if (fde_count_enc == EH.PE.omit) break :blk;
- const table_enc = try fbr.takeByte();
- if (table_enc == EH.PE.omit) break :blk;
-
- const eh_frame_ptr = cast(usize, try readEhPointer(&fbr, eh_frame_ptr_enc, @sizeOf(usize), .{
- .pc_rel_base = @intFromPtr(&eh_frame_hdr[fbr.seek]),
- .follow_indirect = true,
- }, endian) orelse return bad()) orelse return bad();
-
- const fde_count = cast(usize, try readEhPointer(&fbr, fde_count_enc, @sizeOf(usize), .{
- .pc_rel_base = @intFromPtr(&eh_frame_hdr[fbr.seek]),
- .follow_indirect = true,
- }, endian) orelse return bad()) orelse return bad();
-
- const entry_size = try ExceptionFrameHeader.entrySize(table_enc);
- const entries_len = fde_count * entry_size;
- if (entries_len > eh_frame_hdr.len - fbr.seek) return bad();
-
- di.eh_frame_hdr = .{
- .eh_frame_ptr = eh_frame_ptr,
- .table_enc = table_enc,
- .fde_count = fde_count,
- .entries = eh_frame_hdr[fbr.seek..][0..entries_len],
- };
+pub fn scanDebugFrame(
+ unwind: *Unwind,
+ gpa: Allocator,
+ section_vaddr: u64,
+ section_bytes: []const u8,
+ addr_size_bytes: u8,
+ endian: Endian,
+) void {
+ assert(unwind.debug_frame == null);
+
+ var fbr: Reader = .fixed(section_bytes);
+ var fde_list: std.ArrayList(SortedFdeEntry) = .empty;
+ defer fde_list.deinit(gpa);
+ while (fbr.seek < fbr.buffer.len) {
+ const entry_offset = fbr.seek;
+ switch (try EntryHeader.read(&fbr, fbr.seek, .debug_frame, endian)) {
+ // Ignore CIEs; we only need them to parse the FDEs!
+ .cie => |info| {
+ try fbr.discardAll(info.bytes_len);
+ continue;
+ },
+ .fde => |info| {
+ const cie: CommonInformationEntry = cie: {
+ var cie_reader: Reader = .fixed(section_bytes[info.cie_offset..]);
+ const cie_info = switch (try EntryHeader.read(&cie_reader, info.cie_offset, .debug_frame, endian)) {
+ .cie => |cie_info| cie_info,
+ .fde, .terminator => return bad(), // This is meant to be a CIE
+ };
+ break :cie try .parse(try cie_reader.take(cie_info.bytes_len), .debug_frame, addr_size_bytes);
+ };
+ const fde: FrameDescriptionEntry = try .parse(
+ section_vaddr + fbr.seek,
+ try fbr.take(info.bytes_len),
+ cie,
+ endian,
+ );
+ try fde_list.append(.{
+ .pc_begin = fde.pc_begin,
+ .fde_offset = entry_offset, // *not* `fde_offset`, because we need to include the entry header
+ });
+ },
+ .terminator => return bad(), // DWARF `.debug_frame` isn't meant to have terminators
+ }
+ }
+ const fde_slice = try fde_list.toOwnedSlice(gpa);
+ errdefer comptime unreachable;
+ std.mem.sortUnstable(SortedFdeEntry, fde_slice, {}, struct {
+ fn lessThan(ctx: void, a: SortedFdeEntry, b: SortedFdeEntry) bool {
+ ctx;
+ return a.pc_begin < b.pc_begin;
+ }
+ }.lessThan);
+ unwind.debug_frame = .{ .data = section_bytes, .sorted_fdes = fde_slice };
+}
+
+pub fn scanEhFrame(
+ unwind: *Unwind,
+ gpa: Allocator,
+ header: EhFrameHeader,
+ section_bytes_ptr: [*]const u8,
+ /// This is separate from `section_bytes_ptr` because it is unknown when `.eh_frame` is accessed
+ /// through the pointer in the `.eh_frame_hdr` section. If this is non-`null`, we avoid reading
+ /// past this number of bytes, but if `null`, we must assume that the `.eh_frame` data has a
+ /// valid terminator.
+ section_bytes_len: ?usize,
+ addr_size_bytes: u8,
+ endian: Endian,
+) !void {
+ assert(unwind.eh_frame == null);
+
+ const section_bytes: []const u8 = bytes: {
+ // If the length is unknown, let the slice span from `section_bytes_ptr` to the end of memory.
+ const len = section_bytes_len orelse (std.math.maxInt(usize) - @intFromPtr(section_bytes_ptr));
+ break :bytes section_bytes_ptr[0..len];
+ };
- // No need to scan .eh_frame, we have a binary search table already
+ if (header.search_table != null) {
+ // No need to populate `sorted_fdes`, the header contains a search table.
+ unwind.eh_frame = .{
+ .header = header,
+ .eh_frame_data = section_bytes,
+ .sorted_fdes = null,
+ };
return;
}
- try di.scanCieFdeInfo(allocator, base_address);
+ // We aren't told the length of this section. Luckily, we don't need it, because there will be
+ // an `EntryHeader.terminator` after the last CIE/FDE. Just make a `Reader` which will give us
+ // alllll of the bytes!
+ var fbr: Reader = .fixed(section_bytes);
+
+ var fde_list: std.ArrayList(SortedFdeEntry) = .empty;
+ defer fde_list.deinit(gpa);
+
+ while (true) {
+ const entry_offset = fbr.seek;
+ switch (try EntryHeader.read(&fbr, fbr.seek, .eh_frame, endian)) {
+ // Ignore CIEs; we only need them to parse the FDEs!
+ .cie => |info| {
+ try fbr.discardAll(info.bytes_len);
+ continue;
+ },
+ .fde => |info| {
+ const cie: CommonInformationEntry = cie: {
+ var cie_reader: Reader = .fixed(section_bytes[info.cie_offset..]);
+ const cie_info = switch (try EntryHeader.read(&cie_reader, info.cie_offset, .eh_frame, endian)) {
+ .cie => |cie_info| cie_info,
+ .fde, .terminator => return bad(), // This is meant to be a CIE
+ };
+ break :cie try .parse(try cie_reader.take(cie_info.bytes_len), .eh_frame, addr_size_bytes);
+ };
+ const fde: FrameDescriptionEntry = try .parse(
+ header.eh_frame_vaddr + fbr.seek,
+ try fbr.take(info.bytes_len),
+ cie,
+ endian,
+ );
+ try fde_list.append(gpa, .{
+ .pc_begin = fde.pc_begin,
+ .fde_offset = entry_offset, // *not* `fde_offset`, because we need to include the entry header
+ });
+ },
+ // Unlike `.debug_frame`, the `.eh_frame` section does have a terminator CIE -- this is
+ // necessary because `header` doesn't include the length of the `.eh_frame` section
+ .terminator => break,
+ }
+ }
+ const fde_slice = try fde_list.toOwnedSlice(gpa);
+ errdefer comptime unreachable;
+ std.mem.sortUnstable(SortedFdeEntry, fde_slice, {}, struct {
+ fn lessThan(ctx: void, a: SortedFdeEntry, b: SortedFdeEntry) bool {
+ ctx;
+ return a.pc_begin < b.pc_begin;
+ }
+ }.lessThan);
+ unwind.eh_frame = .{
+ .header = header,
+ .eh_frame_data = section_bytes,
+ .sorted_fdes = fde_slice,
+ };
}
-/// Scan `.eh_frame` and `.debug_frame` and build a sorted list of FDEs for binary searching during
-/// unwinding.
-pub fn scanCieFdeInfo(unwind: *Unwind, allocator: Allocator, endian: Endian, base_address: usize) !void {
- const frame_sections = [2]Section.Id{ .eh_frame, .debug_frame };
- for (frame_sections) |frame_section| {
- if (unwind.section(frame_section)) |section_data| {
- var fbr: Reader = .fixed(section_data);
- while (fbr.seek < fbr.buffer.len) {
- const entry_header = try EntryHeader.read(&fbr, frame_section, endian);
- switch (entry_header.type) {
- .cie => {
- const cie = try CommonInformationEntry.parse(
- entry_header.entry_bytes,
- unwind.sectionVirtualOffset(frame_section, base_address).?,
- true,
- entry_header.format,
- frame_section,
- entry_header.length_offset,
- @sizeOf(usize),
- endian,
- );
- try unwind.cie_map.put(allocator, entry_header.length_offset, cie);
- },
- .fde => |cie_offset| {
- const cie = unwind.cie_map.get(cie_offset) orelse return bad();
- const fde = try FrameDescriptionEntry.parse(
- entry_header.entry_bytes,
- unwind.sectionVirtualOffset(frame_section, base_address).?,
- true,
- cie,
- @sizeOf(usize),
- endian,
- );
- try unwind.fde_list.append(allocator, fde);
- },
- .terminator => break,
- }
- }
-
- std.mem.sortUnstable(FrameDescriptionEntry, unwind.fde_list.items, {}, struct {
- fn lessThan(ctx: void, a: FrameDescriptionEntry, b: FrameDescriptionEntry) bool {
- _ = ctx;
- return a.pc_begin < b.pc_begin;
- }
- }.lessThan);
+/// The return value may be a false positive. After loading the FDE with `loadFde`, the caller must
+/// validate that `pc` is indeed in its range -- if it is not, then no FDE matches `pc`.
+pub fn findFdeOffset(unwind: *const Unwind, pc: u64, addr_size_bytes: u8, endian: Endian) !?u64 {
+ // We'll break from this block only if we have a manually-constructed search table.
+ const sorted_fdes: []const SortedFdeEntry = fdes: {
+ if (unwind.debug_frame) |df| break :fdes df.sorted_fdes;
+ if (unwind.eh_frame) |eh_frame| {
+ if (eh_frame.sorted_fdes) |fdes| break :fdes fdes;
+ // Use the search table from the `.eh_frame_hdr` section rather than one of our own
+ return eh_frame.header.findEntry(pc, addr_size_bytes, endian);
}
- }
+ // We have no available unwind info
+ return null;
+ };
+ const first_bad_idx = std.sort.partitionPoint(SortedFdeEntry, sorted_fdes, pc, struct {
+ fn canIncludePc(target_pc: u64, entry: SortedFdeEntry) bool {
+ return target_pc >= entry.pc_begin; // i.e. does 'entry_pc..<last pc>' include 'target_pc'
+ }
+ }.canIncludePc);
+ // `first_bad_idx` is the index of the first FDE whose `pc_begin` is too high to include `pc`.
+ // So if any FDE matches, it'll be the one at `first_bad_idx - 1` (maybe false positive).
+ if (first_bad_idx == 0) return null;
+ return sorted_fdes[first_bad_idx - 1].fde_offset;
+}
+
+pub fn loadFde(unwind: *const Unwind, fde_offset: u64, addr_size_bytes: u8, endian: Endian) !struct { Format, CommonInformationEntry, FrameDescriptionEntry } {
+ const section_bytes: []const u8, const section_vaddr: u64, const section: Section = s: {
+ if (unwind.debug_frame) |df| break :s .{ df.data, if (true) @panic("MLUGG TODO"), .debug_frame };
+ if (unwind.eh_frame) |ef| break :s .{ ef.eh_frame_data, ef.header.eh_frame_vaddr, .eh_frame };
+ unreachable; // how did you get `fde_offset`?!
+ };
+
+ var fde_reader: Reader = .fixed(section_bytes[fde_offset..]);
+ const fde_info = switch (try EntryHeader.read(&fde_reader, fde_offset, section, endian)) {
+ .fde => |info| info,
+ .cie, .terminator => return bad(), // This is meant to be an FDE
+ };
+
+ const cie_offset = fde_info.cie_offset;
+ var cie_reader: Reader = .fixed(section_bytes[cie_offset..]);
+ const cie_info = switch (try EntryHeader.read(&cie_reader, cie_offset, section, endian)) {
+ .cie => |info| info,
+ .fde, .terminator => return bad(), // This is meant to be a CIE
+ };
+
+ const cie: CommonInformationEntry = try .parse(
+ try cie_reader.take(cie_info.bytes_len),
+ section,
+ addr_size_bytes,
+ );
+ const fde: FrameDescriptionEntry = try .parse(
+ section_vaddr + fde_offset + fde_reader.seek,
+ try fde_reader.take(fde_info.bytes_len),
+ cie,
+ endian,
+ );
+
+ return .{ cie_info.format, cie, fde };
}
const EhPointerContext = struct {
// The address of the pointer field itself
pc_rel_base: u64,
- // Whether or not to follow indirect pointers. This should only be
- // used when decoding pointers at runtime using the current process's
- // debug info
- follow_indirect: bool,
-
// These relative addressing modes are only used in specific cases, and
// might not be available / required in all parsing contexts
data_rel_base: ?u64 = null,
text_rel_base: ?u64 = null,
function_rel_base: ?u64 = null,
};
-
-fn readEhPointer(fbr: *Reader, enc: u8, addr_size_bytes: u8, ctx: EhPointerContext, endian: Endian) !?u64 {
- if (enc == EH.PE.omit) return null;
-
- const value: union(enum) {
- signed: i64,
- unsigned: u64,
- } = switch (enc & EH.PE.type_mask) {
- EH.PE.absptr => .{
+/// Returns `error.InvalidDebugInfo` if the encoding is `EH.PE.omit`.
+fn readEhPointerAbs(r: *Reader, enc_ty: EH.PE.Type, addr_size_bytes: u8, endian: Endian) !union(enum) {
+ signed: i64,
+ unsigned: u64,
+} {
+ return switch (enc_ty) {
+ .absptr => .{
.unsigned = switch (addr_size_bytes) {
- 2 => try fbr.takeInt(u16, endian),
- 4 => try fbr.takeInt(u32, endian),
- 8 => try fbr.takeInt(u64, endian),
- else => return error.InvalidAddrSize,
+ 2 => try r.takeInt(u16, endian),
+ 4 => try r.takeInt(u32, endian),
+ 8 => try r.takeInt(u64, endian),
+ else => return error.UnsupportedAddrSize,
},
},
- EH.PE.uleb128 => .{ .unsigned = try fbr.takeLeb128(u64) },
- EH.PE.udata2 => .{ .unsigned = try fbr.takeInt(u16, endian) },
- EH.PE.udata4 => .{ .unsigned = try fbr.takeInt(u32, endian) },
- EH.PE.udata8 => .{ .unsigned = try fbr.takeInt(u64, endian) },
- EH.PE.sleb128 => .{ .signed = try fbr.takeLeb128(i64) },
- EH.PE.sdata2 => .{ .signed = try fbr.takeInt(i16, endian) },
- EH.PE.sdata4 => .{ .signed = try fbr.takeInt(i32, endian) },
- EH.PE.sdata8 => .{ .signed = try fbr.takeInt(i64, endian) },
+ .uleb128 => .{ .unsigned = try r.takeLeb128(u64) },
+ .udata2 => .{ .unsigned = try r.takeInt(u16, endian) },
+ .udata4 => .{ .unsigned = try r.takeInt(u32, endian) },
+ .udata8 => .{ .unsigned = try r.takeInt(u64, endian) },
+ .sleb128 => .{ .signed = try r.takeLeb128(i64) },
+ .sdata2 => .{ .signed = try r.takeInt(i16, endian) },
+ .sdata4 => .{ .signed = try r.takeInt(i32, endian) },
+ .sdata8 => .{ .signed = try r.takeInt(i64, endian) },
else => return bad(),
};
-
- const base = switch (enc & EH.PE.rel_mask) {
- EH.PE.pcrel => ctx.pc_rel_base,
- EH.PE.textrel => ctx.text_rel_base orelse return error.PointerBaseNotSpecified,
- EH.PE.datarel => ctx.data_rel_base orelse return error.PointerBaseNotSpecified,
- EH.PE.funcrel => ctx.function_rel_base orelse return error.PointerBaseNotSpecified,
- else => null,
+}
+/// Returns `error.InvalidDebugInfo` if the encoding is `EH.PE.omit`.
+fn readEhPointer(fbr: *Reader, enc: EH.PE, addr_size_bytes: u8, ctx: EhPointerContext, endian: Endian) !u64 {
+ const offset = try readEhPointerAbs(fbr, enc.type, addr_size_bytes, endian);
+ const base = switch (enc.rel) {
+ .abs, .aligned => 0,
+ .pcrel => ctx.pc_rel_base,
+ .textrel => ctx.text_rel_base orelse return bad(),
+ .datarel => ctx.data_rel_base orelse return bad(),
+ .funcrel => ctx.function_rel_base orelse return bad(),
+ .indirect => return bad(), // GCC extension; not supported
+ _ => return bad(),
};
-
- const ptr: u64 = if (base) |b| switch (value) {
- .signed => |s| @intCast(try std.math.add(i64, s, @as(i64, @intCast(b)))),
+ return switch (offset) {
+ .signed => |s| @intCast(try std.math.add(i64, s, @as(i64, @intCast(base)))),
// absptr can actually contain signed values in some cases (aarch64 MachO)
- .unsigned => |u| u +% b,
- } else switch (value) {
- .signed => |s| @as(u64, @intCast(s)),
- .unsigned => |u| u,
+ .unsigned => |u| u +% base,
};
-
- if ((enc & EH.PE.indirect) > 0 and ctx.follow_indirect) {
- if (@sizeOf(usize) != addr_size_bytes) {
- // See the documentation for `follow_indirect`
- return error.NonNativeIndirection;
- }
-
- const native_ptr = cast(usize, ptr) orelse return error.PointerOverflow;
- return switch (addr_size_bytes) {
- 2, 4, 8 => return @as(*const usize, @ptrFromInt(native_ptr)).*,
- else => return error.UnsupportedAddrSize,
- };
- } else {
- return ptr;
- }
}
-fn pcRelBase(field_ptr: usize, pc_rel_offset: i64) !usize {
- if (pc_rel_offset < 0) {
- return std.math.sub(usize, field_ptr, @as(usize, @intCast(-pc_rel_offset)));
- } else {
- return std.math.add(usize, field_ptr, @as(usize, @intCast(pc_rel_offset)));
- }
+/// Like `Reader.fixed`, but when the length of the data is unknown and we just want to allow
+/// reading indefinitely.
+fn maxSlice(ptr: [*]const u8) []const u8 {
+ const len = std.math.maxInt(usize) - @intFromPtr(ptr);
+ return ptr[0..len];
}
const Allocator = std.mem.Allocator;
lib/std/debug/Dwarf.zig
@@ -78,17 +78,6 @@ pub const Section = struct {
debug_addr,
debug_names,
};
-
- // For sections that are not memory mapped by the loader, this is an offset
- // from `data.ptr` to where the section would have been mapped. Otherwise,
- // `data` is directly backed by the section and the offset is zero.
- pub fn virtualOffset(self: Section, base_address: usize) i64 {
- return if (self.virtual_address) |va|
- @as(i64, @intCast(base_address + va)) -
- @as(i64, @intCast(@intFromPtr(self.data.ptr)))
- else
- 0;
- }
};
pub const Abbrev = struct {
@@ -342,10 +331,6 @@ pub fn section(di: Dwarf, dwarf_section: Section.Id) ?[]const u8 {
return if (di.sections[@intFromEnum(dwarf_section)]) |s| s.data else null;
}
-pub fn sectionVirtualOffset(di: Dwarf, dwarf_section: Section.Id, base_address: usize) ?i64 {
- return if (di.sections[@intFromEnum(dwarf_section)]) |s| s.virtualOffset(base_address) else null;
-}
-
pub fn deinit(di: *Dwarf, gpa: Allocator) void {
for (di.sections) |opt_section| {
if (opt_section) |s| if (s.owned) gpa.free(s.data);
@@ -364,8 +349,6 @@ pub fn deinit(di: *Dwarf, gpa: Allocator) void {
}
di.compile_unit_list.deinit(gpa);
di.func_list.deinit(gpa);
- di.cie_map.deinit(gpa);
- di.fde_list.deinit(gpa);
di.ranges.deinit(gpa);
di.* = undefined;
}
@@ -983,8 +966,8 @@ fn runLineNumberProgram(d: *Dwarf, gpa: Allocator, endian: Endian, compile_unit:
},
0,
};
- _ = addr_size;
- _ = seg_size;
+ if (seg_size != 0) return bad(); // unsupported
+ _ = addr_size; // TODO: ignoring this is incorrect, we should use it to decide address lengths
const prologue_length = try readAddress(&fr, unit_header.format, endian);
const prog_start_offset = fr.seek + prologue_length;
@@ -1472,44 +1455,27 @@ pub const ElfModule = struct {
mapped_memory: ?[]align(std.heap.page_size_min) const u8,
external_mapped_memory: ?[]align(std.heap.page_size_min) const u8,
- pub const Lookup = struct {
- base_address: usize,
- name: []const u8,
- build_id: ?[]const u8,
- gnu_eh_frame: ?[]const u8,
+ pub const init: ElfModule = .{
+ .unwind = .{
+ .debug_frame = null,
+ .eh_frame = null,
+ },
+ .dwarf = .{},
+ .mapped_memory = null,
+ .external_mapped_memory = null,
};
- pub fn init(lookup: *const Lookup) ElfModule {
- var em: ElfModule = .{
- .unwind = .{
- .sections = @splat(null),
- },
- .dwarf = .{},
- .mapped_memory = null,
- .external_mapped_memory = null,
- };
- if (lookup.gnu_eh_frame) |eh_frame_hdr| {
- // This is a special case - pointer offsets inside .eh_frame_hdr
- // are encoded relative to its base address, so we must use the
- // version that is already memory mapped, and not the one that
- // will be mapped separately from the ELF file.
- em.unwind.sections[@intFromEnum(Dwarf.Unwind.Section.Id.eh_frame_hdr)] = .{
- .data = eh_frame_hdr,
- };
- }
- return em;
- }
-
pub fn deinit(self: *@This(), allocator: Allocator) void {
self.dwarf.deinit(allocator);
std.posix.munmap(self.mapped_memory);
if (self.external_mapped_memory) |m| std.posix.munmap(m);
}
- pub fn getSymbolAtAddress(self: *@This(), allocator: Allocator, endian: Endian, base_address: usize, address: usize) !std.debug.Symbol {
- // Translate the VA into an address into this object
- const relocated_address = address - base_address;
- return self.dwarf.getSymbol(allocator, endian, relocated_address);
+ pub fn getSymbolAtAddress(self: *@This(), allocator: Allocator, endian: Endian, load_offset: usize, address: usize) !std.debug.Symbol {
+ // Translate the runtime address into a virtual address into the module
+ // MLUGG TODO: this clearly tells us that the logic should live near SelfInfo...
+ const vaddr = address - load_offset;
+ return self.dwarf.getSymbol(allocator, endian, vaddr);
}
pub fn getDwarfUnwindForAddress(self: *@This(), allocator: Allocator, address: usize) !?*Dwarf.Unwind {
@@ -1548,7 +1514,7 @@ pub const ElfModule = struct {
mapped_mem: []align(std.heap.page_size_min) const u8,
build_id: ?[]const u8,
expected_crc: ?u32,
- parent_sections: *Dwarf.SectionArray,
+ parent_sections: ?*Dwarf.SectionArray,
parent_mapped_mem: ?[]align(std.heap.page_size_min) const u8,
elf_filename: ?[]const u8,
) LoadError!void {
@@ -1577,10 +1543,12 @@ pub const ElfModule = struct {
var sections: Dwarf.SectionArray = @splat(null);
// Combine section list. This takes ownership over any owned sections from the parent scope.
- for (parent_sections, §ions) |*parent, *section_elem| {
- if (parent.*) |*p| {
- section_elem.* = p.*;
- p.owned = false;
+ if (parent_sections) |ps| {
+ for (ps, §ions) |*parent, *section_elem| {
+ if (parent.*) |*p| {
+ section_elem.* = p.*;
+ p.owned = false;
+ }
}
}
errdefer for (sections) |opt_section| if (opt_section) |s| if (s.owned) gpa.free(s.data);
@@ -1647,7 +1615,6 @@ pub const ElfModule = struct {
// Attempt to load debug info from an external file
// See: https://sourceware.org/gdb/onlinedocs/gdb/Separate-Debug-Files.html
if (missing_debug_info) {
-
// Only allow one level of debug info nesting
if (parent_mapped_mem) |_| {
return error.MissingDebugInfo;
@@ -1775,6 +1742,7 @@ pub const ElfModule = struct {
em.mapped_memory = parent_mapped_mem orelse mapped_mem;
em.external_mapped_memory = if (parent_mapped_mem != null) mapped_mem else null;
+ em.dwarf.sections = sections;
try em.dwarf.open(gpa, endian);
}
@@ -1844,7 +1812,8 @@ pub fn chopSlice(ptr: []const u8, offset: u64, size: u64) error{Overflow}![]cons
return ptr[start..end];
}
-pub fn readAddress(r: *Reader, format: std.dwarf.Format, endian: Endian) !u64 {
+fn readAddress(r: *Reader, format: std.dwarf.Format, endian: Endian) !u64 {
+ // MLUGG TODO FIX BEFORE MERGE: this function is slightly bogus. addresses have a byte width which is independent of the `dwarf.Format`!
return switch (format) {
.@"32" => try r.takeInt(u32, endian),
.@"64" => try r.takeInt(u64, endian),
@@ -1852,6 +1821,8 @@ pub fn readAddress(r: *Reader, format: std.dwarf.Format, endian: Endian) !u64 {
}
fn nativeFormat() std.dwarf.Format {
+ // MLUGG TODO FIX BEFORE MERGE: this is nonsensical. this is neither what `dwarf.Format` is for, nor does it make sense to check the NATIVE FUCKING FORMAT
+ // when parsing ARBITRARY DWARF.
return switch (@sizeOf(usize)) {
4 => .@"32",
8 => .@"64",
lib/std/debug/SelfInfo.zig
@@ -13,7 +13,6 @@ const windows = std.os.windows;
const macho = std.macho;
const fs = std.fs;
const coff = std.coff;
-const pdb = std.pdb;
const assert = std.debug.assert;
const posix = std.posix;
const elf = std.elf;
@@ -22,86 +21,37 @@ const Pdb = std.debug.Pdb;
const File = std.fs.File;
const math = std.math;
const testing = std.testing;
-const StackIterator = std.debug.StackIterator;
const regBytes = Dwarf.abi.regBytes;
const regValueNative = Dwarf.abi.regValueNative;
const SelfInfo = @This();
-const root = @import("root");
-
-allocator: Allocator,
-address_map: std.AutoHashMapUnmanaged(usize, Module),
-modules: if (native_os == .windows) std.ArrayListUnmanaged(WindowsModule) else void,
-
-pub const OpenError = error{
- MissingDebugInfo,
- UnsupportedOperatingSystem,
-} || @typeInfo(@typeInfo(@TypeOf(SelfInfo.init)).@"fn".return_type.?).error_union.error_set;
-
-pub fn open(allocator: Allocator) OpenError!SelfInfo {
- if (builtin.strip_debug_info)
- return error.MissingDebugInfo;
- switch (native_os) {
- .linux,
- .freebsd,
- .netbsd,
- .dragonfly,
- .openbsd,
- .macos,
- .solaris,
- .illumos,
- .windows,
- => return try SelfInfo.init(allocator),
- else => return error.UnsupportedOperatingSystem,
- }
-}
-
-pub fn init(allocator: Allocator) !SelfInfo {
- var debug_info: SelfInfo = .{
- .allocator = allocator,
- .address_map = .empty,
- .modules = if (native_os == .windows) .{} else {},
- };
-
- if (native_os == .windows) {
- errdefer debug_info.modules.deinit(allocator);
-
- const handle = windows.kernel32.CreateToolhelp32Snapshot(windows.TH32CS_SNAPMODULE | windows.TH32CS_SNAPMODULE32, 0);
- if (handle == windows.INVALID_HANDLE_VALUE) {
- switch (windows.GetLastError()) {
- else => |err| return windows.unexpectedError(err),
- }
- }
- defer windows.CloseHandle(handle);
-
- var module_entry: windows.MODULEENTRY32 = undefined;
- module_entry.dwSize = @sizeOf(windows.MODULEENTRY32);
- if (windows.kernel32.Module32First(handle, &module_entry) == 0) {
- return error.MissingDebugInfo;
- }
-
- var module_valid = true;
- while (module_valid) {
- const module_info = try debug_info.modules.addOne(allocator);
- const name = allocator.dupe(u8, mem.sliceTo(&module_entry.szModule, 0)) catch &.{};
- errdefer allocator.free(name);
-
- module_info.* = .{
- .base_address = @intFromPtr(module_entry.modBaseAddr),
- .size = module_entry.modBaseSize,
- .name = name,
- .handle = module_entry.hModule,
- };
-
- module_valid = windows.kernel32.Module32Next(handle, &module_entry) == 1;
- }
- }
+/// MLUGG TODO: what if this field had a less stupid name...
+address_map: std.AutoHashMapUnmanaged(usize, Module.DebugInfo),
+
+module_cache: if (native_os == .windows) std.ArrayListUnmanaged(windows.MODULEENTRY32) else void,
+
+pub const target_supported: bool = switch (native_os) {
+ .linux,
+ .freebsd,
+ .netbsd,
+ .dragonfly,
+ .openbsd,
+ .macos,
+ .solaris,
+ .illumos,
+ .windows,
+ => true,
+ else => false,
+};
- return debug_info;
-}
+pub const init: SelfInfo = .{
+ .address_map = .empty,
+ .module_cache = if (native_os == .windows) .empty,
+};
pub fn deinit(self: *SelfInfo) void {
+ // MLUGG TODO: that's amusing, this function is straight-up unused. i... wonder if it even should be used anywhere? perhaps not... so perhaps it should not even exist...????
var it = self.address_map.iterator();
while (it.next()) |entry| {
const mdi = entry.value_ptr.*;
@@ -118,49 +68,91 @@ pub fn deinit(self: *SelfInfo) void {
}
}
-fn lookupModuleForAddress(self: *SelfInfo, address: usize) !Module.Lookup {
+fn lookupModuleForAddress(self: *SelfInfo, gpa: Allocator, address: usize) !Module {
if (builtin.target.os.tag.isDarwin()) {
return self.lookupModuleDyld(address);
} else if (native_os == .windows) {
- return self.lookupModuleWin32(address);
+ return self.lookupModuleWin32(gpa, address);
} else if (native_os == .haiku) {
- return self.lookupModuleHaiku(address);
+ @panic("TODO implement lookup module for Haiku");
} else if (builtin.target.cpu.arch.isWasm()) {
- return self.lookupModuleWasm(address);
+ @panic("TODO implement lookup module for Wasm");
} else {
return self.lookupModuleDl(address);
}
}
-fn loadModuleDebugInfo(self: *SelfInfo, lookup: *const Module.Lookup, module: *Module) !void {
+fn loadModuleDebugInfo(gpa: Allocator, module: *const Module, di: *Module.DebugInfo) !void {
+ // MLUGG TODO: this should totally just go into the `Module` impl or something, right? lol
+ if (builtin.target.os.tag.isDarwin()) {
+ try loadMachODebugInfo(gpa, module, di);
+ } else if (native_os == .windows) {
+ // MLUGG TODO: deal with 'already loaded' properly
+ try readCoffDebugInfo(gpa, module, di);
+ } else if (native_os == .haiku) {
+ unreachable;
+ } else if (builtin.target.cpu.arch.isWasm()) {
+ unreachable;
+ } else {
+ if (di.mapped_memory != null) return; // already loaded
+ const filename: ?[]const u8 = if (module.name.len > 0) module.name else null;
+ const mapped_mem = mapFileOrSelfExe(filename) catch |err| switch (err) {
+ error.FileNotFound => return error.MissingDebugInfo,
+ error.FileTooBig => return error.InvalidDebugInfo,
+ else => |e| return e,
+ };
+ errdefer posix.munmap(mapped_mem);
+ try di.load(gpa, mapped_mem, module.build_id, null, null, null, filename);
+ assert(di.mapped_memory != null);
+ }
+}
+
+fn loadModuleUnwindInfo(gpa: Allocator, module: *const Module, di: *Module.DebugInfo) !void {
if (builtin.target.os.tag.isDarwin()) {
- @compileError("TODO");
+ // MLUGG TODO HACKHACK
+ try loadMachODebugInfo(gpa, module, di);
} else if (native_os == .windows) {
- @compileError("TODO");
+ comptime unreachable; // not supported
} else if (native_os == .haiku) {
- @compileError("TODO");
+ comptime unreachable; // not supported
} else if (builtin.target.cpu.arch.isWasm()) {
- @compileError("TODO");
+ comptime unreachable; // not supported
} else {
- if (module.mapped_memory == null) {
- var sections: Dwarf.SectionArray = @splat(null);
- try readElfDebugInfo(module, self.allocator, if (lookup.name.len > 0) lookup.name else null, lookup.build_id, §ions);
- assert(module.mapped_memory != null);
+ eh_frame: {
+ if (di.unwind.eh_frame != null) break :eh_frame; // already loaded
+ const eh_frame_hdr_bytes = module.gnu_eh_frame orelse break :eh_frame;
+ const eh_frame_hdr: Dwarf.Unwind.EhFrameHeader = try .parse(
+ @intFromPtr(eh_frame_hdr_bytes.ptr) - module.load_offset,
+ eh_frame_hdr_bytes,
+ @sizeOf(usize),
+ native_endian,
+ );
+ const eh_frame_addr = module.load_offset + @as(usize, @intCast(eh_frame_hdr.eh_frame_vaddr));
+ try di.unwind.scanEhFrame(
+ gpa,
+ eh_frame_hdr,
+ @ptrFromInt(eh_frame_addr),
+ null,
+ @sizeOf(usize),
+ native_endian,
+ );
}
}
}
-pub fn unwindFrame(self: *SelfInfo, context: *UnwindContext) !usize {
- const lookup = try self.lookupModuleForAddress(context.pc);
- const gop = try self.address_map.getOrPut(self.allocator, lookup.base_address);
- if (!gop.found_existing) gop.value_ptr.* = .init(&lookup);
+pub fn unwindFrame(self: *SelfInfo, gpa: Allocator, context: *UnwindContext) !usize {
+ comptime assert(target_supported);
+ const module = try self.lookupModuleForAddress(gpa, context.pc);
+ const gop = try self.address_map.getOrPut(gpa, module.load_offset);
+ if (!gop.found_existing) gop.value_ptr.* = .init;
+ try loadModuleUnwindInfo(gpa, &module, gop.value_ptr);
if (native_os.isDarwin()) {
// __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 (gop.value_ptr.unwind_info) |unwind_info| {
if (unwindFrameMachO(
- self.allocator,
- lookup.base_address,
+ module.text_base,
+ module.load_offset,
context,
unwind_info,
gop.value_ptr.eh_frame,
@@ -169,292 +161,42 @@ pub fn unwindFrame(self: *SelfInfo, context: *UnwindContext) !usize {
} else |err| {
if (err != error.RequiresDWARFUnwind) return err;
}
- } else return error.MissingUnwindInfo;
+ }
+ return error.MissingUnwindInfo;
+ }
+ if (try gop.value_ptr.getDwarfUnwindForAddress(gpa, context.pc)) |unwind| {
+ return unwindFrameDwarf(unwind, module.load_offset, context, null);
}
- if (try gop.value_ptr.getDwarfUnwindForAddress(self.allocator, context.pc)) |unwind| {
- return unwindFrameDwarf(self.allocator, unwind, lookup.base_address, context, null);
- } else return error.MissingDebugInfo;
+ return error.MissingDebugInfo;
}
-pub fn getSymbolAtAddress(self: *SelfInfo, address: usize) !std.debug.Symbol {
- const lookup = try self.lookupModuleForAddress(address);
- const gop = try self.address_map.getOrPut(self.allocator, lookup.base_address);
- if (!gop.found_existing) gop.value_ptr.* = .init(&lookup);
- try self.loadModuleDebugInfo(&lookup, gop.value_ptr);
- return gop.value_ptr.getSymbolAtAddress(self.allocator, native_endian, lookup.base_address, address);
+pub fn getSymbolAtAddress(self: *SelfInfo, gpa: Allocator, address: usize) !std.debug.Symbol {
+ comptime assert(target_supported);
+ const module = try self.lookupModuleForAddress(gpa, address);
+ const gop = try self.address_map.getOrPut(gpa, module.key());
+ if (!gop.found_existing) gop.value_ptr.* = .init;
+ try loadModuleDebugInfo(gpa, &module, gop.value_ptr);
+ return module.getSymbolAtAddress(gpa, gop.value_ptr, address);
}
/// Returns the module name for a given address.
/// This can be called when getModuleForAddress fails, so implementations should provide
/// a path that doesn't rely on any side-effects of a prior successful module lookup.
-pub fn getModuleNameForAddress(self: *SelfInfo, address: usize) ?[]const u8 {
- return if (self.lookupModuleForAddress(address)) |lookup| lookup.name else |err| switch (err) {
- error.MissingDebugInfo => null,
- };
-}
-
-fn lookupModuleDyld(self: *SelfInfo, address: usize) !*Module {
- const image_count = std.c._dyld_image_count();
-
- var i: u32 = 0;
- while (i < image_count) : (i += 1) {
- const header = std.c._dyld_get_image_header(i) orelse continue;
- const base_address = @intFromPtr(header);
- if (address < base_address) continue;
- const vmaddr_slide = std.c._dyld_get_image_vmaddr_slide(i);
-
- var it = macho.LoadCommandIterator{
- .ncmds = header.ncmds,
- .buffer = @alignCast(@as(
- [*]u8,
- @ptrFromInt(@intFromPtr(header) + @sizeOf(macho.mach_header_64)),
- )[0..header.sizeofcmds]),
- };
-
- var unwind_info: ?[]const u8 = null;
- var eh_frame: ?[]const u8 = null;
- while (it.next()) |cmd| switch (cmd.cmd()) {
- .SEGMENT_64 => {
- const segment_cmd = cmd.cast(macho.segment_command_64).?;
- if (!mem.eql(u8, "__TEXT", segment_cmd.segName())) continue;
-
- const seg_start = segment_cmd.vmaddr + vmaddr_slide;
- const seg_end = seg_start + segment_cmd.vmsize;
- if (address >= seg_start and address < seg_end) {
- if (self.address_map.get(base_address)) |obj_di| {
- return obj_di;
- }
-
- for (cmd.getSections()) |sect| {
- const sect_addr: usize = @intCast(sect.addr);
- const sect_size: usize = @intCast(sect.size);
- if (mem.eql(u8, "__unwind_info", sect.sectName())) {
- unwind_info = @as([*]const u8, @ptrFromInt(sect_addr + vmaddr_slide))[0..sect_size];
- } else if (mem.eql(u8, "__eh_frame", sect.sectName())) {
- eh_frame = @as([*]const u8, @ptrFromInt(sect_addr + vmaddr_slide))[0..sect_size];
- }
- }
-
- const obj_di = try self.allocator.create(Module);
- errdefer self.allocator.destroy(obj_di);
-
- const macho_path = mem.sliceTo(std.c._dyld_get_image_name(i), 0);
- const macho_file = fs.cwd().openFile(macho_path, .{}) catch |err| switch (err) {
- error.FileNotFound => return error.MissingDebugInfo,
- else => return err,
- };
- obj_di.* = try readMachODebugInfo(self.allocator, macho_file);
- obj_di.base_address = base_address;
- obj_di.vmaddr_slide = vmaddr_slide;
- obj_di.unwind_info = unwind_info;
- obj_di.eh_frame = eh_frame;
-
- try self.address_map.putNoClobber(base_address, obj_di);
-
- return obj_di;
- }
- },
- else => {},
- };
- }
-
- return error.MissingDebugInfo;
-}
-
-fn lookupModuleNameDyld(self: *SelfInfo, address: usize) ?[]const u8 {
- _ = self;
- const image_count = std.c._dyld_image_count();
-
- var i: u32 = 0;
- while (i < image_count) : (i += 1) {
- const header = std.c._dyld_get_image_header(i) orelse continue;
- const base_address = @intFromPtr(header);
- if (address < base_address) continue;
- const vmaddr_slide = std.c._dyld_get_image_vmaddr_slide(i);
-
- var it = macho.LoadCommandIterator{
- .ncmds = header.ncmds,
- .buffer = @alignCast(@as(
- [*]u8,
- @ptrFromInt(@intFromPtr(header) + @sizeOf(macho.mach_header_64)),
- )[0..header.sizeofcmds]),
- };
-
- while (it.next()) |cmd| switch (cmd.cmd()) {
- .SEGMENT_64 => {
- const segment_cmd = cmd.cast(macho.segment_command_64).?;
- if (!mem.eql(u8, "__TEXT", segment_cmd.segName())) continue;
-
- const original_address = address - vmaddr_slide;
- const seg_start = segment_cmd.vmaddr;
- const seg_end = seg_start + segment_cmd.vmsize;
- if (original_address >= seg_start and original_address < seg_end) {
- return fs.path.basename(mem.sliceTo(std.c._dyld_get_image_name(i), 0));
- }
- },
- else => {},
- };
- }
-
- return null;
-}
-
-fn lookupModuleWin32(self: *SelfInfo, address: usize) !*Module {
- for (self.modules.items) |*module| {
- if (address >= module.base_address and address < module.base_address + module.size) {
- if (self.address_map.get(module.base_address)) |obj_di| {
- return obj_di;
- }
-
- const obj_di = try self.allocator.create(Module);
- errdefer self.allocator.destroy(obj_di);
-
- const mapped_module = @as([*]const u8, @ptrFromInt(module.base_address))[0..module.size];
- var coff_obj = try coff.Coff.init(mapped_module, true);
-
- // The string table is not mapped into memory by the loader, so if a section name is in the
- // string table then we have to map the full image file from disk. This can happen when
- // a binary is produced with -gdwarf, since the section names are longer than 8 bytes.
- if (coff_obj.strtabRequired()) {
- var name_buffer: [windows.PATH_MAX_WIDE + 4:0]u16 = undefined;
- // openFileAbsoluteW requires the prefix to be present
- @memcpy(name_buffer[0..4], &[_]u16{ '\\', '?', '?', '\\' });
-
- const process_handle = windows.GetCurrentProcess();
- const len = windows.kernel32.GetModuleFileNameExW(
- process_handle,
- module.handle,
- @ptrCast(&name_buffer[4]),
- windows.PATH_MAX_WIDE,
- );
-
- if (len == 0) return error.MissingDebugInfo;
- const coff_file = fs.openFileAbsoluteW(name_buffer[0 .. len + 4 :0], .{}) catch |err| switch (err) {
- error.FileNotFound => return error.MissingDebugInfo,
- else => return err,
- };
- errdefer coff_file.close();
-
- var section_handle: windows.HANDLE = undefined;
- const create_section_rc = windows.ntdll.NtCreateSection(
- §ion_handle,
- windows.STANDARD_RIGHTS_REQUIRED | windows.SECTION_QUERY | windows.SECTION_MAP_READ,
- null,
- null,
- windows.PAGE_READONLY,
- // The documentation states that if no AllocationAttribute is specified, then SEC_COMMIT is the default.
- // In practice, this isn't the case and specifying 0 will result in INVALID_PARAMETER_6.
- windows.SEC_COMMIT,
- coff_file.handle,
- );
- if (create_section_rc != .SUCCESS) return error.MissingDebugInfo;
- errdefer windows.CloseHandle(section_handle);
-
- var coff_len: usize = 0;
- var base_ptr: usize = 0;
- const map_section_rc = windows.ntdll.NtMapViewOfSection(
- section_handle,
- process_handle,
- @ptrCast(&base_ptr),
- null,
- 0,
- null,
- &coff_len,
- .ViewUnmap,
- 0,
- windows.PAGE_READONLY,
- );
- if (map_section_rc != .SUCCESS) return error.MissingDebugInfo;
- errdefer assert(windows.ntdll.NtUnmapViewOfSection(process_handle, @ptrFromInt(base_ptr)) == .SUCCESS);
-
- const section_view = @as([*]const u8, @ptrFromInt(base_ptr))[0..coff_len];
- coff_obj = try coff.Coff.init(section_view, false);
-
- module.mapped_file = .{
- .file = coff_file,
- .section_handle = section_handle,
- .section_view = section_view,
- };
- }
- errdefer if (module.mapped_file) |mapped_file| mapped_file.deinit();
-
- obj_di.* = try readCoffDebugInfo(self.allocator, &coff_obj);
- obj_di.base_address = module.base_address;
-
- try self.address_map.putNoClobber(module.base_address, obj_di);
- return obj_di;
- }
- }
-
- return error.MissingDebugInfo;
-}
-
-fn lookupModuleNameWin32(self: *SelfInfo, address: usize) ?[]const u8 {
- for (self.modules.items) |module| {
- if (address >= module.base_address and address < module.base_address + module.size) {
- return module.name;
- }
- }
- return null;
-}
-
-fn lookupModuleNameDl(self: *SelfInfo, address: usize) ?[]const u8 {
- _ = self;
-
- var ctx: struct {
- // Input
- address: usize,
- // Output
- name: []const u8 = "",
- } = .{ .address = address };
- const CtxTy = @TypeOf(ctx);
-
- if (posix.dl_iterate_phdr(&ctx, error{Found}, struct {
- fn callback(info: *posix.dl_phdr_info, size: usize, context: *CtxTy) !void {
- _ = size;
- if (context.address < info.addr) return;
- const phdrs = info.phdr[0..info.phnum];
- for (phdrs) |*phdr| {
- if (phdr.p_type != elf.PT_LOAD) continue;
-
- const seg_start = info.addr +% phdr.p_vaddr;
- const seg_end = seg_start + phdr.p_memsz;
- if (context.address >= seg_start and context.address < seg_end) {
- context.name = mem.sliceTo(info.name, 0) orelse "";
- break;
- }
- } else return;
-
- return error.Found;
- }
- }.callback)) {
- return null;
- } else |err| switch (err) {
- error.Found => return fs.path.basename(ctx.name),
- }
-
- return null;
+pub fn getModuleNameForAddress(self: *SelfInfo, gpa: Allocator, address: usize) error{ Unexpected, OutOfMemory, MissingDebugInfo }![]const u8 {
+ comptime assert(target_supported);
+ const module = try self.lookupModuleForAddress(gpa, address);
+ return module.name;
}
-fn lookupModuleDl(self: *SelfInfo, address: usize) !Module.Lookup {
- var ctx: struct {
- // Input
+fn lookupModuleDl(self: *SelfInfo, address: usize) !Module {
+ _ = self; // MLUGG
+ const DlIterContext = struct {
+ /// input
address: usize,
- // Output
- lookup: Module.Lookup,
- } = .{
- .address = address,
- .lookup = .{
- .base_address = undefined,
- .name = undefined,
- .build_id = null,
- .gnu_eh_frame = null,
- },
- };
- const CtxTy = @TypeOf(ctx);
+ /// output
+ module: Module,
- posix.dl_iterate_phdr(&ctx, error{Found}, struct {
- fn callback(info: *posix.dl_phdr_info, size: usize, context: *CtxTy) !void {
+ fn callback(info: *posix.dl_phdr_info, size: usize, context: *@This()) !void {
_ = size;
// The base address is too high
if (context.address < info.addr)
@@ -468,10 +210,13 @@ fn lookupModuleDl(self: *SelfInfo, address: usize) !Module.Lookup {
const seg_start = info.addr +% phdr.p_vaddr;
const seg_end = seg_start + phdr.p_memsz;
if (context.address >= seg_start and context.address < seg_end) {
- // Android libc uses NULL instead of an empty string to mark the
- // main program
- context.lookup.name = mem.sliceTo(info.name, 0) orelse "";
- context.lookup.base_address = info.addr;
+ context.module = .{
+ .load_offset = info.addr,
+ // Android libc uses NULL instead of "" to mark the main program
+ .name = mem.sliceTo(info.name, 0) orelse "",
+ .build_id = null,
+ .gnu_eh_frame = null,
+ };
break;
}
} else return;
@@ -480,17 +225,20 @@ fn lookupModuleDl(self: *SelfInfo, address: usize) !Module.Lookup {
switch (phdr.p_type) {
elf.PT_NOTE => {
// Look for .note.gnu.build-id
- const note_bytes = @as([*]const u8, @ptrFromInt(info.addr + phdr.p_vaddr))[0..phdr.p_memsz];
- const name_size = mem.readInt(u32, note_bytes[0..4], native_endian);
- if (name_size != 4) continue;
- const desc_size = mem.readInt(u32, note_bytes[4..8], native_endian);
- const note_type = mem.readInt(u32, note_bytes[8..12], native_endian);
+ const segment_ptr: [*]const u8 = @ptrFromInt(info.addr + phdr.p_vaddr);
+ var r: std.Io.Reader = .fixed(segment_ptr[0..phdr.p_memsz]);
+ const name_size = r.takeInt(u32, native_endian) catch continue;
+ const desc_size = r.takeInt(u32, native_endian) catch continue;
+ const note_type = r.takeInt(u32, native_endian) catch continue;
+ const name = r.take(name_size) catch continue;
if (note_type != elf.NT_GNU_BUILD_ID) continue;
- if (!mem.eql(u8, "GNU\x00", note_bytes[12..16])) continue;
- context.lookup.build_id = note_bytes[16..][0..desc_size];
+ if (!mem.eql(u8, name, "GNU\x00")) continue;
+ const desc = r.take(desc_size) catch continue;
+ context.module.build_id = desc;
},
elf.PT_GNU_EH_FRAME => {
- context.lookup.gnu_eh_frame = @as([*]const u8, @ptrFromInt(info.addr + phdr.p_vaddr))[0..phdr.p_memsz];
+ const segment_ptr: [*]const u8 = @ptrFromInt(info.addr + phdr.p_vaddr);
+ context.module.gnu_eh_frame = segment_ptr[0..phdr.p_memsz];
},
else => {},
}
@@ -499,425 +247,558 @@ fn lookupModuleDl(self: *SelfInfo, address: usize) !Module.Lookup {
// Stop the iteration
return error.Found;
}
- }.callback) catch |err| switch (err) {
- error.Found => return ctx.lookup,
};
- if (true) return error.MissingDebugInfo;
-
- if (self.address_map.get(ctx.lookup.base_address)) |obj_di| {
- return obj_di;
- }
+ var ctx: DlIterContext = .{
+ .address = address,
+ .module = undefined,
+ };
+ posix.dl_iterate_phdr(&ctx, error{Found}, DlIterContext.callback) catch |err| switch (err) {
+ error.Found => return ctx.module,
+ };
+ return error.MissingDebugInfo;
+}
- var sections: Dwarf.SectionArray = @splat(null);
- if (ctx.lookup.gnu_eh_frame) |eh_frame_hdr| {
- // This is a special case - pointer offsets inside .eh_frame_hdr
- // are encoded relative to its base address, so we must use the
- // version that is already memory mapped, and not the one that
- // will be mapped separately from the ELF file.
- sections[@intFromEnum(Dwarf.Unwind.Section.Id.eh_frame_hdr)] = .{
- .data = eh_frame_hdr,
- .owned = false,
+fn lookupModuleDyld(self: *SelfInfo, address: usize) !Module {
+ _ = self; // MLUGG
+ const image_count = std.c._dyld_image_count();
+ for (0..image_count) |image_idx| {
+ const header = std.c._dyld_get_image_header(@intCast(image_idx)) orelse continue;
+ const text_base = @intFromPtr(header);
+ if (address < text_base) continue;
+ const load_offset = std.c._dyld_get_image_vmaddr_slide(@intCast(image_idx));
+
+ // Find the __TEXT segment
+ var it: macho.LoadCommandIterator = .{
+ .ncmds = header.ncmds,
+ .buffer = @as([*]u8, @ptrCast(header))[@sizeOf(macho.mach_header_64)..][0..header.sizeofcmds],
+ };
+ const text_segment_cmd, const text_sections = while (it.next()) |load_cmd| {
+ if (load_cmd.cmd() != .SEGMENT_64) continue;
+ const segment_cmd = load_cmd.cast(macho.segment_command_64).?;
+ if (!mem.eql(u8, segment_cmd.segName(), "__TEXT")) continue;
+ break .{ segment_cmd, load_cmd.getSections() };
+ } else continue;
+
+ const seg_start = load_offset + text_segment_cmd.vmaddr;
+ assert(seg_start == text_base);
+ const seg_end = seg_start + text_segment_cmd.vmsize;
+ if (address < seg_start or address >= seg_end) continue;
+
+ // We've found the matching __TEXT segment. This is the image we need, but we must look
+ // for unwind info in it before returning.
+
+ var result: Module = .{
+ .text_base = text_base,
+ .load_offset = load_offset,
+ .name = mem.span(std.c._dyld_get_image_name(@intCast(image_idx))),
+ .unwind_info = null,
+ .eh_frame = null,
};
+ for (text_sections) |sect| {
+ if (mem.eql(u8, sect.sectName(), "__unwind_info")) {
+ const sect_ptr: [*]u8 = @ptrFromInt(@as(usize, @intCast(load_offset + sect.addr)));
+ result.unwind_info = sect_ptr[0..@intCast(sect.size)];
+ } else if (mem.eql(u8, sect.sectName(), "__eh_frame")) {
+ const sect_ptr: [*]u8 = @ptrFromInt(@as(usize, @intCast(load_offset + sect.addr)));
+ result.eh_frame = sect_ptr[0..@intCast(sect.size)];
+ }
+ }
+ return result;
}
-
- const obj_di = try self.allocator.create(Module);
- errdefer self.allocator.destroy(obj_di);
- obj_di.* = try readElfDebugInfo(self.allocator, if (ctx.lookup.name.len > 0) ctx.lookup.name else null, ctx.lookup.build_id, §ions);
- obj_di.base_address = ctx.lookup.base_address;
-
- // Missing unwind info isn't treated as a failure, as the unwinder will fall back to FP-based unwinding
- obj_di.dwarf.scanAllUnwindInfo(self.allocator, ctx.lookup.base_address) catch {};
-
- try self.address_map.putNoClobber(self.allocator, ctx.lookup.base_address, obj_di);
-
- return obj_di;
+ return error.MissingDebugInfo;
}
-fn lookupModuleHaiku(self: *SelfInfo, address: usize) !*Module {
- _ = self;
- _ = address;
- @panic("TODO implement lookup module for Haiku");
-}
+fn lookupModuleWin32(self: *SelfInfo, gpa: Allocator, address: usize) !Module {
+ if (self.lookupModuleWin32Cache(address)) |m| return m;
-fn lookupModuleWasm(self: *SelfInfo, address: usize) !*Module {
- _ = self;
- _ = address;
- @panic("TODO implement lookup module for Wasm");
-}
+ {
+ // Check a new module hasn't been loaded
+ self.module_cache.clearRetainingCapacity();
-pub const Module = switch (native_os) {
- .macos, .ios, .watchos, .tvos, .visionos => struct {
- base_address: usize,
- vmaddr_slide: usize,
- mapped_memory: []align(std.heap.page_size_min) const u8,
- symbols: []const MachoSymbol,
- strings: [:0]const u8,
- ofiles: OFileTable,
-
- // Backed by the in-memory sections mapped by the loader
- unwind_info: ?[]const u8 = null,
- eh_frame: ?[]const u8 = null,
-
- const OFileTable = std.StringHashMap(OFileInfo);
- const OFileInfo = struct {
- di: Dwarf,
- addr_table: std.StringHashMap(u64),
- };
+ const handle = windows.kernel32.CreateToolhelp32Snapshot(windows.TH32CS_SNAPMODULE | windows.TH32CS_SNAPMODULE32, 0);
+ if (handle == windows.INVALID_HANDLE_VALUE) {
+ return windows.unexpectedError(windows.GetLastError());
+ }
+ defer windows.CloseHandle(handle);
- pub fn deinit(self: *@This(), allocator: Allocator) void {
- var it = self.ofiles.iterator();
- while (it.next()) |entry| {
- const ofile = entry.value_ptr;
- ofile.di.deinit(allocator);
- ofile.addr_table.deinit();
+ var entry: windows.MODULEENTRY32 = undefined;
+ entry.dwSize = @sizeOf(windows.MODULEENTRY32);
+ if (windows.kernel32.Module32First(handle, &entry) != 0) {
+ try self.module_cache.append(gpa, entry);
+ while (windows.kernel32.Module32Next(handle, &entry) != 0) {
+ try self.module_cache.append(gpa, entry);
}
- self.ofiles.deinit();
- allocator.free(self.symbols);
- posix.munmap(self.mapped_memory);
}
+ }
- fn loadOFile(self: *@This(), allocator: Allocator, o_file_path: []const u8) !*OFileInfo {
- const o_file = try fs.cwd().openFile(o_file_path, .{});
- const mapped_mem = try mapWholeFile(o_file);
-
- const hdr: *const macho.mach_header_64 = @ptrCast(@alignCast(mapped_mem.ptr));
- if (hdr.magic != std.macho.MH_MAGIC_64)
- return error.InvalidDebugInfo;
-
- var segcmd: ?macho.LoadCommandIterator.LoadCommand = null;
- var symtabcmd: ?macho.symtab_command = null;
- var it = macho.LoadCommandIterator{
- .ncmds = hdr.ncmds,
- .buffer = mapped_mem[@sizeOf(macho.mach_header_64)..][0..hdr.sizeofcmds],
- };
- while (it.next()) |cmd| switch (cmd.cmd()) {
- .SEGMENT_64 => segcmd = cmd,
- .SYMTAB => symtabcmd = cmd.cast(macho.symtab_command).?,
- else => {},
+ if (self.lookupModuleWin32Cache(address)) |m| return m;
+ return error.MissingDebugInfo;
+}
+fn lookupModuleWin32Cache(self: *SelfInfo, address: usize) ?Module {
+ for (self.module_cache.items) |*entry| {
+ const base_address = @intFromPtr(entry.modBaseAddr);
+ if (address >= base_address and address < base_address + entry.modBaseSize) {
+ return .{
+ .base_address = base_address,
+ .size = entry.modBaseSize,
+ .name = std.mem.sliceTo(&entry.szModule, 0),
+ .handle = entry.hModule,
};
+ }
+ }
+ return null;
+}
- if (segcmd == null or symtabcmd == null) return error.MissingDebugInfo;
-
- // Parse symbols
- const strtab = @as(
- [*]const u8,
- @ptrCast(&mapped_mem[symtabcmd.?.stroff]),
- )[0 .. symtabcmd.?.strsize - 1 :0];
- const symtab = @as(
- [*]const macho.nlist_64,
- @ptrCast(@alignCast(&mapped_mem[symtabcmd.?.symoff])),
- )[0..symtabcmd.?.nsyms];
-
- // TODO handle tentative (common) symbols
- var addr_table = std.StringHashMap(u64).init(allocator);
- try addr_table.ensureTotalCapacity(@as(u32, @intCast(symtab.len)));
- for (symtab) |sym| {
- if (sym.n_strx == 0) continue;
- if (sym.undf() or sym.tentative() or sym.abs()) continue;
- const sym_name = mem.sliceTo(strtab[sym.n_strx..], 0);
- // TODO is it possible to have a symbol collision?
- addr_table.putAssumeCapacityNoClobber(sym_name, sym.n_value);
- }
+fn readCoffDebugInfo(gpa: Allocator, module: *const Module, di: *Module.DebugInfo) !void {
+ const mapped_ptr: [*]const u8 = @ptrFromInt(module.base_address);
+ const mapped = mapped_ptr[0..module.size];
+ var coff_obj = coff.Coff.init(mapped, true) catch return error.InvalidDebugInfo;
+ // The string table is not mapped into memory by the loader, so if a section name is in the
+ // string table then we have to map the full image file from disk. This can happen when
+ // a binary is produced with -gdwarf, since the section names are longer than 8 bytes.
+ if (coff_obj.strtabRequired()) {
+ var name_buffer: [windows.PATH_MAX_WIDE + 4:0]u16 = undefined;
+ name_buffer[0..4].* = .{ '\\', '?', '?', '\\' }; // openFileAbsoluteW requires the prefix to be present
+ const process_handle = windows.GetCurrentProcess();
+ const len = windows.kernel32.GetModuleFileNameExW(
+ process_handle,
+ module.handle,
+ name_buffer[4..],
+ windows.PATH_MAX_WIDE,
+ );
+ if (len == 0) return error.MissingDebugInfo;
+ const coff_file = fs.openFileAbsoluteW(name_buffer[0 .. len + 4 :0], .{}) catch |err| switch (err) {
+ error.FileNotFound => return error.MissingDebugInfo,
+ else => |e| return e,
+ };
+ errdefer coff_file.close();
+ var section_handle: windows.HANDLE = undefined;
+ const create_section_rc = windows.ntdll.NtCreateSection(
+ §ion_handle,
+ windows.STANDARD_RIGHTS_REQUIRED | windows.SECTION_QUERY | windows.SECTION_MAP_READ,
+ null,
+ null,
+ windows.PAGE_READONLY,
+ // The documentation states that if no AllocationAttribute is specified, then SEC_COMMIT is the default.
+ // In practice, this isn't the case and specifying 0 will result in INVALID_PARAMETER_6.
+ windows.SEC_COMMIT,
+ coff_file.handle,
+ );
+ if (create_section_rc != .SUCCESS) return error.MissingDebugInfo;
+ errdefer windows.CloseHandle(section_handle);
+ var coff_len: usize = 0;
+ var section_view_ptr: [*]const u8 = undefined;
+ const map_section_rc = windows.ntdll.NtMapViewOfSection(
+ section_handle,
+ process_handle,
+ @ptrCast(§ion_view_ptr),
+ null,
+ 0,
+ null,
+ &coff_len,
+ .ViewUnmap,
+ 0,
+ windows.PAGE_READONLY,
+ );
+ if (map_section_rc != .SUCCESS) return error.MissingDebugInfo;
+ errdefer assert(windows.ntdll.NtUnmapViewOfSection(process_handle, @constCast(section_view_ptr)) == .SUCCESS);
+ const section_view = section_view_ptr[0..coff_len];
+ coff_obj = coff.Coff.init(section_view, false) catch return error.InvalidDebugInfo;
+ di.mapped_file = .{
+ .file = coff_file,
+ .section_handle = section_handle,
+ .section_view = section_view,
+ };
+ }
+ di.coff_image_base = coff_obj.getImageBase();
- var sections: Dwarf.SectionArray = Dwarf.null_section_array;
- if (self.eh_frame) |eh_frame| sections[@intFromEnum(Dwarf.Section.Id.eh_frame)] = .{
- .data = eh_frame,
- .owned = false,
- };
+ if (coff_obj.getSectionByName(".debug_info")) |_| {
+ di.dwarf = .{};
- for (segcmd.?.getSections()) |sect| {
- if (!std.mem.eql(u8, "__DWARF", sect.segName())) continue;
+ inline for (@typeInfo(Dwarf.Section.Id).@"enum".fields, 0..) |section, i| {
+ di.dwarf.?.sections[i] = if (coff_obj.getSectionByName("." ++ section.name)) |section_header| blk: {
+ break :blk .{
+ .data = try coff_obj.getSectionDataAlloc(section_header, gpa),
+ .virtual_address = section_header.virtual_address,
+ .owned = true,
+ };
+ } else null;
+ }
- var section_index: ?usize = null;
- inline for (@typeInfo(Dwarf.Section.Id).@"enum".fields, 0..) |section, i| {
- if (mem.eql(u8, "__" ++ section.name, sect.sectName())) section_index = i;
- }
- if (section_index == null) continue;
+ try di.dwarf.?.open(gpa, native_endian);
+ }
- const section_bytes = try Dwarf.chopSlice(mapped_mem, sect.offset, sect.size);
- sections[section_index.?] = .{
- .data = section_bytes,
- .virtual_address = @intCast(sect.addr),
- .owned = false,
- };
+ if (try coff_obj.getPdbPath()) |raw_path| pdb: {
+ const path = blk: {
+ if (fs.path.isAbsolute(raw_path)) {
+ break :blk raw_path;
+ } else {
+ const self_dir = try fs.selfExeDirPathAlloc(gpa);
+ defer gpa.free(self_dir);
+ break :blk try fs.path.join(gpa, &.{ self_dir, raw_path });
}
+ };
+ defer if (path.ptr != raw_path.ptr) gpa.free(path);
- const missing_debug_info =
- sections[@intFromEnum(Dwarf.Section.Id.debug_info)] == null or
- sections[@intFromEnum(Dwarf.Section.Id.debug_abbrev)] == null or
- sections[@intFromEnum(Dwarf.Section.Id.debug_str)] == null or
- sections[@intFromEnum(Dwarf.Section.Id.debug_line)] == null;
- if (missing_debug_info) return error.MissingDebugInfo;
-
- var di: Dwarf = .{
- .endian = .little,
- .sections = sections,
- .is_macho = true,
- };
+ di.pdb = Pdb.init(gpa, path) catch |err| switch (err) {
+ error.FileNotFound, error.IsDir => break :pdb,
+ else => return err,
+ };
+ try di.pdb.?.parseInfoStream();
+ try di.pdb.?.parseDbiStream();
- try Dwarf.open(&di, allocator);
- const info = OFileInfo{
- .di = di,
- .addr_table = addr_table,
- };
+ if (!mem.eql(u8, &coff_obj.guid, &di.pdb.?.guid) or coff_obj.age != di.pdb.?.age)
+ return error.InvalidDebugInfo;
- // Add the debug info to the cache
- const result = try self.ofiles.getOrPut(o_file_path);
- assert(!result.found_existing);
- result.value_ptr.* = info;
+ di.coff_section_headers = try coff_obj.getSectionHeadersAlloc(gpa);
+ }
+}
- return result.value_ptr;
+const Module = switch (native_os) {
+ else => "MLUGG TODO", // Dwarf, // TODO MLUGG: it's this on master but that's definitely broken atm...
+ .macos, .ios, .watchos, .tvos, .visionos => struct {
+ /// The runtime address where __TEXT is loaded.
+ text_base: usize,
+ load_offset: usize,
+ name: []const u8,
+ unwind_info: ?[]const u8,
+ eh_frame: ?[]const u8,
+ fn key(m: *const Module) usize {
+ return m.text_base;
}
+ fn getSymbolAtAddress(module: *const Module, gpa: Allocator, di: *DebugInfo, address: usize) !std.debug.Symbol {
+ const vaddr = address - module.load_offset;
+ const symbol = MachoSymbol.find(di.symbols, vaddr) orelse return .{}; // MLUGG TODO null?
- pub fn getSymbolAtAddress(self: *@This(), allocator: Allocator, address: usize) !std.debug.Symbol {
- const result = try self.getOFileInfoForAddress(allocator, address);
- if (result.symbol == null) return .{};
+ // offset of `address` from start of `symbol`
+ const address_symbol_offset = vaddr - symbol.addr;
// Take the symbol name from the N_FUN STAB entry, we're going to
// use it if we fail to find the DWARF infos
- const stab_symbol = mem.sliceTo(self.strings[result.symbol.?.strx..], 0);
- if (result.o_file_info == null) return .{ .name = stab_symbol };
-
- // Translate again the address, this time into an address inside the
- // .o file
- const relocated_address_o = result.o_file_info.?.addr_table.get(stab_symbol) orelse return .{
- .name = "???",
+ const stab_symbol = mem.sliceTo(di.strings[symbol.strx..], 0);
+ const o_file_path = mem.sliceTo(di.strings[symbol.ofile..], 0);
+
+ const o_file: *DebugInfo.OFile = of: {
+ const gop = try di.ofiles.getOrPut(gpa, o_file_path);
+ if (!gop.found_existing) {
+ gop.value_ptr.* = DebugInfo.loadOFile(gpa, o_file_path) catch |err| {
+ defer _ = di.ofiles.pop().?;
+ switch (err) {
+ error.FileNotFound,
+ error.MissingDebugInfo,
+ error.InvalidDebugInfo,
+ => return .{ .name = stab_symbol },
+ else => |e| return e,
+ }
+ };
+ }
+ break :of gop.value_ptr;
};
- const addr_off = result.relocated_address - result.symbol.?.addr;
- const o_file_di = &result.o_file_info.?.di;
- if (o_file_di.findCompileUnit(relocated_address_o)) |compile_unit| {
- return .{
- .name = o_file_di.getSymbolName(relocated_address_o) orelse "???",
- .compile_unit_name = compile_unit.die.getAttrString(
- o_file_di,
- std.dwarf.AT.name,
- o_file_di.section(.debug_str),
- compile_unit.*,
- ) catch |err| switch (err) {
- error.MissingDebugInfo, error.InvalidDebugInfo => "???",
- },
- .source_location = o_file_di.getLineNumberInfo(
- allocator,
- compile_unit,
- relocated_address_o + addr_off,
- ) catch |err| switch (err) {
- error.MissingDebugInfo, error.InvalidDebugInfo => null,
- else => return err,
- },
- };
- } else |err| switch (err) {
- error.MissingDebugInfo, error.InvalidDebugInfo => {
- return .{ .name = stab_symbol };
- },
- else => return err,
- }
- }
+ const symbol_ofile_vaddr = o_file.addr_table.get(stab_symbol) orelse return .{ .name = stab_symbol };
- pub fn getOFileInfoForAddress(self: *@This(), allocator: Allocator, address: usize) !struct {
- relocated_address: usize,
- symbol: ?*const MachoSymbol = null,
- o_file_info: ?*OFileInfo = null,
- } {
- // Translate the VA into an address into this object
- const relocated_address = address - self.vmaddr_slide;
-
- // Find the .o file where this symbol is defined
- const symbol = machoSearchSymbols(self.symbols, relocated_address) orelse return .{
- .relocated_address = relocated_address,
+ const compile_unit = o_file.dwarf.findCompileUnit(native_endian, symbol_ofile_vaddr) catch |err| switch (err) {
+ error.MissingDebugInfo, error.InvalidDebugInfo => return .{ .name = stab_symbol },
+ else => |e| return e,
};
- // Check if its debug infos are already in the cache
- const o_file_path = mem.sliceTo(self.strings[symbol.ofile..], 0);
- const o_file_info = self.ofiles.getPtr(o_file_path) orelse
- (self.loadOFile(allocator, o_file_path) catch |err| switch (err) {
- error.FileNotFound,
- error.MissingDebugInfo,
- error.InvalidDebugInfo,
- => return .{
- .relocated_address = relocated_address,
- .symbol = symbol,
- },
- else => return err,
- });
-
return .{
- .relocated_address = relocated_address,
- .symbol = symbol,
- .o_file_info = o_file_info,
+ .name = o_file.dwarf.getSymbolName(symbol_ofile_vaddr) orelse stab_symbol,
+ .compile_unit_name = compile_unit.die.getAttrString(
+ &o_file.dwarf,
+ native_endian,
+ std.dwarf.AT.name,
+ o_file.dwarf.section(.debug_str),
+ compile_unit,
+ ) catch |err| switch (err) {
+ error.MissingDebugInfo, error.InvalidDebugInfo => "???",
+ },
+ .source_location = o_file.dwarf.getLineNumberInfo(
+ gpa,
+ native_endian,
+ compile_unit,
+ symbol_ofile_vaddr + address_symbol_offset,
+ ) catch |err| switch (err) {
+ error.MissingDebugInfo, error.InvalidDebugInfo => null,
+ else => return err,
+ },
};
}
+ const DebugInfo = struct {
+ // MLUGG TODO: these are duplicated state. i actually reckon they should be removed from Module, and loadMachODebugInfo should be the one discovering them!
+ mapped_memory: []align(std.heap.page_size_min) const u8,
+ symbols: []const MachoSymbol,
+ strings: [:0]const u8,
+ // MLUGG TODO: this could use an adapter to just index straight into `strings`!
+ ofiles: std.StringArrayHashMapUnmanaged(OFile),
+
+ // Backed by the in-memory sections mapped by the loader
+ unwind_info: ?[]const u8,
+ eh_frame: ?[]const u8,
+
+ // MLUGG TODO HACKHACK: this is awful
+ const init: DebugInfo = undefined;
+
+ const OFile = struct {
+ dwarf: Dwarf,
+ // MLUGG TODO: this could use an adapter to just index straight into the strtab!
+ addr_table: std.StringArrayHashMapUnmanaged(u64),
+ };
- pub fn getDwarfInfoForAddress(self: *@This(), allocator: Allocator, address: usize) !?*Dwarf {
- return if ((try self.getOFileInfoForAddress(allocator, address)).o_file_info) |o_file_info| &o_file_info.di else null;
- }
- },
- .uefi, .windows => struct {
- base_address: usize,
- pdb: ?Pdb,
- dwarf: ?Dwarf,
- coff_image_base: u64,
-
- /// Only used if pdb is non-null
- coff_section_headers: []coff.SectionHeader,
-
- pub fn deinit(self: *@This(), gpa: Allocator) void {
- if (self.dwarf) |*dwarf| {
- dwarf.deinit(gpa);
- }
-
- if (self.pdb) |*p| {
- gpa.free(p.file_reader.interface.buffer);
- gpa.destroy(p.file_reader);
- p.deinit();
- gpa.free(self.coff_section_headers);
+ fn deinit(di: *DebugInfo, gpa: Allocator) void {
+ for (di.ofiles.values()) |*ofile| {
+ ofile.dwarf.deinit(gpa);
+ ofile.addr_table.deinit(gpa);
+ }
+ di.ofiles.deinit();
+ gpa.free(di.symbols);
+ posix.munmap(di.mapped_memory);
}
- self.* = undefined;
- }
+ fn loadOFile(gpa: Allocator, o_file_path: []const u8) !OFile {
+ const mapped_mem = try mapFileOrSelfExe(o_file_path);
+ errdefer posix.munmap(mapped_mem);
- fn getSymbolFromPdb(self: *@This(), relocated_address: usize) !?std.debug.Symbol {
- var coff_section: *align(1) const coff.SectionHeader = undefined;
- const mod_index = for (self.pdb.?.sect_contribs) |sect_contrib| {
- if (sect_contrib.section > self.coff_section_headers.len) continue;
- // Remember that SectionContribEntry.Section is 1-based.
- coff_section = &self.coff_section_headers[sect_contrib.section - 1];
-
- const vaddr_start = coff_section.virtual_address + sect_contrib.offset;
- const vaddr_end = vaddr_start + sect_contrib.size;
- if (relocated_address >= vaddr_start and relocated_address < vaddr_end) {
- break sect_contrib.module_index;
- }
- } else {
- // we have no information to add to the address
- return null;
- };
+ if (mapped_mem.len < @sizeOf(macho.mach_header_64)) return error.InvalidDebugInfo;
+ const hdr: *const macho.mach_header_64 = @ptrCast(@alignCast(mapped_mem.ptr));
+ if (hdr.magic != std.macho.MH_MAGIC_64) return error.InvalidDebugInfo;
- const module = (try self.pdb.?.getModule(mod_index)) orelse
- return error.InvalidDebugInfo;
- const obj_basename = fs.path.basename(module.obj_file_name);
-
- const symbol_name = self.pdb.?.getSymbolName(
- module,
- relocated_address - coff_section.virtual_address,
- ) orelse "???";
- const opt_line_info = try self.pdb.?.getLineNumberInfo(
- module,
- relocated_address - coff_section.virtual_address,
- );
+ const seg_cmd: macho.LoadCommandIterator.LoadCommand, const symtab_cmd: macho.symtab_command = cmds: {
+ var seg_cmd: ?macho.LoadCommandIterator.LoadCommand = null;
+ var symtab_cmd: ?macho.symtab_command = null;
+ var it: macho.LoadCommandIterator = .{
+ .ncmds = hdr.ncmds,
+ .buffer = mapped_mem[@sizeOf(macho.mach_header_64)..][0..hdr.sizeofcmds],
+ };
+ while (it.next()) |cmd| switch (cmd.cmd()) {
+ .SEGMENT_64 => seg_cmd = cmd,
+ .SYMTAB => symtab_cmd = cmd.cast(macho.symtab_command) orelse return error.InvalidDebugInfo,
+ else => {},
+ };
+ break :cmds .{
+ seg_cmd orelse return error.MissingDebugInfo,
+ symtab_cmd orelse return error.MissingDebugInfo,
+ };
+ };
- return .{
- .name = symbol_name,
- .compile_unit_name = obj_basename,
- .source_location = opt_line_info,
- };
- }
+ if (mapped_mem.len < symtab_cmd.stroff + symtab_cmd.strsize) return error.InvalidDebugInfo;
+ if (mapped_mem[symtab_cmd.stroff + symtab_cmd.strsize - 1] != 0) return error.InvalidDebugInfo;
+ const strtab = mapped_mem[symtab_cmd.stroff..][0 .. symtab_cmd.strsize - 1];
+
+ const n_sym_bytes = symtab_cmd.nsyms * @sizeOf(macho.nlist_64);
+ if (mapped_mem.len < symtab_cmd.symoff + n_sym_bytes) return error.InvalidDebugInfo;
+ const symtab: []align(1) const macho.nlist_64 = @ptrCast(mapped_mem[symtab_cmd.symoff..][0..n_sym_bytes]);
+
+ // TODO handle tentative (common) symbols
+ // MLUGG TODO: does initCapacity actually make sense?
+ var addr_table: std.StringArrayHashMapUnmanaged(u64) = .empty;
+ defer addr_table.deinit(gpa);
+ try addr_table.ensureUnusedCapacity(gpa, @intCast(symtab.len));
+ for (symtab) |sym| {
+ if (sym.n_strx == 0) continue;
+ switch (sym.n_type.bits.type) {
+ .undf => continue, // includes tentative symbols
+ .abs => continue,
+ else => {},
+ }
+ const sym_name = mem.sliceTo(strtab[sym.n_strx..], 0);
+ const gop = addr_table.getOrPutAssumeCapacity(sym_name);
+ if (gop.found_existing) return error.InvalidDebugInfo;
+ gop.value_ptr.* = sym.n_value;
+ }
- pub fn getSymbolAtAddress(self: *@This(), allocator: Allocator, address: usize) !std.debug.Symbol {
- // Translate the VA into an address into this object
- const relocated_address = address - self.base_address;
+ var sections: Dwarf.SectionArray = @splat(null);
+ for (seg_cmd.getSections()) |sect| {
+ if (!std.mem.eql(u8, "__DWARF", sect.segName())) continue;
- if (self.pdb != null) {
- if (try self.getSymbolFromPdb(relocated_address)) |symbol| return symbol;
- }
+ const section_index: usize = inline for (@typeInfo(Dwarf.Section.Id).@"enum".fields, 0..) |section, i| {
+ if (mem.eql(u8, "__" ++ section.name, sect.sectName())) break i;
+ } else continue;
- if (self.dwarf) |*dwarf| {
- const dwarf_address = relocated_address + self.coff_image_base;
- return dwarf.getSymbol(allocator, dwarf_address);
- }
+ const section_bytes = try Dwarf.chopSlice(mapped_mem, sect.offset, sect.size);
+ sections[section_index] = .{
+ .data = section_bytes,
+ .virtual_address = @intCast(sect.addr),
+ .owned = false,
+ };
+ }
- return .{};
- }
+ const missing_debug_info =
+ sections[@intFromEnum(Dwarf.Section.Id.debug_info)] == null or
+ sections[@intFromEnum(Dwarf.Section.Id.debug_abbrev)] == null or
+ sections[@intFromEnum(Dwarf.Section.Id.debug_str)] == null or
+ sections[@intFromEnum(Dwarf.Section.Id.debug_line)] == null;
+ if (missing_debug_info) return error.MissingDebugInfo;
- pub fn getDwarfInfoForAddress(self: *@This(), allocator: Allocator, address: usize) !?*Dwarf {
- _ = allocator;
- _ = address;
+ var dwarf: Dwarf = .{ .sections = sections };
+ errdefer dwarf.deinit(gpa);
+ try dwarf.open(gpa, native_endian);
- return switch (self.debug_data) {
- .dwarf => |*dwarf| dwarf,
- else => null,
- };
- }
+ return .{
+ .dwarf = dwarf,
+ .addr_table = addr_table.move(),
+ };
+ }
+ };
},
- .linux, .netbsd, .freebsd, .dragonfly, .openbsd, .haiku, .solaris, .illumos => Dwarf.ElfModule,
.wasi, .emscripten => struct {
- pub fn deinit(self: *@This(), allocator: Allocator) void {
- _ = self;
- _ = allocator;
- }
-
- pub fn getSymbolAtAddress(self: *@This(), allocator: Allocator, address: usize) !std.debug.Symbol {
- _ = self;
- _ = allocator;
- _ = address;
- return .{};
+ const DebugInfo = struct {
+ const init: DebugInfo = .{};
+ fn getSymbolAtAddress(di: *DebugInfo, gpa: Allocator, base_address: usize, address: usize) !std.debug.Symbol {
+ _ = di;
+ _ = gpa;
+ _ = base_address;
+ _ = address;
+ unreachable;
+ }
+ };
+ },
+ .linux, .netbsd, .freebsd, .dragonfly, .openbsd, .haiku, .solaris, .illumos => struct {
+ load_offset: usize,
+ name: []const u8,
+ build_id: ?[]const u8,
+ gnu_eh_frame: ?[]const u8,
+ fn key(m: Module) usize {
+ return m.load_offset; // MLUGG TODO: is this technically valid? idk
}
-
- pub fn getDwarfInfoForAddress(self: *@This(), allocator: Allocator, address: usize) !?*Dwarf {
- _ = self;
- _ = allocator;
- _ = address;
- return null;
+ const DebugInfo = Dwarf.ElfModule;
+ fn getSymbolAtAddress(mod: *const Module, gpa: Allocator, di: *DebugInfo, address: usize) !std.debug.Symbol {
+ return di.getSymbolAtAddress(gpa, native_endian, mod.load_offset, address);
}
},
- else => Dwarf,
-};
+ .uefi, .windows => struct {
+ base_address: usize,
+ size: usize,
+ name: []const u8,
+ handle: windows.HMODULE,
+ fn key(m: Module) usize {
+ return m.base_address;
+ }
+ const DebugInfo = struct {
+ coff_image_base: u64,
+ mapped_file: ?struct {
+ file: File,
+ section_handle: windows.HANDLE,
+ section_view: []const u8,
+ fn deinit(mapped: @This()) void {
+ const process_handle = windows.GetCurrentProcess();
+ assert(windows.ntdll.NtUnmapViewOfSection(process_handle, @constCast(mapped.section_view.ptr)) == .SUCCESS);
+ windows.CloseHandle(mapped.section_handle);
+ mapped.file.close();
+ }
+ },
-/// How is this different than `Module` when the host is Windows?
-/// Why are both stored in the `SelfInfo` struct?
-/// Boy, it sure would be nice if someone added documentation comments for this
-/// struct explaining it.
-pub const WindowsModule = struct {
- base_address: usize,
- size: u32,
- name: []const u8,
- handle: windows.HMODULE,
-
- // Set when the image file needed to be mapped from disk
- mapped_file: ?struct {
- file: File,
- section_handle: windows.HANDLE,
- section_view: []const u8,
-
- pub fn deinit(self: @This()) void {
- const process_handle = windows.GetCurrentProcess();
- assert(windows.ntdll.NtUnmapViewOfSection(process_handle, @ptrCast(@constCast(self.section_view.ptr))) == .SUCCESS);
- windows.CloseHandle(self.section_handle);
- self.file.close();
+ dwarf: ?Dwarf,
+
+ pdb: ?Pdb,
+ /// Populated iff `pdb != null`; otherwise `&.{}`.
+ coff_section_headers: []coff.SectionHeader,
+
+ const init: DebugInfo = .{
+ .coff_image_base = undefined,
+ .mapped_file = null,
+ .dwarf = null,
+ .pdb = null,
+ .coff_section_headers = &.{},
+ };
+
+ fn deinit(di: *DebugInfo, gpa: Allocator) void {
+ if (di.dwarf) |*dwarf| dwarf.deinit(gpa);
+ if (di.pdb) |*pdb| pdb.deinit();
+ gpa.free(di.coff_section_headers);
+ if (di.mapped_file) |mapped| mapped.deinit();
+ }
+
+ fn getSymbolFromPdb(di: *DebugInfo, relocated_address: usize) !?std.debug.Symbol {
+ var coff_section: *align(1) const coff.SectionHeader = undefined;
+ const mod_index = for (di.pdb.?.sect_contribs) |sect_contrib| {
+ if (sect_contrib.section > di.coff_section_headers.len) continue;
+ // Remember that SectionContribEntry.Section is 1-based.
+ coff_section = &di.coff_section_headers[sect_contrib.section - 1];
+
+ const vaddr_start = coff_section.virtual_address + sect_contrib.offset;
+ const vaddr_end = vaddr_start + sect_contrib.size;
+ if (relocated_address >= vaddr_start and relocated_address < vaddr_end) {
+ break sect_contrib.module_index;
+ }
+ } else {
+ // we have no information to add to the address
+ return null;
+ };
+
+ const module = (try di.pdb.?.getModule(mod_index)) orelse
+ return error.InvalidDebugInfo;
+ const obj_basename = fs.path.basename(module.obj_file_name);
+
+ const symbol_name = di.pdb.?.getSymbolName(
+ module,
+ relocated_address - coff_section.virtual_address,
+ ) orelse "???";
+ const opt_line_info = try di.pdb.?.getLineNumberInfo(
+ module,
+ relocated_address - coff_section.virtual_address,
+ );
+
+ return .{
+ .name = symbol_name,
+ .compile_unit_name = obj_basename,
+ .source_location = opt_line_info,
+ };
+ }
+ };
+
+ fn getSymbolAtAddress(mod: *const Module, gpa: Allocator, di: *DebugInfo, address: usize) !std.debug.Symbol {
+ // Translate the runtime address into a virtual address into the module
+ const vaddr = address - mod.base_address;
+
+ if (di.pdb != null) {
+ if (try di.getSymbolFromPdb(vaddr)) |symbol| return symbol;
+ }
+
+ if (di.dwarf) |*dwarf| {
+ const dwarf_address = vaddr + di.coff_image_base;
+ return dwarf.getSymbol(gpa, native_endian, dwarf_address);
+ }
+
+ return error.MissingDebugInfo;
}
- } = null,
+ },
};
-/// This takes ownership of macho_file: users of this function should not close
-/// it themselves, even on error.
-/// TODO it's weird to take ownership even on error, rework this code.
-fn readMachODebugInfo(allocator: Allocator, macho_file: File) !Module {
- const mapped_mem = try mapWholeFile(macho_file);
+fn loadMachODebugInfo(gpa: Allocator, module: *const Module, di: *Module.DebugInfo) !void {
+ const mapped_mem = mapFileOrSelfExe(module.name) catch |err| switch (err) {
+ error.FileNotFound => return error.MissingDebugInfo,
+ error.FileTooBig => return error.InvalidDebugInfo,
+ else => |e| return e,
+ };
+ errdefer posix.munmap(mapped_mem);
const hdr: *const macho.mach_header_64 = @ptrCast(@alignCast(mapped_mem.ptr));
if (hdr.magic != macho.MH_MAGIC_64)
return error.InvalidDebugInfo;
- var it = macho.LoadCommandIterator{
- .ncmds = hdr.ncmds,
- .buffer = mapped_mem[@sizeOf(macho.mach_header_64)..][0..hdr.sizeofcmds],
+ const symtab: macho.symtab_command = symtab: {
+ var it: macho.LoadCommandIterator = .{
+ .ncmds = hdr.ncmds,
+ .buffer = mapped_mem[@sizeOf(macho.mach_header_64)..][0..hdr.sizeofcmds],
+ };
+ while (it.next()) |cmd| switch (cmd.cmd()) {
+ .SYMTAB => break :symtab cmd.cast(macho.symtab_command) orelse return error.InvalidDebugInfo,
+ else => {},
+ };
+ return error.MissingDebugInfo;
};
- const symtab = while (it.next()) |cmd| switch (cmd.cmd()) {
- .SYMTAB => break cmd.cast(macho.symtab_command).?,
- else => {},
- } else return error.MissingDebugInfo;
-
- const syms = @as(
- [*]const macho.nlist_64,
- @ptrCast(@alignCast(&mapped_mem[symtab.symoff])),
- )[0..symtab.nsyms];
+
+ const syms_ptr: [*]align(1) const macho.nlist_64 = @ptrCast(mapped_mem[symtab.symoff..]);
+ const syms = syms_ptr[0..symtab.nsyms];
const strings = mapped_mem[symtab.stroff..][0 .. symtab.strsize - 1 :0];
- const symbols_buf = try allocator.alloc(MachoSymbol, syms.len);
+ // MLUGG TODO: does it really make sense to initCapacity here? how many of syms are omitted?
+ var symbols: std.ArrayList(MachoSymbol) = try .initCapacity(gpa, syms.len);
+ defer symbols.deinit(gpa);
var ofile: u32 = undefined;
var last_sym: MachoSymbol = undefined;
- var symbol_index: usize = 0;
var state: enum {
init,
oso_open,
@@ -929,64 +810,53 @@ fn readMachODebugInfo(allocator: Allocator, macho_file: File) !Module {
} = .init;
for (syms) |*sym| {
- if (!sym.stab()) continue;
+ if (sym.n_type.bits.is_stab == 0) continue;
// TODO handle globals N_GSYM, and statics N_STSYM
- switch (sym.n_type) {
- macho.N_OSO => {
- switch (state) {
- .init, .oso_close => {
- state = .oso_open;
- ofile = sym.n_strx;
- },
- else => return error.InvalidDebugInfo,
- }
+ switch (sym.n_type.stab) {
+ .oso => switch (state) {
+ .init, .oso_close => {
+ state = .oso_open;
+ ofile = sym.n_strx;
+ },
+ else => return error.InvalidDebugInfo,
},
- macho.N_BNSYM => {
- switch (state) {
- .oso_open, .ensym => {
- state = .bnsym;
- last_sym = .{
- .strx = 0,
- .addr = sym.n_value,
- .size = 0,
- .ofile = ofile,
- };
- },
- else => return error.InvalidDebugInfo,
- }
+ .bnsym => switch (state) {
+ .oso_open, .ensym => {
+ state = .bnsym;
+ last_sym = .{
+ .strx = 0,
+ .addr = sym.n_value,
+ .size = 0,
+ .ofile = ofile,
+ };
+ },
+ else => return error.InvalidDebugInfo,
},
- macho.N_FUN => {
- switch (state) {
- .bnsym => {
- state = .fun_strx;
- last_sym.strx = sym.n_strx;
- },
- .fun_strx => {
- state = .fun_size;
- last_sym.size = @as(u32, @intCast(sym.n_value));
- },
- else => return error.InvalidDebugInfo,
- }
+ .fun => switch (state) {
+ .bnsym => {
+ state = .fun_strx;
+ last_sym.strx = sym.n_strx;
+ },
+ .fun_strx => {
+ state = .fun_size;
+ last_sym.size = @intCast(sym.n_value);
+ },
+ else => return error.InvalidDebugInfo,
},
- macho.N_ENSYM => {
- switch (state) {
- .fun_size => {
- state = .ensym;
- symbols_buf[symbol_index] = last_sym;
- symbol_index += 1;
- },
- else => return error.InvalidDebugInfo,
- }
+ .ensym => switch (state) {
+ .fun_size => {
+ state = .ensym;
+ symbols.appendAssumeCapacity(last_sym);
+ },
+ else => return error.InvalidDebugInfo,
},
- macho.N_SO => {
- switch (state) {
- .init, .oso_close => {},
- .oso_open, .ensym => {
- state = .oso_close;
- },
- else => return error.InvalidDebugInfo,
- }
+ .so => switch (state) {
+ .init, .oso_close => {},
+ .oso_open, .ensym => {
+ state = .oso_close;
+ },
+ else => return error.InvalidDebugInfo,
},
else => {},
}
@@ -998,560 +868,187 @@ fn readMachODebugInfo(allocator: Allocator, macho_file: File) !Module {
else => return error.InvalidDebugInfo,
}
- const symbols = try allocator.realloc(symbols_buf, symbol_index);
+ const symbols_slice = try symbols.toOwnedSlice(gpa);
+ errdefer gpa.free(symbols_slice);
// Even though lld emits symbols in ascending order, this debug code
// should work for programs linked in any valid way.
// This sort is so that we can binary search later.
- mem.sort(MachoSymbol, symbols, {}, MachoSymbol.addressLessThan);
+ mem.sort(MachoSymbol, symbols_slice, {}, MachoSymbol.addressLessThan);
- return .{
- .base_address = undefined,
- .vmaddr_slide = undefined,
+ di.* = .{
+ .unwind_info = module.unwind_info,
+ .eh_frame = module.eh_frame,
.mapped_memory = mapped_mem,
- .ofiles = Module.OFileTable.init(allocator),
- .symbols = symbols,
+ .symbols = symbols_slice,
.strings = strings,
+ .ofiles = .empty,
};
}
-fn readCoffDebugInfo(allocator: Allocator, coff_obj: *coff.Coff) !Module {
- var di: Module = .{
- .base_address = undefined,
- .coff_image_base = coff_obj.getImageBase(),
- .coff_section_headers = undefined,
- };
-
- if (coff_obj.getSectionByName(".debug_info")) |_| {
- // This coff file has embedded DWARF debug info
- var sections: Dwarf.SectionArray = Dwarf.null_section_array;
- errdefer for (sections) |section| if (section) |s| if (s.owned) allocator.free(s.data);
-
- inline for (@typeInfo(Dwarf.Section.Id).@"enum".fields, 0..) |section, i| {
- sections[i] = if (coff_obj.getSectionByName("." ++ section.name)) |section_header| blk: {
- break :blk .{
- .data = try coff_obj.getSectionDataAlloc(section_header, allocator),
- .virtual_address = section_header.virtual_address,
- .owned = true,
- };
- } else null;
- }
-
- var dwarf: Dwarf = .{
- .endian = native_endian,
- .sections = sections,
- .is_macho = false,
- };
-
- try Dwarf.open(&dwarf, allocator);
- di.dwarf = dwarf;
- }
-
- const raw_path = try coff_obj.getPdbPath() orelse return di;
- const path = blk: {
- if (fs.path.isAbsolute(raw_path)) {
- break :blk raw_path;
- } else {
- const self_dir = try fs.selfExeDirPathAlloc(allocator);
- defer allocator.free(self_dir);
- break :blk try fs.path.join(allocator, &.{ self_dir, raw_path });
- }
- };
- defer if (path.ptr != raw_path.ptr) allocator.free(path);
-
- di.pdb = Pdb.init(allocator, path) catch |err| switch (err) {
- error.FileNotFound, error.IsDir => {
- if (di.dwarf == null) return error.MissingDebugInfo;
- return di;
- },
- else => return err,
- };
- try di.pdb.?.parseInfoStream();
- try di.pdb.?.parseDbiStream();
-
- if (!mem.eql(u8, &coff_obj.guid, &di.pdb.?.guid) or coff_obj.age != di.pdb.?.age)
- return error.InvalidDebugInfo;
-
- // Only used by the pdb path
- di.coff_section_headers = try coff_obj.getSectionHeadersAlloc(allocator);
- errdefer allocator.free(di.coff_section_headers);
-
- return di;
-}
-
-/// Reads debug info from an ELF file, or the current binary if none in specified.
-/// If the required sections aren't present but a reference to external debug info is,
-/// then this this function will recurse to attempt to load the debug sections from
-/// an external file.
-pub fn readElfDebugInfo(
- em: *Dwarf.ElfModule,
- allocator: Allocator,
- elf_filename: ?[]const u8,
- build_id: ?[]const u8,
- parent_sections: *Dwarf.SectionArray,
-) !void {
- const elf_file = (if (elf_filename) |filename| blk: {
- break :blk fs.cwd().openFile(filename, .{});
- } else fs.openSelfExe(.{})) catch |err| switch (err) {
- error.FileNotFound => return error.MissingDebugInfo,
- else => return err,
- };
-
- const mapped_mem = try mapWholeFile(elf_file);
- return em.load(
- allocator,
- mapped_mem,
- build_id,
- null,
- parent_sections,
- null,
- elf_filename,
- );
-}
-
const MachoSymbol = struct {
strx: u32,
addr: u64,
size: u32,
ofile: u32,
-
- /// Returns the address from the macho file
- fn address(self: MachoSymbol) u64 {
- return self.addr;
- }
-
fn addressLessThan(context: void, lhs: MachoSymbol, rhs: MachoSymbol) bool {
_ = context;
return lhs.addr < rhs.addr;
}
-};
-
-/// Takes ownership of file, even on error.
-/// TODO it's weird to take ownership even on error, rework this code.
-fn mapWholeFile(file: File) ![]align(std.heap.page_size_min) const u8 {
- defer file.close();
-
- const file_len = math.cast(usize, try file.getEndPos()) orelse math.maxInt(usize);
- const mapped_mem = try posix.mmap(
- null,
- file_len,
- posix.PROT.READ,
- .{ .TYPE = .SHARED },
- file.handle,
- 0,
- );
- errdefer posix.munmap(mapped_mem);
-
- return mapped_mem;
-}
-
-fn machoSearchSymbols(symbols: []const MachoSymbol, address: usize) ?*const MachoSymbol {
- var min: usize = 0;
- var max: usize = symbols.len - 1;
- while (min < max) {
- const mid = min + (max - min) / 2;
- const curr = &symbols[mid];
- const next = &symbols[mid + 1];
- if (address >= next.address()) {
- min = mid + 1;
- } else if (address < curr.address()) {
- max = mid;
- } else {
- return curr;
- }
- }
-
- const max_sym = &symbols[symbols.len - 1];
- if (address >= max_sym.address())
- return max_sym;
-
- return null;
-}
-
-test machoSearchSymbols {
- const symbols = [_]MachoSymbol{
- .{ .addr = 100, .strx = undefined, .size = undefined, .ofile = undefined },
- .{ .addr = 200, .strx = undefined, .size = undefined, .ofile = undefined },
- .{ .addr = 300, .strx = undefined, .size = undefined, .ofile = undefined },
- };
-
- try testing.expectEqual(null, machoSearchSymbols(&symbols, 0));
- try testing.expectEqual(null, machoSearchSymbols(&symbols, 99));
- try testing.expectEqual(&symbols[0], machoSearchSymbols(&symbols, 100).?);
- try testing.expectEqual(&symbols[0], machoSearchSymbols(&symbols, 150).?);
- try testing.expectEqual(&symbols[0], machoSearchSymbols(&symbols, 199).?);
-
- try testing.expectEqual(&symbols[1], machoSearchSymbols(&symbols, 200).?);
- try testing.expectEqual(&symbols[1], machoSearchSymbols(&symbols, 250).?);
- try testing.expectEqual(&symbols[1], machoSearchSymbols(&symbols, 299).?);
-
- try testing.expectEqual(&symbols[2], machoSearchSymbols(&symbols, 300).?);
- try testing.expectEqual(&symbols[2], machoSearchSymbols(&symbols, 301).?);
- try testing.expectEqual(&symbols[2], machoSearchSymbols(&symbols, 5000).?);
-}
-
-/// 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.
-fn unwindFrameMachO(
- allocator: Allocator,
- base_address: usize,
- context: *UnwindContext,
- unwind_info: []const u8,
- eh_frame: ?[]const u8,
-) !usize {
- const header = std.mem.bytesAsValue(
- macho.unwind_info_section_header,
- unwind_info[0..@sizeOf(macho.unwind_info_section_header)],
- );
- const indices = std.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 - base_address;
- const second_level_index = blk: {
+ /// Assumes that `symbols` is sorted in order of ascending `addr`.
+ fn find(symbols: []const MachoSymbol, address: usize) ?*const MachoSymbol {
+ if (symbols.len == 0) return null; // no potential match
+ if (address < symbols[0].addr) return null; // address is before the lowest-address symbol
var left: usize = 0;
- var len: usize = indices.len;
-
+ var len: usize = symbols.len;
while (len > 1) {
const mid = left + len / 2;
- const offset = indices[mid].functionOffset;
- if (mapped_pc < offset) {
+ if (address < symbols[mid].addr) {
len /= 2;
} else {
left = mid;
- if (mapped_pc == offset) break;
len -= len / 2;
}
}
+ return &symbols[left];
+ }
- // 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 = std.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 = std.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 = std.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 = std.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;
- }
- }
+ test find {
+ const symbols: []const MachoSymbol = &.{
+ .{ .addr = 100, .strx = undefined, .size = undefined, .ofile = undefined },
+ .{ .addr = 200, .strx = undefined, .size = undefined, .ofile = undefined },
+ .{ .addr = 300, .strx = undefined, .size = undefined, .ofile = undefined },
+ };
- break :blk .{
- .function_offset = entries[left].functionOffset,
- .raw_encoding = entries[left].encoding,
- };
- },
- .COMPRESSED => blk: {
- const page_header = std.mem.bytesAsValue(
- macho.unwind_info_compressed_second_level_page_header,
- unwind_info[start_offset..][0..@sizeOf(macho.unwind_info_compressed_second_level_page_header)],
- );
+ try testing.expectEqual(null, find(symbols, 0));
+ try testing.expectEqual(null, find(symbols, 99));
+ try testing.expectEqual(&symbols[0], find(symbols, 100).?);
+ try testing.expectEqual(&symbols[0], find(symbols, 150).?);
+ try testing.expectEqual(&symbols[0], find(symbols, 199).?);
- const entries = std.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;
+ try testing.expectEqual(&symbols[1], find(symbols, 200).?);
+ try testing.expectEqual(&symbols[1], find(symbols, 250).?);
+ try testing.expectEqual(&symbols[1], find(symbols, 299).?);
- 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;
- }
- }
+ try testing.expectEqual(&symbols[2], find(symbols, 300).?);
+ try testing.expectEqual(&symbols[2], find(symbols, 301).?);
+ try testing.expectEqual(&symbols[2], find(symbols, 5000).?);
+ }
+};
+test {
+ _ = MachoSymbol;
+}
- 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 math.sub(
- u8,
- entry.encodingIndex,
- math.cast(u8, header.commonEncodingsArrayCount) orelse return error.InvalidUnwindInfo,
- );
- const local_encodings = std.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,
- };
+pub const UnwindContext = struct {
+ gpa: Allocator,
+ cfa: ?usize,
+ pc: usize,
+ thread_context: *std.debug.ThreadContext,
+ reg_context: Dwarf.abi.RegisterContext,
+ vm: Dwarf.Unwind.VirtualMachine,
+ stack_machine: Dwarf.expression.StackMachine(.{ .call_frame_context = true }),
- if (entry.raw_encoding == 0) return error.NoUnwindInfo;
- const reg_context = Dwarf.abi.RegisterContext{
- .eh_frame = false,
- .is_macho = true,
- };
+ pub fn init(gpa: Allocator, thread_context: *std.debug.ThreadContext) !UnwindContext {
+ comptime assert(supports_unwinding);
- 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 pc = stripInstructionPtrAuthCode(
+ (try regValueNative(thread_context, ip_reg_num, null)).*,
+ );
- 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 context_copy = try gpa.create(std.debug.ThreadContext);
+ std.debug.copyContext(thread_context, context_copy);
- const fp = (try regValueNative(context.thread_context, fpRegNum(reg_context), reg_context)).*;
- const new_sp = fp + 2 * @sizeOf(usize);
+ return .{
+ .gpa = gpa,
+ .cfa = null,
+ .pc = pc,
+ .thread_context = context_copy,
+ .reg_context = undefined,
+ .vm = .{},
+ .stack_machine = .{},
+ };
+ }
- const ip_ptr = fp + @sizeOf(usize);
- const new_ip = @as(*const usize, @ptrFromInt(ip_ptr)).*;
- const new_fp = @as(*const usize, @ptrFromInt(fp)).*;
+ pub fn deinit(self: *UnwindContext) void {
+ self.vm.deinit(self.gpa);
+ self.stack_machine.deinit(self.gpa);
+ self.gpa.destroy(self.thread_context);
+ self.* = undefined;
+ }
- (try regValueNative(context.thread_context, fpRegNum(reg_context), reg_context)).* = new_fp;
- (try regValueNative(context.thread_context, spRegNum(reg_context), reg_context)).* = new_sp;
- (try regValueNative(context.thread_context, ip_reg_num, reg_context)).* = new_ip;
+ pub fn getFp(self: *const UnwindContext) !usize {
+ return (try regValueNative(self.thread_context, fpRegNum(self.reg_context), self.reg_context)).*;
+ }
- for (regs, 0..) |reg, i| {
- if (reg == 0) continue;
- const addr = fp - frame_offset + i * @sizeOf(usize);
- const reg_number = try Dwarf.compactUnwindToDwarfRegNumber(reg);
- (try regValueNative(context.thread_context, reg_number, reg_context)).* = @as(*const usize, @ptrFromInt(addr)).*;
+ /// Resolves the register rule and places the result into `out` (see regBytes)
+ pub fn resolveRegisterRule(
+ context: *UnwindContext,
+ col: Dwarf.Unwind.VirtualMachine.Column,
+ expression_context: std.debug.Dwarf.expression.Context,
+ out: []u8,
+ ) !void {
+ switch (col.rule) {
+ .default => {
+ const register = col.register orelse return error.InvalidRegister;
+ // The default type is usually undefined, but can be overriden by ABI authors.
+ // See the doc comment on `Dwarf.Unwind.VirtualMachine.RegisterRule.default`.
+ if (builtin.cpu.arch.isAARCH64() and register >= 19 and register <= 18) {
+ // Callee-saved registers are initialized as if they had the .same_value rule
+ const src = try regBytes(context.thread_context, register, context.reg_context);
+ if (src.len != out.len) return error.RegisterSizeMismatch;
+ @memcpy(out, src);
+ return;
}
-
- break :blk new_ip;
+ @memset(out, undefined);
},
- .STACK_IMMD,
- .STACK_IND,
- => blk: {
- const sp = (try regValueNative(context.thread_context, 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 =
- base_address +
- entry.function_offset +
- encoding.value.x86_64.frameless.stack.indirect.sub_offset;
-
- // `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);
- for (0..reg_count) |i| {
- const reg_number = try Dwarf.compactUnwindToDwarfRegNumber(registers[i]);
- (try regValueNative(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);
-
- (try regValueNative(context.thread_context, spRegNum(reg_context), reg_context)).* = new_sp;
- (try regValueNative(context.thread_context, ip_reg_num, reg_context)).* = new_ip;
-
- break :blk new_ip;
+ .undefined => {
+ @memset(out, undefined);
},
- .DWARF => {
- return unwindFrameMachODwarf(allocator, base_address, context, eh_frame orelse return error.MissingEhFrame, @intCast(encoding.value.x86_64.dwarf));
+ .same_value => {
+ // TODO: This copy could be eliminated if callers always copy the state then call this function to update it
+ const register = col.register orelse return error.InvalidRegister;
+ const src = try regBytes(context.thread_context, register, context.reg_context);
+ if (src.len != out.len) return error.RegisterSizeMismatch;
+ @memcpy(out, src);
},
- },
- .aarch64, .aarch64_be => switch (encoding.mode.arm64) {
- .OLD => return error.UnimplementedUnwindEncoding,
- .FRAMELESS => blk: {
- const sp = (try regValueNative(context.thread_context, spRegNum(reg_context), reg_context)).*;
- const new_sp = sp + encoding.value.arm64.frameless.stack_size * 16;
- const new_ip = (try regValueNative(context.thread_context, 30, reg_context)).*;
- (try regValueNative(context.thread_context, spRegNum(reg_context), reg_context)).* = new_sp;
- break :blk new_ip;
+ .offset => |offset| {
+ if (context.cfa) |cfa| {
+ const addr = try applyOffset(cfa, offset);
+ const ptr: *const usize = @ptrFromInt(addr);
+ mem.writeInt(usize, out[0..@sizeOf(usize)], ptr.*, native_endian);
+ } else return error.InvalidCFA;
},
- .DWARF => {
- return unwindFrameMachODwarf(allocator, base_address, context, eh_frame orelse return error.MissingEhFrame, @intCast(encoding.value.arm64.dwarf));
+ .val_offset => |offset| {
+ if (context.cfa) |cfa| {
+ mem.writeInt(usize, out[0..@sizeOf(usize)], try applyOffset(cfa, offset), native_endian);
+ } else return error.InvalidCFA;
},
- .FRAME => blk: {
- const fp = (try regValueNative(context.thread_context, fpRegNum(reg_context), reg_context)).*;
- const ip_ptr = fp + @sizeOf(usize);
-
- 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 regValueNative(context.thread_context, 19 + i, reg_context)).* = @as(*const usize, @ptrFromInt(reg_addr)).*;
- reg_addr += @sizeOf(usize);
- (try regValueNative(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 regBytes(context.thread_context, 64 + 8 + i, context.reg_context),
- std.mem.asBytes(@as(*const usize, @ptrFromInt(reg_addr))),
- );
- reg_addr += @sizeOf(usize);
- @memcpy(
- try regBytes(context.thread_context, 64 + 9 + i, context.reg_context),
- std.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 regValueNative(context.thread_context, fpRegNum(reg_context), reg_context)).* = new_fp;
- (try regValueNative(context.thread_context, ip_reg_num, reg_context)).* = new_ip;
-
- break :blk new_ip;
+ .register => |register| {
+ const src = try regBytes(context.thread_context, register, context.reg_context);
+ if (src.len != out.len) return error.RegisterSizeMismatch;
+ @memcpy(out, try regBytes(context.thread_context, register, context.reg_context));
},
- },
- else => return error.UnimplementedArch,
- };
-
- context.pc = stripInstructionPtrAuthCode(new_ip);
- if (context.pc > 0) context.pc -= 1;
- return new_ip;
-}
-
-pub const UnwindContext = struct {
- allocator: Allocator,
- cfa: ?usize,
- pc: usize,
- thread_context: *std.debug.ThreadContext,
- reg_context: Dwarf.abi.RegisterContext,
- vm: VirtualMachine,
- stack_machine: Dwarf.expression.StackMachine(.{ .call_frame_context = true }),
-
- pub fn init(
- allocator: Allocator,
- thread_context: *std.debug.ThreadContext,
- ) !UnwindContext {
- comptime assert(supports_unwinding);
-
- const pc = stripInstructionPtrAuthCode(
- (try regValueNative(thread_context, ip_reg_num, null)).*,
- );
-
- const context_copy = try allocator.create(std.debug.ThreadContext);
- std.debug.copyContext(thread_context, context_copy);
-
- return .{
- .allocator = allocator,
- .cfa = null,
- .pc = pc,
- .thread_context = context_copy,
- .reg_context = undefined,
- .vm = .{},
- .stack_machine = .{},
- };
- }
-
- pub fn deinit(self: *UnwindContext) void {
- self.vm.deinit(self.allocator);
- self.stack_machine.deinit(self.allocator);
- self.allocator.destroy(self.thread_context);
- self.* = undefined;
- }
-
- pub fn getFp(self: *const UnwindContext) !usize {
- return (try regValueNative(self.thread_context, fpRegNum(self.reg_context), self.reg_context)).*;
+ .expression => |expression| {
+ context.stack_machine.reset();
+ const value = try context.stack_machine.run(expression, context.gpa, expression_context, context.cfa.?);
+ const addr = if (value) |v| blk: {
+ if (v != .generic) return error.InvalidExpressionValue;
+ break :blk v.generic;
+ } else return error.NoExpressionValue;
+
+ const ptr: *usize = @ptrFromInt(addr);
+ mem.writeInt(usize, out[0..@sizeOf(usize)], ptr.*, native_endian);
+ },
+ .val_expression => |expression| {
+ context.stack_machine.reset();
+ const value = try context.stack_machine.run(expression, context.gpa, expression_context, context.cfa.?);
+ if (value) |v| {
+ if (v != .generic) return error.InvalidExpressionValue;
+ mem.writeInt(usize, out[0..@sizeOf(usize)], v.generic, native_endian);
+ } else return error.NoExpressionValue;
+ },
+ .architectural => return error.UnimplementedRegisterRule,
+ }
}
};
@@ -1584,113 +1081,30 @@ pub inline fn stripInstructionPtrAuthCode(ptr: usize) usize {
/// `explicit_fde_offset` is for cases where the FDE offset is known, such as when __unwind_info
/// defers unwinding to DWARF. This is an offset into the `.eh_frame` section.
fn unwindFrameDwarf(
- allocator: Allocator,
- unwind: *Dwarf.Unwind,
- base_address: usize,
+ unwind: *const Dwarf.Unwind,
+ load_offset: usize,
context: *UnwindContext,
explicit_fde_offset: ?usize,
) !usize {
if (!supports_unwinding) return error.UnsupportedCpuArchitecture;
if (context.pc == 0) return 0;
- // Find the FDE and CIE
- const cie, const fde = if (explicit_fde_offset) |fde_offset| blk: {
- const frame_section = unwind.section(.eh_frame) orelse return error.MissingFDE;
- if (fde_offset >= frame_section.len) return error.MissingFDE;
-
- var fbr: std.Io.Reader = .fixed(frame_section);
- fbr.seek = fde_offset;
+ const pc_vaddr = context.pc - load_offset;
- const fde_entry_header = try Dwarf.Unwind.EntryHeader.read(&fbr, .eh_frame, native_endian);
- if (fde_entry_header.type != .fde) return error.MissingFDE;
+ const fde_offset = explicit_fde_offset orelse try unwind.findFdeOffset(
+ pc_vaddr,
+ @sizeOf(usize),
+ native_endian,
+ ) orelse return error.MissingDebugInfo;
+ const format, const cie, const fde = try unwind.loadFde(fde_offset, @sizeOf(usize), native_endian);
- const cie_offset = fde_entry_header.type.fde;
- fbr.seek = @intCast(cie_offset);
-
- const cie_entry_header = try Dwarf.Unwind.EntryHeader.read(&fbr, .eh_frame, native_endian);
- if (cie_entry_header.type != .cie) return Dwarf.bad();
-
- const cie = try Dwarf.Unwind.CommonInformationEntry.parse(
- cie_entry_header.entry_bytes,
- 0,
- true,
- cie_entry_header.format,
- .eh_frame,
- cie_entry_header.length_offset,
- @sizeOf(usize),
- native_endian,
- );
- const fde = try Dwarf.Unwind.FrameDescriptionEntry.parse(
- fde_entry_header.entry_bytes,
- 0,
- true,
- cie,
- @sizeOf(usize),
- native_endian,
- );
-
- break :blk .{ cie, fde };
- } else blk: {
- // `.eh_frame_hdr` may be incomplete. We'll try it first, but if the lookup fails, we fall
- // back to loading `.eh_frame`/`.debug_frame` and using those from that point on.
-
- if (unwind.eh_frame_hdr) |header| hdr: {
- const eh_frame_len = if (unwind.section(.eh_frame)) |eh_frame| eh_frame.len else {
- try unwind.scanCieFdeInfo(allocator, native_endian, base_address);
- unwind.eh_frame_hdr = null;
- break :hdr;
- };
-
- var cie: Dwarf.Unwind.CommonInformationEntry = undefined;
- var fde: Dwarf.Unwind.FrameDescriptionEntry = undefined;
-
- header.findEntry(
- eh_frame_len,
- @intFromPtr(unwind.section(.eh_frame_hdr).?.ptr),
- context.pc,
- &cie,
- &fde,
- native_endian,
- ) catch |err| switch (err) {
- error.MissingDebugInfo => {
- // `.eh_frame_hdr` appears to be incomplete, so go ahead and populate `cie_map`
- // and `fde_list`, and fall back to the binary search logic below.
- try unwind.scanCieFdeInfo(allocator, native_endian, base_address);
-
- // Since `.eh_frame_hdr` is incomplete, we're very likely to get more lookup
- // failures using it, and we've just built a complete, sorted list of FDEs
- // anyway, so just stop using `.eh_frame_hdr` altogether.
- unwind.eh_frame_hdr = null;
-
- break :hdr;
- },
- else => return err,
- };
-
- break :blk .{ cie, fde };
- }
-
- const index = std.sort.binarySearch(Dwarf.Unwind.FrameDescriptionEntry, unwind.fde_list.items, context.pc, struct {
- pub fn compareFn(pc: usize, item: Dwarf.Unwind.FrameDescriptionEntry) std.math.Order {
- if (pc < item.pc_begin) return .lt;
-
- const range_end = item.pc_begin + item.pc_range;
- if (pc < range_end) return .eq;
-
- return .gt;
- }
- }.compareFn);
-
- const fde = if (index) |i| unwind.fde_list.items[i] else return error.MissingFDE;
- const cie = unwind.cie_map.get(fde.cie_length_offset) orelse return error.MissingCIE;
-
- break :blk .{ cie, fde };
- };
+ // Check if this FDE *actually* includes the address.
+ if (pc_vaddr < fde.pc_begin or pc_vaddr >= fde.pc_begin + fde.pc_range) return error.MissingDebugInfo;
// Do not set `compile_unit` because the spec states that CFIs
// may not reference other debug sections anyway.
var expression_context: Dwarf.expression.Context = .{
- .format = cie.format,
+ .format = format,
.thread_context = context.thread_context,
.reg_context = context.reg_context,
.cfa = context.cfa,
@@ -1700,7 +1114,7 @@ fn unwindFrameDwarf(
context.reg_context.eh_frame = cie.version != 4;
context.reg_context.is_macho = native_os.isDarwin();
- const row = try context.vm.runToNative(context.allocator, context.pc, cie, fde);
+ const row = try context.vm.runTo(context.gpa, context.pc - load_offset, cie, fde, @sizeOf(usize), native_endian);
context.cfa = switch (row.cfa.rule) {
.val_offset => |offset| blk: {
const register = row.cfa.register orelse return error.InvalidCFARule;
@@ -1711,7 +1125,7 @@ fn unwindFrameDwarf(
context.stack_machine.reset();
const value = try context.stack_machine.run(
expr,
- context.allocator,
+ context.gpa,
expression_context,
context.cfa,
);
@@ -1728,9 +1142,9 @@ fn unwindFrameDwarf(
// Buffering the modifications is done because copying the thread context is not portable,
// some implementations (ie. darwin) use internal pointers to the mcontext.
- var arena = std.heap.ArenaAllocator.init(context.allocator);
+ var arena: std.heap.ArenaAllocator = .init(context.gpa);
defer arena.deinit();
- const update_allocator = arena.allocator();
+ const update_arena = arena.allocator();
const RegisterUpdate = struct {
// Backed by thread_context
@@ -1749,17 +1163,16 @@ fn unwindFrameDwarf(
}
const dest = try regBytes(context.thread_context, register, context.reg_context);
- const src = try update_allocator.alloc(u8, dest.len);
+ const src = try update_arena.alloc(u8, dest.len);
+ try context.resolveRegisterRule(column, expression_context, src);
- const prev = update_tail;
- update_tail = try update_allocator.create(RegisterUpdate);
- update_tail.?.* = .{
+ const new_update = try update_arena.create(RegisterUpdate);
+ new_update.* = .{
.dest = dest,
.src = src,
- .prev = prev,
+ .prev = update_tail,
};
-
- try column.resolveValue(context, expression_context, src);
+ update_tail = new_update;
}
}
@@ -1792,7 +1205,7 @@ fn unwindFrameDwarf(
// The exception to this rule is signal frames, where we return execution would be returned to the instruction
// that triggered the handler.
const return_address = context.pc;
- if (context.pc > 0 and !cie.isSignalFrame()) context.pc -= 1;
+ if (context.pc > 0 and !cie.is_signal_frame) context.pc -= 1;
return return_address;
}
@@ -1843,415 +1256,345 @@ pub fn supportsUnwinding(target: *const std.Target) bool {
};
}
-fn unwindFrameMachODwarf(
- allocator: Allocator,
- base_address: usize,
- context: *UnwindContext,
- eh_frame: []const u8,
- fde_offset: usize,
-) !usize {
- var di: Dwarf = .{
- .endian = native_endian,
- .is_macho = true,
- };
- defer di.deinit(context.allocator);
+/// Since register rules are applied (usually) during a panic,
+/// checked addition / subtraction is used so that we can return
+/// an error and fall back to FP-based unwinding.
+fn applyOffset(base: usize, offset: i64) !usize {
+ return if (offset >= 0)
+ try std.math.add(usize, base, @as(usize, @intCast(offset)))
+ else
+ try std.math.sub(usize, base, @as(usize, @intCast(-offset)));
+}
- di.sections[@intFromEnum(Dwarf.Section.Id.eh_frame)] = .{
- .data = eh_frame,
- .owned = false,
- };
+/// Uses `mmap` to map the file at `opt_path` (or, if `null`, the self executable image) into memory.
+fn mapFileOrSelfExe(opt_path: ?[]const u8) ![]align(std.heap.page_size_min) const u8 {
+ const file = if (opt_path) |path|
+ try fs.cwd().openFile(path, .{})
+ else
+ try fs.openSelfExe(.{});
+ defer file.close();
- return unwindFrameDwarf(allocator, &di, base_address, context, fde_offset);
+ const file_len = math.cast(usize, try file.getEndPos()) orelse return error.FileTooBig;
+
+ return posix.mmap(
+ null,
+ file_len,
+ posix.PROT.READ,
+ .{ .TYPE = .SHARED },
+ file.handle,
+ 0,
+ );
}
-/// This is a virtual machine that runs DWARF call frame instructions.
-pub const VirtualMachine = struct {
- /// See section 6.4.1 of the DWARF5 specification for details on each
- const RegisterRule = union(enum) {
- // The spec says that the default rule for each column is the undefined rule.
- // However, it also allows ABI / compiler authors to specify alternate defaults, so
- // there is a distinction made here.
- default: void,
- undefined: void,
- same_value: void,
- // offset(N)
- offset: i64,
- // val_offset(N)
- val_offset: i64,
- // register(R)
- register: u8,
- // expression(E)
- expression: []const u8,
- // val_expression(E)
- val_expression: []const u8,
- // Augmenter-defined rule
- architectural: void,
- };
+/// 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.
+fn unwindFrameMachO(
+ text_base: usize,
+ load_offset: usize,
+ context: *UnwindContext,
+ unwind_info: []const u8,
+ eh_frame: ?[]const u8,
+) !usize {
+ if (unwind_info.len < @sizeOf(macho.unwind_info_section_header)) return error.InvalidUnwindInfo;
+ const header: *align(1) const macho.unwind_info_section_header = @ptrCast(unwind_info);
- /// Each row contains unwinding rules for a set of registers.
- pub const Row = struct {
- /// Offset from `FrameDescriptionEntry.pc_begin`
- offset: u64 = 0,
- /// Special-case column that defines the CFA (Canonical Frame Address) rule.
- /// The register field of this column defines the register that CFA is derived from.
- cfa: Column = .{},
- /// The register fields in these columns define the register the rule applies to.
- columns: ColumnRange = .{},
- /// Indicates that the next write to any column in this row needs to copy
- /// the backing column storage first, as it may be referenced by previous rows.
- copy_on_write: bool = false,
- };
+ 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.InvalidUnwindInfo;
+ 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.MissingUnwindInfo;
- pub const Column = struct {
- register: ?u8 = null,
- rule: RegisterRule = .{ .default = {} },
-
- /// Resolves the register rule and places the result into `out` (see regBytes)
- pub fn resolveValue(
- self: Column,
- context: *SelfInfo.UnwindContext,
- expression_context: std.debug.Dwarf.expression.Context,
- out: []u8,
- ) !void {
- switch (self.rule) {
- .default => {
- const register = self.register orelse return error.InvalidRegister;
- try getRegDefaultValue(register, context, out);
- },
- .undefined => {
- @memset(out, undefined);
- },
- .same_value => {
- // TODO: This copy could be eliminated if callers always copy the state then call this function to update it
- const register = self.register orelse return error.InvalidRegister;
- const src = try regBytes(context.thread_context, register, context.reg_context);
- if (src.len != out.len) return error.RegisterSizeMismatch;
- @memcpy(out, src);
- },
- .offset => |offset| {
- if (context.cfa) |cfa| {
- const addr = try applyOffset(cfa, offset);
- const ptr: *const usize = @ptrFromInt(addr);
- mem.writeInt(usize, out[0..@sizeOf(usize)], ptr.*, native_endian);
- } else return error.InvalidCFA;
- },
- .val_offset => |offset| {
- if (context.cfa) |cfa| {
- mem.writeInt(usize, out[0..@sizeOf(usize)], try applyOffset(cfa, offset), native_endian);
- } else return error.InvalidCFA;
- },
- .register => |register| {
- const src = try regBytes(context.thread_context, register, context.reg_context);
- if (src.len != out.len) return error.RegisterSizeMismatch;
- @memcpy(out, try regBytes(context.thread_context, register, context.reg_context));
- },
- .expression => |expression| {
- context.stack_machine.reset();
- const value = try context.stack_machine.run(expression, context.allocator, expression_context, context.cfa.?);
- const addr = if (value) |v| blk: {
- if (v != .generic) return error.InvalidExpressionValue;
- break :blk v.generic;
- } else return error.NoExpressionValue;
-
- const ptr: *usize = @ptrFromInt(addr);
- mem.writeInt(usize, out[0..@sizeOf(usize)], ptr.*, native_endian);
- },
- .val_expression => |expression| {
- context.stack_machine.reset();
- const value = try context.stack_machine.run(expression, context.allocator, expression_context, context.cfa.?);
- if (value) |v| {
- if (v != .generic) return error.InvalidExpressionValue;
- mem.writeInt(usize, out[0..@sizeOf(usize)], v.generic, native_endian);
- } else return error.NoExpressionValue;
- },
- .architectural => return error.UnimplementedRegisterRule,
+ // MLUGG TODO HACKHACK -- Unwind needs a slight refactor to make this work well
+ const opt_dwarf_unwind: ?Dwarf.Unwind = if (eh_frame) |eh_frame_data| .{
+ .debug_frame = null,
+ .eh_frame = .{
+ .header = .{
+ .vaddr = undefined,
+ .eh_frame_vaddr = @intFromPtr(eh_frame_data.ptr) - load_offset,
+ .search_table = null,
+ },
+ .eh_frame_data = eh_frame_data,
+ .sorted_fdes = null,
+ },
+ } else null;
+
+ // offset of the PC into the `__TEXT` segment
+ const pc_text_offset = context.pc - 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 };
};
+ // An offset of 0 is a sentinel indicating a range does not have unwind info.
+ if (start_offset == 0) return error.MissingUnwindInfo;
- const ColumnRange = struct {
- /// Index into `columns` of the first column in this row.
- start: usize = undefined,
- len: u8 = 0,
- };
+ 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.InvalidUnwindInfo;
+ const common_encodings: []align(1) const macho.compact_unwind_encoding_t = @ptrCast(
+ unwind_info[header.commonEncodingsArraySectionOffset..][0..common_encodings_byte_count],
+ );
- columns: std.ArrayListUnmanaged(Column) = .empty,
- stack: std.ArrayListUnmanaged(ColumnRange) = .empty,
- current_row: Row = .{},
+ if (unwind_info.len < start_offset + @sizeOf(macho.UNWIND_SECOND_LEVEL)) return error.InvalidUnwindInfo;
+ const kind: *align(1) const macho.UNWIND_SECOND_LEVEL = @ptrCast(unwind_info[start_offset..]);
- /// The result of executing the CIE's initial_instructions
- cie_row: ?Row = null,
+ 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.InvalidUnwindInfo;
+ 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.InvalidUnwindInfo;
+ 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.InvalidUnwindInfo;
- pub fn deinit(self: *VirtualMachine, allocator: std.mem.Allocator) void {
- self.stack.deinit(allocator);
- self.columns.deinit(allocator);
- self.* = undefined;
- }
+ 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.InvalidUnwindInfo;
+ 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.InvalidUnwindInfo;
+ 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.InvalidUnwindInfo;
- pub fn reset(self: *VirtualMachine) void {
- self.stack.clearRetainingCapacity();
- self.columns.clearRetainingCapacity();
- self.current_row = .{};
- self.cie_row = null;
- }
+ 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];
- /// Return a slice backed by the row's non-CFA columns
- pub fn rowColumns(self: VirtualMachine, row: Row) []Column {
- if (row.columns.len == 0) return &.{};
- return self.columns.items[row.columns.start..][0..row.columns.len];
- }
+ 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],
+ };
+ }
- /// Either retrieves or adds a column for `register` (non-CFA) in the current row.
- fn getOrAddColumn(self: *VirtualMachine, allocator: std.mem.Allocator, register: u8) !*Column {
- for (self.rowColumns(self.current_row)) |*c| {
- if (c.register == register) return c;
- }
+ 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.InvalidUnwindInfo;
+ 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.InvalidUnwindInfo;
+ break :entry .{
+ .function_offset = function_offset,
+ .raw_encoding = local_encodings[local_index],
+ };
+ },
+ else => return error.InvalidUnwindInfo,
+ };
- if (self.current_row.columns.len == 0) {
- self.current_row.columns.start = self.columns.items.len;
- }
- self.current_row.columns.len += 1;
+ if (entry.raw_encoding == 0) return error.NoUnwindInfo;
+ const reg_context: Dwarf.abi.RegisterContext = .{ .eh_frame = false, .is_macho = true };
- const column = try self.columns.addOne(allocator);
- column.* = .{
- .register = register,
- };
+ 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 => ip: {
+ const frame = encoding.value.x86_64.frame;
- return column;
- }
+ const fp = (try regValueNative(context.thread_context, fpRegNum(reg_context), reg_context)).*;
+ const new_sp = fp + 2 * @sizeOf(usize);
- /// Runs the CIE instructions, then the FDE instructions. Execution halts
- /// once the row that corresponds to `pc` is known, and the row is returned.
- pub fn runTo(
- self: *VirtualMachine,
- allocator: std.mem.Allocator,
- pc: u64,
- cie: std.debug.Dwarf.Unwind.CommonInformationEntry,
- fde: std.debug.Dwarf.Unwind.FrameDescriptionEntry,
- addr_size_bytes: u8,
- endian: std.builtin.Endian,
- ) !Row {
- assert(self.cie_row == null);
- if (pc < fde.pc_begin or pc >= fde.pc_begin + fde.pc_range) return error.AddressOutOfRange;
-
- var prev_row: Row = self.current_row;
-
- var cie_stream: std.Io.Reader = .fixed(cie.initial_instructions);
- var fde_stream: std.Io.Reader = .fixed(fde.instructions);
- const streams = [_]*std.Io.Reader{ &cie_stream, &fde_stream };
-
- for (&streams, 0..) |stream, i| {
- while (stream.seek < stream.buffer.len) {
- const instruction = try std.debug.Dwarf.call_frame.Instruction.read(stream, addr_size_bytes, endian);
- prev_row = try self.step(allocator, cie, i == 0, instruction);
- if (pc < fde.pc_begin + self.current_row.offset) return prev_row;
- }
- }
+ const ip_ptr = fp + @sizeOf(usize);
+ const new_ip = @as(*const usize, @ptrFromInt(ip_ptr)).*;
+ const new_fp = @as(*const usize, @ptrFromInt(fp)).*;
- return self.current_row;
- }
+ (try regValueNative(context.thread_context, fpRegNum(reg_context), reg_context)).* = new_fp;
+ (try regValueNative(context.thread_context, spRegNum(reg_context), reg_context)).* = new_sp;
+ (try regValueNative(context.thread_context, ip_reg_num, reg_context)).* = new_ip;
- pub fn runToNative(
- self: *VirtualMachine,
- allocator: std.mem.Allocator,
- pc: u64,
- cie: std.debug.Dwarf.Unwind.CommonInformationEntry,
- fde: std.debug.Dwarf.Unwind.FrameDescriptionEntry,
- ) !Row {
- return self.runTo(allocator, pc, cie, fde, @sizeOf(usize), native_endian);
- }
+ 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 regValueNative(context.thread_context, reg_number, reg_context)).* = @as(*const usize, @ptrFromInt(addr)).*;
+ }
- fn resolveCopyOnWrite(self: *VirtualMachine, allocator: std.mem.Allocator) !void {
- if (!self.current_row.copy_on_write) return;
+ break :ip new_ip;
+ },
+ .STACK_IMMD,
+ .STACK_IND,
+ => ip: {
+ const frameless = encoding.value.x86_64.frameless;
- const new_start = self.columns.items.len;
- if (self.current_row.columns.len > 0) {
- try self.columns.ensureUnusedCapacity(allocator, self.current_row.columns.len);
- self.columns.appendSliceAssumeCapacity(self.rowColumns(self.current_row));
- self.current_row.columns.start = new_start;
- }
- }
+ const sp = (try regValueNative(context.thread_context, spRegNum(reg_context), reg_context)).*;
+ 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);
+ }
+ // In .STACK_IND, the stack size is inferred from the subq instruction at the beginning of the function.
+ const sub_offset_addr =
+ text_base +
+ entry.function_offset +
+ frameless.stack.indirect.sub_offset;
+ // `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, frameless.stack.indirect.stack_adjust);
+ };
- /// Executes a single instruction.
- /// If this instruction is from the CIE, `is_initial` should be set.
- /// Returns the value of `current_row` before executing this instruction.
- pub fn step(
- self: *VirtualMachine,
- allocator: std.mem.Allocator,
- cie: std.debug.Dwarf.Unwind.CommonInformationEntry,
- is_initial: bool,
- instruction: Dwarf.call_frame.Instruction,
- ) !Row {
- // CIE instructions must be run before FDE instructions
- assert(!is_initial or self.cie_row == null);
- if (!is_initial and self.cie_row == null) {
- self.cie_row = self.current_row;
- self.current_row.copy_on_write = true;
- }
+ // 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
- const prev_row = self.current_row;
- switch (instruction) {
- .set_loc => |i| {
- if (i.address <= self.current_row.offset) return error.InvalidOperation;
- // TODO: Check cie.segment_selector_size != 0 for DWARFV4
- self.current_row.offset = i.address;
- },
- inline .advance_loc,
- .advance_loc1,
- .advance_loc2,
- .advance_loc4,
- => |i| {
- self.current_row.offset += i.delta * cie.code_alignment_factor;
- self.current_row.copy_on_write = true;
- },
- inline .offset,
- .offset_extended,
- .offset_extended_sf,
- => |i| {
- try self.resolveCopyOnWrite(allocator);
- const column = try self.getOrAddColumn(allocator, i.register);
- column.rule = .{ .offset = @as(i64, @intCast(i.offset)) * cie.data_alignment_factor };
- },
- inline .restore,
- .restore_extended,
- => |i| {
- try self.resolveCopyOnWrite(allocator);
- if (self.cie_row) |cie_row| {
- const column = try self.getOrAddColumn(allocator, i.register);
- column.rule = for (self.rowColumns(cie_row)) |cie_column| {
- if (cie_column.register == i.register) break cie_column.rule;
- } else .{ .default = {} };
- } else return error.InvalidOperation;
- },
- .nop => {},
- .undefined => |i| {
- try self.resolveCopyOnWrite(allocator);
- const column = try self.getOrAddColumn(allocator, i.register);
- column.rule = .{ .undefined = {} };
- },
- .same_value => |i| {
- try self.resolveCopyOnWrite(allocator);
- const column = try self.getOrAddColumn(allocator, i.register);
- column.rule = .{ .same_value = {} };
- },
- .register => |i| {
- try self.resolveCopyOnWrite(allocator);
- const column = try self.getOrAddColumn(allocator, i.register);
- column.rule = .{ .register = i.target_register };
- },
- .remember_state => {
- try self.stack.append(allocator, self.current_row.columns);
- self.current_row.copy_on_write = true;
- },
- .restore_state => {
- const restored_columns = self.stack.pop() orelse return error.InvalidOperation;
- self.columns.shrinkRetainingCapacity(self.columns.items.len - self.current_row.columns.len);
- try self.columns.ensureUnusedCapacity(allocator, restored_columns.len);
-
- self.current_row.columns.start = self.columns.items.len;
- self.current_row.columns.len = restored_columns.len;
- self.columns.appendSliceAssumeCapacity(self.columns.items[restored_columns.start..][0..restored_columns.len]);
- },
- .def_cfa => |i| {
- try self.resolveCopyOnWrite(allocator);
- self.current_row.cfa = .{
- .register = i.register,
- .rule = .{ .val_offset = @intCast(i.offset) },
- };
- },
- .def_cfa_sf => |i| {
- try self.resolveCopyOnWrite(allocator);
- self.current_row.cfa = .{
- .register = i.register,
- .rule = .{ .val_offset = i.offset * cie.data_alignment_factor },
- };
- },
- .def_cfa_register => |i| {
- try self.resolveCopyOnWrite(allocator);
- if (self.current_row.cfa.register == null or self.current_row.cfa.rule != .val_offset) return error.InvalidOperation;
- self.current_row.cfa.register = i.register;
- },
- .def_cfa_offset => |i| {
- try self.resolveCopyOnWrite(allocator);
- if (self.current_row.cfa.register == null or self.current_row.cfa.rule != .val_offset) return error.InvalidOperation;
- self.current_row.cfa.rule = .{
- .val_offset = @intCast(i.offset),
- };
- },
- .def_cfa_offset_sf => |i| {
- try self.resolveCopyOnWrite(allocator);
- if (self.current_row.cfa.register == null or self.current_row.cfa.rule != .val_offset) return error.InvalidOperation;
- self.current_row.cfa.rule = .{
- .val_offset = i.offset * cie.data_alignment_factor,
- };
- },
- .def_cfa_expression => |i| {
- try self.resolveCopyOnWrite(allocator);
- self.current_row.cfa.register = undefined;
- self.current_row.cfa.rule = .{
- .expression = i.block,
- };
- },
- .expression => |i| {
- try self.resolveCopyOnWrite(allocator);
- const column = try self.getOrAddColumn(allocator, i.register);
- column.rule = .{
- .expression = i.block,
+ // 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 regValueNative(context.thread_context, reg_number, reg_context)).* = @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 regValueNative(context.thread_context, spRegNum(reg_context), reg_context)).* = new_sp;
+ (try regValueNative(context.thread_context, ip_reg_num, reg_context)).* = new_ip;
+
+ break :ip new_ip;
},
- .val_offset => |i| {
- try self.resolveCopyOnWrite(allocator);
- const column = try self.getOrAddColumn(allocator, i.register);
- column.rule = .{
- .val_offset = @as(i64, @intCast(i.offset)) * cie.data_alignment_factor,
- };
+ .DWARF => {
+ const dwarf_unwind = &(opt_dwarf_unwind orelse return error.MissingEhFrame);
+ return unwindFrameDwarf(dwarf_unwind, load_offset, context, @intCast(encoding.value.x86_64.dwarf));
},
- .val_offset_sf => |i| {
- try self.resolveCopyOnWrite(allocator);
- const column = try self.getOrAddColumn(allocator, i.register);
- column.rule = .{
- .val_offset = i.offset * cie.data_alignment_factor,
- };
+ },
+ .aarch64, .aarch64_be => switch (encoding.mode.arm64) {
+ .OLD => return error.UnimplementedUnwindEncoding,
+ .FRAMELESS => ip: {
+ const sp = (try regValueNative(context.thread_context, spRegNum(reg_context), reg_context)).*;
+ const new_sp = sp + encoding.value.arm64.frameless.stack_size * 16;
+ const new_ip = (try regValueNative(context.thread_context, 30, reg_context)).*;
+ (try regValueNative(context.thread_context, spRegNum(reg_context), reg_context)).* = new_sp;
+ break :ip new_ip;
},
- .val_expression => |i| {
- try self.resolveCopyOnWrite(allocator);
- const column = try self.getOrAddColumn(allocator, i.register);
- column.rule = .{
- .val_expression = i.block,
- };
+ .DWARF => {
+ const dwarf_unwind = &(opt_dwarf_unwind orelse return error.MissingEhFrame);
+ return unwindFrameDwarf(dwarf_unwind, load_offset, context, @intCast(encoding.value.arm64.dwarf));
},
- }
+ .FRAME => ip: {
+ const frame = encoding.value.arm64.frame;
- return prev_row;
- }
-};
+ const fp = (try regValueNative(context.thread_context, fpRegNum(reg_context), reg_context)).*;
+ const ip_ptr = fp + @sizeOf(usize);
-/// Returns the ABI-defined default value this register has in the unwinding table
-/// before running any of the CIE instructions. The DWARF spec defines these as having
-/// the .undefined rule by default, but allows ABI authors to override that.
-fn getRegDefaultValue(reg_number: u8, context: *UnwindContext, out: []u8) !void {
- switch (builtin.cpu.arch) {
- .aarch64, .aarch64_be => {
- // Callee-saved registers are initialized as if they had the .same_value rule
- if (reg_number >= 19 and reg_number <= 28) {
- const src = try regBytes(context.thread_context, reg_number, context.reg_context);
- if (src.len != out.len) return error.RegisterSizeMismatch;
- @memcpy(out, src);
- return;
- }
- },
- else => {},
- }
+ var reg_addr = fp - @sizeOf(usize);
+ inline for (@typeInfo(@TypeOf(frame.x_reg_pairs)).@"struct".fields, 0..) |field, i| {
+ if (@field(frame.x_reg_pairs, field.name) != 0) {
+ (try regValueNative(context.thread_context, 19 + i, reg_context)).* = @as(*const usize, @ptrFromInt(reg_addr)).*;
+ reg_addr += @sizeOf(usize);
+ (try regValueNative(context.thread_context, 20 + i, reg_context)).* = @as(*const usize, @ptrFromInt(reg_addr)).*;
+ reg_addr += @sizeOf(usize);
+ }
+ }
- @memset(out, undefined);
-}
+ inline for (@typeInfo(@TypeOf(frame.d_reg_pairs)).@"struct".fields, 0..) |field, i| {
+ if (@field(frame.d_reg_pairs, field.name) != 0) {
+ // Only the lower half of the 128-bit V registers are restored during unwinding
+ {
+ const dest: *align(1) usize = @ptrCast(try regBytes(context.thread_context, 64 + 8 + i, context.reg_context));
+ dest.* = @as(*const usize, @ptrFromInt(reg_addr)).*;
+ }
+ reg_addr += @sizeOf(usize);
+ {
+ const dest: *align(1) usize = @ptrCast(try regBytes(context.thread_context, 64 + 9 + i, context.reg_context));
+ dest.* = @as(*const usize, @ptrFromInt(reg_addr)).*;
+ }
+ reg_addr += @sizeOf(usize);
+ }
+ }
-/// Since register rules are applied (usually) during a panic,
-/// checked addition / subtraction is used so that we can return
-/// an error and fall back to FP-based unwinding.
-fn applyOffset(base: usize, offset: i64) !usize {
- return if (offset >= 0)
- try std.math.add(usize, base, @as(usize, @intCast(offset)))
- else
- try std.math.sub(usize, base, @as(usize, @intCast(-offset)));
+ const new_ip = @as(*const usize, @ptrFromInt(ip_ptr)).*;
+ const new_fp = @as(*const usize, @ptrFromInt(fp)).*;
+
+ (try regValueNative(context.thread_context, fpRegNum(reg_context), reg_context)).* = new_fp;
+ (try regValueNative(context.thread_context, ip_reg_num, reg_context)).* = new_ip;
+
+ break :ip new_ip;
+ },
+ },
+ else => comptime unreachable, // unimplemented
+ };
+
+ context.pc = stripInstructionPtrAuthCode(new_ip);
+ if (context.pc > 0) context.pc -= 1;
+ return new_ip;
}
lib/std/dwarf/EH.zig
@@ -1,27 +1,32 @@
-pub const PE = struct {
- pub const absptr = 0x00;
+pub const PE = packed struct(u8) {
+ type: Type,
+ rel: Rel,
- pub const size_mask = 0x7;
- pub const sign_mask = 0x8;
- pub const type_mask = size_mask | sign_mask;
+ /// This is a special encoding which does not correspond to named `type`/`rel` values.
+ pub const omit: PE = @bitCast(@as(u8, 0xFF));
- pub const uleb128 = 0x01;
- pub const udata2 = 0x02;
- pub const udata4 = 0x03;
- pub const udata8 = 0x04;
- pub const sleb128 = 0x09;
- pub const sdata2 = 0x0A;
- pub const sdata4 = 0x0B;
- pub const sdata8 = 0x0C;
+ pub const Type = enum(u4) {
+ absptr = 0x0,
+ uleb128 = 0x1,
+ udata2 = 0x2,
+ udata4 = 0x3,
+ udata8 = 0x4,
+ sleb128 = 0x9,
+ sdata2 = 0xA,
+ sdata4 = 0xB,
+ sdata8 = 0xC,
+ _,
+ };
- pub const rel_mask = 0x70;
- pub const pcrel = 0x10;
- pub const textrel = 0x20;
- pub const datarel = 0x30;
- pub const funcrel = 0x40;
- pub const aligned = 0x50;
-
- pub const indirect = 0x80;
-
- pub const omit = 0xff;
+ pub const Rel = enum(u4) {
+ abs = 0x0,
+ pcrel = 0x1,
+ textrel = 0x2,
+ datarel = 0x3,
+ funcrel = 0x4,
+ aligned = 0x5,
+ /// Undocumented GCC extension
+ indirect = 0x8,
+ _,
+ };
};
lib/std/coff.zig
@@ -1083,26 +1083,27 @@ pub const Coff = struct {
age: u32 = undefined,
// The lifetime of `data` must be longer than the lifetime of the returned Coff
- pub fn init(data: []const u8, is_loaded: bool) !Coff {
+ pub fn init(data: []const u8, is_loaded: bool) error{ EndOfStream, MissingPEHeader }!Coff {
const pe_pointer_offset = 0x3C;
const pe_magic = "PE\x00\x00";
- var reader: std.Io.Reader = .fixed(data);
- reader.seek = pe_pointer_offset;
- const coff_header_offset = try reader.takeInt(u32, .little);
- reader.seek = coff_header_offset;
- const is_image = mem.eql(u8, pe_magic, try reader.takeArray(4));
+ if (data.len < pe_pointer_offset + 4) return error.EndOfStream;
+ const header_offset = mem.readInt(u32, data[pe_pointer_offset..][0..4], .little);
+ if (data.len < header_offset + 4) return error.EndOfStream;
+ const is_image = mem.eql(u8, data[header_offset..][0..4], pe_magic);
- var coff = @This(){
+ const coff: Coff = .{
.data = data,
.is_image = is_image,
.is_loaded = is_loaded,
- .coff_header_offset = coff_header_offset,
+ .coff_header_offset = o: {
+ if (is_image) break :o header_offset + 4;
+ break :o header_offset;
+ },
};
// Do some basic validation upfront
if (is_image) {
- coff.coff_header_offset = coff.coff_header_offset + 4;
const coff_header = coff.getCoffHeader();
if (coff_header.size_of_optional_header == 0) return error.MissingPEHeader;
}
lib/std/debug.zig
@@ -153,6 +153,7 @@ pub const SourceLocation = struct {
};
pub const Symbol = struct {
+ // MLUGG TODO: remove the defaults and audit everywhere. also grep for '???' across std
name: []const u8 = "???",
compile_unit_name: []const u8 = "???",
source_location: ?SourceLocation = null,
@@ -232,15 +233,14 @@ pub fn print(comptime fmt: []const u8, args: anytype) void {
}
/// TODO multithreaded awareness
-var self_debug_info: ?SelfInfo = null;
-
-pub fn getSelfDebugInfo() !*SelfInfo {
- if (self_debug_info) |*info| {
- return info;
- } else {
- self_debug_info = try SelfInfo.open(getDebugInfoAllocator());
- return &self_debug_info.?;
- }
+/// Marked `inline` to propagate a comptime-known error to callers.
+pub inline fn getSelfDebugInfo() !*SelfInfo {
+ if (builtin.strip_debug_info) return error.MissingDebugInfo;
+ if (!SelfInfo.target_supported) return error.UnsupportedOperatingSystem;
+ const S = struct {
+ var self_info: SelfInfo = .init;
+ };
+ return &S.self_info;
}
/// Tries to print a hexadecimal view of the bytes, unbuffered, and ignores any error returned.
@@ -446,10 +446,7 @@ pub fn dumpStackTraceFromBase(context: *ThreadContext, stderr: *Writer) void {
defer it.deinit();
// DWARF unwinding on aarch64-macos is not complete so we need to get pc address from mcontext
- const pc_addr = if (builtin.target.os.tag.isDarwin() and native_arch == .aarch64)
- context.mcontext.ss.pc
- else
- it.unwind_state.?.dwarf_context.pc;
+ const pc_addr = it.unwind_state.?.dwarf_context.pc;
printSourceAtAddress(debug_info, stderr, pc_addr, tty_config) catch return;
while (it.next()) |return_address| {
@@ -460,7 +457,7 @@ pub fn dumpStackTraceFromBase(context: *ThreadContext, stderr: *Writer) void {
// an overflow. We do not need to signal `StackIterator` as it will correctly detect this
// condition on the subsequent iteration and return `null` thus terminating the loop.
// same behaviour for x86-windows-msvc
- const address = if (return_address == 0) return_address else return_address - 1;
+ const address = return_address -| 1;
printSourceAtAddress(debug_info, stderr, address, tty_config) catch return;
} else printLastUnwindError(&it, debug_info, stderr, tty_config);
}
@@ -758,7 +755,7 @@ pub fn writeStackTrace(
frame_index = (frame_index + 1) % stack_trace.instruction_addresses.len;
}) {
const return_address = stack_trace.instruction_addresses[frame_index];
- try printSourceAtAddress(debug_info, writer, return_address - 1, tty_config);
+ try printSourceAtAddress(debug_info, writer, return_address -| 1, tty_config);
}
if (stack_trace.index > stack_trace.instruction_addresses.len) {
@@ -808,16 +805,11 @@ pub const StackIterator = struct {
}
pub fn initWithContext(first_address: ?usize, debug_info: *SelfInfo, context: *posix.ucontext_t, fp: usize) !StackIterator {
- // The implementation of DWARF unwinding on aarch64-macos is not complete. However, Apple mandates that
- // the frame pointer register is always used, so on this platform we can safely use the FP-based unwinder.
- if (builtin.target.os.tag.isDarwin() and native_arch == .aarch64)
- return init(first_address, @truncate(context.mcontext.ss.fp));
-
if (SelfInfo.supports_unwinding) {
var iterator = init(first_address, fp);
iterator.unwind_state = .{
.debug_info = debug_info,
- .dwarf_context = try SelfInfo.UnwindContext.init(debug_info.allocator, context),
+ .dwarf_context = try SelfInfo.UnwindContext.init(getDebugInfoAllocator(), context),
};
return iterator;
}
@@ -890,7 +882,7 @@ pub const StackIterator = struct {
if (!unwind_state.failed) {
if (unwind_state.dwarf_context.pc == 0) return null;
defer it.fp = unwind_state.dwarf_context.getFp() catch 0;
- if (unwind_state.debug_info.unwindFrame(&unwind_state.dwarf_context)) |return_address| {
+ if (unwind_state.debug_info.unwindFrame(getDebugInfoAllocator(), &unwind_state.dwarf_context)) |return_address| {
return return_address;
} else |err| {
unwind_state.last_error = err;
@@ -1039,19 +1031,6 @@ pub fn writeStackTraceWindows(
}
}
-fn printUnknownSource(debug_info: *SelfInfo, writer: *Writer, address: usize, tty_config: tty.Config) !void {
- const module_name = debug_info.getModuleNameForAddress(address);
- return printLineInfo(
- writer,
- null,
- address,
- "???",
- module_name orelse "???",
- tty_config,
- printLineFromFileAnyOs,
- );
-}
-
fn printLastUnwindError(it: *StackIterator, debug_info: *SelfInfo, writer: *Writer, tty_config: tty.Config) void {
if (!have_ucontext) return;
if (it.getLastError()) |unwind_error| {
@@ -1059,32 +1038,48 @@ fn printLastUnwindError(it: *StackIterator, debug_info: *SelfInfo, writer: *Writ
}
}
-fn printUnwindError(debug_info: *SelfInfo, writer: *Writer, address: usize, err: UnwindError, tty_config: tty.Config) !void {
- const module_name = debug_info.getModuleNameForAddress(address) orelse "???";
+fn printUnwindError(debug_info: *SelfInfo, writer: *Writer, address: usize, unwind_err: UnwindError, tty_config: tty.Config) !void {
+ const module_name = debug_info.getModuleNameForAddress(getDebugInfoAllocator(), address) catch |err| switch (err) {
+ error.Unexpected, error.OutOfMemory => |e| return e,
+ error.MissingDebugInfo => "???",
+ };
try tty_config.setColor(writer, .dim);
- if (err == error.MissingDebugInfo) {
+ if (unwind_err == error.MissingDebugInfo) {
try writer.print("Unwind information for `{s}:0x{x}` was not available, trace may be incomplete\n\n", .{ module_name, address });
} else {
- try writer.print("Unwind error at address `{s}:0x{x}` ({}), trace may be incomplete\n\n", .{ module_name, address, err });
+ try writer.print("Unwind error at address `{s}:0x{x}` ({}), trace may be incomplete\n\n", .{ module_name, address, unwind_err });
}
try tty_config.setColor(writer, .reset);
}
pub fn printSourceAtAddress(debug_info: *SelfInfo, writer: *Writer, address: usize, tty_config: tty.Config) !void {
- const symbol_info = debug_info.getSymbolAtAddress(address) catch |err| switch (err) {
- error.MissingDebugInfo, error.InvalidDebugInfo => return printUnknownSource(debug_info, writer, address, tty_config),
- else => return err,
+ const gpa = getDebugInfoAllocator();
+ if (debug_info.getSymbolAtAddress(gpa, address)) |symbol_info| {
+ defer if (symbol_info.source_location) |sl| gpa.free(sl.file_name);
+ return printLineInfo(
+ writer,
+ symbol_info.source_location,
+ address,
+ symbol_info.name,
+ symbol_info.compile_unit_name,
+ tty_config,
+ );
+ } else |err| switch (err) {
+ error.MissingDebugInfo, error.InvalidDebugInfo => {},
+ else => |e| return e,
+ }
+ // Unknown source location, but perhaps we can at least get a module name
+ const compile_unit_name = debug_info.getModuleNameForAddress(getDebugInfoAllocator(), address) catch |err| switch (err) {
+ error.MissingDebugInfo => "???",
+ error.Unexpected, error.OutOfMemory => |e| return e,
};
- defer if (symbol_info.source_location) |sl| debug_info.allocator.free(sl.file_name);
-
return printLineInfo(
writer,
- symbol_info.source_location,
+ null,
address,
- symbol_info.name,
- symbol_info.compile_unit_name,
+ "???",
+ compile_unit_name,
tty_config,
- printLineFromFileAnyOs,
);
}
@@ -1095,7 +1090,6 @@ fn printLineInfo(
symbol_name: []const u8,
compile_unit_name: []const u8,
tty_config: tty.Config,
- comptime printLineFromFile: anytype,
) !void {
nosuspend {
try tty_config.setColor(writer, .bold);
@@ -1136,7 +1130,7 @@ fn printLineInfo(
}
}
-fn printLineFromFileAnyOs(writer: *Writer, source_location: SourceLocation) !void {
+fn printLineFromFile(writer: *Writer, source_location: SourceLocation) !void {
// Need this to always block even in async I/O mode, because this could potentially
// be called from e.g. the event loop code crashing.
var f = try fs.cwd().openFile(source_location.file_name, .{});
@@ -1190,7 +1184,7 @@ fn printLineFromFileAnyOs(writer: *Writer, source_location: SourceLocation) !voi
}
}
-test printLineFromFileAnyOs {
+test printLineFromFile {
var aw: Writer.Allocating = .init(std.testing.allocator);
defer aw.deinit();
const output_stream = &aw.writer;
@@ -1212,9 +1206,9 @@ test printLineFromFileAnyOs {
defer allocator.free(path);
try test_dir.dir.writeFile(.{ .sub_path = "one_line.zig", .data = "no new lines in this file, but one is printed anyway" });
- try expectError(error.EndOfFile, printLineFromFileAnyOs(output_stream, .{ .file_name = path, .line = 2, .column = 0 }));
+ try expectError(error.EndOfFile, printLineFromFile(output_stream, .{ .file_name = path, .line = 2, .column = 0 }));
- try printLineFromFileAnyOs(output_stream, .{ .file_name = path, .line = 1, .column = 0 });
+ try printLineFromFile(output_stream, .{ .file_name = path, .line = 1, .column = 0 });
try expectEqualStrings("no new lines in this file, but one is printed anyway\n", aw.written());
aw.clearRetainingCapacity();
}
@@ -1230,11 +1224,11 @@ test printLineFromFileAnyOs {
,
});
- try printLineFromFileAnyOs(output_stream, .{ .file_name = path, .line = 1, .column = 0 });
+ try printLineFromFile(output_stream, .{ .file_name = path, .line = 1, .column = 0 });
try expectEqualStrings("1\n", aw.written());
aw.clearRetainingCapacity();
- try printLineFromFileAnyOs(output_stream, .{ .file_name = path, .line = 3, .column = 0 });
+ try printLineFromFile(output_stream, .{ .file_name = path, .line = 3, .column = 0 });
try expectEqualStrings("3\n", aw.written());
aw.clearRetainingCapacity();
}
@@ -1253,7 +1247,7 @@ test printLineFromFileAnyOs {
try writer.splatByteAll('a', overlap);
try writer.flush();
- try printLineFromFileAnyOs(output_stream, .{ .file_name = path, .line = 2, .column = 0 });
+ try printLineFromFile(output_stream, .{ .file_name = path, .line = 2, .column = 0 });
try expectEqualStrings(("a" ** overlap) ++ "\n", aw.written());
aw.clearRetainingCapacity();
}
@@ -1267,7 +1261,7 @@ test printLineFromFileAnyOs {
const writer = &file_writer.interface;
try writer.splatByteAll('a', std.heap.page_size_max);
- try printLineFromFileAnyOs(output_stream, .{ .file_name = path, .line = 1, .column = 0 });
+ try printLineFromFile(output_stream, .{ .file_name = path, .line = 1, .column = 0 });
try expectEqualStrings(("a" ** std.heap.page_size_max) ++ "\n", aw.written());
aw.clearRetainingCapacity();
}
@@ -1281,19 +1275,19 @@ test printLineFromFileAnyOs {
const writer = &file_writer.interface;
try writer.splatByteAll('a', 3 * std.heap.page_size_max);
- try expectError(error.EndOfFile, printLineFromFileAnyOs(output_stream, .{ .file_name = path, .line = 2, .column = 0 }));
+ try expectError(error.EndOfFile, printLineFromFile(output_stream, .{ .file_name = path, .line = 2, .column = 0 }));
- try printLineFromFileAnyOs(output_stream, .{ .file_name = path, .line = 1, .column = 0 });
+ try printLineFromFile(output_stream, .{ .file_name = path, .line = 1, .column = 0 });
try expectEqualStrings(("a" ** (3 * std.heap.page_size_max)) ++ "\n", aw.written());
aw.clearRetainingCapacity();
try writer.writeAll("a\na");
- try printLineFromFileAnyOs(output_stream, .{ .file_name = path, .line = 1, .column = 0 });
+ try printLineFromFile(output_stream, .{ .file_name = path, .line = 1, .column = 0 });
try expectEqualStrings(("a" ** (3 * std.heap.page_size_max)) ++ "a\n", aw.written());
aw.clearRetainingCapacity();
- try printLineFromFileAnyOs(output_stream, .{ .file_name = path, .line = 2, .column = 0 });
+ try printLineFromFile(output_stream, .{ .file_name = path, .line = 2, .column = 0 });
try expectEqualStrings("a\n", aw.written());
aw.clearRetainingCapacity();
}
@@ -1309,26 +1303,23 @@ test printLineFromFileAnyOs {
try writer.splatByteAll('\n', real_file_start);
try writer.writeAll("abc\ndef");
- try printLineFromFileAnyOs(output_stream, .{ .file_name = path, .line = real_file_start + 1, .column = 0 });
+ try printLineFromFile(output_stream, .{ .file_name = path, .line = real_file_start + 1, .column = 0 });
try expectEqualStrings("abc\n", aw.written());
aw.clearRetainingCapacity();
- try printLineFromFileAnyOs(output_stream, .{ .file_name = path, .line = real_file_start + 2, .column = 0 });
+ try printLineFromFile(output_stream, .{ .file_name = path, .line = real_file_start + 2, .column = 0 });
try expectEqualStrings("def\n", aw.written());
aw.clearRetainingCapacity();
}
}
/// TODO multithreaded awareness
-var debug_info_allocator: ?mem.Allocator = null;
-var debug_info_arena_allocator: std.heap.ArenaAllocator = undefined;
+var debug_info_arena: ?std.heap.ArenaAllocator = null;
fn getDebugInfoAllocator() mem.Allocator {
- if (debug_info_allocator) |a| return a;
-
- debug_info_arena_allocator = std.heap.ArenaAllocator.init(std.heap.page_allocator);
- const allocator = debug_info_arena_allocator.allocator();
- debug_info_allocator = allocator;
- return allocator;
+ if (debug_info_arena == null) {
+ debug_info_arena = .init(std.heap.page_allocator);
+ }
+ return debug_info_arena.?.allocator();
}
/// Whether or not the current target can print useful debug information when a segfault occurs.
lib/std/macho.zig
@@ -839,62 +839,112 @@ pub const nlist = extern struct {
pub const nlist_64 = extern struct {
n_strx: u32,
- n_type: u8,
+ n_type: packed union {
+ bits: packed struct(u8) {
+ ext: bool,
+ type: enum(u3) {
+ undf = 0,
+ abs = 1,
+ sect = 7,
+ pbud = 6,
+ indr = 5,
+ _,
+ },
+ pext: bool,
+ /// Any non-zero value indicates this is an stab, so the `stab` field should be used.
+ is_stab: u3,
+ },
+ stab: enum(u8) {
+ gsym = N_GSYM,
+ fname = N_FNAME,
+ fun = N_FUN,
+ stsym = N_STSYM,
+ lcsym = N_LCSYM,
+ bnsym = N_BNSYM,
+ ast = N_AST,
+ opt = N_OPT,
+ rsym = N_RSYM,
+ sline = N_SLINE,
+ ensym = N_ENSYM,
+ ssym = N_SSYM,
+ so = N_SO,
+ oso = N_OSO,
+ lsym = N_LSYM,
+ bincl = N_BINCL,
+ sol = N_SOL,
+ params = N_PARAMS,
+ version = N_VERSION,
+ olevel = N_OLEVEL,
+ psym = N_PSYM,
+ eincl = N_EINCL,
+ entry = N_ENTRY,
+ lbrac = N_LBRAC,
+ excl = N_EXCL,
+ rbrac = N_RBRAC,
+ bcomm = N_BCOMM,
+ ecomm = N_ECOMM,
+ ecoml = N_ECOML,
+ leng = N_LENG,
+ _,
+ },
+ },
n_sect: u8,
- n_desc: u16,
+ n_desc: packed struct(u16) {
+ _pad0: u3 = 0,
+ arm_thumb_def: bool,
+ _pad1: u1 = 0,
+ /// The meaning of this bit is contextual.
+ /// See `N_DESC_DISCARDED` and `N_NO_DEAD_STRIP`.
+ discarded_or_no_dead_strip: bool,
+ weak_ref: bool,
+ /// The meaning of this bit is contextual.
+ /// See `N_WEAK_DEF` and `N_REF_TO_WEAK`.
+ weak_def_or_ref_to_weak: bool,
+ symbol_resolver: bool,
+ alt_entry: bool,
+ _pad2: u6 = 0,
+ },
n_value: u64,
+ // MLUGG TODO DELETE
pub fn stab(sym: nlist_64) bool {
- return N_STAB & sym.n_type != 0;
- }
-
- pub fn pext(sym: nlist_64) bool {
- return N_PEXT & sym.n_type != 0;
- }
-
- pub fn ext(sym: nlist_64) bool {
- return N_EXT & sym.n_type != 0;
+ return sym.n_type.bits.is_stab != 0;
}
-
+ // MLUGG TODO DELETE
pub fn sect(sym: nlist_64) bool {
- const type_ = N_TYPE & sym.n_type;
- return type_ == N_SECT;
+ return sym.n_type.type == .sect;
}
-
+ // MLUGG TODO DELETE
pub fn undf(sym: nlist_64) bool {
- const type_ = N_TYPE & sym.n_type;
- return type_ == N_UNDF;
+ return sym.n_type.type == .undf;
}
-
+ // MLUGG TODO DELETE
pub fn indr(sym: nlist_64) bool {
- const type_ = N_TYPE & sym.n_type;
- return type_ == N_INDR;
+ return sym.n_type.type == .indr;
}
-
+ // MLUGG TODO DELETE
pub fn abs(sym: nlist_64) bool {
- const type_ = N_TYPE & sym.n_type;
- return type_ == N_ABS;
+ return sym.n_type.type == .abs;
}
-
+ // MLUGG TODO DELETE
pub fn weakDef(sym: nlist_64) bool {
- return sym.n_desc & N_WEAK_DEF != 0;
+ return sym.n_desc.weak_def_or_ref_to_weak;
}
-
+ // MLUGG TODO DELETE
pub fn weakRef(sym: nlist_64) bool {
- return sym.n_desc & N_WEAK_REF != 0;
+ return sym.n_desc.weak_ref;
}
-
+ // MLUGG TODO DELETE
pub fn discarded(sym: nlist_64) bool {
- return sym.n_desc & N_DESC_DISCARDED != 0;
+ return sym.n_desc.discarded_or_no_dead_strip;
}
-
+ // MLUGG TODO DELETE
pub fn noDeadStrip(sym: nlist_64) bool {
- return sym.n_desc & N_NO_DEAD_STRIP != 0;
+ return sym.n_desc.discarded_or_no_dead_strip;
}
pub fn tentative(sym: nlist_64) bool {
- if (!sym.undf()) return false;
- return sym.n_value != 0;
+ return sym.n_type.type == .undf and sym.n_value != 0;
}
};
@@ -2046,7 +2096,7 @@ pub const unwind_info_compressed_second_level_page_header = extern struct {
// encodings array
};
-pub const UnwindInfoCompressedEntry = packed struct {
+pub const UnwindInfoCompressedEntry = packed struct(u32) {
funcOffset: u24,
encodingIndex: u8,
};
src/link/Elf/eh_frame.zig
@@ -455,72 +455,23 @@ pub fn writeEhFrameRelocs(elf_file: *Elf, relocs: *std.array_list.Managed(elf.El
}
pub fn writeEhFrameHdr(elf_file: *Elf, writer: anytype) !void {
- const comp = elf_file.base.comp;
- const gpa = comp.gpa;
-
try writer.writeByte(1); // version
- try writer.writeByte(DW_EH_PE.pcrel | DW_EH_PE.sdata4);
- try writer.writeByte(DW_EH_PE.udata4);
- try writer.writeByte(DW_EH_PE.datarel | DW_EH_PE.sdata4);
+ try writer.writeByte(DW_EH_PE.pcrel | DW_EH_PE.sdata4); // eh_frame_ptr_enc
+ // Building the lookup table would be expensive work on every `flush` -- omit it.
+ try writer.writeByte(DW_EH_PE.omit); // fde_count_enc
+ try writer.writeByte(DW_EH_PE.omit); // table_enc
const shdrs = elf_file.sections.items(.shdr);
const eh_frame_shdr = shdrs[elf_file.section_indexes.eh_frame.?];
const eh_frame_hdr_shdr = shdrs[elf_file.section_indexes.eh_frame_hdr.?];
- const num_fdes = @as(u32, @intCast(@divExact(eh_frame_hdr_shdr.sh_size - eh_frame_hdr_header_size, 8)));
- const existing_size = existing_size: {
- const zo = elf_file.zigObjectPtr() orelse break :existing_size 0;
- const sym = zo.symbol(zo.eh_frame_index orelse break :existing_size 0);
- break :existing_size sym.atom(elf_file).?.size;
- };
try writer.writeInt(
u32,
@as(u32, @bitCast(@as(
i32,
- @truncate(@as(i64, @intCast(eh_frame_shdr.sh_addr + existing_size)) - @as(i64, @intCast(eh_frame_hdr_shdr.sh_addr)) - 4),
+ @truncate(@as(i64, @intCast(eh_frame_shdr.sh_addr)) - @as(i64, @intCast(eh_frame_hdr_shdr.sh_addr)) - 4),
))),
.little,
);
- try writer.writeInt(u32, num_fdes, .little);
-
- const Entry = extern struct {
- init_addr: u32,
- fde_addr: u32,
-
- pub fn lessThan(ctx: void, lhs: @This(), rhs: @This()) bool {
- _ = ctx;
- return lhs.init_addr < rhs.init_addr;
- }
- };
-
- var entries = std.array_list.Managed(Entry).init(gpa);
- defer entries.deinit();
- try entries.ensureTotalCapacityPrecise(num_fdes);
-
- for (elf_file.objects.items) |index| {
- const object = elf_file.file(index).?.object;
- for (object.fdes.items) |fde| {
- if (!fde.alive) continue;
-
- const relocs = fde.relocs(object);
- assert(relocs.len > 0); // Should this be an error? Things are completely broken anyhow if this trips...
- const rel = relocs[0];
- const ref = object.resolveSymbol(rel.r_sym(), elf_file);
- const sym = elf_file.symbol(ref).?;
- const P = @as(i64, @intCast(fde.address(elf_file)));
- const S = @as(i64, @intCast(sym.address(.{}, elf_file)));
- const A = rel.r_addend;
- entries.appendAssumeCapacity(.{
- .init_addr = @bitCast(@as(i32, @truncate(S + A - @as(i64, @intCast(eh_frame_hdr_shdr.sh_addr))))),
- .fde_addr = @as(
- u32,
- @bitCast(@as(i32, @truncate(P - @as(i64, @intCast(eh_frame_hdr_shdr.sh_addr))))),
- ),
- });
- }
- }
-
- std.mem.sort(Entry, entries.items, {}, Entry.lessThan);
- try writer.writeSliceEndian(Entry, entries.items, .little);
}
const eh_frame_hdr_header_size: usize = 12;