| ... | @@ -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 | } |
| 332 | | 332 | |
| 333 | pub const have_ucontext = posix.ucontext_t != void; | | |
| 334 | | | |
| 335 | /// Platform-specific thread state. This contains register state, and on some platforms | 333 | /// 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 platforms | 334 | /// 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`. |
| 338 | pub const ThreadContext = blk: { | 336 | pub 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. |
| 348 | pub fn relocateContext(dest: *ThreadContext) void { | 348 | pub 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`. |
| | 355 | const ThreadContextBuf = if (ThreadContext == noreturn) void else ThreadContext; |
| | 356 | /// The pointer through which a `ThreadContext` is received from callers of stack tracing logic. |
| | 357 | const ThreadContextPtr = if (ThreadContext == noreturn) noreturn else *const ThreadContext; |
| 354 | | 358 | |
| 355 | /// Capture the current context. The register values in the context will reflect the | 359 | /// 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. |
| 361 | pub inline fn getContext(context: *ThreadContext) bool { | 365 | pub 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 the | 618 | /// 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 the | 619 | /// 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. |
| 622 | pub fn captureCurrentStackTrace(options: StackUnwindOptions, addr_buf: []usize) std.builtin.StackTrace { | 631 | pub 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 use | 778 | /// 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 one | 779 | /// `@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, |
| 1180 | | 1189 | |
| 1181 | .freebsd, .openbsd => have_ucontext, | 1190 | .freebsd, .openbsd => ThreadContext != noreturn, |
| 1182 | else => false, | 1191 | else => false, |
| 1183 | }; | 1192 | }; |
| 1184 | | 1193 | |
| ... | @@ -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); |
| 1293 | | 1302 | |
| 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 | } |
| 1331 | | 1340 | |
| 1332 | fn handleSegfault(addr: ?usize, name: []const u8, opt_ctx: ?*ThreadContext) noreturn { | 1341 | fn 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 | } |
| 1339 | | 1348 | |
| 1340 | pub fn defaultHandleSegfault(addr: ?usize, name: []const u8, opt_ctx: ?*ThreadContext) noreturn { | 1349 | pub 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, |