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 {...@@ -653,7 +653,7 @@ pub const StackIterator = struct {
653 }653 }
654654
655 if (try module.getDwarfInfoForAddress(unwind_state.debug_info.allocator, unwind_state.dwarf_context.pc)) |di| {655 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);
657 } else return error.MissingDebugInfo;657 } else return error.MissingDebugInfo;
658 }658 }
659659
...@@ -1894,7 +1894,6 @@ pub const DebugInfo = struct {...@@ -1894,7 +1894,6 @@ pub const DebugInfo = struct {
1894 obj_di.* = try readElfDebugInfo(self.allocator, if (ctx.name.len > 0) ctx.name else null, ctx.build_id, null, &sections, null);1894 obj_di.* = try readElfDebugInfo(self.allocator, if (ctx.name.len > 0) ctx.name else null, ctx.build_id, null, &sections, null);
1895 obj_di.base_address = ctx.base_address;1895 obj_di.base_address = ctx.base_address;
18961896
1897 // TODO: Don't actually scan everything, search on demand
1898 // Missing unwind info isn't treated as a failure, as the unwinder will fall back to FP-based unwinding1897 // Missing unwind info isn't treated as a failure, as the unwinder will fall back to FP-based unwinding
1899 obj_di.dwarf.scanAllUnwindInfo(self.allocator, ctx.base_address) catch {};1898 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 {...@@ -1562,8 +1562,7 @@ pub const DwarfInfo = struct {
1562 /// If .eh_frame_hdr is present, then only the header needs to be parsed.1562 /// If .eh_frame_hdr is present, then only the header needs to be parsed.
1563 ///1563 ///
1564 /// Otherwise, .eh_frame and .debug_frame are scanned and a sorted list1564 /// 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 FDEs1565 /// of FDEs is built for binary searching during unwinding.
1566 /// are all normalized to be relative to the module's base.
1567 pub fn scanAllUnwindInfo(di: *DwarfInfo, allocator: mem.Allocator, base_address: usize) !void {1566 pub fn scanAllUnwindInfo(di: *DwarfInfo, allocator: mem.Allocator, base_address: usize) !void {
1568 if (di.section(.eh_frame_hdr)) |eh_frame_hdr| blk: {1567 if (di.section(.eh_frame_hdr)) |eh_frame_hdr| blk: {
1569 var stream = io.fixedBufferStream(eh_frame_hdr);1568 var stream = io.fixedBufferStream(eh_frame_hdr);
...@@ -1650,7 +1649,14 @@ pub const DwarfInfo = struct {...@@ -1650,7 +1649,14 @@ pub const DwarfInfo = struct {
1650 }1649 }
1651 }1650 }
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 {
1654 if (!comptime abi.isSupportedArch(builtin.target.cpu.arch)) return error.UnsupportedCpuArchitecture;1660 if (!comptime abi.isSupportedArch(builtin.target.cpu.arch)) return error.UnsupportedCpuArchitecture;
1655 if (context.pc == 0) return 0;1661 if (context.pc == 0) return 0;
16561662
...@@ -1660,26 +1666,54 @@ pub const DwarfInfo = struct {...@@ -1660,26 +1666,54 @@ pub const DwarfInfo = struct {
1660 var cie: CommonInformationEntry = undefined;1666 var cie: CommonInformationEntry = undefined;
1661 var fde: FrameDescriptionEntry = undefined;1667 var fde: FrameDescriptionEntry = undefined;
16621668
1663 // In order to support reading .eh_frame from the ELF file (vs using the already-mapped section),1669 if (explicit_fde_offset) |fde_offset| {
1664 // scanAllUnwindInfo has already mapped any pc-relative offsets such that they will be relative to zero1670 const dwarf_section: DwarfSection = .eh_frame;
1665 // instead of the actual base address of the module. When using .eh_frame_hdr, PC can be used directly1671 const frame_section = di.section(dwarf_section) orelse return error.MissingFDE;
1666 // as pointers will be decoded relative to the already-mapped .eh_frame.1672 if (fde_offset >= frame_section.len) return error.MissingFDE;
1667 var mapped_pc: usize = undefined;1673
1668 if (di.eh_frame_hdr) |header| {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
1669 const eh_frame_len = if (di.section(.eh_frame)) |eh_frame| eh_frame.len else null;1706 const eh_frame_len = if (di.section(.eh_frame)) |eh_frame| eh_frame.len else null;
1670 mapped_pc = context.pc;
1671 try header.findEntry(1707 try header.findEntry(
1672 context.isValidMemory,1708 context.isValidMemory,
1673 eh_frame_len,1709 eh_frame_len,
1674 @intFromPtr(di.section(.eh_frame_hdr).?.ptr),1710 @intFromPtr(di.section(.eh_frame_hdr).?.ptr),
1675 mapped_pc,1711 context.pc,
1676 &cie,1712 &cie,
1677 &fde,1713 &fde,
1678 );1714 );
1679 } else {1715 } else {
1680 //mapped_pc = context.pc - module_base_address;1716 const index = std.sort.binarySearch(FrameDescriptionEntry, context.pc, di.fde_list.items, {}, struct {
1681 mapped_pc = context.pc;
1682 const index = std.sort.binarySearch(FrameDescriptionEntry, mapped_pc, di.fde_list.items, {}, struct {
1683 pub fn compareFn(_: void, pc: usize, mid_item: FrameDescriptionEntry) std.math.Order {1717 pub fn compareFn(_: void, pc: usize, mid_item: FrameDescriptionEntry) std.math.Order {
1684 if (pc < mid_item.pc_begin) return .lt;1718 if (pc < mid_item.pc_begin) return .lt;
16851719
...@@ -1707,7 +1741,7 @@ pub const DwarfInfo = struct {...@@ -1707,7 +1741,7 @@ pub const DwarfInfo = struct {
1707 context.reg_context.eh_frame = cie.version != 4;1741 context.reg_context.eh_frame = cie.version != 4;
1708 context.reg_context.is_macho = di.is_macho;1742 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);
1711 const row = &context.vm.current_row;1745 const row = &context.vm.current_row;
17121746
1713 context.cfa = switch (row.cfa.rule) {1747 context.cfa = switch (row.cfa.rule) {
...@@ -2056,7 +2090,7 @@ pub const ExceptionFrameHeader = struct {...@@ -2056,7 +2090,7 @@ pub const ExceptionFrameHeader = struct {
2056 if (!self.isValidPtr(@intFromPtr(&fde_entry_header.entry_bytes[fde_entry_header.entry_bytes.len - 1]), isValidMemory, eh_frame_len)) return badDwarf();2090 if (!self.isValidPtr(@intFromPtr(&fde_entry_header.entry_bytes[fde_entry_header.entry_bytes.len - 1]), isValidMemory, eh_frame_len)) return badDwarf();
2057 if (fde_entry_header.type != .fde) return badDwarf();2091 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 readable2093 // CIEs always come before FDEs (the offset is a subtraction), so we can assume this memory is readable
2060 const cie_offset = fde_entry_header.type.fde;2094 const cie_offset = fde_entry_header.type.fde;
2061 try eh_frame_stream.seekTo(cie_offset);2095 try eh_frame_stream.seekTo(cie_offset);
2062 const cie_entry_header = try EntryHeader.read(&eh_frame_stream, .eh_frame, builtin.cpu.arch.endian());2096 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 {...@@ -8,6 +8,14 @@ pub fn build(b: *std.Build) void {
8 const optimize = b.standardOptimizeOption(.{});8 const optimize = b.standardOptimizeOption(.{});
99
10 // Unwinding pure zig code, with a frame pointer10 // 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
11 {19 {
12 const exe = b.addExecutable(.{20 const exe = b.addExecutable(.{
13 .name = "zig_unwind_fp",21 .name = "zig_unwind_fp",
...@@ -23,7 +31,15 @@ pub fn build(b: *std.Build) void {...@@ -23,7 +31,15 @@ pub fn build(b: *std.Build) void {
23 test_step.dependOn(&run_cmd.step);31 test_step.dependOn(&run_cmd.step);
24 }32 }
2533
26 // Unwinding pure zig code, without a frame pointer34 // 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
27 {43 {
28 const exe = b.addExecutable(.{44 const exe = b.addExecutable(.{
29 .name = "zig_unwind_nofp",45 .name = "zig_unwind_nofp",
...@@ -34,12 +50,21 @@ pub fn build(b: *std.Build) void {...@@ -34,12 +50,21 @@ pub fn build(b: *std.Build) void {
3450
35 if (target.isDarwin()) exe.unwind_tables = true;51 if (target.isDarwin()) exe.unwind_tables = true;
36 exe.omit_frame_pointer = true;52 exe.omit_frame_pointer = true;
53 exe.unwind_tables = true;
3754
38 const run_cmd = b.addRunArtifact(exe);55 const run_cmd = b.addRunArtifact(exe);
39 test_step.dependOn(&run_cmd.step);56 test_step.dependOn(&run_cmd.step);
40 }57 }
4158
42 // Unwinding through a C shared library without a frame pointer (libc)59 // 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
43 {68 {
44 const c_shared_lib = b.addSharedLibrary(.{69 const c_shared_lib = b.addSharedLibrary(.{
45 .name = "c_shared_lib",70 .name = "c_shared_lib",