feature. See also
. The project being documented here (as the example) is the Zig library itself.
SelfUnwinder.nextInner
fn nextInner(unwinder: *SelfUnwinder, gpa: Allocator, cache_entry: *const CacheEntry) !usize
File
Code
fn nextInner(unwinder: *SelfUnwinder, gpa: Allocator, cache_entry: *const CacheEntry) !usize {
const format = cache_entry.cie.format;
const cfa = switch (cache_entry.cfa_rule) {
.none => return error.InvalidDebugInfo,
.reg_off => |ro| cfa: {
const ptr = try regNative(&unwinder.cpu_state, ro.register);
break :cfa try applyOffset(@intCast(ptr.*), ro.offset);
},
.expression => |expr| cfa: {
//
// On s390x, it's defined to be SP + 160 (ELF ABI s390x Supplement ยง1.6.3); however,
// what this actually means is that there will be a `def_cfa r15 + 160`, so nothing
// special for us to do.
const prev_cfa_val = (try regNative(&unwinder.cpu_state, sp_reg_num)).*;
unwinder.expr_vm.reset();
const value = try unwinder.expr_vm.run(expr, gpa, .{
.format = format,
.cpu_context = &unwinder.cpu_state,
}, @intCast(prev_cfa_val)) orelse return error.InvalidDebugInfo;
switch (value) {
.generic => |g| break :cfa g,
else => return error.InvalidDebugInfo,
}
},
};
var new_cpu_state = unwinder.cpu_state;
(try regNative(&new_cpu_state, sp_reg_num)).* = cfa;
const return_address_register = cache_entry.cie.return_address_register;
var has_return_address = true;
const rules_len = cache_entry.num_rules;
for (cache_entry.rules_regs[0..rules_len], cache_entry.rules[0..rules_len]) |register, rule| {
const new_val: union(enum) {
same,
undefined,
val: std.debug.cpu_context.Native.Gpr,
bytes: []const u8,
} = switch (rule) {
.default => val: {
// unless an ABI says otherwise (e.g. aarch64, s390x).
//
// Unfortunately, at some point, a decision was made to have libgcc's unwinder
// assume `.same` as the default for all registers. Compilers then started depending
// on this, and the practice was carried forward to LLVM's libunwind and some of its
// backends.
break :val .same;
},
.undefined => .undefined,
.same_value => .same,
.offset => |offset| val: {
const ptr: *const std.debug.cpu_context.Native.Gpr = @ptrFromInt(try applyOffset(cfa, offset));
break :val .{ .val = ptr.* };
},
.val_offset => |offset| .{ .val = try applyOffset(cfa, offset) },
.register => |r| .{ .bytes = try unwinder.cpu_state.dwarfRegisterBytes(r) },
.expression => |expr| val: {
unwinder.expr_vm.reset();
const value = try unwinder.expr_vm.run(expr, gpa, .{
.format = format,
.cpu_context = &unwinder.cpu_state,
}, cfa) orelse return error.InvalidDebugInfo;
const ptr: *const usize = switch (value) {
.generic => |addr| @ptrFromInt(addr),
else => return error.InvalidDebugInfo,
};
break :val .{ .val = ptr.* };
},
.val_expression => |expr| val: {
unwinder.expr_vm.reset();
const value = try unwinder.expr_vm.run(expr, gpa, .{
.format = format,
.cpu_context = &unwinder.cpu_state,
}, cfa) orelse return error.InvalidDebugInfo;
switch (value) {
.generic => |val| break :val .{ .val = val },
else => return error.InvalidDebugInfo,
}
},
};
switch (new_val) {
.same => {},
.undefined => {
const dest = try new_cpu_state.dwarfRegisterBytes(@intCast(register));
@memset(dest, undefined);
// there are no more frames to unwind.
if (register == return_address_register) {
has_return_address = false;
}
},
.val => |val| (try regNative(&new_cpu_state, register)).* = val,
.bytes => |src| {
const dest = try new_cpu_state.dwarfRegisterBytes(@intCast(register));
if (dest.len != src.len) return error.InvalidDebugInfo;
@memcpy(dest, src);
},
}
}
const return_address = if (has_return_address)
stripInstructionPtrAuthCode(@intCast((try regNative(&new_cpu_state, return_address_register)).*))
else
0;
(try regNative(&new_cpu_state, ip_reg_num)).* = return_address;
unwinder.cpu_state = new_cpu_state;
// function call. However, if this is a signal frame, that's actually incorrect, because the
// "return address" we have is the instruction which triggered the signal (if the signal
// handler returned, the instruction would be re-run). Compensate for this by incrementing
// the address in that case.
const adjusted_ret_addr = if (cache_entry.cie.is_signal_frame) return_address +| 1 else return_address;
// This is because if the callee was noreturn, then the function call might be the caller's
// last instruction, so `return_address` might actually point outside of it!
unwinder.pc = adjusted_ret_addr -| 1;
return adjusted_ret_addr;
}