authorgravatar for kcbanner@gmail.comCasey Banner <kcbanner@gmail.com> 2023-07-16 22:07:20-04:00
committergravatar for kcbanner@gmail.comCasey Banner <kcbanner@gmail.com> 2023-07-20 22:58:16-04:00
log774dc2fdb75dc7f6b7e0bd2cfe947fffb9829ba1
tree84acdd4fe9fe07c1122ac15b5fb50f3dd6ba907c
parentbdb0a6fa77cc7eff7d03b022210328b24f9b6662

dwarf: add explicit_fde_offset to support more optimal __unwind_info dwarf lookups


3 files changed, 77 insertions(+), 19 deletions(-)

lib/std/debug.zig+1-2
......@@ -653,7 +653,7 @@ pub const StackIterator = struct {
653653 }
654654
655655 if (try module.getDwarfInfoForAddress(unwind_state.debug_info.allocator, unwind_state.dwarf_context.pc)) |di| {
656 return di.unwindFrame(&unwind_state.dwarf_context, module.base_address);
656 return di.unwindFrame(&unwind_state.dwarf_context, null);
657657 } else return error.MissingDebugInfo;
658658 }
659659
......@@ -1894,7 +1894,6 @@ pub const DebugInfo = struct {
18941894 obj_di.* = try readElfDebugInfo(self.allocator, if (ctx.name.len > 0) ctx.name else null, ctx.build_id, null, &sections, null);
18951895 obj_di.base_address = ctx.base_address;
18961896
1897 // TODO: Don't actually scan everything, search on demand
18981897 // Missing unwind info isn't treated as a failure, as the unwinder will fall back to FP-based unwinding
18991898 obj_di.dwarf.scanAllUnwindInfo(self.allocator, ctx.base_address) catch {};
19001899
lib/std/dwarf.zig+50-16
......@@ -1562,8 +1562,7 @@ pub const DwarfInfo = struct {
15621562 /// If .eh_frame_hdr is present, then only the header needs to be parsed.
15631563 ///
15641564 /// Otherwise, .eh_frame and .debug_frame are scanned and a sorted list
1565 /// of FDEs is built. In this case, the decoded PC ranges in the FDEs
1566 /// are all normalized to be relative to the module's base.
1565 /// of FDEs is built for binary searching during unwinding.
15671566 pub fn scanAllUnwindInfo(di: *DwarfInfo, allocator: mem.Allocator, base_address: usize) !void {
15681567 if (di.section(.eh_frame_hdr)) |eh_frame_hdr| blk: {
15691568 var stream = io.fixedBufferStream(eh_frame_hdr);
......@@ -1650,7 +1649,14 @@ pub const DwarfInfo = struct {
16501649 }
16511650 }
16521651
1653 pub fn unwindFrame(di: *const DwarfInfo, context: *UnwindContext, module_base_address: usize) !usize {
1652 /// Unwind a stack frame using DWARF unwinding info, updating the register context.
1653 ///
1654 /// If `.eh_frame_hdr` is available, it will be used to binary search for the FDE.
1655 /// Otherwise, a linear scan of `.eh_frame` and `.debug_frame` is done to find the FDE.
1656 ///
1657 /// `explicit_fde_offset` is for cases where the FDE offset is known, such as when __unwind_info
1658 /// defers unwinding to DWARF. This is an offset into the `.eh_frame` section.
1659 pub fn unwindFrame(di: *const DwarfInfo, context: *UnwindContext, explicit_fde_offset: ?usize) !usize {
16541660 if (!comptime abi.isSupportedArch(builtin.target.cpu.arch)) return error.UnsupportedCpuArchitecture;
16551661 if (context.pc == 0) return 0;
16561662
......@@ -1660,26 +1666,54 @@ pub const DwarfInfo = struct {
16601666 var cie: CommonInformationEntry = undefined;
16611667 var fde: FrameDescriptionEntry = undefined;
16621668
1663 // In order to support reading .eh_frame from the ELF file (vs using the already-mapped section),
1664 // scanAllUnwindInfo has already mapped any pc-relative offsets such that they will be relative to zero
1665 // instead of the actual base address of the module. When using .eh_frame_hdr, PC can be used directly
1666 // as pointers will be decoded relative to the already-mapped .eh_frame.
1667 var mapped_pc: usize = undefined;
1668 if (di.eh_frame_hdr) |header| {
1669 if (explicit_fde_offset) |fde_offset| {
1670 const dwarf_section: DwarfSection = .eh_frame;
1671 const frame_section = di.section(dwarf_section) orelse return error.MissingFDE;
1672 if (fde_offset >= frame_section.len) return error.MissingFDE;
1673
1674 var stream = io.fixedBufferStream(frame_section);
1675 const fde_entry_header = try EntryHeader.read(&stream, dwarf_section, di.endian);
1676 if (fde_entry_header.type != .fde) return error.MissingFDE;
1677
1678 const cie_offset = fde_entry_header.type.fde;
1679 try stream.seekTo(cie_offset);
1680
1681 const cie_entry_header = try EntryHeader.read(&stream, dwarf_section, builtin.cpu.arch.endian());
1682 if (cie_entry_header.type != .cie) return badDwarf();
1683
1684 cie = try CommonInformationEntry.parse(
1685 cie_entry_header.entry_bytes,
1686 0,
1687 true,
1688 cie_entry_header.is_64,
1689 dwarf_section,
1690 cie_entry_header.length_offset,
1691 @sizeOf(usize),
1692 builtin.cpu.arch.endian(),
1693 );
1694
1695 fde = try FrameDescriptionEntry.parse(
1696 fde_entry_header.entry_bytes,
1697 0,
1698 true,
1699 cie,
1700 @sizeOf(usize),
1701 builtin.cpu.arch.endian(),
1702 );
1703 } else if (di.eh_frame_hdr) |header| {
1704 std.debug.print("EH_FRAME_HDR\n", .{});
1705
16691706 const eh_frame_len = if (di.section(.eh_frame)) |eh_frame| eh_frame.len else null;
1670 mapped_pc = context.pc;
16711707 try header.findEntry(
16721708 context.isValidMemory,
16731709 eh_frame_len,
16741710 @intFromPtr(di.section(.eh_frame_hdr).?.ptr),
1675 mapped_pc,
1711 context.pc,
16761712 &cie,
16771713 &fde,
16781714 );
16791715 } else {
1680 //mapped_pc = context.pc - module_base_address;
1681 mapped_pc = context.pc;
1682 const index = std.sort.binarySearch(FrameDescriptionEntry, mapped_pc, di.fde_list.items, {}, struct {
1716 const index = std.sort.binarySearch(FrameDescriptionEntry, context.pc, di.fde_list.items, {}, struct {
16831717 pub fn compareFn(_: void, pc: usize, mid_item: FrameDescriptionEntry) std.math.Order {
16841718 if (pc < mid_item.pc_begin) return .lt;
16851719
......@@ -1707,7 +1741,7 @@ pub const DwarfInfo = struct {
17071741 context.reg_context.eh_frame = cie.version != 4;
17081742 context.reg_context.is_macho = di.is_macho;
17091743
1710 _ = try context.vm.runToNative(context.allocator, mapped_pc, cie, fde);
1744 _ = try context.vm.runToNative(context.allocator, context.pc, cie, fde);
17111745 const row = &context.vm.current_row;
17121746
17131747 context.cfa = switch (row.cfa.rule) {
......@@ -2056,7 +2090,7 @@ pub const ExceptionFrameHeader = struct {
20562090 if (!self.isValidPtr(@intFromPtr(&fde_entry_header.entry_bytes[fde_entry_header.entry_bytes.len - 1]), isValidMemory, eh_frame_len)) return badDwarf();
20572091 if (fde_entry_header.type != .fde) return badDwarf();
20582092
2059 // CIEs always come before FDEs (the offset is a subtration), so we can assume this memory is readable
2093 // CIEs always come before FDEs (the offset is a subtraction), so we can assume this memory is readable
20602094 const cie_offset = fde_entry_header.type.fde;
20612095 try eh_frame_stream.seekTo(cie_offset);
20622096 const cie_entry_header = try EntryHeader.read(&eh_frame_stream, .eh_frame, builtin.cpu.arch.endian());
test/standalone/stack_iterator/build.zig+26-1
......@@ -8,6 +8,14 @@ pub fn build(b: *std.Build) void {
88 const optimize = b.standardOptimizeOption(.{});
99
1010 // Unwinding pure zig code, with a frame pointer
11 //
12 // getcontext version: zig std
13 //
14 // Unwind info type:
15 // - ELF: DWARF .debug_frame
16 // - MachO: __unwind_info encodings:
17 // - x86_64: RBP_FRAME
18 // - aarch64: FRAME, DWARF
1119 {
1220 const exe = b.addExecutable(.{
1321 .name = "zig_unwind_fp",
......@@ -23,7 +31,15 @@ pub fn build(b: *std.Build) void {
2331 test_step.dependOn(&run_cmd.step);
2432 }
2533
26 // Unwinding pure zig code, without a frame pointer
34 // Unwinding pure zig code, without a frame pointer.
35 //
36 // getcontext version: zig std
37 //
38 // Unwind info type:
39 // - ELF: DWARF .eh_frame_hdr + .eh_frame
40 // - MachO: __unwind_info encodings:
41 // - x86_64: STACK_IMMD, STACK_IND
42 // - aarch64: FRAMELESS, DWARF
2743 {
2844 const exe = b.addExecutable(.{
2945 .name = "zig_unwind_nofp",
......@@ -34,12 +50,21 @@ pub fn build(b: *std.Build) void {
3450
3551 if (target.isDarwin()) exe.unwind_tables = true;
3652 exe.omit_frame_pointer = true;
53 exe.unwind_tables = true;
3754
3855 const run_cmd = b.addRunArtifact(exe);
3956 test_step.dependOn(&run_cmd.step);
4057 }
4158
4259 // Unwinding through a C shared library without a frame pointer (libc)
60 //
61 // getcontext version: libc
62 //
63 // Unwind info type:
64 // - ELF: DWARF .eh_frame + .debug_frame
65 // - MachO: __unwind_info encodings:
66 // - x86_64: STACK_IMMD, STACK_IND
67 // - aarch64: FRAMELESS, DWARF
4368 {
4469 const c_shared_lib = b.addSharedLibrary(.{
4570 .name = "c_shared_lib",