Commit b706949736
Changed files (4)
lib
std
debug
lib/std/debug/Dwarf/Unwind.zig
@@ -0,0 +1,645 @@
+sections: SectionArray = @splat(null),
+
+/// 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 {
+ data: []const u8,
+
+ pub const Id = enum {
+ debug_frame,
+ eh_frame,
+ eh_frame_hdr,
+ };
+};
+
+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;
+}
+
+/// 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 fn findEntry(
+ self: ExceptionFrameHeader,
+ eh_frame_len: usize,
+ eh_frame_hdr_ptr: usize,
+ pc: usize,
+ cie: *CommonInformationEntry,
+ fde: *FrameDescriptionEntry,
+ endian: Endian,
+ ) !void {
+ const entry_size = try entrySize(self.table_enc);
+
+ var left: usize = 0;
+ var len: usize = self.fde_count;
+ var fbr: Reader = .fixed(self.entries);
+
+ while (len > 1) {
+ const mid = left + len / 2;
+
+ 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();
+
+ 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();
+ }
+};
+
+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,
+ },
+ /// The entry's contents, not including the ID field
+ entry_bytes: []const u8,
+
+ /// 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);
+ }
+
+ /// 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,
+ .debug_frame => switch (unit_header.format) {
+ .@"32" => CommonInformationEntry.dwarf32_id,
+ .@"64" => CommonInformationEntry.dwarf64_id,
+ },
+ else => unreachable,
+ };
+
+ 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,
+ };
+ }
+};
+
+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,
+
+ 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,
+
+ pub fn isSignalFrame(self: CommonInformationEntry) bool {
+ for (self.aug_str) |c| if (c == 'S') return true;
+ return false;
+ }
+
+ pub fn addressesSignedWithBKey(self: CommonInformationEntry) bool {
+ for (self.aug_str) |c| if (c == 'B') return true;
+ return false;
+ }
+
+ pub fn mteTaggedFrame(self: CommonInformationEntry) bool {
+ for (self.aug_str) |c| if (c == 'G') return true;
+ return false;
+ }
+
+ /// 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`.
+ ///
+ /// `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,
+ ) !CommonInformationEntry {
+ if (addr_size_bytes > 8) return error.UnsupportedAddrSize;
+
+ var fbr: Reader = .fixed(cie_bytes);
+
+ const version = try fbr.takeByte();
+ switch (dwarf_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;
+ }
+
+ if (has_eh_data) {
+ // legacy data created by older versions of gcc - unsupported here
+ for (0..addr_size_bytes) |_| _ = try fbr.takeByte();
+ }
+
+ 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 initial_instructions = cie_bytes[fbr.seek..];
+ return .{
+ .length_offset = length_offset,
+ .version = version,
+ .address_size = address_size,
+ .format = format,
+ .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,
+ };
+ }
+};
+
+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(
+ 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..];
+ 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,
+ };
+ }
+};
+
+/// 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],
+ };
+
+ // No need to scan .eh_frame, we have a binary search table already
+ return;
+ }
+
+ try di.scanCieFdeInfo(allocator, base_address);
+}
+
+/// 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);
+ }
+ }
+}
+
+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 => .{
+ .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,
+ },
+ },
+ 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) },
+ 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,
+ };
+
+ const ptr: u64 = if (base) |b| switch (value) {
+ .signed => |s| @intCast(try std.math.add(i64, s, @as(i64, @intCast(b)))),
+ // 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,
+ };
+
+ 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)));
+ }
+}
+
+const Allocator = std.mem.Allocator;
+const assert = std.debug.assert;
+const bad = Dwarf.bad;
+const cast = std.math.cast;
+const DW = std.dwarf;
+const Dwarf = std.debug.Dwarf;
+const EH = DW.EH;
+const Endian = std.builtin.Endian;
+const Format = DW.Format;
+const maxInt = std.math.maxInt;
+const missing = Dwarf.missing;
+const Reader = std.Io.Reader;
+const std = @import("std");
+const Unwind = @This();
lib/std/debug/Dwarf.zig
@@ -16,7 +16,6 @@ const elf = std.elf;
const mem = std.mem;
const DW = std.dwarf;
const AT = DW.AT;
-const EH = DW.EH;
const FORM = DW.FORM;
const Format = DW.Format;
const RLE = DW.RLE;
@@ -34,13 +33,12 @@ const Dwarf = @This();
pub const expression = @import("Dwarf/expression.zig");
pub const abi = @import("Dwarf/abi.zig");
pub const call_frame = @import("Dwarf/call_frame.zig");
+pub const Unwind = @import("Dwarf/Unwind.zig");
/// Useful to temporarily enable while working on this file.
const debug_debug_mode = false;
-endian: Endian,
-sections: SectionArray = null_section_array,
-is_macho: bool,
+sections: SectionArray = @splat(null),
/// Filled later by the initializer
abbrev_table_list: ArrayList(Abbrev.Table) = .empty,
@@ -49,14 +47,6 @@ compile_unit_list: ArrayList(CompileUnit) = .empty,
/// Filled later by the initializer
func_list: ArrayList(Func) = .empty,
-/// 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: ArrayList(FrameDescriptionEntry) = .empty,
-
/// Populated by `populateRanges`.
ranges: ArrayList(Range) = .empty,
@@ -87,9 +77,6 @@ pub const Section = struct {
debug_rnglists,
debug_addr,
debug_names,
- debug_frame,
- eh_frame,
- eh_frame_hdr,
};
// For sections that are not memory mapped by the loader, this is an offset
@@ -258,13 +245,14 @@ pub const Die = struct {
fn getAttrAddr(
self: *const Die,
di: *const Dwarf,
+ endian: Endian,
id: u64,
- compile_unit: CompileUnit,
+ compile_unit: *const CompileUnit,
) error{ InvalidDebugInfo, MissingDebugInfo }!u64 {
const form_value = self.getAttr(id) orelse return error.MissingDebugInfo;
return switch (form_value.*) {
.addr => |value| value,
- .addrx => |index| di.readDebugAddr(compile_unit, index),
+ .addrx => |index| di.readDebugAddr(endian, compile_unit, index),
else => bad(),
};
}
@@ -294,9 +282,10 @@ pub const Die = struct {
pub fn getAttrString(
self: *const Die,
di: *Dwarf,
+ endian: Endian,
id: u64,
opt_str: ?[]const u8,
- compile_unit: CompileUnit,
+ compile_unit: *const CompileUnit,
) error{ InvalidDebugInfo, MissingDebugInfo }![]const u8 {
const form_value = self.getAttr(id) orelse return error.MissingDebugInfo;
switch (form_value.*) {
@@ -309,13 +298,13 @@ pub const Die = struct {
.@"32" => {
const byte_offset = compile_unit.str_offsets_base + 4 * index;
if (byte_offset + 4 > debug_str_offsets.len) return bad();
- const offset = mem.readInt(u32, debug_str_offsets[byte_offset..][0..4], di.endian);
+ const offset = mem.readInt(u32, debug_str_offsets[byte_offset..][0..4], endian);
return getStringGeneric(opt_str, offset);
},
.@"64" => {
const byte_offset = compile_unit.str_offsets_base + 8 * index;
if (byte_offset + 8 > debug_str_offsets.len) return bad();
- const offset = mem.readInt(u64, debug_str_offsets[byte_offset..][0..8], di.endian);
+ const offset = mem.readInt(u64, debug_str_offsets[byte_offset..][0..8], endian);
return getStringGeneric(opt_str, offset);
},
}
@@ -326,440 +315,17 @@ pub const Die = struct {
}
};
-/// 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 fn findEntry(
- self: ExceptionFrameHeader,
- eh_frame_len: usize,
- eh_frame_hdr_ptr: usize,
- pc: usize,
- cie: *CommonInformationEntry,
- fde: *FrameDescriptionEntry,
- endian: Endian,
- ) !void {
- const entry_size = try entrySize(self.table_enc);
-
- var left: usize = 0;
- var len: usize = self.fde_count;
- var fbr: Reader = .fixed(self.entries);
-
- while (len > 1) {
- const mid = left + len / 2;
-
- 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();
-
- 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();
- }
-};
-
-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,
- },
- /// The entry's contents, not including the ID field
- entry_bytes: []const u8,
-
- /// 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);
- }
-
- /// 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 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 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,
- .debug_frame => switch (unit_header.format) {
- .@"32" => CommonInformationEntry.dwarf32_id,
- .@"64" => CommonInformationEntry.dwarf64_id,
- },
- else => unreachable,
- };
-
- 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,
- };
- }
-};
-
-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,
-
- 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,
-
- pub fn isSignalFrame(self: CommonInformationEntry) bool {
- for (self.aug_str) |c| if (c == 'S') return true;
- return false;
- }
-
- pub fn addressesSignedWithBKey(self: CommonInformationEntry) bool {
- for (self.aug_str) |c| if (c == 'B') return true;
- return false;
- }
-
- pub fn mteTaggedFrame(self: CommonInformationEntry) bool {
- for (self.aug_str) |c| if (c == 'G') return true;
- return false;
- }
-
- /// 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`.
- ///
- /// `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,
- ) !CommonInformationEntry {
- if (addr_size_bytes > 8) return error.UnsupportedAddrSize;
-
- var fbr: Reader = .fixed(cie_bytes);
-
- const version = try fbr.takeByte();
- switch (dwarf_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;
- }
-
- if (has_eh_data) {
- // legacy data created by older versions of gcc - unsupported here
- for (0..addr_size_bytes) |_| _ = try fbr.takeByte();
- }
-
- 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 initial_instructions = cie_bytes[fbr.seek..];
- return .{
- .length_offset = length_offset,
- .version = version,
- .address_size = address_size,
- .format = format,
- .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,
- };
- }
-};
-
-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(
- 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..];
- 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,
- };
- }
-};
-
const num_sections = std.enums.directEnumArrayLen(Section.Id, 0);
pub const SectionArray = [num_sections]?Section;
-pub const null_section_array = [_]?Section{null} ** num_sections;
pub const OpenError = ScanError;
/// Initialize DWARF info. The caller has the responsibility to initialize most
/// the `Dwarf` fields before calling. `binary_mem` is the raw bytes of the
/// main binary file (not the secondary debug info file).
-pub fn open(d: *Dwarf, gpa: Allocator) OpenError!void {
- try d.scanAllFunctions(gpa);
- try d.scanAllCompileUnits(gpa);
+pub fn open(d: *Dwarf, gpa: Allocator, endian: Endian) OpenError!void {
+ try d.scanAllFunctions(gpa, endian);
+ try d.scanAllCompileUnits(gpa, endian);
}
const PcRange = struct {
@@ -825,31 +391,30 @@ pub const ScanError = error{
StreamTooLong,
} || Allocator.Error;
-fn scanAllFunctions(di: *Dwarf, allocator: Allocator) ScanError!void {
- const endian = di.endian;
- var fbr: Reader = .fixed(di.section(.debug_info).?);
+fn scanAllFunctions(di: *Dwarf, allocator: Allocator, endian: Endian) ScanError!void {
+ var fr: Reader = .fixed(di.section(.debug_info).?);
var this_unit_offset: u64 = 0;
- while (this_unit_offset < fbr.buffer.len) {
- fbr.seek = @intCast(this_unit_offset);
+ while (this_unit_offset < fr.buffer.len) {
+ fr.seek = @intCast(this_unit_offset);
- const unit_header = try readUnitHeader(&fbr, endian);
+ const unit_header = try readUnitHeader(&fr, endian);
if (unit_header.unit_length == 0) return;
const next_offset = unit_header.header_length + unit_header.unit_length;
- const version = try fbr.takeInt(u16, endian);
+ const version = try fr.takeInt(u16, endian);
if (version < 2 or version > 5) return bad();
var address_size: u8 = undefined;
var debug_abbrev_offset: u64 = undefined;
if (version >= 5) {
- const unit_type = try fbr.takeByte();
+ const unit_type = try fr.takeByte();
if (unit_type != DW.UT.compile) return bad();
- address_size = try fbr.takeByte();
- debug_abbrev_offset = try readAddress(&fbr, unit_header.format, endian);
+ address_size = try fr.takeByte();
+ debug_abbrev_offset = try readAddress(&fr, unit_header.format, endian);
} else {
- debug_abbrev_offset = try readAddress(&fbr, unit_header.format, endian);
- address_size = try fbr.takeByte();
+ debug_abbrev_offset = try readAddress(&fr, unit_header.format, endian);
+ address_size = try fr.takeByte();
}
if (address_size != @sizeOf(usize)) return bad();
@@ -890,12 +455,12 @@ fn scanAllFunctions(di: *Dwarf, allocator: Allocator) ScanError!void {
};
while (true) {
- fbr.seek = std.mem.indexOfNonePos(u8, fbr.buffer, fbr.seek, &.{
+ fr.seek = std.mem.indexOfNonePos(u8, fr.buffer, fr.seek, &.{
zig_padding_abbrev_code, 0,
- }) orelse fbr.buffer.len;
- if (fbr.seek >= next_unit_pos) break;
+ }) orelse fr.buffer.len;
+ if (fr.seek >= next_unit_pos) break;
var die_obj = (try parseDie(
- &fbr,
+ &fr,
attrs_bufs[0],
abbrev_table,
unit_header.format,
@@ -920,30 +485,30 @@ fn scanAllFunctions(di: *Dwarf, allocator: Allocator) ScanError!void {
// Prevent endless loops
for (0..3) |_| {
if (this_die_obj.getAttr(AT.name)) |_| {
- break :x try this_die_obj.getAttrString(di, AT.name, di.section(.debug_str), compile_unit);
+ break :x try this_die_obj.getAttrString(di, endian, AT.name, di.section(.debug_str), &compile_unit);
} else if (this_die_obj.getAttr(AT.abstract_origin)) |_| {
- const after_die_offset = fbr.seek;
- defer fbr.seek = after_die_offset;
+ const after_die_offset = fr.seek;
+ defer fr.seek = after_die_offset;
// Follow the DIE it points to and repeat
const ref_offset = try this_die_obj.getAttrRef(AT.abstract_origin, this_unit_offset, next_offset);
- fbr.seek = @intCast(ref_offset);
+ fr.seek = @intCast(ref_offset);
this_die_obj = (try parseDie(
- &fbr,
+ &fr,
attrs_bufs[2],
abbrev_table, // wrong abbrev table for different cu
unit_header.format,
endian,
)) orelse return bad();
} else if (this_die_obj.getAttr(AT.specification)) |_| {
- const after_die_offset = fbr.seek;
- defer fbr.seek = after_die_offset;
+ const after_die_offset = fr.seek;
+ defer fr.seek = after_die_offset;
// Follow the DIE it points to and repeat
const ref_offset = try this_die_obj.getAttrRef(AT.specification, this_unit_offset, next_offset);
- fbr.seek = @intCast(ref_offset);
+ fr.seek = @intCast(ref_offset);
this_die_obj = (try parseDie(
- &fbr,
+ &fr,
attrs_bufs[2],
abbrev_table, // wrong abbrev table for different cu
unit_header.format,
@@ -957,7 +522,7 @@ fn scanAllFunctions(di: *Dwarf, allocator: Allocator) ScanError!void {
break :x null;
};
- var range_added = if (die_obj.getAttrAddr(di, AT.low_pc, compile_unit)) |low_pc| blk: {
+ var range_added = if (die_obj.getAttrAddr(di, endian, AT.low_pc, &compile_unit)) |low_pc| blk: {
if (die_obj.getAttr(AT.high_pc)) |high_pc_value| {
const pc_end = switch (high_pc_value.*) {
.addr => |value| value,
@@ -983,7 +548,7 @@ fn scanAllFunctions(di: *Dwarf, allocator: Allocator) ScanError!void {
};
if (die_obj.getAttr(AT.ranges)) |ranges_value| blk: {
- var iter = DebugRangeIterator.init(ranges_value, di, &compile_unit) catch |err| {
+ var iter = DebugRangeIterator.init(ranges_value, di, endian, &compile_unit) catch |err| {
if (err != error.MissingDebugInfo) return err;
break :blk;
};
@@ -1015,34 +580,33 @@ fn scanAllFunctions(di: *Dwarf, allocator: Allocator) ScanError!void {
}
}
-fn scanAllCompileUnits(di: *Dwarf, allocator: Allocator) ScanError!void {
- const endian = di.endian;
- var fbr: Reader = .fixed(di.section(.debug_info).?);
+fn scanAllCompileUnits(di: *Dwarf, allocator: Allocator, endian: Endian) ScanError!void {
+ var fr: Reader = .fixed(di.section(.debug_info).?);
var this_unit_offset: u64 = 0;
var attrs_buf = std.array_list.Managed(Die.Attr).init(allocator);
defer attrs_buf.deinit();
- while (this_unit_offset < fbr.buffer.len) {
- fbr.seek = @intCast(this_unit_offset);
+ while (this_unit_offset < fr.buffer.len) {
+ fr.seek = @intCast(this_unit_offset);
- const unit_header = try readUnitHeader(&fbr, endian);
+ const unit_header = try readUnitHeader(&fr, endian);
if (unit_header.unit_length == 0) return;
const next_offset = unit_header.header_length + unit_header.unit_length;
- const version = try fbr.takeInt(u16, endian);
+ const version = try fr.takeInt(u16, endian);
if (version < 2 or version > 5) return bad();
var address_size: u8 = undefined;
var debug_abbrev_offset: u64 = undefined;
if (version >= 5) {
- const unit_type = try fbr.takeByte();
+ const unit_type = try fr.takeByte();
if (unit_type != UT.compile) return bad();
- address_size = try fbr.takeByte();
- debug_abbrev_offset = try readAddress(&fbr, unit_header.format, endian);
+ address_size = try fr.takeByte();
+ debug_abbrev_offset = try readAddress(&fr, unit_header.format, endian);
} else {
- debug_abbrev_offset = try readAddress(&fbr, unit_header.format, endian);
- address_size = try fbr.takeByte();
+ debug_abbrev_offset = try readAddress(&fr, unit_header.format, endian);
+ address_size = try fr.takeByte();
}
if (address_size != @sizeOf(usize)) return bad();
@@ -1055,7 +619,7 @@ fn scanAllCompileUnits(di: *Dwarf, allocator: Allocator) ScanError!void {
try attrs_buf.resize(max_attrs);
var compile_unit_die = (try parseDie(
- &fbr,
+ &fr,
attrs_buf.items,
abbrev_table,
unit_header.format,
@@ -1080,7 +644,7 @@ fn scanAllCompileUnits(di: *Dwarf, allocator: Allocator) ScanError!void {
};
compile_unit.pc_range = x: {
- if (compile_unit_die.getAttrAddr(di, AT.low_pc, compile_unit)) |low_pc| {
+ if (compile_unit_die.getAttrAddr(di, endian, AT.low_pc, &compile_unit)) |low_pc| {
if (compile_unit_die.getAttr(AT.high_pc)) |high_pc_value| {
const pc_end = switch (high_pc_value.*) {
.addr => |value| value,
@@ -1144,10 +708,11 @@ const DebugRangeIterator = struct {
base_address: u64,
section_type: Section.Id,
di: *const Dwarf,
+ endian: Endian,
compile_unit: *const CompileUnit,
- fbr: Reader,
+ fr: Reader,
- pub fn init(ranges_value: *const FormValue, di: *const Dwarf, compile_unit: *const CompileUnit) !@This() {
+ pub fn init(ranges_value: *const FormValue, di: *const Dwarf, endian: Endian, compile_unit: *const CompileUnit) !@This() {
const section_type = if (compile_unit.version >= 5) Section.Id.debug_rnglists else Section.Id.debug_ranges;
const debug_ranges = di.section(section_type) orelse return error.MissingDebugInfo;
@@ -1158,13 +723,13 @@ const DebugRangeIterator = struct {
.@"32" => {
const offset_loc = @as(usize, @intCast(compile_unit.rnglists_base + 4 * idx));
if (offset_loc + 4 > debug_ranges.len) return bad();
- const offset = mem.readInt(u32, debug_ranges[offset_loc..][0..4], di.endian);
+ const offset = mem.readInt(u32, debug_ranges[offset_loc..][0..4], endian);
break :off compile_unit.rnglists_base + offset;
},
.@"64" => {
const offset_loc = @as(usize, @intCast(compile_unit.rnglists_base + 8 * idx));
if (offset_loc + 8 > debug_ranges.len) return bad();
- const offset = mem.readInt(u64, debug_ranges[offset_loc..][0..8], di.endian);
+ const offset = mem.readInt(u64, debug_ranges[offset_loc..][0..8], endian);
break :off compile_unit.rnglists_base + offset;
},
}
@@ -1176,42 +741,43 @@ const DebugRangeIterator = struct {
// specified by DW_AT.low_pc or to some other value encoded
// in the list itself.
// If no starting value is specified use zero.
- const base_address = compile_unit.die.getAttrAddr(di, AT.low_pc, compile_unit.*) catch |err| switch (err) {
+ const base_address = compile_unit.die.getAttrAddr(di, endian, AT.low_pc, compile_unit) catch |err| switch (err) {
error.MissingDebugInfo => 0,
else => return err,
};
- var fbr: Reader = .fixed(debug_ranges);
- fbr.seek = cast(usize, ranges_offset) orelse return bad();
+ var fr: Reader = .fixed(debug_ranges);
+ fr.seek = cast(usize, ranges_offset) orelse return bad();
return .{
.base_address = base_address,
.section_type = section_type,
.di = di,
+ .endian = endian,
.compile_unit = compile_unit,
- .fbr = fbr,
+ .fr = fr,
};
}
// Returns the next range in the list, or null if the end was reached.
pub fn next(self: *@This()) !?PcRange {
- const endian = self.di.endian;
+ const endian = self.endian;
switch (self.section_type) {
.debug_rnglists => {
- const kind = try self.fbr.takeByte();
+ const kind = try self.fr.takeByte();
switch (kind) {
RLE.end_of_list => return null,
RLE.base_addressx => {
- const index = try self.fbr.takeLeb128(usize);
- self.base_address = try self.di.readDebugAddr(self.compile_unit.*, index);
+ const index = try self.fr.takeLeb128(usize);
+ self.base_address = try self.di.readDebugAddr(endian, self.compile_unit, index);
return try self.next();
},
RLE.startx_endx => {
- const start_index = try self.fbr.takeLeb128(usize);
- const start_addr = try self.di.readDebugAddr(self.compile_unit.*, start_index);
+ const start_index = try self.fr.takeLeb128(usize);
+ const start_addr = try self.di.readDebugAddr(endian, self.compile_unit, start_index);
- const end_index = try self.fbr.takeLeb128(usize);
- const end_addr = try self.di.readDebugAddr(self.compile_unit.*, end_index);
+ const end_index = try self.fr.takeLeb128(usize);
+ const end_addr = try self.di.readDebugAddr(endian, self.compile_unit, end_index);
return .{
.start = start_addr,
@@ -1219,10 +785,10 @@ const DebugRangeIterator = struct {
};
},
RLE.startx_length => {
- const start_index = try self.fbr.takeLeb128(usize);
- const start_addr = try self.di.readDebugAddr(self.compile_unit.*, start_index);
+ const start_index = try self.fr.takeLeb128(usize);
+ const start_addr = try self.di.readDebugAddr(endian, self.compile_unit, start_index);
- const len = try self.fbr.takeLeb128(usize);
+ const len = try self.fr.takeLeb128(usize);
const end_addr = start_addr + len;
return .{
@@ -1231,8 +797,8 @@ const DebugRangeIterator = struct {
};
},
RLE.offset_pair => {
- const start_addr = try self.fbr.takeLeb128(usize);
- const end_addr = try self.fbr.takeLeb128(usize);
+ const start_addr = try self.fr.takeLeb128(usize);
+ const end_addr = try self.fr.takeLeb128(usize);
// This is the only kind that uses the base address
return .{
@@ -1241,12 +807,12 @@ const DebugRangeIterator = struct {
};
},
RLE.base_address => {
- self.base_address = try self.fbr.takeInt(usize, endian);
+ self.base_address = try self.fr.takeInt(usize, endian);
return try self.next();
},
RLE.start_end => {
- const start_addr = try self.fbr.takeInt(usize, endian);
- const end_addr = try self.fbr.takeInt(usize, endian);
+ const start_addr = try self.fr.takeInt(usize, endian);
+ const end_addr = try self.fr.takeInt(usize, endian);
return .{
.start = start_addr,
@@ -1254,8 +820,8 @@ const DebugRangeIterator = struct {
};
},
RLE.start_length => {
- const start_addr = try self.fbr.takeInt(usize, endian);
- const len = try self.fbr.takeLeb128(usize);
+ const start_addr = try self.fr.takeInt(usize, endian);
+ const len = try self.fr.takeLeb128(usize);
const end_addr = start_addr + len;
return .{
@@ -1267,8 +833,8 @@ const DebugRangeIterator = struct {
}
},
.debug_ranges => {
- const start_addr = try self.fbr.takeInt(usize, endian);
- const end_addr = try self.fbr.takeInt(usize, endian);
+ const start_addr = try self.fr.takeInt(usize, endian);
+ const end_addr = try self.fr.takeInt(usize, endian);
if (start_addr == 0 and end_addr == 0) return null;
// This entry selects a new value for the base address
@@ -1288,14 +854,14 @@ const DebugRangeIterator = struct {
};
/// TODO: change this to binary searching the sorted compile unit list
-pub fn findCompileUnit(di: *const Dwarf, target_address: u64) !*CompileUnit {
+pub fn findCompileUnit(di: *const Dwarf, endian: Endian, target_address: u64) !*CompileUnit {
for (di.compile_unit_list.items) |*compile_unit| {
if (compile_unit.pc_range) |range| {
if (target_address >= range.start and target_address < range.end) return compile_unit;
}
const ranges_value = compile_unit.die.getAttr(AT.ranges) orelse continue;
- var iter = DebugRangeIterator.init(ranges_value, di, compile_unit) catch continue;
+ var iter = DebugRangeIterator.init(ranges_value, di, endian, compile_unit) catch continue;
while (try iter.next()) |range| {
if (target_address >= range.start and target_address < range.end) return compile_unit;
}
@@ -1320,8 +886,8 @@ fn getAbbrevTable(di: *Dwarf, allocator: Allocator, abbrev_offset: u64) !*const
}
fn parseAbbrevTable(di: *Dwarf, allocator: Allocator, offset: u64) !Abbrev.Table {
- var fbr: Reader = .fixed(di.section(.debug_abbrev).?);
- fbr.seek = cast(usize, offset) orelse return bad();
+ var fr: Reader = .fixed(di.section(.debug_abbrev).?);
+ fr.seek = cast(usize, offset) orelse return bad();
var abbrevs = std.array_list.Managed(Abbrev).init(allocator);
defer {
@@ -1335,20 +901,20 @@ fn parseAbbrevTable(di: *Dwarf, allocator: Allocator, offset: u64) !Abbrev.Table
defer attrs.deinit();
while (true) {
- const code = try fbr.takeLeb128(u64);
+ const code = try fr.takeLeb128(u64);
if (code == 0) break;
- const tag_id = try fbr.takeLeb128(u64);
- const has_children = (try fbr.takeByte()) == DW.CHILDREN.yes;
+ const tag_id = try fr.takeLeb128(u64);
+ const has_children = (try fr.takeByte()) == DW.CHILDREN.yes;
while (true) {
- const attr_id = try fbr.takeLeb128(u64);
- const form_id = try fbr.takeLeb128(u64);
+ const attr_id = try fr.takeLeb128(u64);
+ const form_id = try fr.takeLeb128(u64);
if (attr_id == 0 and form_id == 0) break;
try attrs.append(.{
.id = attr_id,
.form_id = form_id,
.payload = switch (form_id) {
- FORM.implicit_const => try fbr.takeLeb128(i64),
+ FORM.implicit_const => try fr.takeLeb128(i64),
else => undefined,
},
});
@@ -1369,20 +935,20 @@ fn parseAbbrevTable(di: *Dwarf, allocator: Allocator, offset: u64) !Abbrev.Table
}
fn parseDie(
- fbr: *Reader,
+ fr: *Reader,
attrs_buf: []Die.Attr,
abbrev_table: *const Abbrev.Table,
format: Format,
endian: Endian,
) ScanError!?Die {
- const abbrev_code = try fbr.takeLeb128(u64);
+ const abbrev_code = try fr.takeLeb128(u64);
if (abbrev_code == 0) return null;
const table_entry = abbrev_table.get(abbrev_code) orelse return bad();
const attrs = attrs_buf[0..table_entry.attrs.len];
for (attrs, table_entry.attrs) |*result_attr, attr| result_attr.* = .{
.id = attr.id,
- .value = try parseFormValue(fbr, attr.form_id, format, endian, attr.payload),
+ .value = try parseFormValue(fr, attr.form_id, format, endian, attr.payload),
};
return .{
.tag_id = table_entry.tag_id,
@@ -1392,25 +958,24 @@ fn parseDie(
}
/// Ensures that addresses in the returned LineTable are monotonically increasing.
-fn runLineNumberProgram(d: *Dwarf, gpa: Allocator, compile_unit: *CompileUnit) !CompileUnit.SrcLocCache {
- const endian = d.endian;
- const compile_unit_cwd = try compile_unit.die.getAttrString(d, AT.comp_dir, d.section(.debug_line_str), compile_unit.*);
+fn runLineNumberProgram(d: *Dwarf, gpa: Allocator, endian: Endian, compile_unit: *const CompileUnit) !CompileUnit.SrcLocCache {
+ const compile_unit_cwd = try compile_unit.die.getAttrString(d, endian, AT.comp_dir, d.section(.debug_line_str), compile_unit);
const line_info_offset = try compile_unit.die.getAttrSecOffset(AT.stmt_list);
- var fbr: Reader = .fixed(d.section(.debug_line).?);
- fbr.seek = @intCast(line_info_offset);
+ var fr: Reader = .fixed(d.section(.debug_line).?);
+ fr.seek = @intCast(line_info_offset);
- const unit_header = try readUnitHeader(&fbr, endian);
+ const unit_header = try readUnitHeader(&fr, endian);
if (unit_header.unit_length == 0) return missing();
const next_offset = unit_header.header_length + unit_header.unit_length;
- const version = try fbr.takeInt(u16, endian);
+ const version = try fr.takeInt(u16, endian);
if (version < 2) return bad();
const addr_size: u8, const seg_size: u8 = if (version >= 5) .{
- try fbr.takeByte(),
- try fbr.takeByte(),
+ try fr.takeByte(),
+ try fr.takeByte(),
} else .{
switch (unit_header.format) {
.@"32" => 4,
@@ -1421,26 +986,26 @@ fn runLineNumberProgram(d: *Dwarf, gpa: Allocator, compile_unit: *CompileUnit) !
_ = addr_size;
_ = seg_size;
- const prologue_length = try readAddress(&fbr, unit_header.format, endian);
- const prog_start_offset = fbr.seek + prologue_length;
+ const prologue_length = try readAddress(&fr, unit_header.format, endian);
+ const prog_start_offset = fr.seek + prologue_length;
- const minimum_instruction_length = try fbr.takeByte();
+ const minimum_instruction_length = try fr.takeByte();
if (minimum_instruction_length == 0) return bad();
if (version >= 4) {
- const maximum_operations_per_instruction = try fbr.takeByte();
+ const maximum_operations_per_instruction = try fr.takeByte();
_ = maximum_operations_per_instruction;
}
- const default_is_stmt = (try fbr.takeByte()) != 0;
- const line_base = try fbr.takeByteSigned();
+ const default_is_stmt = (try fr.takeByte()) != 0;
+ const line_base = try fr.takeByteSigned();
- const line_range = try fbr.takeByte();
+ const line_range = try fr.takeByte();
if (line_range == 0) return bad();
- const opcode_base = try fbr.takeByte();
+ const opcode_base = try fr.takeByte();
- const standard_opcode_lengths = try fbr.take(opcode_base - 1);
+ const standard_opcode_lengths = try fr.take(opcode_base - 1);
var directories: ArrayList(FileEntry) = .empty;
defer directories.deinit(gpa);
@@ -1451,17 +1016,17 @@ fn runLineNumberProgram(d: *Dwarf, gpa: Allocator, compile_unit: *CompileUnit) !
try directories.append(gpa, .{ .path = compile_unit_cwd });
while (true) {
- const dir = try fbr.takeSentinel(0);
+ const dir = try fr.takeSentinel(0);
if (dir.len == 0) break;
try directories.append(gpa, .{ .path = dir });
}
while (true) {
- const file_name = try fbr.takeSentinel(0);
+ const file_name = try fr.takeSentinel(0);
if (file_name.len == 0) break;
- const dir_index = try fbr.takeLeb128(u32);
- const mtime = try fbr.takeLeb128(u64);
- const size = try fbr.takeLeb128(u64);
+ const dir_index = try fr.takeLeb128(u32);
+ const mtime = try fr.takeLeb128(u64);
+ const size = try fr.takeLeb128(u64);
try file_entries.append(gpa, .{
.path = file_name,
.dir_index = dir_index,
@@ -1476,21 +1041,21 @@ fn runLineNumberProgram(d: *Dwarf, gpa: Allocator, compile_unit: *CompileUnit) !
};
{
var dir_ent_fmt_buf: [10]FileEntFmt = undefined;
- const directory_entry_format_count = try fbr.takeByte();
+ const directory_entry_format_count = try fr.takeByte();
if (directory_entry_format_count > dir_ent_fmt_buf.len) return bad();
for (dir_ent_fmt_buf[0..directory_entry_format_count]) |*ent_fmt| {
ent_fmt.* = .{
- .content_type_code = try fbr.takeLeb128(u8),
- .form_code = try fbr.takeLeb128(u16),
+ .content_type_code = try fr.takeLeb128(u8),
+ .form_code = try fr.takeLeb128(u16),
};
}
- const directories_count = try fbr.takeLeb128(usize);
+ const directories_count = try fr.takeLeb128(usize);
for (try directories.addManyAsSlice(gpa, directories_count)) |*e| {
e.* = .{ .path = &.{} };
for (dir_ent_fmt_buf[0..directory_entry_format_count]) |ent_fmt| {
- const form_value = try parseFormValue(&fbr, ent_fmt.form_code, unit_header.format, endian, null);
+ const form_value = try parseFormValue(&fr, ent_fmt.form_code, unit_header.format, endian, null);
switch (ent_fmt.content_type_code) {
DW.LNCT.path => e.path = try form_value.getString(d.*),
DW.LNCT.directory_index => e.dir_index = try form_value.getUInt(u32),
@@ -1507,22 +1072,22 @@ fn runLineNumberProgram(d: *Dwarf, gpa: Allocator, compile_unit: *CompileUnit) !
}
var file_ent_fmt_buf: [10]FileEntFmt = undefined;
- const file_name_entry_format_count = try fbr.takeByte();
+ const file_name_entry_format_count = try fr.takeByte();
if (file_name_entry_format_count > file_ent_fmt_buf.len) return bad();
for (file_ent_fmt_buf[0..file_name_entry_format_count]) |*ent_fmt| {
ent_fmt.* = .{
- .content_type_code = try fbr.takeLeb128(u16),
- .form_code = try fbr.takeLeb128(u16),
+ .content_type_code = try fr.takeLeb128(u16),
+ .form_code = try fr.takeLeb128(u16),
};
}
- const file_names_count = try fbr.takeLeb128(usize);
+ const file_names_count = try fr.takeLeb128(usize);
try file_entries.ensureUnusedCapacity(gpa, file_names_count);
for (try file_entries.addManyAsSlice(gpa, file_names_count)) |*e| {
e.* = .{ .path = &.{} };
for (file_ent_fmt_buf[0..file_name_entry_format_count]) |ent_fmt| {
- const form_value = try parseFormValue(&fbr, ent_fmt.form_code, unit_header.format, endian, null);
+ const form_value = try parseFormValue(&fr, ent_fmt.form_code, unit_header.format, endian, null);
switch (ent_fmt.content_type_code) {
DW.LNCT.path => e.path = try form_value.getString(d.*),
DW.LNCT.directory_index => e.dir_index = try form_value.getUInt(u32),
@@ -1542,17 +1107,17 @@ fn runLineNumberProgram(d: *Dwarf, gpa: Allocator, compile_unit: *CompileUnit) !
var line_table: CompileUnit.SrcLocCache.LineTable = .{};
errdefer line_table.deinit(gpa);
- fbr.seek = @intCast(prog_start_offset);
+ fr.seek = @intCast(prog_start_offset);
const next_unit_pos = line_info_offset + next_offset;
- while (fbr.seek < next_unit_pos) {
- const opcode = try fbr.takeByte();
+ while (fr.seek < next_unit_pos) {
+ const opcode = try fr.takeByte();
if (opcode == DW.LNS.extended_op) {
- const op_size = try fbr.takeLeb128(u64);
+ const op_size = try fr.takeLeb128(u64);
if (op_size < 1) return bad();
- const sub_op = try fbr.takeByte();
+ const sub_op = try fr.takeByte();
switch (sub_op) {
DW.LNE.end_sequence => {
// The row being added here is an "end" address, meaning
@@ -1571,14 +1136,14 @@ fn runLineNumberProgram(d: *Dwarf, gpa: Allocator, compile_unit: *CompileUnit) !
prog.reset();
},
DW.LNE.set_address => {
- const addr = try fbr.takeInt(usize, endian);
+ const addr = try fr.takeInt(usize, endian);
prog.address = addr;
},
DW.LNE.define_file => {
- const path = try fbr.takeSentinel(0);
- const dir_index = try fbr.takeLeb128(u32);
- const mtime = try fbr.takeLeb128(u64);
- const size = try fbr.takeLeb128(u64);
+ const path = try fr.takeSentinel(0);
+ const dir_index = try fr.takeLeb128(u32);
+ const mtime = try fr.takeLeb128(u64);
+ const size = try fr.takeLeb128(u64);
try file_entries.append(gpa, .{
.path = path,
.dir_index = dir_index,
@@ -1586,7 +1151,7 @@ fn runLineNumberProgram(d: *Dwarf, gpa: Allocator, compile_unit: *CompileUnit) !
.size = size,
});
},
- else => try fbr.discardAll64(op_size - 1),
+ else => try fr.discardAll64(op_size - 1),
}
} else if (opcode >= opcode_base) {
// special opcodes
@@ -1604,19 +1169,19 @@ fn runLineNumberProgram(d: *Dwarf, gpa: Allocator, compile_unit: *CompileUnit) !
prog.basic_block = false;
},
DW.LNS.advance_pc => {
- const arg = try fbr.takeLeb128(usize);
+ const arg = try fr.takeLeb128(usize);
prog.address += arg * minimum_instruction_length;
},
DW.LNS.advance_line => {
- const arg = try fbr.takeLeb128(i64);
+ const arg = try fr.takeLeb128(i64);
prog.line += arg;
},
DW.LNS.set_file => {
- const arg = try fbr.takeLeb128(usize);
+ const arg = try fr.takeLeb128(usize);
prog.file = arg;
},
DW.LNS.set_column => {
- const arg = try fbr.takeLeb128(u64);
+ const arg = try fr.takeLeb128(u64);
prog.column = arg;
},
DW.LNS.negate_stmt => {
@@ -1630,13 +1195,13 @@ fn runLineNumberProgram(d: *Dwarf, gpa: Allocator, compile_unit: *CompileUnit) !
prog.address += inc_addr;
},
DW.LNS.fixed_advance_pc => {
- const arg = try fbr.takeInt(u16, endian);
+ const arg = try fr.takeInt(u16, endian);
prog.address += arg;
},
DW.LNS.set_prologue_end => {},
else => {
if (opcode - 1 >= standard_opcode_lengths.len) return bad();
- try fbr.discardAll(standard_opcode_lengths[opcode - 1]);
+ try fr.discardAll(standard_opcode_lengths[opcode - 1]);
},
}
}
@@ -1661,18 +1226,19 @@ fn runLineNumberProgram(d: *Dwarf, gpa: Allocator, compile_unit: *CompileUnit) !
};
}
-pub fn populateSrcLocCache(d: *Dwarf, gpa: Allocator, cu: *CompileUnit) ScanError!void {
+pub fn populateSrcLocCache(d: *Dwarf, gpa: Allocator, endian: Endian, cu: *CompileUnit) ScanError!void {
if (cu.src_loc_cache != null) return;
- cu.src_loc_cache = try runLineNumberProgram(d, gpa, cu);
+ cu.src_loc_cache = try d.runLineNumberProgram(gpa, endian, cu);
}
pub fn getLineNumberInfo(
d: *Dwarf,
gpa: Allocator,
+ endian: Endian,
compile_unit: *CompileUnit,
target_address: u64,
) !std.debug.SourceLocation {
- try populateSrcLocCache(d, gpa, compile_unit);
+ try d.populateSrcLocCache(gpa, endian, compile_unit);
const slc = &compile_unit.src_loc_cache.?;
const entry = try slc.findSource(target_address);
const file_index = entry.file - @intFromBool(slc.version < 5);
@@ -1696,7 +1262,7 @@ fn getLineString(di: Dwarf, offset: u64) ![:0]const u8 {
return getStringGeneric(di.section(.debug_line_str), offset);
}
-fn readDebugAddr(di: Dwarf, compile_unit: CompileUnit, index: u64) !u64 {
+fn readDebugAddr(di: Dwarf, endian: Endian, compile_unit: *const CompileUnit, index: u64) !u64 {
const debug_addr = di.section(.debug_addr) orelse return bad();
// addr_base points to the first item after the header, however we
@@ -1705,7 +1271,7 @@ fn readDebugAddr(di: Dwarf, compile_unit: CompileUnit, index: u64) !u64 {
// The header is 8 or 12 bytes depending on is_64.
if (compile_unit.addr_base < 8) return bad();
- const version = mem.readInt(u16, debug_addr[compile_unit.addr_base - 4 ..][0..2], di.endian);
+ const version = mem.readInt(u16, debug_addr[compile_unit.addr_base - 4 ..][0..2], endian);
if (version != 5) return bad();
const addr_size = debug_addr[compile_unit.addr_base - 2];
@@ -1715,113 +1281,13 @@ fn readDebugAddr(di: Dwarf, compile_unit: CompileUnit, index: u64) !u64 {
if (byte_offset + addr_size > debug_addr.len) return bad();
return switch (addr_size) {
1 => debug_addr[byte_offset],
- 2 => mem.readInt(u16, debug_addr[byte_offset..][0..2], di.endian),
- 4 => mem.readInt(u32, debug_addr[byte_offset..][0..4], di.endian),
- 8 => mem.readInt(u64, debug_addr[byte_offset..][0..8], di.endian),
+ 2 => mem.readInt(u16, debug_addr[byte_offset..][0..2], endian),
+ 4 => mem.readInt(u32, debug_addr[byte_offset..][0..4], endian),
+ 8 => mem.readInt(u64, debug_addr[byte_offset..][0..8], endian),
else => bad(),
};
}
-/// 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],
- };
-
- // No need to scan .eh_frame, we have a binary search table already
- return;
- }
-
- try di.scanCieFdeInfo(allocator, base_address);
-}
-
-/// Scan `.eh_frame` and `.debug_frame` and build a sorted list of FDEs for binary searching during
-/// unwinding.
-pub fn scanCieFdeInfo(di: *Dwarf, allocator: Allocator, base_address: usize) !void {
- const endian = di.endian;
- const frame_sections = [2]Section.Id{ .eh_frame, .debug_frame };
- for (frame_sections) |frame_section| {
- if (di.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,
- di.sectionVirtualOffset(frame_section, base_address).?,
- true,
- entry_header.format,
- frame_section,
- entry_header.length_offset,
- @sizeOf(usize),
- di.endian,
- );
- try di.cie_map.put(allocator, entry_header.length_offset, cie);
- },
- .fde => |cie_offset| {
- const cie = di.cie_map.get(cie_offset) orelse return bad();
- const fde = try FrameDescriptionEntry.parse(
- entry_header.entry_bytes,
- di.sectionVirtualOffset(frame_section, base_address).?,
- true,
- cie,
- @sizeOf(usize),
- di.endian,
- );
- try di.fde_list.append(allocator, fde);
- },
- .terminator => break,
- }
- }
-
- std.mem.sortUnstable(FrameDescriptionEntry, di.fde_list.items, {}, struct {
- fn lessThan(ctx: void, a: FrameDescriptionEntry, b: FrameDescriptionEntry) bool {
- _ = ctx;
- return a.pc_begin < b.pc_begin;
- }
- }.lessThan);
- }
- }
-}
-
fn parseFormValue(
r: *Reader,
form_id: u64,
@@ -1946,7 +1412,7 @@ const UnitHeader = struct {
unit_length: u64,
};
-fn readUnitHeader(r: *Reader, endian: Endian) ScanError!UnitHeader {
+pub fn readUnitHeader(r: *Reader, endian: Endian) ScanError!UnitHeader {
return switch (try r.takeInt(u32, endian)) {
0...0xfffffff0 - 1 => |unit_length| .{
.format = .@"32",
@@ -1986,7 +1452,7 @@ fn invalidDebugInfoDetected() void {
if (debug_debug_mode) @panic("bad dwarf");
}
-fn missing() error{MissingDebugInfo} {
+pub fn missing() error{MissingDebugInfo} {
if (debug_debug_mode) @panic("missing dwarf");
return error.MissingDebugInfo;
}
@@ -2000,94 +1466,39 @@ fn getStringGeneric(opt_str: ?[]const u8, offset: u64) ![:0]const u8 {
return str[casted_offset..last :0];
}
-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 => .{
- .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,
- },
- },
- 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) },
- 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,
- };
+pub const ElfModule = struct {
+ unwind: Dwarf.Unwind,
+ dwarf: Dwarf,
+ mapped_memory: ?[]align(std.heap.page_size_min) const u8,
+ external_mapped_memory: ?[]align(std.heap.page_size_min) const u8,
- const ptr: u64 = if (base) |b| switch (value) {
- .signed => |s| @intCast(try std.math.add(i64, s, @as(i64, @intCast(b)))),
- // 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,
+ pub const Lookup = struct {
+ base_address: usize,
+ name: []const u8,
+ build_id: ?[]const u8,
+ gnu_eh_frame: ?[]const u8,
};
- 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,
+ pub fn init(lookup: *const Lookup) ElfModule {
+ var em: ElfModule = .{
+ .unwind = .{
+ .sections = @splat(null),
+ },
+ .dwarf = .{},
+ .mapped_memory = null,
+ .external_mapped_memory = null,
};
- } 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)));
+ 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 const ElfModule = struct {
- base_address: usize,
- dwarf: Dwarf,
- mapped_memory: []align(std.heap.page_size_min) const u8,
- external_mapped_memory: ?[]align(std.heap.page_size_min) const u8,
pub fn deinit(self: *@This(), allocator: Allocator) void {
self.dwarf.deinit(allocator);
@@ -2095,16 +1506,16 @@ pub const ElfModule = struct {
if (self.external_mapped_memory) |m| std.posix.munmap(m);
}
- pub fn getSymbolAtAddress(self: *@This(), allocator: Allocator, address: usize) !std.debug.Symbol {
+ 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 - self.base_address;
- return self.dwarf.getSymbol(allocator, relocated_address);
+ const relocated_address = address - base_address;
+ return self.dwarf.getSymbol(allocator, endian, relocated_address);
}
- pub fn getDwarfInfoForAddress(self: *@This(), allocator: Allocator, address: usize) !?*Dwarf {
+ pub fn getDwarfUnwindForAddress(self: *@This(), allocator: Allocator, address: usize) !?*Dwarf.Unwind {
_ = allocator;
_ = address;
- return &self.dwarf;
+ return &self.unwind;
}
pub const LoadError = error{
@@ -2132,6 +1543,7 @@ pub const ElfModule = struct {
/// info is, then this this function will recurse to attempt to load the debug
/// sections from an external file.
pub fn load(
+ em: *ElfModule,
gpa: Allocator,
mapped_mem: []align(std.heap.page_size_min) const u8,
build_id: ?[]const u8,
@@ -2139,7 +1551,7 @@ pub const ElfModule = struct {
parent_sections: *Dwarf.SectionArray,
parent_mapped_mem: ?[]align(std.heap.page_size_min) const u8,
elf_filename: ?[]const u8,
- ) LoadError!Dwarf.ElfModule {
+ ) LoadError!void {
if (expected_crc) |crc| if (crc != std.hash.crc.Crc32.hash(mapped_mem)) return error.InvalidDebugInfo;
const hdr: *const elf.Ehdr = @ptrCast(&mapped_mem[0]);
@@ -2162,7 +1574,7 @@ pub const ElfModule = struct {
@ptrCast(@alignCast(&mapped_mem[shoff])),
)[0..hdr.e_shnum];
- var sections: Dwarf.SectionArray = Dwarf.null_section_array;
+ 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| {
@@ -2276,7 +1688,7 @@ pub const ElfModule = struct {
.sub_path = filename,
};
- return loadPath(gpa, path, null, separate_debug_crc, §ions, mapped_mem) catch break :blk;
+ return em.loadPath(gpa, path, null, separate_debug_crc, §ions, mapped_mem) catch break :blk;
}
const global_debug_directories = [_][]const u8{
@@ -2304,7 +1716,7 @@ pub const ElfModule = struct {
};
defer gpa.free(path.sub_path);
- return loadPath(gpa, path, null, separate_debug_crc, §ions, mapped_mem) catch continue;
+ return em.loadPath(gpa, path, null, separate_debug_crc, §ions, mapped_mem) catch continue;
}
}
@@ -2320,7 +1732,7 @@ pub const ElfModule = struct {
defer exe_dir.close();
// <exe_dir>/<gnu_debuglink>
- if (loadPath(
+ if (em.loadPath(
gpa,
.{
.root_dir = .{ .path = null, .handle = exe_dir },
@@ -2341,7 +1753,7 @@ pub const ElfModule = struct {
};
defer gpa.free(path.sub_path);
- if (loadPath(gpa, path, null, separate_debug_crc, §ions, mapped_mem)) |debug_info| return debug_info else |_| {}
+ if (em.loadPath(gpa, path, null, separate_debug_crc, §ions, mapped_mem)) |debug_info| return debug_info else |_| {}
}
var cwd_buf: [std.fs.max_path_bytes]u8 = undefined;
@@ -2354,37 +1766,27 @@ pub const ElfModule = struct {
.sub_path = try std.fs.path.join(gpa, &.{ global_directory, cwd_path, separate_filename }),
};
defer gpa.free(path.sub_path);
- if (loadPath(gpa, path, null, separate_debug_crc, §ions, mapped_mem)) |debug_info| return debug_info else |_| {}
+ if (em.loadPath(gpa, path, null, separate_debug_crc, §ions, mapped_mem)) |debug_info| return debug_info else |_| {}
}
}
return error.MissingDebugInfo;
}
- var di: Dwarf = .{
- .endian = endian,
- .sections = sections,
- .is_macho = false,
- };
-
- try Dwarf.open(&di, gpa);
-
- return .{
- .base_address = 0,
- .dwarf = di,
- .mapped_memory = parent_mapped_mem orelse mapped_mem,
- .external_mapped_memory = if (parent_mapped_mem != null) mapped_mem else null,
- };
+ em.mapped_memory = parent_mapped_mem orelse mapped_mem;
+ em.external_mapped_memory = if (parent_mapped_mem != null) mapped_mem else null;
+ try em.dwarf.open(gpa, endian);
}
pub fn loadPath(
+ em: *ElfModule,
gpa: Allocator,
elf_file_path: Path,
build_id: ?[]const u8,
expected_crc: ?u32,
parent_sections: *Dwarf.SectionArray,
parent_mapped_mem: ?[]align(std.heap.page_size_min) const u8,
- ) LoadError!Dwarf.ElfModule {
+ ) LoadError!void {
const elf_file = elf_file_path.root_dir.handle.openFile(elf_file_path.sub_path, .{}) catch |err| switch (err) {
error.FileNotFound => return missing(),
else => return err,
@@ -2407,7 +1809,7 @@ pub const ElfModule = struct {
};
errdefer std.posix.munmap(mapped_mem);
- return load(
+ return em.load(
gpa,
mapped_mem,
build_id,
@@ -2419,22 +1821,21 @@ pub const ElfModule = struct {
}
};
-pub fn getSymbol(di: *Dwarf, allocator: Allocator, address: u64) !std.debug.Symbol {
- if (di.findCompileUnit(address)) |compile_unit| {
- return .{
- .name = di.getSymbolName(address) orelse "???",
- .compile_unit_name = compile_unit.die.getAttrString(di, std.dwarf.AT.name, di.section(.debug_str), compile_unit.*) catch |err| switch (err) {
- error.MissingDebugInfo, error.InvalidDebugInfo => "???",
- },
- .source_location = di.getLineNumberInfo(allocator, compile_unit, address) catch |err| switch (err) {
- error.MissingDebugInfo, error.InvalidDebugInfo => null,
- else => return err,
- },
- };
- } else |err| switch (err) {
+pub fn getSymbol(di: *Dwarf, allocator: Allocator, endian: Endian, address: u64) !std.debug.Symbol {
+ const compile_unit = di.findCompileUnit(endian, address) catch |err| switch (err) {
error.MissingDebugInfo, error.InvalidDebugInfo => return .{},
else => return err,
- }
+ };
+ return .{
+ .name = di.getSymbolName(address) orelse "???",
+ .compile_unit_name = compile_unit.die.getAttrString(di, endian, std.dwarf.AT.name, di.section(.debug_str), compile_unit) catch |err| switch (err) {
+ error.MissingDebugInfo, error.InvalidDebugInfo => "???",
+ },
+ .source_location = di.getLineNumberInfo(allocator, endian, compile_unit, address) catch |err| switch (err) {
+ error.MissingDebugInfo, error.InvalidDebugInfo => null,
+ else => return err,
+ },
+ };
}
pub fn chopSlice(ptr: []const u8, offset: u64, size: u64) error{Overflow}![]const u8 {
@@ -2443,7 +1844,7 @@ pub fn chopSlice(ptr: []const u8, offset: u64, size: u64) error{Overflow}![]cons
return ptr[start..end];
}
-fn readAddress(r: *Reader, format: std.dwarf.Format, endian: Endian) !u64 {
+pub fn readAddress(r: *Reader, format: std.dwarf.Format, endian: Endian) !u64 {
return switch (format) {
.@"32" => try r.takeInt(u32, endian),
.@"64" => try r.takeInt(u64, endian),
lib/std/debug/SelfInfo.zig
@@ -31,7 +31,7 @@ const SelfInfo = @This();
const root = @import("root");
allocator: Allocator,
-address_map: std.AutoHashMap(usize, *Module),
+address_map: std.AutoHashMapUnmanaged(usize, Module),
modules: if (native_os == .windows) std.ArrayListUnmanaged(WindowsModule) else void,
pub const OpenError = error{
@@ -40,29 +40,27 @@ pub const OpenError = error{
} || @typeInfo(@typeInfo(@TypeOf(SelfInfo.init)).@"fn".return_type.?).error_union.error_set;
pub fn open(allocator: Allocator) OpenError!SelfInfo {
- nosuspend {
- 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,
- }
+ 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 = std.AutoHashMap(usize, *Module).init(allocator),
+ .address_map = .empty,
.modules = if (native_os == .windows) .{} else {},
};
@@ -110,7 +108,7 @@ pub fn deinit(self: *SelfInfo) void {
mdi.deinit(self.allocator);
self.allocator.destroy(mdi);
}
- self.address_map.deinit();
+ self.address_map.deinit(self.allocator);
if (native_os == .windows) {
for (self.modules.items) |module| {
self.allocator.free(module.name);
@@ -120,7 +118,7 @@ pub fn deinit(self: *SelfInfo) void {
}
}
-pub fn getModuleForAddress(self: *SelfInfo, address: usize) !*Module {
+fn lookupModuleForAddress(self: *SelfInfo, address: usize) !Module.Lookup {
if (builtin.target.os.tag.isDarwin()) {
return self.lookupModuleDyld(address);
} else if (native_os == .windows) {
@@ -134,21 +132,65 @@ pub fn getModuleForAddress(self: *SelfInfo, address: usize) !*Module {
}
}
-// 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 {
+fn loadModuleDebugInfo(self: *SelfInfo, lookup: *const Module.Lookup, module: *Module) !void {
if (builtin.target.os.tag.isDarwin()) {
- return self.lookupModuleNameDyld(address);
+ @compileError("TODO");
} else if (native_os == .windows) {
- return self.lookupModuleNameWin32(address);
+ @compileError("TODO");
} else if (native_os == .haiku) {
- return null;
+ @compileError("TODO");
} else if (builtin.target.cpu.arch.isWasm()) {
- return null;
+ @compileError("TODO");
} else {
- return self.lookupModuleNameDl(address);
+ 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);
+ }
+ }
+}
+
+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);
+ 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,
+ context,
+ unwind_info,
+ gop.value_ptr.eh_frame,
+ )) |return_address| {
+ return return_address;
+ } else |err| {
+ if (err != error.RequiresDWARFUnwind) return err;
+ }
+ } else return error.MissingUnwindInfo;
}
+ 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;
+}
+
+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);
+}
+
+/// 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 {
@@ -394,19 +436,24 @@ fn lookupModuleNameDl(self: *SelfInfo, address: usize) ?[]const u8 {
return null;
}
-fn lookupModuleDl(self: *SelfInfo, address: usize) !*Module {
+fn lookupModuleDl(self: *SelfInfo, address: usize) !Module.Lookup {
var ctx: struct {
// Input
address: usize,
// Output
- base_address: usize = undefined,
- name: []const u8 = undefined,
- build_id: ?[]const u8 = null,
- gnu_eh_frame: ?[]const u8 = null,
- } = .{ .address = address };
+ lookup: Module.Lookup,
+ } = .{
+ .address = address,
+ .lookup = .{
+ .base_address = undefined,
+ .name = undefined,
+ .build_id = null,
+ .gnu_eh_frame = null,
+ },
+ };
const CtxTy = @TypeOf(ctx);
- if (posix.dl_iterate_phdr(&ctx, error{Found}, struct {
+ posix.dl_iterate_phdr(&ctx, error{Found}, struct {
fn callback(info: *posix.dl_phdr_info, size: usize, context: *CtxTy) !void {
_ = size;
// The base address is too high
@@ -423,8 +470,8 @@ fn lookupModuleDl(self: *SelfInfo, address: usize) !*Module {
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.name = mem.sliceTo(info.name, 0) orelse "";
- context.base_address = info.addr;
+ context.lookup.name = mem.sliceTo(info.name, 0) orelse "";
+ context.lookup.base_address = info.addr;
break;
}
} else return;
@@ -440,10 +487,10 @@ fn lookupModuleDl(self: *SelfInfo, address: usize) !*Module {
const note_type = mem.readInt(u32, note_bytes[8..12], native_endian);
if (note_type != elf.NT_GNU_BUILD_ID) continue;
if (!mem.eql(u8, "GNU\x00", note_bytes[12..16])) continue;
- context.build_id = note_bytes[16..][0..desc_size];
+ context.lookup.build_id = note_bytes[16..][0..desc_size];
},
elf.PT_GNU_EH_FRAME => {
- context.gnu_eh_frame = @as([*]const u8, @ptrFromInt(info.addr + phdr.p_vaddr))[0..phdr.p_memsz];
+ context.lookup.gnu_eh_frame = @as([*]const u8, @ptrFromInt(info.addr + phdr.p_vaddr))[0..phdr.p_memsz];
},
else => {},
}
@@ -452,38 +499,36 @@ fn lookupModuleDl(self: *SelfInfo, address: usize) !*Module {
// Stop the iteration
return error.Found;
}
- }.callback)) {
- return error.MissingDebugInfo;
- } else |err| switch (err) {
- error.Found => {},
- }
+ }.callback) catch |err| switch (err) {
+ error.Found => return ctx.lookup,
+ };
+ if (true) return error.MissingDebugInfo;
- if (self.address_map.get(ctx.base_address)) |obj_di| {
+ if (self.address_map.get(ctx.lookup.base_address)) |obj_di| {
return obj_di;
}
- const obj_di = try self.allocator.create(Module);
- errdefer self.allocator.destroy(obj_di);
-
- var sections: Dwarf.SectionArray = Dwarf.null_section_array;
- if (ctx.gnu_eh_frame) |eh_frame_hdr| {
+ 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.Section.Id.eh_frame_hdr)] = .{
+ sections[@intFromEnum(Dwarf.Unwind.Section.Id.eh_frame_hdr)] = .{
.data = eh_frame_hdr,
.owned = false,
};
}
- obj_di.* = try readElfDebugInfo(self.allocator, if (ctx.name.len > 0) ctx.name else null, ctx.build_id, null, §ions, null);
- obj_di.base_address = ctx.base_address;
+ 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.base_address) catch {};
+ obj_di.dwarf.scanAllUnwindInfo(self.allocator, ctx.lookup.base_address) catch {};
- try self.address_map.putNoClobber(ctx.base_address, obj_di);
+ try self.address_map.putNoClobber(self.allocator, ctx.lookup.base_address, obj_di);
return obj_di;
}
@@ -625,49 +670,47 @@ pub const Module = switch (native_os) {
}
pub fn getSymbolAtAddress(self: *@This(), allocator: Allocator, address: usize) !std.debug.Symbol {
- nosuspend {
- const result = try self.getOFileInfoForAddress(allocator, address);
- if (result.symbol == null) return .{};
-
- // 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 result = try self.getOFileInfoForAddress(allocator, address);
+ if (result.symbol == null) return .{};
+
+ // 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 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 };
+ 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 => "???",
},
- else => return err,
- }
+ .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,
}
}
@@ -676,35 +719,33 @@ pub const Module = switch (native_os) {
symbol: ?*const MachoSymbol = null,
o_file_info: ?*OFileInfo = null,
} {
- nosuspend {
- // Translate the VA into an address into this object
- const relocated_address = address - self.vmaddr_slide;
+ // 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,
- };
+ // Find the .o file where this symbol is defined
+ const symbol = machoSearchSymbols(self.symbols, relocated_address) orelse return .{
+ .relocated_address = relocated_address,
+ };
- // 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,
- });
+ // 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,
- };
- }
+ return .{
+ .relocated_address = relocated_address,
+ .symbol = symbol,
+ .o_file_info = o_file_info,
+ };
}
pub fn getDwarfInfoForAddress(self: *@This(), allocator: Allocator, address: usize) !?*Dwarf {
@@ -974,83 +1015,68 @@ fn readMachODebugInfo(allocator: Allocator, macho_file: File) !Module {
};
}
-fn readCoffDebugInfo(gpa: Allocator, coff_obj: *coff.Coff) !Module {
- nosuspend {
- var di: Module = .{
- .base_address = undefined,
- .coff_image_base = coff_obj.getImageBase(),
- .coff_section_headers = undefined,
- .pdb = null,
- .dwarf = null,
- };
+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) gpa.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, gpa),
- .virtual_address = section_header.virtual_address,
- .owned = true,
- };
- } else null;
- }
+ 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);
- var dwarf: Dwarf = .{
- .endian = native_endian,
- .sections = sections,
- .is_macho = false,
- };
-
- try Dwarf.open(&dwarf, gpa);
- di.dwarf = dwarf;
+ 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;
}
- 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(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 pdb_file = std.fs.cwd().openFile(path, .{}) catch |err| switch (err) {
- error.FileNotFound, error.IsDir => {
- if (di.dwarf == null) return error.MissingDebugInfo;
- return di;
- },
- else => |e| return e,
+ var dwarf: Dwarf = .{
+ .endian = native_endian,
+ .sections = sections,
+ .is_macho = false,
};
- errdefer pdb_file.close();
-
- const pdb_file_reader_buffer = try gpa.alloc(u8, 4096);
- errdefer gpa.free(pdb_file_reader_buffer);
- const pdb_file_reader = try gpa.create(File.Reader);
- errdefer gpa.destroy(pdb_file_reader);
+ try Dwarf.open(&dwarf, allocator);
+ di.dwarf = dwarf;
+ }
- pdb_file_reader.* = pdb_file.reader(pdb_file_reader_buffer);
+ 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 = try Pdb.init(gpa, pdb_file_reader);
- try di.pdb.?.parseInfoStream();
- try di.pdb.?.parseDbiStream();
+ 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;
+ 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(gpa);
- errdefer gpa.free(di.coff_section_headers);
+ // Only used by the pdb path
+ di.coff_section_headers = try coff_obj.getSectionHeadersAlloc(allocator);
+ errdefer allocator.free(di.coff_section_headers);
- return di;
- }
+ return di;
}
/// Reads debug info from an ELF file, or the current binary if none in specified.
@@ -1058,32 +1084,29 @@ fn readCoffDebugInfo(gpa: Allocator, coff_obj: *coff.Coff) !Module {
/// 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,
- expected_crc: ?u32,
parent_sections: *Dwarf.SectionArray,
- parent_mapped_mem: ?[]align(std.heap.page_size_min) const u8,
-) !Dwarf.ElfModule {
- nosuspend {
- 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,
- };
+) !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 Dwarf.ElfModule.load(
- allocator,
- mapped_mem,
- build_id,
- expected_crc,
- parent_sections,
- parent_mapped_mem,
- elf_filename,
- );
- }
+ const mapped_mem = try mapWholeFile(elf_file);
+ return em.load(
+ allocator,
+ mapped_mem,
+ build_id,
+ null,
+ parent_sections,
+ null,
+ elf_filename,
+ );
}
const MachoSymbol = struct {
@@ -1106,22 +1129,20 @@ const MachoSymbol = struct {
/// 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 {
- nosuspend {
- 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);
+ 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;
- }
+ return mapped_mem;
}
fn machoSearchSymbols(symbols: []const MachoSymbol, address: usize) ?*const MachoSymbol {
@@ -1172,7 +1193,7 @@ test machoSearchSymbols {
/// Unwind a frame using MachO compact unwind info (from __unwind_info).
/// If the compact encoding can't encode a way to unwind a frame, it will
/// defer unwinding to DWARF, in which case `.eh_frame` will be used if available.
-pub fn unwindFrameMachO(
+fn unwindFrameMachO(
allocator: Allocator,
base_address: usize,
context: *UnwindContext,
@@ -1562,9 +1583,9 @@ 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.
-pub fn unwindFrameDwarf(
+fn unwindFrameDwarf(
allocator: Allocator,
- di: *Dwarf,
+ unwind: *Dwarf.Unwind,
base_address: usize,
context: *UnwindContext,
explicit_fde_offset: ?usize,
@@ -1572,37 +1593,34 @@ pub fn unwindFrameDwarf(
if (!supports_unwinding) return error.UnsupportedCpuArchitecture;
if (context.pc == 0) return 0;
- const endian = di.endian;
-
// Find the FDE and CIE
const cie, const fde = if (explicit_fde_offset) |fde_offset| blk: {
- const dwarf_section: Dwarf.Section.Id = .eh_frame;
- const frame_section = di.section(dwarf_section) orelse return error.MissingFDE;
+ 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 fde_entry_header = try Dwarf.EntryHeader.read(&fbr, dwarf_section, endian);
+ const fde_entry_header = try Dwarf.Unwind.EntryHeader.read(&fbr, .eh_frame, native_endian);
if (fde_entry_header.type != .fde) return error.MissingFDE;
const cie_offset = fde_entry_header.type.fde;
fbr.seek = @intCast(cie_offset);
- const cie_entry_header = try Dwarf.EntryHeader.read(&fbr, dwarf_section, endian);
+ 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.CommonInformationEntry.parse(
+ const cie = try Dwarf.Unwind.CommonInformationEntry.parse(
cie_entry_header.entry_bytes,
0,
true,
cie_entry_header.format,
- dwarf_section,
+ .eh_frame,
cie_entry_header.length_offset,
@sizeOf(usize),
native_endian,
);
- const fde = try Dwarf.FrameDescriptionEntry.parse(
+ const fde = try Dwarf.Unwind.FrameDescriptionEntry.parse(
fde_entry_header.entry_bytes,
0,
true,
@@ -1616,33 +1634,33 @@ pub fn unwindFrameDwarf(
// `.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 (di.eh_frame_hdr) |header| hdr: {
- const eh_frame_len = if (di.section(.eh_frame)) |eh_frame| eh_frame.len else {
- try di.scanCieFdeInfo(allocator, base_address);
- di.eh_frame_hdr = null;
+ 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.CommonInformationEntry = undefined;
- var fde: Dwarf.FrameDescriptionEntry = undefined;
+ var cie: Dwarf.Unwind.CommonInformationEntry = undefined;
+ var fde: Dwarf.Unwind.FrameDescriptionEntry = undefined;
header.findEntry(
eh_frame_len,
- @intFromPtr(di.section(.eh_frame_hdr).?.ptr),
+ @intFromPtr(unwind.section(.eh_frame_hdr).?.ptr),
context.pc,
&cie,
&fde,
- endian,
+ 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 di.scanCieFdeInfo(allocator, base_address);
+ 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.
- di.eh_frame_hdr = null;
+ unwind.eh_frame_hdr = null;
break :hdr;
},
@@ -1652,8 +1670,8 @@ pub fn unwindFrameDwarf(
break :blk .{ cie, fde };
}
- const index = std.sort.binarySearch(Dwarf.FrameDescriptionEntry, di.fde_list.items, context.pc, struct {
- pub fn compareFn(pc: usize, item: Dwarf.FrameDescriptionEntry) std.math.Order {
+ 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;
@@ -1663,15 +1681,16 @@ pub fn unwindFrameDwarf(
}
}.compareFn);
- const fde = if (index) |i| di.fde_list.items[i] else return error.MissingFDE;
- const cie = di.cie_map.get(fde.cie_length_offset) orelse return error.MissingCIE;
+ 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 };
};
+ // 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,
- .compile_unit = di.findCompileUnit(fde.pc_begin) catch null,
.thread_context = context.thread_context,
.reg_context = context.reg_context,
.cfa = context.cfa,
@@ -1679,7 +1698,7 @@ pub fn unwindFrameDwarf(
context.vm.reset();
context.reg_context.eh_frame = cie.version != 4;
- context.reg_context.is_macho = di.is_macho;
+ context.reg_context.is_macho = native_os.isDarwin();
const row = try context.vm.runToNative(context.allocator, context.pc, cie, fde);
context.cfa = switch (row.cfa.rule) {
@@ -2007,8 +2026,8 @@ pub const VirtualMachine = struct {
self: *VirtualMachine,
allocator: std.mem.Allocator,
pc: u64,
- cie: std.debug.Dwarf.CommonInformationEntry,
- fde: std.debug.Dwarf.FrameDescriptionEntry,
+ cie: std.debug.Dwarf.Unwind.CommonInformationEntry,
+ fde: std.debug.Dwarf.Unwind.FrameDescriptionEntry,
addr_size_bytes: u8,
endian: std.builtin.Endian,
) !Row {
@@ -2036,8 +2055,8 @@ pub const VirtualMachine = struct {
self: *VirtualMachine,
allocator: std.mem.Allocator,
pc: u64,
- cie: std.debug.Dwarf.CommonInformationEntry,
- fde: std.debug.Dwarf.FrameDescriptionEntry,
+ cie: std.debug.Dwarf.Unwind.CommonInformationEntry,
+ fde: std.debug.Dwarf.Unwind.FrameDescriptionEntry,
) !Row {
return self.runTo(allocator, pc, cie, fde, @sizeOf(usize), native_endian);
}
@@ -2059,7 +2078,7 @@ pub const VirtualMachine = struct {
pub fn step(
self: *VirtualMachine,
allocator: std.mem.Allocator,
- cie: std.debug.Dwarf.CommonInformationEntry,
+ cie: std.debug.Dwarf.Unwind.CommonInformationEntry,
is_initial: bool,
instruction: Dwarf.call_frame.Instruction,
) !Row {
lib/std/debug.zig
@@ -498,10 +498,17 @@ pub fn captureStackTrace(first_address: ?usize, stack_trace: *std.builtin.StackT
}
stack_trace.index = slice.len;
} else {
- // TODO: This should use the DWARF unwinder if .eh_frame_hdr is available (so that full debug info parsing isn't required).
- // A new path for loading SelfInfo needs to be created which will only attempt to parse in-memory sections, because
- // stopping to load other debug info (ie. source line info) from disk here is not required for unwinding.
- var it = StackIterator.init(first_address, @frameAddress());
+ if (builtin.cpu.arch == .powerpc64) {
+ // https://github.com/ziglang/zig/issues/24970
+ stack_trace.index = 0;
+ return;
+ }
+ var context: ThreadContext = undefined;
+ const has_context = getContext(&context);
+
+ var it = (if (has_context) blk: {
+ break :blk StackIterator.initWithContext(first_address, getSelfDebugInfo() catch break :blk null, &context) catch null;
+ } else null) orelse StackIterator.init(first_address, null);
defer it.deinit();
for (stack_trace.instruction_addresses, 0..) |*addr, i| {
addr.* = it.next() orelse {
@@ -764,7 +771,7 @@ pub fn writeStackTrace(
}
pub const UnwindError = if (have_ucontext)
- @typeInfo(@typeInfo(@TypeOf(StackIterator.next_unwind)).@"fn".return_type.?).error_union.error_set
+ @typeInfo(@typeInfo(@TypeOf(SelfInfo.unwindFrame)).@"fn".return_type.?).error_union.error_set
else
void;
@@ -865,11 +872,11 @@ pub const StackIterator = struct {
@sizeOf(usize);
pub fn next(it: *StackIterator) ?usize {
- var address = it.next_internal() orelse return null;
+ var address = it.nextInternal() orelse return null;
if (it.first_address) |first_address| {
while (address != first_address) {
- address = it.next_internal() orelse return null;
+ address = it.nextInternal() orelse return null;
}
it.first_address = null;
}
@@ -877,48 +884,13 @@ pub const StackIterator = struct {
return address;
}
- fn next_unwind(it: *StackIterator) !usize {
- const unwind_state = &it.unwind_state.?;
- const module = try unwind_state.debug_info.getModuleForAddress(unwind_state.dwarf_context.pc);
- switch (native_os) {
- .macos, .ios, .watchos, .tvos, .visionos => {
- // __unwind_info is a requirement for unwinding on Darwin. It may fall back to DWARF, but unwinding
- // via DWARF before attempting to use the compact unwind info will produce incorrect results.
- if (module.unwind_info) |unwind_info| {
- if (SelfInfo.unwindFrameMachO(
- unwind_state.debug_info.allocator,
- module.base_address,
- &unwind_state.dwarf_context,
- unwind_info,
- module.eh_frame,
- )) |return_address| {
- return return_address;
- } else |err| {
- if (err != error.RequiresDWARFUnwind) return err;
- }
- } else return error.MissingUnwindInfo;
- },
- else => {},
- }
-
- if (try module.getDwarfInfoForAddress(unwind_state.debug_info.allocator, unwind_state.dwarf_context.pc)) |di| {
- return SelfInfo.unwindFrameDwarf(
- unwind_state.debug_info.allocator,
- di,
- module.base_address,
- &unwind_state.dwarf_context,
- null,
- );
- } else return error.MissingDebugInfo;
- }
-
- fn next_internal(it: *StackIterator) ?usize {
+ fn nextInternal(it: *StackIterator) ?usize {
if (have_ucontext) {
if (it.unwind_state) |*unwind_state| {
if (!unwind_state.failed) {
if (unwind_state.dwarf_context.pc == 0) return null;
defer it.fp = unwind_state.dwarf_context.getFp() catch 0;
- if (it.next_unwind()) |return_address| {
+ if (unwind_state.debug_info.unwindFrame(&unwind_state.dwarf_context)) |return_address| {
return return_address;
} else |err| {
unwind_state.last_error = err;
@@ -948,7 +920,7 @@ pub const StackIterator = struct {
// Sanity check: the stack grows down thus all the parent frames must be
// be at addresses that are greater (or equal) than the previous one.
// A zero frame pointer often signals this is the last frame, that case
- // is gracefully handled by the next call to next_internal.
+ // is gracefully handled by the next call to nextInternal.
if (new_fp != 0 and new_fp < it.fp) return null;
const new_pc = @as(*usize, @ptrFromInt(math.add(usize, fp, pc_offset) catch return null)).*;
@@ -1099,12 +1071,7 @@ fn printUnwindError(debug_info: *SelfInfo, writer: *Writer, address: usize, err:
}
pub fn printSourceAtAddress(debug_info: *SelfInfo, writer: *Writer, address: usize, tty_config: tty.Config) !void {
- const module = debug_info.getModuleForAddress(address) catch |err| switch (err) {
- error.MissingDebugInfo, error.InvalidDebugInfo => return printUnknownSource(debug_info, writer, address, tty_config),
- else => return err,
- };
-
- const symbol_info = module.getSymbolAtAddress(debug_info.allocator, address) catch |err| switch (err) {
+ 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,
};