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.
9cpu_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.
12pc: usize,
13
14cfi_vm: Dwarf.Unwind.VirtualMachine,
15expr_vm: Dwarf.expression.StackMachine(.{ .call_frame_context = true }),
16
17pub 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
49pub 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
58pub 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
65pub 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`.
74pub 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`.
131pub 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
164fn 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
297pub 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.
310fn 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
317const ip_reg_num = Dwarf.ipRegNum(builtin.target.cpu.arch).?;
318const sp_reg_num = Dwarf.spRegNum(builtin.target.cpu.arch);
319
320const std = @import("std");
321const Allocator = std.mem.Allocator;
322const Dwarf = std.debug.Dwarf;
323const assert = std.debug.assert;
324const stripInstructionPtrAuthCode = std.debug.stripInstructionPtrAuthCode;
325
326const builtin = @import("builtin");
327const native_endian = builtin.target.cpu.arch.endian();
328
329const SelfUnwinder = @This();