| 1 | //! Implements stack unwinding based on `Dwarf.Unwind`. The caller is responsible for providing the |
| 2 | //! initialized `Dwarf.Unwind` from the `.debug_frame` (or equivalent) section; this type handles |
| 3 | //! computing and applying the CFI register rules to evolve a `std.debug.cpu_context.Native` through |
| 4 | //! stack frames, hence performing the virtual unwind. |
| 5 | //! |
| 6 | //! Notably, this type is a valid implementation of `std.debug.SelfInfo.UnwindContext`. |
| 7 | |
| 8 | /// The state of the CPU in the current stack frame. |
| 9 | cpu_state: std.debug.cpu_context.Native, |
| 10 | /// The value of the Program Counter in this frame. This is almost the same as the value of the IP |
| 11 | /// register in `cpu_state`, but may be off by one because the IP is typically a *return* address. |
| 12 | pc: usize, |
| 13 | |
| 14 | cfi_vm: Dwarf.Unwind.VirtualMachine, |
| 15 | expr_vm: Dwarf.expression.StackMachine(.{ .call_frame_context = true }), |
| 16 | |
| 17 | pub const CacheEntry = struct { |
| 18 | const max_rules = 32; |
| 19 | |
| 20 | pc: usize, |
| 21 | cie: *const Dwarf.Unwind.CommonInformationEntry, |
| 22 | cfa_rule: Dwarf.Unwind.VirtualMachine.CfaRule, |
| 23 | num_rules: u8, |
| 24 | rules_regs: [max_rules]u16, |
| 25 | rules: [max_rules]Dwarf.Unwind.VirtualMachine.RegisterRule, |
| 26 | |
| 27 | pub fn find(entries: []const CacheEntry, pc: usize) ?*const CacheEntry { |
| 28 | assert(pc != 0); |
| 29 | const idx = std.hash.int(pc) % entries.len; |
| 30 | const entry = &entries[idx]; |
| 31 | return if (entry.pc == pc) entry else null; |
| 32 | } |
| 33 | |
| 34 | pub fn populate(entry: *const CacheEntry, entries: []CacheEntry) void { |
| 35 | const idx = std.hash.int(entry.pc) % entries.len; |
| 36 | entries[idx] = entry.*; |
| 37 | } |
| 38 | |
| 39 | pub const empty: CacheEntry = .{ |
| 40 | .pc = 0, |
| 41 | .cie = undefined, |
| 42 | .cfa_rule = undefined, |
| 43 | .num_rules = undefined, |
| 44 | .rules_regs = undefined, |
| 45 | .rules = undefined, |
| 46 | }; |
| 47 | }; |
| 48 | |
| 49 | pub fn init(cpu_context: *const std.debug.cpu_context.Native) SelfUnwinder { |
| 50 | return .{ |
| 51 | .cpu_state = cpu_context.*, |
| 52 | .pc = stripInstructionPtrAuthCode(cpu_context.getPc()), |
| 53 | .cfi_vm = .{}, |
| 54 | .expr_vm = .{}, |
| 55 | }; |
| 56 | } |
| 57 | |
| 58 | pub fn deinit(unwinder: *SelfUnwinder) void { |
| 59 | const gpa = std.debug.getDebugInfoAllocator(); |
| 60 | unwinder.cfi_vm.deinit(gpa); |
| 61 | unwinder.expr_vm.deinit(gpa); |
| 62 | unwinder.* = undefined; |
| 63 | } |
| 64 | |
| 65 | pub fn getFp(unwinder: *const SelfUnwinder) usize { |
| 66 | return unwinder.cpu_state.getFp(); |
| 67 | } |
| 68 | |
| 69 | /// Compute the rule set for the address `unwinder.pc` from the information in `unwind`. The caller |
| 70 | /// may store the returned rule set in a simple fixed-size cache keyed on the `pc` field to avoid |
| 71 | /// frequently recomputing register rules when unwinding many times. |
| 72 | /// |
| 73 | /// To actually apply the computed rules, see `next`. |
| 74 | pub fn computeRules( |
| 75 | unwinder: *SelfUnwinder, |
| 76 | gpa: Allocator, |
| 77 | unwind: *const Dwarf.Unwind, |
| 78 | load_offset: usize, |
| 79 | explicit_fde_offset: ?usize, |
| 80 | ) !CacheEntry { |
| 81 | assert(unwinder.pc != 0); |
| 82 | |
| 83 | const pc_vaddr = unwinder.pc - load_offset; |
| 84 | |
| 85 | const fde_offset = explicit_fde_offset orelse try unwind.lookupPc( |
| 86 | pc_vaddr, |
| 87 | @sizeOf(usize), |
| 88 | native_endian, |
| 89 | ) orelse return error.MissingDebugInfo; |
| 90 | const cie, const fde = try unwind.getFde(fde_offset, native_endian); |
| 91 | |
| 92 | // `lookupPc` can return false positives, so check if the FDE *actually* includes the pc |
| 93 | if (pc_vaddr < fde.pc_begin or pc_vaddr >= fde.pc_begin + fde.pc_range) { |
| 94 | return error.MissingDebugInfo; |
| 95 | } |
| 96 | |
| 97 | unwinder.cfi_vm.reset(); |
| 98 | const row = try unwinder.cfi_vm.runTo(gpa, pc_vaddr, cie, &fde, @sizeOf(usize), native_endian); |
| 99 | |
| 100 | var entry: CacheEntry = .{ |
| 101 | .pc = unwinder.pc, |
| 102 | .cie = cie, |
| 103 | .cfa_rule = row.cfa, |
| 104 | .num_rules = undefined, |
| 105 | .rules_regs = undefined, |
| 106 | .rules = undefined, |
| 107 | }; |
| 108 | var i: usize = 0; |
| 109 | for (unwinder.cfi_vm.rowColumns(&row)) |col| { |
| 110 | if (i == CacheEntry.max_rules) return error.UnsupportedDebugInfo; |
| 111 | |
| 112 | _ = unwinder.cpu_state.dwarfRegisterBytes(col.register) catch |err| switch (err) { |
| 113 | // Reading an unsupported register during unwinding will result in an error, so there is |
| 114 | // no point wasting a rule slot in the cache entry for it. |
| 115 | error.UnsupportedRegister => continue, |
| 116 | error.InvalidRegister => return error.InvalidDebugInfo, |
| 117 | }; |
| 118 | entry.rules_regs[i] = col.register; |
| 119 | entry.rules[i] = col.rule; |
| 120 | i += 1; |
| 121 | } |
| 122 | entry.num_rules = @intCast(i); |
| 123 | return entry; |
| 124 | } |
| 125 | |
| 126 | /// Applies the register rules given in `cache_entry` to the current state of `unwinder`. The caller |
| 127 | /// is responsible for ensuring that `cache_entry` contains the correct rule set for `unwinder.pc`. |
| 128 | /// |
| 129 | /// `unwinder.cpu_state` and `unwinder.pc` are updated to refer to the next frame, and this frame's |
| 130 | /// return address is returned as a `usize`. |
| 131 | pub fn next(unwinder: *SelfUnwinder, gpa: Allocator, cache_entry: *const CacheEntry) std.debug.SelfInfoError!usize { |
| 132 | return unwinder.nextInner(gpa, cache_entry) catch |err| switch (err) { |
| 133 | error.OutOfMemory, |
| 134 | error.InvalidDebugInfo, |
| 135 | => |e| return e, |
| 136 | |
| 137 | error.UnsupportedRegister, |
| 138 | error.UnimplementedExpressionCall, |
| 139 | error.UnimplementedOpcode, |
| 140 | error.UnimplementedUserOpcode, |
| 141 | error.UnimplementedTypedComparison, |
| 142 | error.UnimplementedTypeConversion, |
| 143 | error.UnknownExpressionOpcode, |
| 144 | => return error.UnsupportedDebugInfo, |
| 145 | |
| 146 | error.ReadFailed, |
| 147 | error.EndOfStream, |
| 148 | error.Overflow, |
| 149 | error.IncompatibleRegisterSize, |
| 150 | error.InvalidRegister, |
| 151 | error.IncompleteExpressionContext, |
| 152 | error.InvalidCFAOpcode, |
| 153 | error.InvalidExpression, |
| 154 | error.InvalidFrameBase, |
| 155 | error.InvalidIntegralTypeSize, |
| 156 | error.InvalidSubExpression, |
| 157 | error.InvalidTypeLength, |
| 158 | error.TruncatedIntegralType, |
| 159 | error.DivisionByZero, |
| 160 | => return error.InvalidDebugInfo, |
| 161 | }; |
| 162 | } |
| 163 | |
| 164 | fn nextInner(unwinder: *SelfUnwinder, gpa: Allocator, cache_entry: *const CacheEntry) !usize { |
| 165 | const format = cache_entry.cie.format; |
| 166 | |
| 167 | const cfa = switch (cache_entry.cfa_rule) { |
| 168 | .none => return error.InvalidDebugInfo, |
| 169 | .reg_off => |ro| cfa: { |
| 170 | const ptr = try regNative(&unwinder.cpu_state, ro.register); |
| 171 | break :cfa try applyOffset(@intCast(ptr.*), ro.offset); |
| 172 | }, |
| 173 | .expression => |expr| cfa: { |
| 174 | // On most implemented architectures, the CFA is defined to be the previous frame's SP. |
| 175 | // |
| 176 | // On s390x, it's defined to be SP + 160 (ELF ABI s390x Supplement §1.6.3); however, |
| 177 | // what this actually means is that there will be a `def_cfa r15 + 160`, so nothing |
| 178 | // special for us to do. |
| 179 | const prev_cfa_val = (try regNative(&unwinder.cpu_state, sp_reg_num)).*; |
| 180 | unwinder.expr_vm.reset(); |
| 181 | const value = try unwinder.expr_vm.run(expr, gpa, .{ |
| 182 | .format = format, |
| 183 | .cpu_context = &unwinder.cpu_state, |
| 184 | }, @intCast(prev_cfa_val)) orelse return error.InvalidDebugInfo; |
| 185 | switch (value) { |
| 186 | .generic => |g| break :cfa g, |
| 187 | else => return error.InvalidDebugInfo, |
| 188 | } |
| 189 | }, |
| 190 | }; |
| 191 | |
| 192 | // Create a copy of the CPU state, to which we will apply the new rules. |
| 193 | var new_cpu_state = unwinder.cpu_state; |
| 194 | |
| 195 | // On all implemented architectures, the CFA is defined to be the previous frame's SP |
| 196 | (try regNative(&new_cpu_state, sp_reg_num)).* = cfa; |
| 197 | |
| 198 | const return_address_register = cache_entry.cie.return_address_register; |
| 199 | var has_return_address = true; |
| 200 | |
| 201 | const rules_len = cache_entry.num_rules; |
| 202 | for (cache_entry.rules_regs[0..rules_len], cache_entry.rules[0..rules_len]) |register, rule| { |
| 203 | const new_val: union(enum) { |
| 204 | same, |
| 205 | undefined, |
| 206 | val: std.debug.cpu_context.Native.Gpr, |
| 207 | bytes: []const u8, |
| 208 | } = switch (rule) { |
| 209 | .default => val: { |
| 210 | // The way things are supposed to work is that `.undefined` is the default rule |
| 211 | // unless an ABI says otherwise (e.g. aarch64, s390x). |
| 212 | // |
| 213 | // Unfortunately, at some point, a decision was made to have libgcc's unwinder |
| 214 | // assume `.same` as the default for all registers. Compilers then started depending |
| 215 | // on this, and the practice was carried forward to LLVM's libunwind and some of its |
| 216 | // backends. |
| 217 | break :val .same; |
| 218 | }, |
| 219 | .undefined => .undefined, |
| 220 | .same_value => .same, |
| 221 | .offset => |offset| val: { |
| 222 | const ptr: *const std.debug.cpu_context.Native.Gpr = @ptrFromInt(try applyOffset(cfa, offset)); |
| 223 | break :val .{ .val = ptr.* }; |
| 224 | }, |
| 225 | .val_offset => |offset| .{ .val = try applyOffset(cfa, offset) }, |
| 226 | .register => |r| .{ .bytes = try unwinder.cpu_state.dwarfRegisterBytes(r) }, |
| 227 | .expression => |expr| val: { |
| 228 | unwinder.expr_vm.reset(); |
| 229 | const value = try unwinder.expr_vm.run(expr, gpa, .{ |
| 230 | .format = format, |
| 231 | .cpu_context = &unwinder.cpu_state, |
| 232 | }, cfa) orelse return error.InvalidDebugInfo; |
| 233 | const ptr: *const usize = switch (value) { |
| 234 | .generic => |addr| @ptrFromInt(addr), |
| 235 | else => return error.InvalidDebugInfo, |
| 236 | }; |
| 237 | break :val .{ .val = ptr.* }; |
| 238 | }, |
| 239 | .val_expression => |expr| val: { |
| 240 | unwinder.expr_vm.reset(); |
| 241 | const value = try unwinder.expr_vm.run(expr, gpa, .{ |
| 242 | .format = format, |
| 243 | .cpu_context = &unwinder.cpu_state, |
| 244 | }, cfa) orelse return error.InvalidDebugInfo; |
| 245 | switch (value) { |
| 246 | .generic => |val| break :val .{ .val = val }, |
| 247 | else => return error.InvalidDebugInfo, |
| 248 | } |
| 249 | }, |
| 250 | }; |
| 251 | switch (new_val) { |
| 252 | .same => {}, |
| 253 | .undefined => { |
| 254 | const dest = try new_cpu_state.dwarfRegisterBytes(@intCast(register)); |
| 255 | @memset(dest, undefined); |
| 256 | |
| 257 | // If the return address register is explicitly set to `.undefined`, it means that |
| 258 | // there are no more frames to unwind. |
| 259 | if (register == return_address_register) { |
| 260 | has_return_address = false; |
| 261 | } |
| 262 | }, |
| 263 | .val => |val| (try regNative(&new_cpu_state, register)).* = val, |
| 264 | .bytes => |src| { |
| 265 | const dest = try new_cpu_state.dwarfRegisterBytes(@intCast(register)); |
| 266 | if (dest.len != src.len) return error.InvalidDebugInfo; |
| 267 | @memcpy(dest, src); |
| 268 | }, |
| 269 | } |
| 270 | } |
| 271 | |
| 272 | const return_address = if (has_return_address) |
| 273 | stripInstructionPtrAuthCode(@intCast((try regNative(&new_cpu_state, return_address_register)).*)) |
| 274 | else |
| 275 | 0; |
| 276 | |
| 277 | (try regNative(&new_cpu_state, ip_reg_num)).* = return_address; |
| 278 | |
| 279 | // The new CPU state is complete; flush changes. |
| 280 | unwinder.cpu_state = new_cpu_state; |
| 281 | |
| 282 | // The caller will subtract 1 from the return address to get an address corresponding to the |
| 283 | // function call. However, if this is a signal frame, that's actually incorrect, because the |
| 284 | // "return address" we have is the instruction which triggered the signal (if the signal |
| 285 | // handler returned, the instruction would be re-run). Compensate for this by incrementing |
| 286 | // the address in that case. |
| 287 | const adjusted_ret_addr = if (cache_entry.cie.is_signal_frame) return_address +| 1 else return_address; |
| 288 | |
| 289 | // We also want to do that same subtraction here to get the PC for the next frame's FDE. |
| 290 | // This is because if the callee was noreturn, then the function call might be the caller's |
| 291 | // last instruction, so `return_address` might actually point outside of it! |
| 292 | unwinder.pc = adjusted_ret_addr -| 1; |
| 293 | |
| 294 | return adjusted_ret_addr; |
| 295 | } |
| 296 | |
| 297 | pub fn regNative(ctx: *std.debug.cpu_context.Native, num: u16) error{ |
| 298 | InvalidRegister, |
| 299 | UnsupportedRegister, |
| 300 | IncompatibleRegisterSize, |
| 301 | }!*align(1) std.debug.cpu_context.Native.Gpr { |
| 302 | const bytes = try ctx.dwarfRegisterBytes(num); |
| 303 | if (bytes.len != @sizeOf(std.debug.cpu_context.Native.Gpr)) return error.IncompatibleRegisterSize; |
| 304 | return @ptrCast(bytes); |
| 305 | } |
| 306 | |
| 307 | /// Since register rules are applied (usually) during a panic, |
| 308 | /// checked addition / subtraction is used so that we can return |
| 309 | /// an error and fall back to FP-based unwinding. |
| 310 | fn applyOffset(base: usize, offset: i64) !usize { |
| 311 | return if (offset >= 0) |
| 312 | try std.math.add(usize, base, @as(usize, @intCast(offset))) |
| 313 | else |
| 314 | try std.math.sub(usize, base, @as(usize, @intCast(-offset))); |
| 315 | } |
| 316 | |
| 317 | const ip_reg_num = Dwarf.ipRegNum(builtin.target.cpu.arch).?; |
| 318 | const sp_reg_num = Dwarf.spRegNum(builtin.target.cpu.arch); |
| 319 | |
| 320 | const std = @import("std"); |
| 321 | const Allocator = std.mem.Allocator; |
| 322 | const Dwarf = std.debug.Dwarf; |
| 323 | const assert = std.debug.assert; |
| 324 | const stripInstructionPtrAuthCode = std.debug.stripInstructionPtrAuthCode; |
| 325 | |
| 326 | const builtin = @import("builtin"); |
| 327 | const native_endian = builtin.target.cpu.arch.endian(); |
| 328 | |
| 329 | const SelfUnwinder = @This(); |