authorgravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2025-09-08 20:46:58+01:00
committergravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2025-09-30 13:44:52+01:00
log229f0a01b86a04b354a0d6ad29d3c0e7be05d03e
treebaff1402f03df333af1b120934c6a4310722e4a3
parent1392a7af171c00679b0754775a8f6f54e967eafd
signaturelock-open Commit is signed but in an unrecognized format.

std.debug: handle ThreadContext slightly better

It's now user-overrideable, and uses `noreturn` types to neatly stop analysis.

2 files changed, 30 insertions(+), 22 deletions(-)

lib/std/debug.zig+29-21
......@@ -330,19 +330,19 @@ test dumpHexFallible {
330330 try std.testing.expectEqualStrings(expected, aw.written());
331331}
332332
333pub const have_ucontext = posix.ucontext_t != void;
334
335333/// Platform-specific thread state. This contains register state, and on some platforms
336334/// information about the stack. This is not safe to trivially copy, because some platforms
337335/// use internal pointers within this structure. After copying, call `relocateContext`.
338pub const ThreadContext = blk: {
339 if (native_os == .windows) {
340 break :blk windows.CONTEXT;
341 } else if (have_ucontext) {
342 break :blk posix.ucontext_t;
343 } else {
344 break :blk void;
336pub const ThreadContext = ThreadContext: {
337 // Allow overriding the target's `ThreadContext` by exposing `root.debug.ThreadContext`.
338 if (@hasDecl(root, "debug") and @hasDecl(root.debug, "ThreadContext")) {
339 break :ThreadContext root.debug.ThreadContext;
345340 }
341
342 if (native_os == .windows) break :ThreadContext windows.CONTEXT;
343 if (posix.ucontext_t != void) break :ThreadContext posix.ucontext_t;
344
345 break :ThreadContext noreturn;
346346};
347347/// Updates any internal pointers of a `ThreadContext` after the caller copies it.
348348pub fn relocateContext(dest: *ThreadContext) void {
......@@ -351,6 +351,10 @@ pub fn relocateContext(dest: *ThreadContext) void {
351351 else => {},
352352 }
353353}
354/// The value which is placed on the stack to make a copy of a `ThreadContext`.
355const ThreadContextBuf = if (ThreadContext == noreturn) void else ThreadContext;
356/// The pointer through which a `ThreadContext` is received from callers of stack tracing logic.
357const ThreadContextPtr = if (ThreadContext == noreturn) noreturn else *const ThreadContext;
354358
355359/// Capture the current context. The register values in the context will reflect the
356360/// state after the platform `getcontext` function returns.
......@@ -358,7 +362,12 @@ pub fn relocateContext(dest: *ThreadContext) void {
358362/// It is valid to call this if the platform doesn't have context capturing support,
359363/// in that case `false` will be returned. This function is `inline` so that the `false`
360364/// is comptime-known at the call site in that case.
361pub inline fn getContext(context: *ThreadContext) bool {
365pub inline fn getContext(context: *ThreadContextBuf) bool {
366 // Allow overriding the target's `getContext` by exposing `root.debug.getContext`.
367 if (@hasDecl(root, "debug") and @hasDecl(root.debug, "getContext")) {
368 return root.debug.getContext(context);
369 }
370
362371 if (native_os == .windows) {
363372 context.* = std.mem.zeroes(windows.CONTEXT);
364373 windows.ntdll.RtlCaptureContext(context);
......@@ -608,8 +617,8 @@ pub const StackUnwindOptions = struct {
608617 first_address: ?usize = null,
609618 /// If not `null`, we will unwind from this `ThreadContext` instead of the current top of the
610619 /// stack. The main use case here is printing stack traces from signal handlers, where the
611 /// kernel provides a `*ThreadContext` of the state before the signal.
612 context: ?*const ThreadContext = null,
620 /// kernel provides a `*const ThreadContext` of the state before the signal.
621 context: ?ThreadContextPtr = null,
613622 /// If `true`, stack unwinding strategies which may cause crashes are used as a last resort.
614623 /// If `false`, only known-safe mechanisms will be attempted.
615624 allow_unsafe_unwind: bool = false,
......@@ -620,7 +629,7 @@ pub const StackUnwindOptions = struct {
620629///
621630/// See `writeCurrentStackTrace` to immediately print the trace instead of capturing it.
622631pub fn captureCurrentStackTrace(options: StackUnwindOptions, addr_buf: []usize) std.builtin.StackTrace {
623 var context_buf: ThreadContext = undefined;
632 var context_buf: ThreadContextBuf = undefined;
624633 var it = StackIterator.init(options.context, &context_buf) catch {
625634 return .{ .index = 0, .instruction_addresses = &.{} };
626635 };
......@@ -660,7 +669,7 @@ pub fn writeCurrentStackTrace(options: StackUnwindOptions, writer: *Writer, tty_
660669 return;
661670 },
662671 };
663 var context_buf: ThreadContext = undefined;
672 var context_buf: ThreadContextBuf = undefined;
664673 var it = StackIterator.init(options.context, &context_buf) catch |err| switch (err) {
665674 error.OutOfMemory => {
666675 tty_config.setColor(writer, .dim) catch {};
......@@ -769,7 +778,7 @@ const StackIterator = union(enum) {
769778 /// It is important that this function is marked `inline` so that it can safely use
770779 /// `@frameAddress` and `getContext` as the caller's stack frame and our own are one
771780 /// and the same.
772 inline fn init(context_opt: ?*const ThreadContext, context_buf: *ThreadContext) error{OutOfMemory}!StackIterator {
781 inline fn init(context_opt: ?ThreadContextPtr, context_buf: *ThreadContextBuf) error{OutOfMemory}!StackIterator {
773782 if (builtin.cpu.arch.isSPARC()) {
774783 // Flush all the register windows on stack.
775784 if (builtin.cpu.has(.sparc, .v9)) {
......@@ -1178,7 +1187,7 @@ pub const have_segfault_handling_support = switch (native_os) {
11781187 .windows,
11791188 => true,
11801189
1181 .freebsd, .openbsd => have_ucontext,
1190 .freebsd, .openbsd => ThreadContext != noreturn,
11821191 else => false,
11831192};
11841193
......@@ -1289,10 +1298,10 @@ fn handleSegfaultPosix(sig: i32, info: *const posix.siginfo_t, ctx_ptr: ?*anyopa
12891298 => true,
12901299 else => false,
12911300 };
1292 if (!have_ucontext or !use_context) return handleSegfault(addr, name, null);
1301 if (ThreadContext == noreturn or !use_context) return handleSegfault(addr, name, null);
12931302
12941303 // Some kernels don't align `ctx_ptr` properly, so we'll copy it into a local buffer.
1295 var copied_ctx: ThreadContext = undefined;
1304 var copied_ctx: ThreadContextBuf = undefined;
12961305 const orig_ctx: *align(1) posix.ucontext_t = @ptrCast(ctx_ptr);
12971306 copied_ctx = orig_ctx.*;
12981307 if (builtin.os.tag.isDarwin() and builtin.cpu.arch == .aarch64) {
......@@ -1329,7 +1338,7 @@ fn handleSegfaultWindows(info: *windows.EXCEPTION_POINTERS) callconv(.winapi) c_
13291338 handleSegfault(addr, name, info.ContextRecord);
13301339}
13311340
1332fn handleSegfault(addr: ?usize, name: []const u8, opt_ctx: ?*ThreadContext) noreturn {
1341fn handleSegfault(addr: ?usize, name: []const u8, opt_ctx: ?ThreadContextPtr) noreturn {
13331342 // Allow overriding the target-agnostic segfault handler by exposing `root.debug.handleSegfault`.
13341343 if (@hasDecl(root, "debug") and @hasDecl(root.debug, "handleSegfault")) {
13351344 return root.debug.handleSegfault(addr, name, opt_ctx);
......@@ -1337,7 +1346,7 @@ fn handleSegfault(addr: ?usize, name: []const u8, opt_ctx: ?*ThreadContext) nore
13371346 return defaultHandleSegfault(addr, name, opt_ctx);
13381347}
13391348
1340pub fn defaultHandleSegfault(addr: ?usize, name: []const u8, opt_ctx: ?*ThreadContext) noreturn {
1349pub fn defaultHandleSegfault(addr: ?usize, name: []const u8, opt_ctx: ?ThreadContextPtr) noreturn {
13411350 // There is very similar logic to the following in `defaultPanic`.
13421351 switch (panic_stage) {
13431352 0 => {
......@@ -1355,7 +1364,6 @@ pub fn defaultHandleSegfault(addr: ?usize, name: []const u8, opt_ctx: ?*ThreadCo
13551364 } else {
13561365 stderr.print("{s} (no address available)\n", .{name}) catch break :trace;
13571366 }
1358 // MLUGG TODO: for this to work neatly, `ThreadContext` needs to be `noreturn` when not supported!
13591367 if (opt_ctx) |context| {
13601368 writeCurrentStackTrace(.{
13611369 .context = context,
lib/std/debug/Dwarf/abi.zig+1-1
......@@ -139,7 +139,7 @@ pub fn regBytes(
139139 };
140140 }
141141
142 if (!std.debug.have_ucontext) return error.ThreadContextNotSupported;
142 if (posix.ucontext_t == void) return error.ThreadContextNotSupported;
143143
144144 const ucontext_ptr = thread_context_ptr;
145145 return switch (builtin.cpu.arch) {