Function unwindFrameDwarf [src]

Unwind a stack frame using DWARF unwinding info, updating the register context. If .eh_frame_hdr is available and complete, 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. The latter may require lazily loading the data in those sections. 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.

Prototype

pub fn unwindFrameDwarf( allocator: Allocator, di: *Dwarf, base_address: usize, context: *UnwindContext, ma: *std.debug.MemoryAccessor, explicit_fde_offset: ?usize, ) !usize

Parameters

allocator: Allocatordi: *Dwarfbase_address: usizecontext: *UnwindContextma: *std.debug.MemoryAccessorexplicit_fde_offset: ?usize

Source

pub fn unwindFrameDwarf( allocator: Allocator, di: *Dwarf, base_address: usize, context: *UnwindContext, ma: *std.debug.MemoryAccessor, explicit_fde_offset: ?usize, ) !usize { if (!supports_unwinding) return error.UnsupportedCpuArchitecture; if (context.pc == 0) return 0; // 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; if (fde_offset >= frame_section.len) return error.MissingFDE; var fbr: std.debug.FixedBufferReader = .{ .buf = frame_section, .pos = fde_offset, .endian = di.endian, }; const fde_entry_header = try Dwarf.EntryHeader.read(&fbr, null, dwarf_section); if (fde_entry_header.type != .fde) return error.MissingFDE; const cie_offset = fde_entry_header.type.fde; try fbr.seekTo(cie_offset); fbr.endian = native_endian; const cie_entry_header = try Dwarf.EntryHeader.read(&fbr, null, dwarf_section); if (cie_entry_header.type != .cie) return Dwarf.bad(); const cie = try Dwarf.CommonInformationEntry.parse( cie_entry_header.entry_bytes, 0, true, cie_entry_header.format, dwarf_section, cie_entry_header.length_offset, @sizeOf(usize), native_endian, ); const fde = try Dwarf.FrameDescriptionEntry.parse( fde_entry_header.entry_bytes, 0, true, cie, @sizeOf(usize), native_endian, ); break :blk .{ cie, fde }; } else blk: { // `.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 null; var cie: Dwarf.CommonInformationEntry = undefined; var fde: Dwarf.FrameDescriptionEntry = undefined; header.findEntry( ma, eh_frame_len, @intFromPtr(di.section(.eh_frame_hdr).?.ptr), context.pc, &cie, &fde, ) catch |err| switch (err) { error.InvalidDebugInfo => { // `.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); // 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; break :hdr; }, else => return err, }; 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 { if (pc < item.pc_begin) return .lt; const range_end = item.pc_begin + item.pc_range; if (pc < range_end) return .eq; return .gt; } }.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; break :blk .{ cie, fde }; }; var expression_context: Dwarf.expression.Context = .{ .format = cie.format, .memory_accessor = ma, .compile_unit = di.findCompileUnit(fde.pc_begin) catch null, .thread_context = context.thread_context, .reg_context = context.reg_context, .cfa = context.cfa, }; context.vm.reset(); context.reg_context.eh_frame = cie.version != 4; context.reg_context.is_macho = di.is_macho; const row = try context.vm.runToNative(context.allocator, context.pc, cie, fde); context.cfa = switch (row.cfa.rule) { .val_offset => |offset| blk: { const register = row.cfa.register orelse return error.InvalidCFARule; const value = mem.readInt(usize, (try regBytes(context.thread_context, register, context.reg_context))[0..@sizeOf(usize)], native_endian); break :blk try applyOffset(value, offset); }, .expression => |expr| blk: { context.stack_machine.reset(); const value = try context.stack_machine.run( expr, context.allocator, expression_context, context.cfa, ); if (value) |v| { if (v != .generic) return error.InvalidExpressionValue; break :blk v.generic; } else return error.NoExpressionValue; }, else => return error.InvalidCFARule, }; if (ma.load(usize, context.cfa.?) == null) return error.InvalidCFA; expression_context.cfa = context.cfa; // Buffering the modifications is done because copying the thread context is not portable, // some implementations (ie. darwin) use internal pointers to the mcontext. var arena = std.heap.ArenaAllocator.init(context.allocator); defer arena.deinit(); const update_allocator = arena.allocator(); const RegisterUpdate = struct { // Backed by thread_context dest: []u8, // Backed by arena src: []const u8, prev: ?*@This(), }; var update_tail: ?*RegisterUpdate = null; var has_return_address = true; for (context.vm.rowColumns(row)) |column| { if (column.register) |register| { if (register == cie.return_address_register) { has_return_address = column.rule != .undefined; } const dest = try regBytes(context.thread_context, register, context.reg_context); const src = try update_allocator.alloc(u8, dest.len); const prev = update_tail; update_tail = try update_allocator.create(RegisterUpdate); update_tail.?.* = .{ .dest = dest, .src = src, .prev = prev, }; try column.resolveValue( context, expression_context, ma, src, ); } } // On all implemented architectures, the CFA is defined as being the previous frame's SP (try regValueNative(context.thread_context, spRegNum(context.reg_context), context.reg_context)).* = context.cfa.?; while (update_tail) |tail| { @memcpy(tail.dest, tail.src); update_tail = tail.prev; } if (has_return_address) { context.pc = stripInstructionPtrAuthCode(mem.readInt(usize, (try regBytes( context.thread_context, cie.return_address_register, context.reg_context, ))[0..@sizeOf(usize)], native_endian)); } else { context.pc = 0; } (try regValueNative(context.thread_context, ip_reg_num, context.reg_context)).* = context.pc; // The call instruction will have pushed the address of the instruction that follows the call as the return address. // This next instruction may be past the end of the function if the caller was `noreturn` (ie. the last instruction in // the function was the call). If we were to look up an FDE entry using the return address directly, it could end up // either not finding an FDE at all, or using the next FDE in the program, producing incorrect results. To prevent this, // we subtract one so that the next lookup is guaranteed to land inside the // // The exception to this rule is signal frames, where we return execution would be returned to the instruction // that triggered the handler. const return_address = context.pc; if (context.pc > 0 and !cie.isSignalFrame()) context.pc -= 1; return return_address; }