Skip to content

Commit 8dbcb2f

Browse files
committed
std.debug: allow fp unwind from context
It's easy to do FP unwinding from a CPU context: you just report the captured ip/pc value first, and then unwind from the captured fp value. All this really needed was a couple of new functions on the `std.debug.cpu_context` implementations so that we don't need to rely on `std.debug.Dwarf` to access the captured registers. Resolves: #25576
1 parent 43eb9b5 commit 8dbcb2f

File tree

3 files changed

+158
-41
lines changed

3 files changed

+158
-41
lines changed

lib/std/debug.zig

Lines changed: 30 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -617,7 +617,7 @@ pub const StackUnwindOptions = struct {
617617
pub noinline fn captureCurrentStackTrace(options: StackUnwindOptions, addr_buf: []usize) StackTrace {
618618
const empty_trace: StackTrace = .{ .index = 0, .instruction_addresses = &.{} };
619619
if (!std.options.allow_stack_tracing) return empty_trace;
620-
var it = StackIterator.init(options.context) catch return empty_trace;
620+
var it: StackIterator = .init(options.context);
621621
defer it.deinit();
622622
if (!it.stratOk(options.allow_unsafe_unwind)) return empty_trace;
623623
var total_frames: usize = 0;
@@ -671,14 +671,7 @@ pub noinline fn writeCurrentStackTrace(options: StackUnwindOptions, writer: *Wri
671671
return;
672672
},
673673
};
674-
var it = StackIterator.init(options.context) catch |err| switch (err) {
675-
error.CannotUnwindFromContext => {
676-
tty_config.setColor(writer, .dim) catch {};
677-
try writer.print("Cannot print stack trace: context unwind unavailable for target\n", .{});
678-
tty_config.setColor(writer, .reset) catch {};
679-
return;
680-
},
681-
};
674+
var it: StackIterator = .init(options.context);
682675
defer it.deinit();
683676
if (!it.stratOk(options.allow_unsafe_unwind)) {
684677
tty_config.setColor(writer, .dim) catch {};
@@ -821,22 +814,32 @@ pub fn dumpStackTrace(st: *const StackTrace) void {
821814
}
822815

823816
const StackIterator = union(enum) {
817+
/// We will first report the current PC of this `CpuContextPtr`, then we will switch to a
818+
/// different strategy to actually unwind.
819+
ctx_first: CpuContextPtr,
824820
/// Unwinding using debug info (e.g. DWARF CFI).
825-
di: if (SelfInfo != void and SelfInfo.can_unwind) SelfInfo.UnwindContext else noreturn,
826-
/// We will first report the *current* PC of this `UnwindContext`, then we will switch to `di`.
827-
di_first: if (SelfInfo != void and SelfInfo.can_unwind) SelfInfo.UnwindContext else noreturn,
821+
di: if (SelfInfo != void and SelfInfo.can_unwind and fp_usability != .ideal)
822+
SelfInfo.UnwindContext
823+
else
824+
noreturn,
828825
/// Naive frame-pointer-based unwinding. Very simple, but typically unreliable.
829826
fp: usize,
830827

831828
/// It is important that this function is marked `inline` so that it can safely use
832829
/// `@frameAddress` and `cpu_context.Native.current` as the caller's stack frame and
833830
/// our own are one and the same.
834-
inline fn init(opt_context_ptr: ?CpuContextPtr) error{CannotUnwindFromContext}!StackIterator {
831+
///
832+
/// `opt_context_ptr` must remain valid while the `StackIterator` is used.
833+
inline fn init(opt_context_ptr: ?CpuContextPtr) StackIterator {
835834
if (opt_context_ptr) |context_ptr| {
836-
if (SelfInfo == void or !SelfInfo.can_unwind) return error.CannotUnwindFromContext;
837-
// Use `di_first` here so we report the PC in the context before unwinding any further.
838-
return .{ .di_first = .init(context_ptr) };
835+
// Use `ctx_first` here so we report the PC in the context before unwinding any further.
836+
return .{ .ctx_first = context_ptr };
839837
}
838+
839+
// Otherwise, we're going to capture the current context or frame address, so we don't need
840+
// `ctx_first`, because the first PC is in `std.debug` and we need to unwind before reaching
841+
// a frame we want to report.
842+
840843
// Workaround the C backend being unable to use inline assembly on MSVC by disabling the
841844
// call to `current`. This effectively constrains stack trace collection and dumping to FP
842845
// unwinding when building with CBE for MSVC.
@@ -846,8 +849,6 @@ const StackIterator = union(enum) {
846849
cpu_context.Native != noreturn and
847850
fp_usability != .ideal)
848851
{
849-
// We don't need `di_first` here, because our PC is in `std.debug`; we're only interested
850-
// in our caller's frame and above.
851852
return .{ .di = .init(&.current()) };
852853
}
853854
return .{
@@ -866,8 +867,9 @@ const StackIterator = union(enum) {
866867
}
867868
fn deinit(si: *StackIterator) void {
868869
switch (si.*) {
870+
.ctx_first => {},
869871
.fp => {},
870-
.di, .di_first => |*unwind_context| unwind_context.deinit(getDebugInfoAllocator()),
872+
.di => |*unwind_context| unwind_context.deinit(getDebugInfoAllocator()),
871873
}
872874
}
873875

@@ -931,7 +933,7 @@ const StackIterator = union(enum) {
931933
/// Whether the current unwind strategy is allowed given `allow_unsafe`.
932934
fn stratOk(it: *const StackIterator, allow_unsafe: bool) bool {
933935
return switch (it.*) {
934-
.di, .di_first => true,
936+
.ctx_first, .di => true,
935937
// If we omitted frame pointers from *this* compilation, FP unwinding would crash
936938
// immediately regardless of anything. But FPs could also be omitted from a different
937939
// linked object, so it's not guaranteed to be safe, unless the target specifically
@@ -959,13 +961,16 @@ const StackIterator = union(enum) {
959961

960962
fn next(it: *StackIterator) Result {
961963
switch (it.*) {
962-
.di_first => |unwind_context| {
963-
const first_pc = unwind_context.pc;
964-
if (first_pc == 0) return .end;
965-
it.* = .{ .di = unwind_context };
964+
.ctx_first => |context_ptr| {
965+
// After the first frame, start actually unwinding.
966+
it.* = if (SelfInfo != void and SelfInfo.can_unwind and fp_usability != .ideal)
967+
.{ .di = .init(context_ptr) }
968+
else
969+
.{ .fp = context_ptr.getFp() };
970+
966971
// The caller expects *return* addresses, where they will subtract 1 to find the address of the call.
967972
// However, we have the actual current PC, which should not be adjusted. Compensate by adding 1.
968-
return .{ .frame = first_pc +| 1 };
973+
return .{ .frame = context_ptr.getPc() +| 1 };
969974
},
970975
.di => |*unwind_context| {
971976
const di = getSelfDebugInfo() catch unreachable;

lib/std/debug/Dwarf/SelfUnwinder.zig

Lines changed: 2 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -47,16 +47,9 @@ pub const CacheEntry = struct {
4747
};
4848

4949
pub fn init(cpu_context: *const std.debug.cpu_context.Native) SelfUnwinder {
50-
// `@constCast` is safe because we aren't going to store to the resulting pointer.
51-
const raw_pc_ptr = regNative(@constCast(cpu_context), ip_reg_num) catch |err| switch (err) {
52-
error.InvalidRegister => unreachable, // `ip_reg_num` is definitely valid
53-
error.UnsupportedRegister => unreachable, // the implementation needs to support ip
54-
error.IncompatibleRegisterSize => unreachable, // ip is definitely `usize`-sized
55-
};
56-
const pc = stripInstructionPtrAuthCode(raw_pc_ptr.*);
5750
return .{
5851
.cpu_state = cpu_context.*,
59-
.pc = pc,
52+
.pc = stripInstructionPtrAuthCode(cpu_context.getPc()),
6053
.cfi_vm = .{},
6154
.expr_vm = .{},
6255
};
@@ -69,13 +62,7 @@ pub fn deinit(unwinder: *SelfUnwinder, gpa: Allocator) void {
6962
}
7063

7164
pub fn getFp(unwinder: *const SelfUnwinder) usize {
72-
// `@constCast` is safe because we aren't going to store to the resulting pointer.
73-
const ptr = regNative(@constCast(&unwinder.cpu_state), fp_reg_num) catch |err| switch (err) {
74-
error.InvalidRegister => unreachable, // `fp_reg_num` is definitely valid
75-
error.UnsupportedRegister => unreachable, // the implementation needs to support fp
76-
error.IncompatibleRegisterSize => unreachable, // fp is a pointer so is `usize`-sized
77-
};
78-
return ptr.*;
65+
return unwinder.cpu_state.getFp();
7966
}
8067

8168
/// Compute the rule set for the address `unwinder.pc` from the information in `unwind`. The caller
@@ -332,7 +319,6 @@ fn applyOffset(base: usize, offset: i64) !usize {
332319
}
333320

334321
const ip_reg_num = Dwarf.ipRegNum(builtin.target.cpu.arch).?;
335-
const fp_reg_num = Dwarf.fpRegNum(builtin.target.cpu.arch);
336322
const sp_reg_num = Dwarf.spRegNum(builtin.target.cpu.arch);
337323

338324
const std = @import("std");

0 commit comments

Comments
 (0)