authorgravatar for alex@alexrp.comAlex Rønne Petersen <alex@alexrp.com> 2025-01-23 20:55:20+01:00
committergravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2025-01-23 23:22:38+00:00
log180db2bf23f05a02876d4567cac3b04842c11acb
treec30cddc29ae1f1162f3ec38fcaf89b94ecb4e6dd
parent41185d297ffcaf61776aa8e7610aea9d00fce3a4
signaturelock-open Commit is signed but in an unrecognized format.

std.debug: Fall back to .eh_frame/.debug_frame if .eh_frame_hdr is incomplete.

When using the self-hosted backends, especially in incremental mode, the .eh_frame_hdr section may be incomplete, so we can't treat it as authoritative. Instead, if we started out intending to use .eh_frame_hdr but find that it's incomplete, load .eh_frame/.debug_frame on demand and use that info going forward.

3 files changed, 88 insertions(+), 39 deletions(-)

lib/std/debug.zig+10-2
...@@ -732,11 +732,12 @@ pub const StackIterator = struct {...@@ -732,11 +732,12 @@ pub const StackIterator = struct {
732 // via DWARF before attempting to use the compact unwind info will produce incorrect results.732 // via DWARF before attempting to use the compact unwind info will produce incorrect results.
733 if (module.unwind_info) |unwind_info| {733 if (module.unwind_info) |unwind_info| {
734 if (SelfInfo.unwindFrameMachO(734 if (SelfInfo.unwindFrameMachO(
735 unwind_state.debug_info.allocator,
736 module.base_address,
735 &unwind_state.dwarf_context,737 &unwind_state.dwarf_context,
736 &it.ma,738 &it.ma,
737 unwind_info,739 unwind_info,
738 module.eh_frame,740 module.eh_frame,
739 module.base_address,
740 )) |return_address| {741 )) |return_address| {
741 return return_address;742 return return_address;
742 } else |err| {743 } else |err| {
...@@ -748,7 +749,14 @@ pub const StackIterator = struct {...@@ -748,7 +749,14 @@ pub const StackIterator = struct {
748 }749 }
749750
750 if (try module.getDwarfInfoForAddress(unwind_state.debug_info.allocator, unwind_state.dwarf_context.pc)) |di| {751 if (try module.getDwarfInfoForAddress(unwind_state.debug_info.allocator, unwind_state.dwarf_context.pc)) |di| {
751 return SelfInfo.unwindFrameDwarf(di, &unwind_state.dwarf_context, &it.ma, null);752 return SelfInfo.unwindFrameDwarf(
753 unwind_state.debug_info.allocator,
754 di,
755 module.base_address,
756 &unwind_state.dwarf_context,
757 &it.ma,
758 null,
759 );
752 } else return error.MissingDebugInfo;760 } else return error.MissingDebugInfo;
753 }761 }
754762
lib/std/debug/Dwarf.zig+14-4
...@@ -48,6 +48,8 @@ compile_unit_list: std.ArrayListUnmanaged(CompileUnit) = .empty,...@@ -48,6 +48,8 @@ compile_unit_list: std.ArrayListUnmanaged(CompileUnit) = .empty,
48/// Filled later by the initializer48/// Filled later by the initializer
49func_list: std.ArrayListUnmanaged(Func) = .empty,49func_list: std.ArrayListUnmanaged(Func) = .empty,
5050
51/// Starts out non-`null` if the `.eh_frame_hdr` section is present. May become `null` later if we
52/// find that `.eh_frame_hdr` is incomplete.
51eh_frame_hdr: ?ExceptionFrameHeader = null,53eh_frame_hdr: ?ExceptionFrameHeader = null,
52/// These lookup tables are only used if `eh_frame_hdr` is null54/// These lookup tables are only used if `eh_frame_hdr` is null
53cie_map: std.AutoArrayHashMapUnmanaged(u64, CommonInformationEntry) = .empty,55cie_map: std.AutoArrayHashMapUnmanaged(u64, CommonInformationEntry) = .empty,
...@@ -1754,10 +1756,12 @@ fn readDebugAddr(di: Dwarf, compile_unit: CompileUnit, index: u64) !u64 {...@@ -1754,10 +1756,12 @@ fn readDebugAddr(di: Dwarf, compile_unit: CompileUnit, index: u64) !u64 {
1754 };1756 };
1755}1757}
17561758
1757/// If .eh_frame_hdr is present, then only the header needs to be parsed.1759/// If `.eh_frame_hdr` is present, then only the header needs to be parsed. Otherwise, `.eh_frame`
1760/// and `.debug_frame` are scanned and a sorted list of FDEs is built for binary searching during
1761/// unwinding. Even if `.eh_frame_hdr` is used, we may find during unwinding that it's incomplete,
1762/// in which case we build the sorted list of FDEs at that point.
1758///1763///
1759/// Otherwise, .eh_frame and .debug_frame are scanned and a sorted list1764/// See also `scanCieFdeInfo`.
1760/// of FDEs is built for binary searching during unwinding.
1761pub fn scanAllUnwindInfo(di: *Dwarf, allocator: Allocator, base_address: usize) !void {1765pub fn scanAllUnwindInfo(di: *Dwarf, allocator: Allocator, base_address: usize) !void {
1762 if (di.section(.eh_frame_hdr)) |eh_frame_hdr| blk: {1766 if (di.section(.eh_frame_hdr)) |eh_frame_hdr| blk: {
1763 var fbr: FixedBufferReader = .{ .buf = eh_frame_hdr, .endian = native_endian };1767 var fbr: FixedBufferReader = .{ .buf = eh_frame_hdr, .endian = native_endian };
...@@ -1797,6 +1801,12 @@ pub fn scanAllUnwindInfo(di: *Dwarf, allocator: Allocator, base_address: usize)...@@ -1797,6 +1801,12 @@ pub fn scanAllUnwindInfo(di: *Dwarf, allocator: Allocator, base_address: usize)
1797 return;1801 return;
1798 }1802 }
17991803
1804 try di.scanCieFdeInfo(allocator, base_address);
1805}
1806
1807/// Scan `.eh_frame` and `.debug_frame` and build a sorted list of FDEs for binary searching during
1808/// unwinding.
1809pub fn scanCieFdeInfo(di: *Dwarf, allocator: Allocator, base_address: usize) !void {
1800 const frame_sections = [2]Section.Id{ .eh_frame, .debug_frame };1810 const frame_sections = [2]Section.Id{ .eh_frame, .debug_frame };
1801 for (frame_sections) |frame_section| {1811 for (frame_sections) |frame_section| {
1802 if (di.section(frame_section)) |section_data| {1812 if (di.section(frame_section)) |section_data| {
...@@ -2125,7 +2135,7 @@ pub const ElfModule = struct {...@@ -2125,7 +2135,7 @@ pub const ElfModule = struct {
2125 return self.dwarf.getSymbol(allocator, relocated_address);2135 return self.dwarf.getSymbol(allocator, relocated_address);
2126 }2136 }
21272137
2128 pub fn getDwarfInfoForAddress(self: *@This(), allocator: Allocator, address: usize) !?*const Dwarf {2138 pub fn getDwarfInfoForAddress(self: *@This(), allocator: Allocator, address: usize) !?*Dwarf {
2129 _ = allocator;2139 _ = allocator;
2130 _ = address;2140 _ = address;
2131 return &self.dwarf;2141 return &self.dwarf;
lib/std/debug/SelfInfo.zig+64-33
...@@ -707,7 +707,7 @@ pub const Module = switch (native_os) {...@@ -707,7 +707,7 @@ pub const Module = switch (native_os) {
707 }707 }
708 }708 }
709709
710 pub fn getDwarfInfoForAddress(self: *@This(), allocator: Allocator, address: usize) !?*const Dwarf {710 pub fn getDwarfInfoForAddress(self: *@This(), allocator: Allocator, address: usize) !?*Dwarf {
711 return if ((try self.getOFileInfoForAddress(allocator, address)).o_file_info) |o_file_info| &o_file_info.di else null;711 return if ((try self.getOFileInfoForAddress(allocator, address)).o_file_info) |o_file_info| &o_file_info.di else null;
712 }712 }
713 },713 },
...@@ -784,7 +784,7 @@ pub const Module = switch (native_os) {...@@ -784,7 +784,7 @@ pub const Module = switch (native_os) {
784 return .{};784 return .{};
785 }785 }
786786
787 pub fn getDwarfInfoForAddress(self: *@This(), allocator: Allocator, address: usize) !?*const Dwarf {787 pub fn getDwarfInfoForAddress(self: *@This(), allocator: Allocator, address: usize) !?*Dwarf {
788 _ = allocator;788 _ = allocator;
789 _ = address;789 _ = address;
790790
...@@ -808,7 +808,7 @@ pub const Module = switch (native_os) {...@@ -808,7 +808,7 @@ pub const Module = switch (native_os) {
808 return .{};808 return .{};
809 }809 }
810810
811 pub fn getDwarfInfoForAddress(self: *@This(), allocator: Allocator, address: usize) !?*const Dwarf {811 pub fn getDwarfInfoForAddress(self: *@This(), allocator: Allocator, address: usize) !?*Dwarf {
812 _ = self;812 _ = self;
813 _ = allocator;813 _ = allocator;
814 _ = address;814 _ = address;
...@@ -1156,11 +1156,12 @@ test machoSearchSymbols {...@@ -1156,11 +1156,12 @@ test machoSearchSymbols {
1156/// If the compact encoding can't encode a way to unwind a frame, it will1156/// If the compact encoding can't encode a way to unwind a frame, it will
1157/// defer unwinding to DWARF, in which case `.eh_frame` will be used if available.1157/// defer unwinding to DWARF, in which case `.eh_frame` will be used if available.
1158pub fn unwindFrameMachO(1158pub fn unwindFrameMachO(
1159 allocator: Allocator,
1160 base_address: usize,
1159 context: *UnwindContext,1161 context: *UnwindContext,
1160 ma: *std.debug.MemoryAccessor,1162 ma: *std.debug.MemoryAccessor,
1161 unwind_info: []const u8,1163 unwind_info: []const u8,
1162 eh_frame: ?[]const u8,1164 eh_frame: ?[]const u8,
1163 module_base_address: usize,
1164) !usize {1165) !usize {
1165 const header = std.mem.bytesAsValue(1166 const header = std.mem.bytesAsValue(
1166 macho.unwind_info_section_header,1167 macho.unwind_info_section_header,
...@@ -1172,7 +1173,7 @@ pub fn unwindFrameMachO(...@@ -1172,7 +1173,7 @@ pub fn unwindFrameMachO(
1172 );1173 );
1173 if (indices.len == 0) return error.MissingUnwindInfo;1174 if (indices.len == 0) return error.MissingUnwindInfo;
11741175
1175 const mapped_pc = context.pc - module_base_address;1176 const mapped_pc = context.pc - base_address;
1176 const second_level_index = blk: {1177 const second_level_index = blk: {
1177 var left: usize = 0;1178 var left: usize = 0;
1178 var len: usize = indices.len;1179 var len: usize = indices.len;
...@@ -1351,7 +1352,7 @@ pub fn unwindFrameMachO(...@@ -1351,7 +1352,7 @@ pub fn unwindFrameMachO(
1351 else stack_size: {1352 else stack_size: {
1352 // In .STACK_IND, the stack size is inferred from the subq instruction at the beginning of the function.1353 // In .STACK_IND, the stack size is inferred from the subq instruction at the beginning of the function.
1353 const sub_offset_addr =1354 const sub_offset_addr =
1354 module_base_address +1355 base_address +
1355 entry.function_offset +1356 entry.function_offset +
1356 encoding.value.x86_64.frameless.stack.indirect.sub_offset;1357 encoding.value.x86_64.frameless.stack.indirect.sub_offset;
1357 if (ma.load(usize, sub_offset_addr) == null) return error.InvalidUnwindInfo;1358 if (ma.load(usize, sub_offset_addr) == null) return error.InvalidUnwindInfo;
...@@ -1416,7 +1417,7 @@ pub fn unwindFrameMachO(...@@ -1416,7 +1417,7 @@ pub fn unwindFrameMachO(
1416 break :blk new_ip;1417 break :blk new_ip;
1417 },1418 },
1418 .DWARF => {1419 .DWARF => {
1419 return unwindFrameMachODwarf(context, ma, eh_frame orelse return error.MissingEhFrame, @intCast(encoding.value.x86_64.dwarf));1420 return unwindFrameMachODwarf(allocator, base_address, context, ma, eh_frame orelse return error.MissingEhFrame, @intCast(encoding.value.x86_64.dwarf));
1420 },1421 },
1421 },1422 },
1422 .aarch64, .aarch64_be => switch (encoding.mode.arm64) {1423 .aarch64, .aarch64_be => switch (encoding.mode.arm64) {
...@@ -1430,7 +1431,7 @@ pub fn unwindFrameMachO(...@@ -1430,7 +1431,7 @@ pub fn unwindFrameMachO(
1430 break :blk new_ip;1431 break :blk new_ip;
1431 },1432 },
1432 .DWARF => {1433 .DWARF => {
1433 return unwindFrameMachODwarf(context, ma, eh_frame orelse return error.MissingEhFrame, @intCast(encoding.value.arm64.dwarf));1434 return unwindFrameMachODwarf(allocator, base_address, context, ma, eh_frame orelse return error.MissingEhFrame, @intCast(encoding.value.arm64.dwarf));
1434 },1435 },
1435 .FRAME => blk: {1436 .FRAME => blk: {
1436 const fp = (try regValueNative(context.thread_context, fpRegNum(reg_context), reg_context)).*;1437 const fp = (try regValueNative(context.thread_context, fpRegNum(reg_context), reg_context)).*;
...@@ -1555,13 +1556,16 @@ pub inline fn stripInstructionPtrAuthCode(ptr: usize) usize {...@@ -1555,13 +1556,16 @@ pub inline fn stripInstructionPtrAuthCode(ptr: usize) usize {
15551556
1556/// Unwind a stack frame using DWARF unwinding info, updating the register context.1557/// Unwind a stack frame using DWARF unwinding info, updating the register context.
1557///1558///
1558/// If `.eh_frame_hdr` is available, it will be used to binary search for the FDE.1559/// If `.eh_frame_hdr` is available and complete, it will be used to binary search for the FDE.
1559/// Otherwise, a linear scan of `.eh_frame` and `.debug_frame` is done to find the FDE.1560/// Otherwise, a linear scan of `.eh_frame` and `.debug_frame` is done to find the FDE. The latter
1561/// may require lazily loading the data in those sections.
1560///1562///
1561/// `explicit_fde_offset` is for cases where the FDE offset is known, such as when __unwind_info1563/// `explicit_fde_offset` is for cases where the FDE offset is known, such as when __unwind_info
1562/// defers unwinding to DWARF. This is an offset into the `.eh_frame` section.1564/// defers unwinding to DWARF. This is an offset into the `.eh_frame` section.
1563pub fn unwindFrameDwarf(1565pub fn unwindFrameDwarf(
1564 di: *const Dwarf,1566 allocator: Allocator,
1567 di: *Dwarf,
1568 base_address: usize,
1565 context: *UnwindContext,1569 context: *UnwindContext,
1566 ma: *std.debug.MemoryAccessor,1570 ma: *std.debug.MemoryAccessor,
1567 explicit_fde_offset: ?usize,1571 explicit_fde_offset: ?usize,
...@@ -1570,10 +1574,7 @@ pub fn unwindFrameDwarf(...@@ -1570,10 +1574,7 @@ pub fn unwindFrameDwarf(
1570 if (context.pc == 0) return 0;1574 if (context.pc == 0) return 0;
15711575
1572 // Find the FDE and CIE1576 // Find the FDE and CIE
1573 var cie: Dwarf.CommonInformationEntry = undefined;1577 const cie, const fde = if (explicit_fde_offset) |fde_offset| blk: {
1574 var fde: Dwarf.FrameDescriptionEntry = undefined;
1575
1576 if (explicit_fde_offset) |fde_offset| {
1577 const dwarf_section: Dwarf.Section.Id = .eh_frame;1578 const dwarf_section: Dwarf.Section.Id = .eh_frame;
1578 const frame_section = di.section(dwarf_section) orelse return error.MissingFDE;1579 const frame_section = di.section(dwarf_section) orelse return error.MissingFDE;
1579 if (fde_offset >= frame_section.len) return error.MissingFDE;1580 if (fde_offset >= frame_section.len) return error.MissingFDE;
...@@ -1594,7 +1595,7 @@ pub fn unwindFrameDwarf(...@@ -1594,7 +1595,7 @@ pub fn unwindFrameDwarf(
1594 const cie_entry_header = try Dwarf.EntryHeader.read(&fbr, null, dwarf_section);1595 const cie_entry_header = try Dwarf.EntryHeader.read(&fbr, null, dwarf_section);
1595 if (cie_entry_header.type != .cie) return Dwarf.bad();1596 if (cie_entry_header.type != .cie) return Dwarf.bad();
15961597
1597 cie = try Dwarf.CommonInformationEntry.parse(1598 const cie = try Dwarf.CommonInformationEntry.parse(
1598 cie_entry_header.entry_bytes,1599 cie_entry_header.entry_bytes,
1599 0,1600 0,
1600 true,1601 true,
...@@ -1604,8 +1605,7 @@ pub fn unwindFrameDwarf(...@@ -1604,8 +1605,7 @@ pub fn unwindFrameDwarf(
1604 @sizeOf(usize),1605 @sizeOf(usize),
1605 native_endian,1606 native_endian,
1606 );1607 );
16071608 const fde = try Dwarf.FrameDescriptionEntry.parse(
1608 fde = try Dwarf.FrameDescriptionEntry.parse(
1609 fde_entry_header.entry_bytes,1609 fde_entry_header.entry_bytes,
1610 0,1610 0,
1611 true,1611 true,
...@@ -1613,17 +1613,44 @@ pub fn unwindFrameDwarf(...@@ -1613,17 +1613,44 @@ pub fn unwindFrameDwarf(
1613 @sizeOf(usize),1613 @sizeOf(usize),
1614 native_endian,1614 native_endian,
1615 );1615 );
1616 } else if (di.eh_frame_hdr) |header| {1616
1617 const eh_frame_len = if (di.section(.eh_frame)) |eh_frame| eh_frame.len else null;1617 break :blk .{ cie, fde };
1618 try header.findEntry(1618 } else blk: {
1619 ma,1619 // `.eh_frame_hdr` may be incomplete. We'll try it first, but if the lookup fails, we fall
1620 eh_frame_len,1620 // back to loading `.eh_frame`/`.debug_frame` and using those from that point on.
1621 @intFromPtr(di.section(.eh_frame_hdr).?.ptr),1621
1622 context.pc,1622 if (di.eh_frame_hdr) |header| hdr: {
1623 &cie,1623 const eh_frame_len = if (di.section(.eh_frame)) |eh_frame| eh_frame.len else null;
1624 &fde,1624
1625 );1625 var cie: Dwarf.CommonInformationEntry = undefined;
1626 } else {1626 var fde: Dwarf.FrameDescriptionEntry = undefined;
1627
1628 header.findEntry(
1629 ma,
1630 eh_frame_len,
1631 @intFromPtr(di.section(.eh_frame_hdr).?.ptr),
1632 context.pc,
1633 &cie,
1634 &fde,
1635 ) catch |err| switch (err) {
1636 error.InvalidDebugInfo => {
1637 // `.eh_frame_hdr` appears to be incomplete, so go ahead and populate `cie_map`
1638 // and `fde_list`, and fall back to the binary search logic below.
1639 try di.scanCieFdeInfo(allocator, base_address);
1640
1641 // Since `.eh_frame_hdr` is incomplete, we're very likely to get more lookup
1642 // failures using it, and we've just built a complete, sorted list of FDEs
1643 // anyway, so just stop using `.eh_frame_hdr` altogether.
1644 di.eh_frame_hdr = null;
1645
1646 break :hdr;
1647 },
1648 else => return err,
1649 };
1650
1651 break :blk .{ cie, fde };
1652 }
1653
1627 const index = std.sort.binarySearch(Dwarf.FrameDescriptionEntry, di.fde_list.items, context.pc, struct {1654 const index = std.sort.binarySearch(Dwarf.FrameDescriptionEntry, di.fde_list.items, context.pc, struct {
1628 pub fn compareFn(pc: usize, item: Dwarf.FrameDescriptionEntry) std.math.Order {1655 pub fn compareFn(pc: usize, item: Dwarf.FrameDescriptionEntry) std.math.Order {
1629 if (pc < item.pc_begin) return .lt;1656 if (pc < item.pc_begin) return .lt;
...@@ -1635,9 +1662,11 @@ pub fn unwindFrameDwarf(...@@ -1635,9 +1662,11 @@ pub fn unwindFrameDwarf(
1635 }1662 }
1636 }.compareFn);1663 }.compareFn);
16371664
1638 fde = if (index) |i| di.fde_list.items[i] else return error.MissingFDE;1665 const fde = if (index) |i| di.fde_list.items[i] else return error.MissingFDE;
1639 cie = di.cie_map.get(fde.cie_length_offset) orelse return error.MissingCIE;1666 const cie = di.cie_map.get(fde.cie_length_offset) orelse return error.MissingCIE;
1640 }1667
1668 break :blk .{ cie, fde };
1669 };
16411670
1642 var expression_context: Dwarf.expression.Context = .{1671 var expression_context: Dwarf.expression.Context = .{
1643 .format = cie.format,1672 .format = cie.format,
...@@ -1802,6 +1831,8 @@ pub fn supportsUnwinding(target: std.Target) bool {...@@ -1802,6 +1831,8 @@ pub fn supportsUnwinding(target: std.Target) bool {
1802}1831}
18031832
1804fn unwindFrameMachODwarf(1833fn unwindFrameMachODwarf(
1834 allocator: Allocator,
1835 base_address: usize,
1805 context: *UnwindContext,1836 context: *UnwindContext,
1806 ma: *std.debug.MemoryAccessor,1837 ma: *std.debug.MemoryAccessor,
1807 eh_frame: []const u8,1838 eh_frame: []const u8,
...@@ -1818,7 +1849,7 @@ fn unwindFrameMachODwarf(...@@ -1818,7 +1849,7 @@ fn unwindFrameMachODwarf(
1818 .owned = false,1849 .owned = false,
1819 };1850 };
18201851
1821 return unwindFrameDwarf(&di, context, ma, fde_offset);1852 return unwindFrameDwarf(allocator, &di, base_address, context, ma, fde_offset);
1822}1853}
18231854
1824/// This is a virtual machine that runs DWARF call frame instructions.1855/// This is a virtual machine that runs DWARF call frame instructions.