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();