feature. See also
. The project being documented here (as the example) is the Zig library itself.
debug.StackIterator
const StackIterator = union(enum)
File
Code
const StackIterator = union(enum) {
ctx_first: CpuContextPtr,
di: if (SelfInfo != void and SelfInfo.can_unwind and fp_usability != .ideal)
SelfInfo.UnwindContext
else
noreturn,
fp: usize,
inline fn init(opt_context_ptr: ?CpuContextPtr) StackIterator {
if (opt_context_ptr) |context_ptr| {
return .{ .ctx_first = context_ptr };
}
// `ctx_first`, because the first PC is in `std.debug` and we need to unwind before reaching
// a frame we want to report.
// Workaround the C backend being unable to use inline assembly on MSVC by disabling the
// call to `current`. This effectively constrains stack trace collection and dumping to FP
// unwinding when building with CBE for MSVC.
if (!(builtin.zig_backend == .stage2_c and builtin.target.abi == .msvc) and
SelfInfo != void and
SelfInfo.can_unwind and
cpu_context.Native != noreturn and
fp_usability != .ideal)
{
return .{ .di = .init(&.current()) };
}
return .{
// meaning we will read the previous return address and thus miss a frame.
// Instead, start at the stack pointer so we get the return address from the
// current frame's save area. The addition of the stack bias cannot fail here
// since we know we have a valid stack pointer.
.fp = if (native_arch.isSPARC()) sp: {
flushSparcWindows();
break :sp asm (""
: [_] "={o6}" (-> usize),
) + stack_bias;
} else @frameAddress(),
};
}
fn deinit(si: *StackIterator) void {
switch (si.*) {
.ctx_first => {},
.fp => {},
.di => |*unwind_context| unwind_context.deinit(),
}
}
noinline fn flushSparcWindows() void {
// we actually see meaningful data on the stack when we walk the frame chain.
if (comptime builtin.target.cpu.has(.sparc, .v9))
asm volatile ("flushw" ::: .{ .memory = true })
else
asm volatile ("ta 3" ::: .{ .memory = true });
}
const FpUsability = enum {
useless,
unsafe,
safe,
ideal,
};
const fp_usability: FpUsability = switch (builtin.target.cpu.arch) {
.alpha,
.avr,
.csky,
.microblaze,
.microblazeel,
.mips,
.mipsel,
.mips64,
.mips64el,
.msp430,
.sh,
.sheb,
.xcore,
.xtensa,
.xtensaeb,
=> .useless,
.hexagon,
// it when full unwind info is present. Despite this, both GCC and Clang always enforce the
// presence of the backchain pointer no matter what options they are given. This seems to be
// a case of "the spec is only a polite suggestion", except it works in our favor this time!
.powerpc,
.powerpcle,
.powerpc64,
.powerpc64le,
.sparc,
.sparc64,
=> .ideal,
.aarch64 => if (builtin.target.os.tag.isDarwin()) .safe else .unsafe,
else => .unsafe,
};
fn stratOk(it: *const StackIterator, allow_unsafe: bool) bool {
return switch (it.*) {
.ctx_first, .di => true,
// immediately regardless of anything. But FPs could also be omitted from a different
// linked object, so it's not guaranteed to be safe, unless the target specifically
// requires it.
.fp => switch (fp_usability) {
.useless => false,
.unsafe => allow_unsafe and !builtin.omit_frame_pointer,
.safe => !builtin.omit_frame_pointer,
.ideal => true,
},
};
}
const Result = union(enum) {
frame: usize,
end,
switch_to_fp: struct {
address: usize,
err: SelfInfoError,
},
};
fn next(it: *StackIterator, io: Io) Result {
switch (it.*) {
.ctx_first => |context_ptr| {
it.* = if (SelfInfo != void and SelfInfo.can_unwind and fp_usability != .ideal)
.{ .di = .init(context_ptr) }
else
.{ .fp = context_ptr.getFp() };
// However, we have the actual current PC, which should not be adjusted. Compensate by adding 1.
return .{ .frame = context_ptr.getPc() +| 1 };
},
.di => |*unwind_context| {
const di = getSelfDebugInfo() catch unreachable;
const ret_addr = di.unwindFrame(io, unwind_context) catch |err| {
const pc = unwind_context.pc;
const fp = unwind_context.getFp();
it.* = .{ .fp = fp };
return .{ .switch_to_fp = .{
.address = pc,
.err = err,
} };
};
if (ret_addr <= 1) return .end;
return .{ .frame = ret_addr };
},
.fp => |fp| {
if (fp == 0) return .end;
const bp_addr = applyOffset(fp, fp_to_bp_offset) orelse return .end;
const ra_addr = applyOffset(fp, fp_to_ra_offset) orelse return .end;
if (bp_addr == 0 or !mem.isAligned(bp_addr, @alignOf(usize)) or
ra_addr == 0 or !mem.isAligned(ra_addr, @alignOf(usize)))
{
return .end;
}
const bp_ptr: *const usize = @ptrFromInt(bp_addr);
const ra_ptr: *const usize = @ptrFromInt(ra_addr);
const bp = applyOffset(bp_ptr.*, stack_bias) orelse return .end;
// grows upwards, `bp < fp` should always hold. If that is not the case, this
// frame is invalid, so we'll treat it as though we reached end of stack. The
// exception is address 0, which is a graceful end-of-stack signal, in which case
// *this* return address is valid and the *next* iteration will be the last.
if (bp != 0 and switch (comptime builtin.target.stackGrowth()) {
.down => bp <= fp,
.up => bp >= fp,
}) return .end;
it.fp = bp;
const ra = stripInstructionPtrAuthCode(ra_ptr.*);
if (ra <= 1) return .end;
return .{ .frame = ra };
},
}
}
const fp_to_bp_offset = off: {
if (native_arch == .hppa) break :off -1 * @sizeOf(usize);
// address followed by the base pointer.
if (native_arch == .hppa64) break :off -1 * @sizeOf(usize);
// in which the base pointer is the first word.
if (native_arch.isLoongArch() or native_arch.isRISCV()) break :off -2 * @sizeOf(usize);
if (native_arch == .or1k) break :off -2 * @sizeOf(usize);
// and incoming registers. The base pointer (i6) is stored in its customary save slot.
if (native_arch.isSPARC()) break :off 14 * @sizeOf(usize);
break :off 0;
};
const fp_to_ra_offset = off: {
if (native_arch == .hppa) break :off -5 * @sizeOf(usize);
// address followed by the base pointer.
if (native_arch == .hppa64) break :off -2 * @sizeOf(usize);
// in which the return address is the second word.
if (native_arch.isLoongArch() or native_arch.isRISCV()) break :off -1 * @sizeOf(usize);
if (native_arch == .or1k) break :off -1 * @sizeOf(usize);
if (native_arch.isPowerPC64()) break :off 2 * @sizeOf(usize);
// register save area (ELF ABI s390x Supplement ยง1.2.2.2).
if (native_arch == .s390x) break :off 14 * @sizeOf(usize);
// and incoming registers. The return address (i7) is stored in its customary save slot.
if (native_arch.isSPARC()) break :off 15 * @sizeOf(usize);
break :off @sizeOf(usize);
};
const stack_bias = bias: {
if (native_arch == .sparc64) break :bias 2047;
break :bias 0;
};
const ra_call_offset = off: {
if (native_arch.isSPARC()) break :off 0;
break :off 1;
};
fn applyOffset(addr: usize, comptime off: comptime_int) ?usize {
if (off >= 0) return math.add(usize, addr, off) catch return null;
return math.sub(usize, addr, -off) catch return null;
}
}