Commit 774dc2fdb7

kcbanner <kcbanner@gmail.com>
2023-07-17 04:07:20
dwarf: add explicit_fde_offset to support more optimal __unwind_info dwarf lookups
1 parent bdb0a6f
Changed files (3)
lib
test
standalone
stack_iterator
lib/std/debug.zig
@@ -653,7 +653,7 @@ pub const StackIterator = struct {
         }
 
         if (try module.getDwarfInfoForAddress(unwind_state.debug_info.allocator, unwind_state.dwarf_context.pc)) |di| {
-            return di.unwindFrame(&unwind_state.dwarf_context, module.base_address);
+            return di.unwindFrame(&unwind_state.dwarf_context, null);
         } else return error.MissingDebugInfo;
     }
 
@@ -1894,7 +1894,6 @@ pub const DebugInfo = struct {
         obj_di.* = try readElfDebugInfo(self.allocator, if (ctx.name.len > 0) ctx.name else null, ctx.build_id, null, &sections, null);
         obj_di.base_address = ctx.base_address;
 
-        // TODO: Don't actually scan everything, search on demand
         // 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 {};
 
lib/std/dwarf.zig
@@ -1562,8 +1562,7 @@ pub const DwarfInfo = struct {
     /// 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. In this case, the decoded PC ranges in the FDEs
-    /// are all normalized to be relative to the module's base.
+    /// of FDEs is built for binary searching during unwinding.
     pub fn scanAllUnwindInfo(di: *DwarfInfo, allocator: mem.Allocator, base_address: usize) !void {
         if (di.section(.eh_frame_hdr)) |eh_frame_hdr| blk: {
             var stream = io.fixedBufferStream(eh_frame_hdr);
@@ -1650,7 +1649,14 @@ pub const DwarfInfo = struct {
         }
     }
 
-    pub fn unwindFrame(di: *const DwarfInfo, context: *UnwindContext, module_base_address: usize) !usize {
+    /// Unwind a stack frame using DWARF unwinding info, updating the register context.
+    ///
+    /// If `.eh_frame_hdr` is available, it will be used to binary search for the FDE.
+    /// Otherwise, a linear scan of `.eh_frame` and `.debug_frame` is done to find the FDE.
+    ///
+    /// `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 unwindFrame(di: *const DwarfInfo, context: *UnwindContext, explicit_fde_offset: ?usize) !usize {
         if (!comptime abi.isSupportedArch(builtin.target.cpu.arch)) return error.UnsupportedCpuArchitecture;
         if (context.pc == 0) return 0;
 
@@ -1660,26 +1666,54 @@ pub const DwarfInfo = struct {
         var cie: CommonInformationEntry = undefined;
         var fde: FrameDescriptionEntry = undefined;
 
-        // In order to support reading .eh_frame from the ELF file (vs using the already-mapped section),
-        // scanAllUnwindInfo has already mapped any pc-relative offsets such that they will be relative to zero
-        // instead of the actual base address of the module. When using .eh_frame_hdr, PC can be used directly
-        // as pointers will be decoded relative to the already-mapped .eh_frame.
-        var mapped_pc: usize = undefined;
-        if (di.eh_frame_hdr) |header| {
+        if (explicit_fde_offset) |fde_offset| {
+            const dwarf_section: DwarfSection = .eh_frame;
+            const frame_section = di.section(dwarf_section) orelse return error.MissingFDE;
+            if (fde_offset >= frame_section.len) return error.MissingFDE;
+
+            var stream = io.fixedBufferStream(frame_section);
+            const fde_entry_header = try EntryHeader.read(&stream, dwarf_section, di.endian);
+            if (fde_entry_header.type != .fde) return error.MissingFDE;
+
+            const cie_offset = fde_entry_header.type.fde;
+            try stream.seekTo(cie_offset);
+
+            const cie_entry_header = try EntryHeader.read(&stream, dwarf_section, builtin.cpu.arch.endian());
+            if (cie_entry_header.type != .cie) return badDwarf();
+
+            cie = try CommonInformationEntry.parse(
+                cie_entry_header.entry_bytes,
+                0,
+                true,
+                cie_entry_header.is_64,
+                dwarf_section,
+                cie_entry_header.length_offset,
+                @sizeOf(usize),
+                builtin.cpu.arch.endian(),
+            );
+
+            fde = try FrameDescriptionEntry.parse(
+                fde_entry_header.entry_bytes,
+                0,
+                true,
+                cie,
+                @sizeOf(usize),
+                builtin.cpu.arch.endian(),
+            );
+        } else if (di.eh_frame_hdr) |header| {
+            std.debug.print("EH_FRAME_HDR\n", .{});
+
             const eh_frame_len = if (di.section(.eh_frame)) |eh_frame| eh_frame.len else null;
-            mapped_pc = context.pc;
             try header.findEntry(
                 context.isValidMemory,
                 eh_frame_len,
                 @intFromPtr(di.section(.eh_frame_hdr).?.ptr),
-                mapped_pc,
+                context.pc,
                 &cie,
                 &fde,
             );
         } else {
-            //mapped_pc = context.pc - module_base_address;
-            mapped_pc = context.pc;
-            const index = std.sort.binarySearch(FrameDescriptionEntry, mapped_pc, di.fde_list.items, {}, struct {
+            const index = std.sort.binarySearch(FrameDescriptionEntry, context.pc, di.fde_list.items, {}, struct {
                 pub fn compareFn(_: void, pc: usize, mid_item: FrameDescriptionEntry) std.math.Order {
                     if (pc < mid_item.pc_begin) return .lt;
 
@@ -1707,7 +1741,7 @@ pub const DwarfInfo = struct {
         context.reg_context.eh_frame = cie.version != 4;
         context.reg_context.is_macho = di.is_macho;
 
-        _ = try context.vm.runToNative(context.allocator, mapped_pc, cie, fde);
+        _ = try context.vm.runToNative(context.allocator, context.pc, cie, fde);
         const row = &context.vm.current_row;
 
         context.cfa = switch (row.cfa.rule) {
@@ -2056,7 +2090,7 @@ pub const ExceptionFrameHeader = struct {
         if (!self.isValidPtr(@intFromPtr(&fde_entry_header.entry_bytes[fde_entry_header.entry_bytes.len - 1]), isValidMemory, eh_frame_len)) return badDwarf();
         if (fde_entry_header.type != .fde) return badDwarf();
 
-        // CIEs always come before FDEs (the offset is a subtration), so we can assume this memory is readable
+        // 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;
         try eh_frame_stream.seekTo(cie_offset);
         const cie_entry_header = try EntryHeader.read(&eh_frame_stream, .eh_frame, builtin.cpu.arch.endian());
test/standalone/stack_iterator/build.zig
@@ -8,6 +8,14 @@ pub fn build(b: *std.Build) void {
     const optimize = b.standardOptimizeOption(.{});
 
     // Unwinding pure zig code, with a frame pointer
+    //
+    // getcontext version: zig std
+    //
+    // Unwind info type:
+    //   - ELF: DWARF .debug_frame
+    //   - MachO: __unwind_info encodings:
+    //     - x86_64: RBP_FRAME
+    //     - aarch64: FRAME, DWARF
     {
         const exe = b.addExecutable(.{
             .name = "zig_unwind_fp",
@@ -23,7 +31,15 @@ pub fn build(b: *std.Build) void {
         test_step.dependOn(&run_cmd.step);
     }
 
-    // Unwinding pure zig code, without a frame pointer
+    // Unwinding pure zig code, without a frame pointer.
+    //
+    // getcontext version: zig std
+    //
+    // Unwind info type:
+    //   - ELF: DWARF .eh_frame_hdr + .eh_frame
+    //   - MachO: __unwind_info encodings:
+    //     - x86_64: STACK_IMMD, STACK_IND
+    //     - aarch64: FRAMELESS, DWARF
     {
         const exe = b.addExecutable(.{
             .name = "zig_unwind_nofp",
@@ -34,12 +50,21 @@ pub fn build(b: *std.Build) void {
 
         if (target.isDarwin()) exe.unwind_tables = true;
         exe.omit_frame_pointer = true;
+        exe.unwind_tables = true;
 
         const run_cmd = b.addRunArtifact(exe);
         test_step.dependOn(&run_cmd.step);
     }
 
     // Unwinding through a C shared library without a frame pointer (libc)
+    //
+    // getcontext version: libc
+    //
+    // Unwind info type:
+    //   - ELF: DWARF .eh_frame + .debug_frame
+    //   - MachO: __unwind_info encodings:
+    //     - x86_64: STACK_IMMD, STACK_IND
+    //     - aarch64: FRAMELESS, DWARF
     {
         const c_shared_lib = b.addSharedLibrary(.{
             .name = "c_shared_lib",