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