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 {...@@ -330,19 +330,19 @@ test dumpHexFallible {
330 try std.testing.expectEqualStrings(expected, aw.written());330 try std.testing.expectEqualStrings(expected, aw.written());
331}331}
332332
333pub const have_ucontext = posix.ucontext_t != void;
334
335/// Platform-specific thread state. This contains register state, and on some platforms333/// Platform-specific thread state. This contains register state, and on some platforms
336/// information about the stack. This is not safe to trivially copy, because some platforms334/// information about the stack. This is not safe to trivially copy, because some platforms
337/// use internal pointers within this structure. After copying, call `relocateContext`.335/// use internal pointers within this structure. After copying, call `relocateContext`.
338pub const ThreadContext = blk: {336pub const ThreadContext = ThreadContext: {
339 if (native_os == .windows) {337 // Allow overriding the target's `ThreadContext` by exposing `root.debug.ThreadContext`.
340 break :blk windows.CONTEXT;338 if (@hasDecl(root, "debug") and @hasDecl(root.debug, "ThreadContext")) {
341 } else if (have_ucontext) {339 break :ThreadContext root.debug.ThreadContext;
342 break :blk posix.ucontext_t;
343 } else {
344 break :blk void;
345 }340 }
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;
346};346};
347/// Updates any internal pointers of a `ThreadContext` after the caller copies it.347/// Updates any internal pointers of a `ThreadContext` after the caller copies it.
348pub fn relocateContext(dest: *ThreadContext) void {348pub fn relocateContext(dest: *ThreadContext) void {
...@@ -351,6 +351,10 @@ pub fn relocateContext(dest: *ThreadContext) void {...@@ -351,6 +351,10 @@ pub fn relocateContext(dest: *ThreadContext) void {
351 else => {},351 else => {},
352 }352 }
353}353}
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
355/// Capture the current context. The register values in the context will reflect the359/// Capture the current context. The register values in the context will reflect the
356/// state after the platform `getcontext` function returns.360/// state after the platform `getcontext` function returns.
...@@ -358,7 +362,12 @@ pub fn relocateContext(dest: *ThreadContext) void {...@@ -358,7 +362,12 @@ pub fn relocateContext(dest: *ThreadContext) void {
358/// It is valid to call this if the platform doesn't have context capturing support,362/// It is valid to call this if the platform doesn't have context capturing support,
359/// in that case `false` will be returned. This function is `inline` so that the `false`363/// in that case `false` will be returned. This function is `inline` so that the `false`
360/// is comptime-known at the call site in that case.364/// 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
362 if (native_os == .windows) {371 if (native_os == .windows) {
363 context.* = std.mem.zeroes(windows.CONTEXT);372 context.* = std.mem.zeroes(windows.CONTEXT);
364 windows.ntdll.RtlCaptureContext(context);373 windows.ntdll.RtlCaptureContext(context);
...@@ -608,8 +617,8 @@ pub const StackUnwindOptions = struct {...@@ -608,8 +617,8 @@ pub const StackUnwindOptions = struct {
608 first_address: ?usize = null,617 first_address: ?usize = null,
609 /// If not `null`, we will unwind from this `ThreadContext` instead of the current top of the618 /// If not `null`, we will unwind from this `ThreadContext` instead of the current top of the
610 /// stack. The main use case here is printing stack traces from signal handlers, where the619 /// 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.620 /// kernel provides a `*const ThreadContext` of the state before the signal.
612 context: ?*const ThreadContext = null,621 context: ?ThreadContextPtr = null,
613 /// If `true`, stack unwinding strategies which may cause crashes are used as a last resort.622 /// If `true`, stack unwinding strategies which may cause crashes are used as a last resort.
614 /// If `false`, only known-safe mechanisms will be attempted.623 /// If `false`, only known-safe mechanisms will be attempted.
615 allow_unsafe_unwind: bool = false,624 allow_unsafe_unwind: bool = false,
...@@ -620,7 +629,7 @@ pub const StackUnwindOptions = struct {...@@ -620,7 +629,7 @@ pub const StackUnwindOptions = struct {
620///629///
621/// See `writeCurrentStackTrace` to immediately print the trace instead of capturing it.630/// See `writeCurrentStackTrace` to immediately print the trace instead of capturing it.
622pub fn captureCurrentStackTrace(options: StackUnwindOptions, addr_buf: []usize) std.builtin.StackTrace {631pub fn captureCurrentStackTrace(options: StackUnwindOptions, addr_buf: []usize) std.builtin.StackTrace {
623 var context_buf: ThreadContext = undefined;632 var context_buf: ThreadContextBuf = undefined;
624 var it = StackIterator.init(options.context, &context_buf) catch {633 var it = StackIterator.init(options.context, &context_buf) catch {
625 return .{ .index = 0, .instruction_addresses = &.{} };634 return .{ .index = 0, .instruction_addresses = &.{} };
626 };635 };
...@@ -660,7 +669,7 @@ pub fn writeCurrentStackTrace(options: StackUnwindOptions, writer: *Writer, tty_...@@ -660,7 +669,7 @@ pub fn writeCurrentStackTrace(options: StackUnwindOptions, writer: *Writer, tty_
660 return;669 return;
661 },670 },
662 };671 };
663 var context_buf: ThreadContext = undefined;672 var context_buf: ThreadContextBuf = undefined;
664 var it = StackIterator.init(options.context, &context_buf) catch |err| switch (err) {673 var it = StackIterator.init(options.context, &context_buf) catch |err| switch (err) {
665 error.OutOfMemory => {674 error.OutOfMemory => {
666 tty_config.setColor(writer, .dim) catch {};675 tty_config.setColor(writer, .dim) catch {};
...@@ -769,7 +778,7 @@ const StackIterator = union(enum) {...@@ -769,7 +778,7 @@ const StackIterator = union(enum) {
769 /// It is important that this function is marked `inline` so that it can safely use778 /// It is important that this function is marked `inline` so that it can safely use
770 /// `@frameAddress` and `getContext` as the caller's stack frame and our own are one779 /// `@frameAddress` and `getContext` as the caller's stack frame and our own are one
771 /// and the same.780 /// 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 {
773 if (builtin.cpu.arch.isSPARC()) {782 if (builtin.cpu.arch.isSPARC()) {
774 // Flush all the register windows on stack.783 // Flush all the register windows on stack.
775 if (builtin.cpu.has(.sparc, .v9)) {784 if (builtin.cpu.has(.sparc, .v9)) {
...@@ -1178,7 +1187,7 @@ pub const have_segfault_handling_support = switch (native_os) {...@@ -1178,7 +1187,7 @@ pub const have_segfault_handling_support = switch (native_os) {
1178 .windows,1187 .windows,
1179 => true,1188 => true,
11801189
1181 .freebsd, .openbsd => have_ucontext,1190 .freebsd, .openbsd => ThreadContext != noreturn,
1182 else => false,1191 else => false,
1183};1192};
11841193
...@@ -1289,10 +1298,10 @@ fn handleSegfaultPosix(sig: i32, info: *const posix.siginfo_t, ctx_ptr: ?*anyopa...@@ -1289,10 +1298,10 @@ fn handleSegfaultPosix(sig: i32, info: *const posix.siginfo_t, ctx_ptr: ?*anyopa
1289 => true,1298 => true,
1290 else => false,1299 else => false,
1291 };1300 };
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
1294 // Some kernels don't align `ctx_ptr` properly, so we'll copy it into a local buffer.1303 // 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;
1296 const orig_ctx: *align(1) posix.ucontext_t = @ptrCast(ctx_ptr);1305 const orig_ctx: *align(1) posix.ucontext_t = @ptrCast(ctx_ptr);
1297 copied_ctx = orig_ctx.*;1306 copied_ctx = orig_ctx.*;
1298 if (builtin.os.tag.isDarwin() and builtin.cpu.arch == .aarch64) {1307 if (builtin.os.tag.isDarwin() and builtin.cpu.arch == .aarch64) {
...@@ -1329,7 +1338,7 @@ fn handleSegfaultWindows(info: *windows.EXCEPTION_POINTERS) callconv(.winapi) c_...@@ -1329,7 +1338,7 @@ fn handleSegfaultWindows(info: *windows.EXCEPTION_POINTERS) callconv(.winapi) c_
1329 handleSegfault(addr, name, info.ContextRecord);1338 handleSegfault(addr, name, info.ContextRecord);
1330}1339}
13311340
1332fn handleSegfault(addr: ?usize, name: []const u8, opt_ctx: ?*ThreadContext) noreturn {1341fn handleSegfault(addr: ?usize, name: []const u8, opt_ctx: ?ThreadContextPtr) noreturn {
1333 // Allow overriding the target-agnostic segfault handler by exposing `root.debug.handleSegfault`.1342 // Allow overriding the target-agnostic segfault handler by exposing `root.debug.handleSegfault`.
1334 if (@hasDecl(root, "debug") and @hasDecl(root.debug, "handleSegfault")) {1343 if (@hasDecl(root, "debug") and @hasDecl(root.debug, "handleSegfault")) {
1335 return root.debug.handleSegfault(addr, name, opt_ctx);1344 return root.debug.handleSegfault(addr, name, opt_ctx);
...@@ -1337,7 +1346,7 @@ fn handleSegfault(addr: ?usize, name: []const u8, opt_ctx: ?*ThreadContext) nore...@@ -1337,7 +1346,7 @@ fn handleSegfault(addr: ?usize, name: []const u8, opt_ctx: ?*ThreadContext) nore
1337 return defaultHandleSegfault(addr, name, opt_ctx);1346 return defaultHandleSegfault(addr, name, opt_ctx);
1338}1347}
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 {
1341 // There is very similar logic to the following in `defaultPanic`.1350 // There is very similar logic to the following in `defaultPanic`.
1342 switch (panic_stage) {1351 switch (panic_stage) {
1343 0 => {1352 0 => {
...@@ -1355,7 +1364,6 @@ pub fn defaultHandleSegfault(addr: ?usize, name: []const u8, opt_ctx: ?*ThreadCo...@@ -1355,7 +1364,6 @@ pub fn defaultHandleSegfault(addr: ?usize, name: []const u8, opt_ctx: ?*ThreadCo
1355 } else {1364 } else {
1356 stderr.print("{s} (no address available)\n", .{name}) catch break :trace;1365 stderr.print("{s} (no address available)\n", .{name}) catch break :trace;
1357 }1366 }
1358 // MLUGG TODO: for this to work neatly, `ThreadContext` needs to be `noreturn` when not supported!
1359 if (opt_ctx) |context| {1367 if (opt_ctx) |context| {
1360 writeCurrentStackTrace(.{1368 writeCurrentStackTrace(.{
1361 .context = context,1369 .context = context,
lib/std/debug/Dwarf/abi.zig+1-1
...@@ -139,7 +139,7 @@ pub fn regBytes(...@@ -139,7 +139,7 @@ pub fn regBytes(
139 };139 };
140 }140 }
141141
142 if (!std.debug.have_ucontext) return error.ThreadContextNotSupported;142 if (posix.ucontext_t == void) return error.ThreadContextNotSupported;
143143
144 const ucontext_ptr = thread_context_ptr;144 const ucontext_ptr = thread_context_ptr;
145 return switch (builtin.cpu.arch) {145 return switch (builtin.cpu.arch) {