| ... | @@ -1,34 +1,31 @@ | ... | @@ -1,34 +1,31 @@ |
| 1 | const std = @import("std"); | 1 | /// We override the panic implementation to our own one, so we can print our own information before |
| 2 | const builtin = @import("builtin"); | 2 | /// calling the default panic handler. This declaration must be re-exposed from `@import("root")`. |
| 3 | const build_options = @import("build_options"); | 3 | pub const panic = if (dev.env == .bootstrap) |
| 4 | const debug = std.debug; | | |
| 5 | const print_zir = @import("print_zir.zig"); | | |
| 6 | const windows = std.os.windows; | | |
| 7 | const posix = std.posix; | | |
| 8 | const native_os = builtin.os.tag; | | |
| 9 | | | |
| 10 | const Zcu = @import("Zcu.zig"); | | |
| 11 | const Sema = @import("Sema.zig"); | | |
| 12 | const InternPool = @import("InternPool.zig"); | | |
| 13 | const Zir = std.zig.Zir; | | |
| 14 | const Decl = Zcu.Decl; | | |
| 15 | const dev = @import("dev.zig"); | | |
| 16 | | | |
| 17 | /// To use these crash report diagnostics, publish this panic in your main file | | |
| 18 | /// and add `pub const enable_segfault_handler = false;` to your `std_options`. | | |
| 19 | /// You will also need to call initialize() on startup, preferably as the very first operation in your program. | | |
| 20 | pub const panic = if (build_options.enable_debug_extensions) | | |
| 21 | std.debug.FullPanic(compilerPanic) | | |
| 22 | else if (dev.env == .bootstrap) | | |
| 23 | std.debug.simple_panic | 4 | std.debug.simple_panic |
| 24 | else | 5 | else |
| 25 | std.debug.FullPanic(std.debug.defaultPanic); | 6 | std.debug.FullPanic(panicImpl); |
| 26 | | 7 | |
| 27 | /// Install signal handlers to identify crashes and report diagnostics. | 8 | /// We let std install its segfault handler, but we override the target-agnostic handler it calls, |
| 28 | pub fn initialize() void { | 9 | /// so we can print our own information before calling the default segfault logic. This declaration |
| 29 | if (build_options.enable_debug_extensions and debug.have_segfault_handling_support) { | 10 | /// must be re-exposed from `@import("root")`. |
| 30 | attachSegfaultHandler(); | 11 | pub const debug = struct { |
| 31 | } | 12 | pub const handleSegfault = handleSegfaultImpl; |
| | 13 | }; |
| | 14 | |
| | 15 | /// Printed in panic messages when suggesting a command to run, allowing copy-pasting the command. |
| | 16 | /// Set by `main` as soon as arguments are known. The value here is a default in case we somehow |
| | 17 | /// crash earlier than that. |
| | 18 | pub var zig_argv0: []const u8 = "zig"; |
| | 19 | |
| | 20 | fn handleSegfaultImpl(addr: ?usize, name: []const u8, opt_ctx: ?*std.debug.ThreadContext) noreturn { |
| | 21 | @branchHint(.cold); |
| | 22 | dumpCrashContext() catch {}; |
| | 23 | std.debug.defaultHandleSegfault(addr, name, opt_ctx); |
| | 24 | } |
| | 25 | fn panicImpl(msg: []const u8, first_trace_addr: ?usize) noreturn { |
| | 26 | @branchHint(.cold); |
| | 27 | dumpCrashContext() catch {}; |
| | 28 | std.debug.defaultPanic(msg, first_trace_addr orelse @returnAddress()); |
| 32 | } | 29 | } |
| 33 | | 30 | |
| 34 | pub const AnalyzeBody = if (build_options.enable_debug_extensions) struct { | 31 | pub const AnalyzeBody = if (build_options.enable_debug_extensions) struct { |
| ... | @@ -38,63 +35,96 @@ pub const AnalyzeBody = if (build_options.enable_debug_extensions) struct { | ... | @@ -38,63 +35,96 @@ pub const AnalyzeBody = if (build_options.enable_debug_extensions) struct { |
| 38 | body: []const Zir.Inst.Index, | 35 | body: []const Zir.Inst.Index, |
| 39 | body_index: usize, | 36 | body_index: usize, |
| 40 | | 37 | |
| 41 | pub fn push(self: *@This()) void { | 38 | threadlocal var current: ?*AnalyzeBody = null; |
| 42 | const head = &zir_state; | | |
| 43 | debug.assert(self.parent == null); | | |
| 44 | self.parent = head.*; | | |
| 45 | head.* = self; | | |
| 46 | } | | |
| 47 | | 39 | |
| 48 | pub fn pop(self: *@This()) void { | 40 | pub fn setBodyIndex(ab: *AnalyzeBody, index: usize) void { |
| 49 | const head = &zir_state; | 41 | ab.body_index = index; |
| 50 | const old = head.*.?; | | |
| 51 | debug.assert(old == self); | | |
| 52 | head.* = old.parent; | | |
| 53 | } | 42 | } |
| 54 | | 43 | |
| 55 | pub fn setBodyIndex(self: *@This(), index: usize) void { | 44 | pub fn push(ab: *AnalyzeBody, sema: *Sema, block: *Sema.Block, body: []const Zir.Inst.Index) void { |
| 56 | self.body_index = index; | 45 | ab.* = .{ |
| | 46 | .parent = current, |
| | 47 | .sema = sema, |
| | 48 | .block = block, |
| | 49 | .body = body, |
| | 50 | .body_index = 0, |
| | 51 | }; |
| | 52 | current = ab; |
| | 53 | } |
| | 54 | pub fn pop(ab: *AnalyzeBody) void { |
| | 55 | std.debug.assert(current.? == ab); // `Sema.analyzeBodyInner` did not match push/pop calls |
| | 56 | current = ab.parent; |
| 57 | } | 57 | } |
| 58 | } else struct { | 58 | } else struct { |
| 59 | pub inline fn push(_: @This()) void {} | 59 | // Dummy implementation, with functions marked `inline` to avoid interfering with tail calls. |
| 60 | pub inline fn pop(_: @This()) void {} | 60 | pub inline fn push(_: AnalyzeBody, _: *Sema, _: *Sema.Block, _: []const Zir.Inst.Index) void {} |
| | 61 | pub inline fn pop(_: AnalyzeBody) void {} |
| 61 | pub inline fn setBodyIndex(_: @This(), _: usize) void {} | 62 | pub inline fn setBodyIndex(_: @This(), _: usize) void {} |
| 62 | }; | 63 | }; |
| 63 | | 64 | |
| 64 | threadlocal var zir_state: ?*AnalyzeBody = if (build_options.enable_debug_extensions) null else @compileError("Cannot use zir_state without debug extensions."); | 65 | pub const CodegenFunc = if (build_options.enable_debug_extensions) struct { |
| | 66 | zcu: *const Zcu, |
| | 67 | func_index: InternPool.Index, |
| | 68 | threadlocal var current: ?CodegenFunc = null; |
| | 69 | pub fn start(zcu: *const Zcu, func_index: InternPool.Index) void { |
| | 70 | std.debug.assert(current == null); |
| | 71 | current = .{ .zcu = zcu, .func_index = func_index }; |
| | 72 | } |
| | 73 | pub fn stop(func_index: InternPool.Index) void { |
| | 74 | std.debug.assert(current.?.func_index == func_index); |
| | 75 | current = null; |
| | 76 | } |
| | 77 | } else struct { |
| | 78 | // Dummy implementation |
| | 79 | pub fn start(_: *const Zcu, _: InternPool.Index) void {} |
| | 80 | pub fn stop(_: InternPool.Index) void {} |
| | 81 | }; |
| 65 | | 82 | |
| 66 | pub fn prepAnalyzeBody(sema: *Sema, block: *Sema.Block, body: []const Zir.Inst.Index) AnalyzeBody { | 83 | fn dumpCrashContext() Io.Writer.Error!void { |
| 67 | return if (build_options.enable_debug_extensions) .{ | 84 | const S = struct { |
| 68 | .parent = null, | 85 | /// In the case of recursive panics or segfaults, don't print the context for a second time. |
| 69 | .sema = sema, | 86 | threadlocal var already_dumped = false; |
| 70 | .block = block, | 87 | /// TODO: make this unnecessary. It exists because `print_zir` currently needs an allocator, |
| 71 | .body = body, | 88 | /// but that shouldn't be necessary---it's already only used in one place. |
| 72 | .body_index = 0, | 89 | threadlocal var crash_heap: [64 * 1024]u8 = undefined; |
| 73 | } else .{}; | 90 | }; |
| 74 | } | 91 | if (S.already_dumped) return; |
| | 92 | S.already_dumped = true; |
| | 93 | |
| | 94 | // TODO: this does mean that a different thread could grab the stderr mutex between the context |
| | 95 | // and the actual panic printing, which would be quite confusing. |
| | 96 | const stderr = std.debug.lockStderrWriter(&.{}); |
| | 97 | defer std.debug.unlockStderrWriter(); |
| 75 | | 98 | |
| 76 | fn dumpStatusReport() !void { | 99 | try stderr.writeAll("Compiler crash context:\n"); |
| 77 | const anal = zir_state orelse return; | | |
| 78 | // Note: We have the panic mutex here, so we can safely use the global crash heap. | | |
| 79 | var fba = std.heap.FixedBufferAllocator.init(&crash_heap); | | |
| 80 | const allocator = fba.allocator(); | | |
| 81 | | 100 | |
| 82 | var stderr_fw = std.fs.File.stderr().writer(&.{}); | 101 | if (CodegenFunc.current) |*cg| { |
| 83 | const stderr = &stderr_fw.interface; | 102 | const func_nav = cg.zcu.funcInfo(cg.func_index).owner_nav; |
| | 103 | const func_fqn = cg.zcu.intern_pool.getNav(func_nav).fqn; |
| | 104 | try stderr.print("Generating function '{f}'\n\n", .{func_fqn.fmt(&cg.zcu.intern_pool)}); |
| | 105 | } else if (AnalyzeBody.current) |anal| { |
| | 106 | try dumpCrashContextSema(anal, stderr, &S.crash_heap); |
| | 107 | } else { |
| | 108 | try stderr.writeAll("(no context)\n\n"); |
| | 109 | } |
| | 110 | } |
| | 111 | fn dumpCrashContextSema(anal: *AnalyzeBody, stderr: *Io.Writer, crash_heap: []u8) Io.Writer.Error!void { |
| 84 | const block: *Sema.Block = anal.block; | 112 | const block: *Sema.Block = anal.block; |
| 85 | const zcu = anal.sema.pt.zcu; | 113 | const zcu = anal.sema.pt.zcu; |
| | 114 | const comp = zcu.comp; |
| | 115 | |
| | 116 | var fba: std.heap.FixedBufferAllocator = .init(crash_heap); |
| 86 | | 117 | |
| 87 | const file, const src_base_node = Zcu.LazySrcLoc.resolveBaseNode(block.src_base_inst, zcu) orelse { | 118 | const file, const src_base_node = Zcu.LazySrcLoc.resolveBaseNode(block.src_base_inst, zcu) orelse { |
| 88 | const file = zcu.fileByIndex(block.src_base_inst.resolveFile(&zcu.intern_pool)); | 119 | const file = zcu.fileByIndex(block.src_base_inst.resolveFile(&zcu.intern_pool)); |
| 89 | try stderr.print("Analyzing lost instruction in file '{f}'. This should not happen!\n\n", .{file.path.fmt(zcu.comp)}); | 120 | try stderr.print("Analyzing lost instruction in file '{f}'. This should not happen!\n\n", .{file.path.fmt(comp)}); |
| 90 | return; | 121 | return; |
| 91 | }; | 122 | }; |
| 92 | | 123 | |
| 93 | try stderr.writeAll("Analyzing "); | 124 | try stderr.print("Analyzing '{f}'\n", .{file.path.fmt(comp)}); |
| 94 | try stderr.print("Analyzing '{f}'\n", .{file.path.fmt(zcu.comp)}); | | |
| 95 | | 125 | |
| 96 | print_zir.renderInstructionContext( | 126 | print_zir.renderInstructionContext( |
| 97 | allocator, | 127 | fba.allocator(), |
| 98 | anal.body, | 128 | anal.body, |
| 99 | anal.body_index, | 129 | anal.body_index, |
| 100 | file, | 130 | file, |
| ... | @@ -107,16 +137,16 @@ fn dumpStatusReport() !void { | ... | @@ -107,16 +137,16 @@ fn dumpStatusReport() !void { |
| 107 | }; | 137 | }; |
| 108 | try stderr.print( | 138 | try stderr.print( |
| 109 | \\ For full context, use the command | 139 | \\ For full context, use the command |
| 110 | \\ zig ast-check -t {f} | 140 | \\ {s} ast-check -t {f} |
| 111 | \\ | 141 | \\ |
| 112 | \\ | 142 | \\ |
| 113 | , .{file.path.fmt(zcu.comp)}); | 143 | , .{ zig_argv0, file.path.fmt(comp) }); |
| 114 | | 144 | |
| 115 | var parent = anal.parent; | 145 | var parent = anal.parent; |
| 116 | while (parent) |curr| { | 146 | while (parent) |curr| { |
| 117 | fba.reset(); | 147 | fba.reset(); |
| 118 | const cur_block_file = zcu.fileByIndex(curr.block.src_base_inst.resolveFile(&zcu.intern_pool)); | 148 | const cur_block_file = zcu.fileByIndex(curr.block.src_base_inst.resolveFile(&zcu.intern_pool)); |
| 119 | try stderr.print(" in {f}\n", .{cur_block_file.path.fmt(zcu.comp)}); | 149 | try stderr.print(" in {f}\n", .{cur_block_file.path.fmt(comp)}); |
| 120 | _, const cur_block_src_base_node = Zcu.LazySrcLoc.resolveBaseNode(curr.block.src_base_inst, zcu) orelse { | 150 | _, const cur_block_src_base_node = Zcu.LazySrcLoc.resolveBaseNode(curr.block.src_base_inst, zcu) orelse { |
| 121 | try stderr.writeAll(" > [lost instruction; this should not happen]\n"); | 151 | try stderr.writeAll(" > [lost instruction; this should not happen]\n"); |
| 122 | parent = curr.parent; | 152 | parent = curr.parent; |
| ... | @@ -124,7 +154,7 @@ fn dumpStatusReport() !void { | ... | @@ -124,7 +154,7 @@ fn dumpStatusReport() !void { |
| 124 | }; | 154 | }; |
| 125 | try stderr.writeAll(" > "); | 155 | try stderr.writeAll(" > "); |
| 126 | print_zir.renderSingleInstruction( | 156 | print_zir.renderSingleInstruction( |
| 127 | allocator, | 157 | fba.allocator(), |
| 128 | curr.body[curr.body_index], | 158 | curr.body[curr.body_index], |
| 129 | cur_block_file, | 159 | cur_block_file, |
| 130 | cur_block_src_base_node, | 160 | cur_block_src_base_node, |
| ... | @@ -142,398 +172,14 @@ fn dumpStatusReport() !void { | ... | @@ -142,398 +172,14 @@ fn dumpStatusReport() !void { |
| 142 | try stderr.writeByte('\n'); | 172 | try stderr.writeByte('\n'); |
| 143 | } | 173 | } |
| 144 | | 174 | |
| 145 | var crash_heap: [16 * 4096]u8 = undefined; | 175 | const std = @import("std"); |
| 146 | | 176 | const Io = std.Io; |
| 147 | pub fn compilerPanic(msg: []const u8, maybe_ret_addr: ?usize) noreturn { | 177 | const Zir = std.zig.Zir; |
| 148 | @branchHint(.cold); | | |
| 149 | PanicSwitch.preDispatch(); | | |
| 150 | const ret_addr = maybe_ret_addr orelse @returnAddress(); | | |
| 151 | const stack_ctx: StackContext = .{ .current = .{ .ret_addr = ret_addr } }; | | |
| 152 | PanicSwitch.dispatch(@errorReturnTrace(), stack_ctx, msg); | | |
| 153 | } | | |
| 154 | | | |
| 155 | /// Attaches a global SIGSEGV handler | | |
| 156 | pub fn attachSegfaultHandler() void { | | |
| 157 | if (!debug.have_segfault_handling_support) { | | |
| 158 | @compileError("segfault handler not supported for this target"); | | |
| 159 | } | | |
| 160 | if (native_os == .windows) { | | |
| 161 | _ = windows.kernel32.AddVectoredExceptionHandler(0, handleSegfaultWindows); | | |
| 162 | return; | | |
| 163 | } | | |
| 164 | const act: posix.Sigaction = .{ | | |
| 165 | .handler = .{ .sigaction = handleSegfaultPosix }, | | |
| 166 | .mask = posix.sigemptyset(), | | |
| 167 | .flags = (posix.SA.SIGINFO | posix.SA.RESTART | posix.SA.RESETHAND), | | |
| 168 | }; | | |
| 169 | debug.updateSegfaultHandler(&act); | | |
| 170 | } | | |
| 171 | | | |
| 172 | fn handleSegfaultPosix(sig: i32, info: *const posix.siginfo_t, ctx_ptr: ?*anyopaque) callconv(.c) noreturn { | | |
| 173 | // TODO: use alarm() here to prevent infinite loops | | |
| 174 | PanicSwitch.preDispatch(); | | |
| 175 | | | |
| 176 | const addr = switch (native_os) { | | |
| 177 | .linux => @intFromPtr(info.fields.sigfault.addr), | | |
| 178 | .freebsd, .macos => @intFromPtr(info.addr), | | |
| 179 | .netbsd => @intFromPtr(info.info.reason.fault.addr), | | |
| 180 | .openbsd => @intFromPtr(info.data.fault.addr), | | |
| 181 | .solaris, .illumos => @intFromPtr(info.reason.fault.addr), | | |
| 182 | else => @compileError("TODO implement handleSegfaultPosix for new POSIX OS"), | | |
| 183 | }; | | |
| 184 | | | |
| 185 | var err_buffer: [128]u8 = undefined; | | |
| 186 | const error_msg = switch (sig) { | | |
| 187 | posix.SIG.SEGV => std.fmt.bufPrint(&err_buffer, "Segmentation fault at address 0x{x}", .{addr}) catch "Segmentation fault", | | |
| 188 | posix.SIG.ILL => std.fmt.bufPrint(&err_buffer, "Illegal instruction at address 0x{x}", .{addr}) catch "Illegal instruction", | | |
| 189 | posix.SIG.BUS => std.fmt.bufPrint(&err_buffer, "Bus error at address 0x{x}", .{addr}) catch "Bus error", | | |
| 190 | else => std.fmt.bufPrint(&err_buffer, "Unknown error (signal {}) at address 0x{x}", .{ sig, addr }) catch "Unknown error", | | |
| 191 | }; | | |
| 192 | | | |
| 193 | const stack_ctx: StackContext = switch (builtin.cpu.arch) { | | |
| 194 | .x86, | | |
| 195 | .x86_64, | | |
| 196 | .arm, | | |
| 197 | .aarch64, | | |
| 198 | => StackContext{ .exception = @ptrCast(@alignCast(ctx_ptr)) }, | | |
| 199 | else => .not_supported, | | |
| 200 | }; | | |
| 201 | | | |
| 202 | PanicSwitch.dispatch(null, stack_ctx, error_msg); | | |
| 203 | } | | |
| 204 | | | |
| 205 | const WindowsSegfaultMessage = union(enum) { | | |
| 206 | literal: []const u8, | | |
| 207 | segfault: void, | | |
| 208 | illegal_instruction: void, | | |
| 209 | }; | | |
| 210 | | | |
| 211 | fn handleSegfaultWindows(info: *windows.EXCEPTION_POINTERS) callconv(.winapi) c_long { | | |
| 212 | switch (info.ExceptionRecord.ExceptionCode) { | | |
| 213 | windows.EXCEPTION_DATATYPE_MISALIGNMENT => handleSegfaultWindowsExtra(info, .{ .literal = "Unaligned Memory Access" }), | | |
| 214 | windows.EXCEPTION_ACCESS_VIOLATION => handleSegfaultWindowsExtra(info, .segfault), | | |
| 215 | windows.EXCEPTION_ILLEGAL_INSTRUCTION => handleSegfaultWindowsExtra(info, .illegal_instruction), | | |
| 216 | windows.EXCEPTION_STACK_OVERFLOW => handleSegfaultWindowsExtra(info, .{ .literal = "Stack Overflow" }), | | |
| 217 | else => return windows.EXCEPTION_CONTINUE_SEARCH, | | |
| 218 | } | | |
| 219 | } | | |
| 220 | | | |
| 221 | fn handleSegfaultWindowsExtra(info: *windows.EXCEPTION_POINTERS, comptime msg: WindowsSegfaultMessage) noreturn { | | |
| 222 | PanicSwitch.preDispatch(); | | |
| 223 | | | |
| 224 | const stack_ctx = if (@hasDecl(windows, "CONTEXT")) | | |
| 225 | StackContext{ .exception = info.ContextRecord } | | |
| 226 | else ctx: { | | |
| 227 | const addr = @intFromPtr(info.ExceptionRecord.ExceptionAddress); | | |
| 228 | break :ctx StackContext{ .current = .{ .ret_addr = addr } }; | | |
| 229 | }; | | |
| 230 | | | |
| 231 | switch (msg) { | | |
| 232 | .literal => |err| PanicSwitch.dispatch(null, stack_ctx, err), | | |
| 233 | .segfault => { | | |
| 234 | const format_item = "Segmentation fault at address 0x{x}"; | | |
| 235 | var buf: [format_item.len + 32]u8 = undefined; // 32 is arbitrary, but sufficiently large | | |
| 236 | const to_print = std.fmt.bufPrint(&buf, format_item, .{info.ExceptionRecord.ExceptionInformation[1]}) catch unreachable; | | |
| 237 | PanicSwitch.dispatch(null, stack_ctx, to_print); | | |
| 238 | }, | | |
| 239 | .illegal_instruction => { | | |
| 240 | const ip: ?usize = switch (stack_ctx) { | | |
| 241 | .exception => |ex| ex.getRegs().ip, | | |
| 242 | .current => |cur| cur.ret_addr, | | |
| 243 | .not_supported => null, | | |
| 244 | }; | | |
| 245 | | | |
| 246 | if (ip) |addr| { | | |
| 247 | const format_item = "Illegal instruction at address 0x{x}"; | | |
| 248 | var buf: [format_item.len + 32]u8 = undefined; // 32 is arbitrary, but sufficiently large | | |
| 249 | const to_print = std.fmt.bufPrint(&buf, format_item, .{addr}) catch unreachable; | | |
| 250 | PanicSwitch.dispatch(null, stack_ctx, to_print); | | |
| 251 | } else { | | |
| 252 | PanicSwitch.dispatch(null, stack_ctx, "Illegal Instruction"); | | |
| 253 | } | | |
| 254 | }, | | |
| 255 | } | | |
| 256 | } | | |
| 257 | | | |
| 258 | const StackContext = union(enum) { | | |
| 259 | current: struct { | | |
| 260 | ret_addr: ?usize, | | |
| 261 | }, | | |
| 262 | exception: *debug.ThreadContext, | | |
| 263 | not_supported: void, | | |
| 264 | | | |
| 265 | pub fn dumpStackTrace(ctx: @This()) void { | | |
| 266 | switch (ctx) { | | |
| 267 | .current => |ct| { | | |
| 268 | debug.dumpCurrentStackTrace(ct.ret_addr); | | |
| 269 | }, | | |
| 270 | .exception => |context| { | | |
| 271 | var stderr_fw = std.fs.File.stderr().writer(&.{}); | | |
| 272 | const stderr = &stderr_fw.interface; | | |
| 273 | debug.dumpStackTraceFromBase(context, stderr); | | |
| 274 | }, | | |
| 275 | .not_supported => { | | |
| 276 | std.fs.File.stderr().writeAll("Stack trace not supported on this platform.\n") catch {}; | | |
| 277 | }, | | |
| 278 | } | | |
| 279 | } | | |
| 280 | }; | | |
| 281 | | | |
| 282 | const PanicSwitch = struct { | | |
| 283 | const RecoverStage = enum { | | |
| 284 | initialize, | | |
| 285 | report_stack, | | |
| 286 | release_mutex, | | |
| 287 | release_ref_count, | | |
| 288 | abort, | | |
| 289 | silent_abort, | | |
| 290 | }; | | |
| 291 | | | |
| 292 | const RecoverVerbosity = enum { | | |
| 293 | message_and_stack, | | |
| 294 | message_only, | | |
| 295 | silent, | | |
| 296 | }; | | |
| 297 | | | |
| 298 | const PanicState = struct { | | |
| 299 | recover_stage: RecoverStage = .initialize, | | |
| 300 | recover_verbosity: RecoverVerbosity = .message_and_stack, | | |
| 301 | panic_ctx: StackContext = undefined, | | |
| 302 | panic_trace: ?*const std.builtin.StackTrace = null, | | |
| 303 | awaiting_dispatch: bool = false, | | |
| 304 | }; | | |
| 305 | | | |
| 306 | /// Counter for the number of threads currently panicking. | | |
| 307 | /// Updated atomically before taking the panic_mutex. | | |
| 308 | /// In recoverable cases, the program will not abort | | |
| 309 | /// until all panicking threads have dumped their traces. | | |
| 310 | var panicking = std.atomic.Value(u8).init(0); | | |
| 311 | | | |
| 312 | /// Tracks the state of the current panic. If the code within the | | |
| 313 | /// panic triggers a secondary panic, this allows us to recover. | | |
| 314 | threadlocal var panic_state_raw: PanicState = .{}; | | |
| 315 | | | |
| 316 | /// The segfault handlers above need to do some work before they can dispatch | | |
| 317 | /// this switch. Calling preDispatch() first makes that work fault tolerant. | | |
| 318 | pub fn preDispatch() void { | | |
| 319 | // TODO: We want segfaults to trigger the panic recursively here, | | |
| 320 | // but if there is a segfault accessing this TLS slot it will cause an | | |
| 321 | // infinite loop. We should use `alarm()` to prevent the infinite | | |
| 322 | // loop and maybe also use a non-thread-local global to detect if | | |
| 323 | // it's happening and print a message. | | |
| 324 | var panic_state: *volatile PanicState = &panic_state_raw; | | |
| 325 | if (panic_state.awaiting_dispatch) { | | |
| 326 | dispatch(null, .{ .current = .{ .ret_addr = null } }, "Panic while preparing callstack"); | | |
| 327 | } | | |
| 328 | panic_state.awaiting_dispatch = true; | | |
| 329 | } | | |
| 330 | | | |
| 331 | /// This is the entry point to a panic-tolerant panic handler. | | |
| 332 | /// preDispatch() *MUST* be called exactly once before calling this. | | |
| 333 | /// A threadlocal "recover_stage" is updated throughout the process. | | |
| 334 | /// If a panic happens during the panic, the recover_stage will be | | |
| 335 | /// used to select a recover* function to call to resume the panic. | | |
| 336 | /// The recover_verbosity field is used to handle panics while reporting | | |
| 337 | /// panics within panics. If the panic handler triggers a panic, it will | | |
| 338 | /// attempt to log an additional stack trace for the secondary panic. If | | |
| 339 | /// that panics, it will fall back to just logging the panic message. If | | |
| 340 | /// it can't even do that witout panicing, it will recover without logging | | |
| 341 | /// anything about the internal panic. Depending on the state, "recover" | | |
| 342 | /// here may just mean "call abort". | | |
| 343 | pub fn dispatch( | | |
| 344 | trace: ?*const std.builtin.StackTrace, | | |
| 345 | stack_ctx: StackContext, | | |
| 346 | msg: []const u8, | | |
| 347 | ) noreturn { | | |
| 348 | var panic_state: *volatile PanicState = &panic_state_raw; | | |
| 349 | debug.assert(panic_state.awaiting_dispatch); | | |
| 350 | panic_state.awaiting_dispatch = false; | | |
| 351 | nosuspend switch (panic_state.recover_stage) { | | |
| 352 | .initialize => goTo(initPanic, .{ panic_state, trace, stack_ctx, msg }), | | |
| 353 | .report_stack => goTo(recoverReportStack, .{ panic_state, trace, stack_ctx, msg }), | | |
| 354 | .release_mutex => goTo(recoverReleaseMutex, .{ panic_state, trace, stack_ctx, msg }), | | |
| 355 | .release_ref_count => goTo(recoverReleaseRefCount, .{ panic_state, trace, stack_ctx, msg }), | | |
| 356 | .abort => goTo(recoverAbort, .{ panic_state, trace, stack_ctx, msg }), | | |
| 357 | .silent_abort => goTo(abort, .{}), | | |
| 358 | }; | | |
| 359 | } | | |
| 360 | | | |
| 361 | noinline fn initPanic( | | |
| 362 | state: *volatile PanicState, | | |
| 363 | trace: ?*const std.builtin.StackTrace, | | |
| 364 | stack: StackContext, | | |
| 365 | msg: []const u8, | | |
| 366 | ) noreturn { | | |
| 367 | // use a temporary so there's only one volatile store | | |
| 368 | const new_state = PanicState{ | | |
| 369 | .recover_stage = .abort, | | |
| 370 | .panic_ctx = stack, | | |
| 371 | .panic_trace = trace, | | |
| 372 | }; | | |
| 373 | state.* = new_state; | | |
| 374 | | | |
| 375 | _ = panicking.fetchAdd(1, .seq_cst); | | |
| 376 | | | |
| 377 | state.recover_stage = .release_ref_count; | | |
| 378 | | | |
| 379 | std.debug.lockStdErr(); | | |
| 380 | | | |
| 381 | state.recover_stage = .release_mutex; | | |
| 382 | | | |
| 383 | var stderr_fw = std.fs.File.stderr().writer(&.{}); | | |
| 384 | const stderr = &stderr_fw.interface; | | |
| 385 | if (builtin.single_threaded) { | | |
| 386 | stderr.print("panic: ", .{}) catch goTo(releaseMutex, .{state}); | | |
| 387 | } else { | | |
| 388 | const current_thread_id = std.Thread.getCurrentId(); | | |
| 389 | stderr.print("thread {} panic: ", .{current_thread_id}) catch goTo(releaseMutex, .{state}); | | |
| 390 | } | | |
| 391 | stderr.print("{s}\n", .{msg}) catch goTo(releaseMutex, .{state}); | | |
| 392 | | | |
| 393 | state.recover_stage = .report_stack; | | |
| 394 | | | |
| 395 | dumpStatusReport() catch |err| { | | |
| 396 | stderr.print("\nIntercepted error.{} while dumping current state. Continuing...\n", .{err}) catch {}; | | |
| 397 | }; | | |
| 398 | | | |
| 399 | goTo(reportStack, .{state}); | | |
| 400 | } | | |
| 401 | | | |
| 402 | noinline fn recoverReportStack( | | |
| 403 | state: *volatile PanicState, | | |
| 404 | trace: ?*const std.builtin.StackTrace, | | |
| 405 | stack: StackContext, | | |
| 406 | msg: []const u8, | | |
| 407 | ) noreturn { | | |
| 408 | recover(state, trace, stack, msg); | | |
| 409 | | | |
| 410 | state.recover_stage = .release_mutex; | | |
| 411 | var stderr_fw = std.fs.File.stderr().writer(&.{}); | | |
| 412 | const stderr = &stderr_fw.interface; | | |
| 413 | stderr.writeAll("\nOriginal Error:\n") catch {}; | | |
| 414 | goTo(reportStack, .{state}); | | |
| 415 | } | | |
| 416 | | | |
| 417 | noinline fn reportStack(state: *volatile PanicState) noreturn { | | |
| 418 | state.recover_stage = .release_mutex; | | |
| 419 | | | |
| 420 | if (state.panic_trace) |t| { | | |
| 421 | debug.dumpStackTrace(t.*); | | |
| 422 | } | | |
| 423 | state.panic_ctx.dumpStackTrace(); | | |
| 424 | | | |
| 425 | goTo(releaseMutex, .{state}); | | |
| 426 | } | | |
| 427 | | | |
| 428 | noinline fn recoverReleaseMutex( | | |
| 429 | state: *volatile PanicState, | | |
| 430 | trace: ?*const std.builtin.StackTrace, | | |
| 431 | stack: StackContext, | | |
| 432 | msg: []const u8, | | |
| 433 | ) noreturn { | | |
| 434 | recover(state, trace, stack, msg); | | |
| 435 | goTo(releaseMutex, .{state}); | | |
| 436 | } | | |
| 437 | | | |
| 438 | noinline fn releaseMutex(state: *volatile PanicState) noreturn { | | |
| 439 | state.recover_stage = .abort; | | |
| 440 | | | |
| 441 | std.debug.unlockStdErr(); | | |
| 442 | | | |
| 443 | goTo(releaseRefCount, .{state}); | | |
| 444 | } | | |
| 445 | | | |
| 446 | noinline fn recoverReleaseRefCount( | | |
| 447 | state: *volatile PanicState, | | |
| 448 | trace: ?*const std.builtin.StackTrace, | | |
| 449 | stack: StackContext, | | |
| 450 | msg: []const u8, | | |
| 451 | ) noreturn { | | |
| 452 | recover(state, trace, stack, msg); | | |
| 453 | goTo(releaseRefCount, .{state}); | | |
| 454 | } | | |
| 455 | | | |
| 456 | noinline fn releaseRefCount(state: *volatile PanicState) noreturn { | | |
| 457 | state.recover_stage = .abort; | | |
| 458 | | | |
| 459 | if (panicking.fetchSub(1, .seq_cst) != 1) { | | |
| 460 | // Another thread is panicking, wait for the last one to finish | | |
| 461 | // and call abort() | | |
| 462 | | | |
| 463 | // Sleep forever without hammering the CPU | | |
| 464 | var futex = std.atomic.Value(u32).init(0); | | |
| 465 | while (true) std.Thread.Futex.wait(&futex, 0); | | |
| 466 | | | |
| 467 | // This should be unreachable, recurse into recoverAbort. | | |
| 468 | @panic("event.wait() returned"); | | |
| 469 | } | | |
| 470 | | | |
| 471 | goTo(abort, .{}); | | |
| 472 | } | | |
| 473 | | | |
| 474 | noinline fn recoverAbort( | | |
| 475 | state: *volatile PanicState, | | |
| 476 | trace: ?*const std.builtin.StackTrace, | | |
| 477 | stack: StackContext, | | |
| 478 | msg: []const u8, | | |
| 479 | ) noreturn { | | |
| 480 | recover(state, trace, stack, msg); | | |
| 481 | | | |
| 482 | state.recover_stage = .silent_abort; | | |
| 483 | var stderr_fw = std.fs.File.stderr().writer(&.{}); | | |
| 484 | const stderr = &stderr_fw.interface; | | |
| 485 | stderr.writeAll("Aborting...\n") catch {}; | | |
| 486 | goTo(abort, .{}); | | |
| 487 | } | | |
| 488 | | | |
| 489 | noinline fn abort() noreturn { | | |
| 490 | std.process.abort(); | | |
| 491 | } | | |
| 492 | | | |
| 493 | inline fn goTo(comptime func: anytype, args: anytype) noreturn { | | |
| 494 | // TODO: Tailcall is broken right now, but eventually this should be used | | |
| 495 | // to avoid blowing up the stack. It's ok for now though, there are no | | |
| 496 | // cycles in the state machine so the max stack usage is bounded. | | |
| 497 | //@call(.always_tail, func, args); | | |
| 498 | @call(.auto, func, args); | | |
| 499 | } | | |
| 500 | | | |
| 501 | fn recover( | | |
| 502 | state: *volatile PanicState, | | |
| 503 | trace: ?*const std.builtin.StackTrace, | | |
| 504 | stack: StackContext, | | |
| 505 | msg: []const u8, | | |
| 506 | ) void { | | |
| 507 | switch (state.recover_verbosity) { | | |
| 508 | .message_and_stack => { | | |
| 509 | // lower the verbosity, and restore it at the end if we don't panic. | | |
| 510 | state.recover_verbosity = .message_only; | | |
| 511 | | | |
| 512 | var stderr_fw = std.fs.File.stderr().writer(&.{}); | | |
| 513 | const stderr = &stderr_fw.interface; | | |
| 514 | stderr.writeAll("\nPanicked during a panic: ") catch {}; | | |
| 515 | stderr.writeAll(msg) catch {}; | | |
| 516 | stderr.writeAll("\nInner panic stack:\n") catch {}; | | |
| 517 | if (trace) |t| { | | |
| 518 | debug.dumpStackTrace(t.*); | | |
| 519 | } | | |
| 520 | stack.dumpStackTrace(); | | |
| 521 | | | |
| 522 | state.recover_verbosity = .message_and_stack; | | |
| 523 | }, | | |
| 524 | .message_only => { | | |
| 525 | state.recover_verbosity = .silent; | | |
| 526 | | 178 | |
| 527 | var stderr_fw = std.fs.File.stderr().writer(&.{}); | 179 | const Sema = @import("Sema.zig"); |
| 528 | const stderr = &stderr_fw.interface; | 180 | const Zcu = @import("Zcu.zig"); |
| 529 | stderr.writeAll("\nPanicked while dumping inner panic stack: ") catch {}; | 181 | const InternPool = @import("InternPool.zig"); |
| 530 | stderr.writeAll(msg) catch {}; | 182 | const dev = @import("dev.zig"); |
| 531 | stderr.writeByte('\n') catch {}; | 183 | const print_zir = @import("print_zir.zig"); |
| 532 | | 184 | |
| 533 | // If we succeed, restore all the way to dumping the stack. | 185 | const build_options = @import("build_options"); |
| 534 | state.recover_verbosity = .message_and_stack; | | |
| 535 | }, | | |
| 536 | .silent => {}, | | |
| 537 | } | | |
| 538 | } | | |
| 539 | }; | | |