diff options
| author | Andrew Kelley <andrew@ziglang.org> | 2024-09-25 11:11:48 -0700 |
|---|---|---|
| committer | Andrew Kelley <andrew@ziglang.org> | 2024-09-26 12:35:14 -0700 |
| commit | 4f8d244e7ea47a8cdb41496d51961ef4ba3ec2af (patch) | |
| tree | c12d45a7aeadec432e0e40760923ac3c6ea937bc /lib/std/debug.zig | |
| parent | 04e694ad116ad2706328c13ce3643347330a861f (diff) | |
| download | zig-4f8d244e7ea47a8cdb41496d51961ef4ba3ec2af.tar.gz zig-4f8d244e7ea47a8cdb41496d51961ef4ba3ec2af.zip | |
remove formatted panics
implements #17969
Diffstat (limited to 'lib/std/debug.zig')
| -rw-r--r-- | lib/std/debug.zig | 294 |
1 files changed, 228 insertions, 66 deletions
diff --git a/lib/std/debug.zig b/lib/std/debug.zig index fd6676504f..8e5498b3f5 100644 --- a/lib/std/debug.zig +++ b/lib/std/debug.zig @@ -408,14 +408,15 @@ pub fn assertReadable(slice: []const volatile u8) void { for (slice) |*byte| _ = byte.*; } +/// Equivalent to `@panic` but with a formatted message. pub fn panic(comptime format: []const u8, args: anytype) noreturn { @branchHint(.cold); panicExtra(@errorReturnTrace(), @returnAddress(), format, args); } -/// `panicExtra` is useful when you want to print out an `@errorReturnTrace` -/// and also print out some values. +/// Equivalent to `@panic` but with a formatted message, and with an explicitly +/// provided `@errorReturnTrace` and return address. pub fn panicExtra( trace: ?*std.builtin.StackTrace, ret_addr: ?usize, @@ -447,11 +448,104 @@ var panicking = std.atomic.Value(u8).init(0); /// This is used to catch and handle panics triggered by the panic handler. threadlocal var panic_stage: usize = 0; -// `panicImpl` could be useful in implementing a custom panic handler which -// calls the default handler (on supported platforms) -pub fn panicImpl(trace: ?*const std.builtin.StackTrace, first_trace_addr: ?usize, msg: []const u8) noreturn { +// Dumps a stack trace to standard error, then aborts. +// +// This function avoids a dependency on formatted printing. +pub fn defaultPanic( + cause: std.builtin.PanicCause, + trace: ?*const std.builtin.StackTrace, + first_trace_addr: ?usize, +) noreturn { @branchHint(.cold); + // For backends that cannot handle the language features depended on by the + // default panic handler, we have a simpler panic handler: + if (builtin.zig_backend == .stage2_wasm or + builtin.zig_backend == .stage2_arm or + builtin.zig_backend == .stage2_aarch64 or + builtin.zig_backend == .stage2_x86 or + (builtin.zig_backend == .stage2_x86_64 and (builtin.target.ofmt != .elf and builtin.target.ofmt != .macho)) or + builtin.zig_backend == .stage2_sparc64 or + builtin.zig_backend == .stage2_spirv64) + { + @trap(); + } + + if (builtin.zig_backend == .stage2_riscv64) { + var buffer: [1000]u8 = undefined; + var i: usize = 0; + i += fmtPanicCause(buffer[i..], cause); + buffer[i] = '\n'; + i += 1; + const msg = buffer[0..i]; + lockStdErr(); + io.getStdErr().writeAll(msg) catch {}; + @trap(); + } + + switch (builtin.os.tag) { + .freestanding => { + @trap(); + }, + .wasi => { + // TODO: before merging my branch, unify this logic with the main panic logic + var buffer: [1000]u8 = undefined; + var i: usize = 0; + i += fmtPanicCause(buffer[i..], cause); + buffer[i] = '\n'; + i += 1; + const msg = buffer[0..i]; + lockStdErr(); + io.getStdErr().writeAll(msg) catch {}; + @trap(); + }, + .uefi => { + const uefi = std.os.uefi; + + var buffer: [1000]u8 = undefined; + var i: usize = 0; + i += fmtBuf(buffer[i..], "panic: "); + i += fmtPanicCause(buffer[i..], cause); + i += fmtBuf(buffer[i..], "\r\n\x00"); + + var utf16_buffer: [1000]u16 = undefined; + const len = std.unicode.utf8ToUtf16Le(&utf16_buffer, buffer[0..i]) catch 0; + const exit_msg = utf16_buffer[0 .. len - 1 :0]; + + // Output to both std_err and con_out, as std_err is easier + // to read in stuff like QEMU at times, but, unlike con_out, + // isn't visible on actual hardware if directly booted into + inline for ([_]?*uefi.protocol.SimpleTextOutput{ uefi.system_table.std_err, uefi.system_table.con_out }) |o| { + if (o) |out| { + _ = out.setAttribute(uefi.protocol.SimpleTextOutput.red); + _ = out.outputString(exit_msg); + _ = out.setAttribute(uefi.protocol.SimpleTextOutput.white); + } + } + + if (uefi.system_table.boot_services) |bs| { + // ExitData buffer must be allocated using boot_services.allocatePool (spec: page 220) + const exit_data: []u16 = uefi.raw_pool_allocator.alloc(u16, exit_msg.len + 1) catch @trap(); + @memcpy(exit_data, exit_msg[0..exit_data.len]); // Includes null terminator. + _ = bs.exit(uefi.handle, .Aborted, exit_msg.len + 1, exit_data); + } + @trap(); + }, + .cuda, .amdhsa => std.posix.abort(), + .plan9 => { + var buffer: [1000]u8 = undefined; + comptime assert(buffer.len > std.os.plan9.ERRMAX); + var i: usize = 0; + i += fmtPanicCause(buffer[i..], cause); + buffer[i] = '\n'; + i += 1; + const len = @min(i, std.os.plan9.ERRMAX - 1); + buffer[len] = 0; + std.os.plan9.exits(buffer[0..len :0]); + }, + else => {}, + } + if (enable_segfault_handler) { // If a segfault happens while panicking, we want it to actually segfault, not trigger // the handler. @@ -465,23 +559,29 @@ pub fn panicImpl(trace: ?*const std.builtin.StackTrace, first_trace_addr: ?usize _ = panicking.fetchAdd(1, .seq_cst); - // Make sure to release the mutex when done { - lockStdErr(); - defer unlockStdErr(); - - const stderr = io.getStdErr().writer(); + // This code avoids a dependency on formatted printing, the writer interface, + // and limits to only 1 syscall made to print the panic message to stderr. + var buffer: [0x1000]u8 = undefined; + var i: usize = 0; if (builtin.single_threaded) { - stderr.print("panic: ", .{}) catch posix.abort(); + i += fmtBuf(buffer[i..], "panic: "); } else { - const current_thread_id = std.Thread.getCurrentId(); - stderr.print("thread {} panic: ", .{current_thread_id}) catch posix.abort(); - } - stderr.print("{s}\n", .{msg}) catch posix.abort(); - if (trace) |t| { - dumpStackTrace(t.*); + i += fmtBuf(buffer[i..], "thread "); + i += fmtInt10(buffer[i..], std.Thread.getCurrentId()); + i += fmtBuf(buffer[i..], " panic: "); } - dumpCurrentStackTrace(first_trace_addr); + i += fmtPanicCause(&buffer, cause); + buffer[i] = '\n'; + i += 1; + const msg = buffer[0..i]; + + lockStdErr(); + defer unlockStdErr(); + + io.getStdErr().writeAll(msg) catch posix.abort(); + if (trace) |t| dumpStackTrace(t.*); + dumpCurrentStackTrace(first_trace_addr orelse @returnAddress()); } waitForOtherThreadToFinishPanicking(); @@ -489,20 +589,99 @@ pub fn panicImpl(trace: ?*const std.builtin.StackTrace, first_trace_addr: ?usize 1 => { panic_stage = 2; - // A panic happened while trying to print a previous panic message, - // we're still holding the mutex but that's fine as we're going to - // call abort() - const stderr = io.getStdErr().writer(); - stderr.print("Panicked during a panic. Aborting.\n", .{}) catch posix.abort(); - }, - else => { - // Panicked while printing "Panicked during a panic." + // A panic happened while trying to print a previous panic message. + // We're still holding the mutex but that's fine as we're going to + // call abort(). + io.getStdErr().writeAll("aborting due to recursive panic\n") catch {}; }, + else => {}, // Panicked while printing the recursive panic message. }; posix.abort(); } +pub fn fmtPanicCause(buffer: []u8, cause: std.builtin.PanicCause) usize { + var i: usize = 0; + + switch (cause) { + .reached_unreachable => i += fmtBuf(buffer[i..], "reached unreachable code"), + .unwrap_null => i += fmtBuf(buffer[i..], "attempt to use null value"), + .cast_to_null => i += fmtBuf(buffer[i..], "cast causes pointer to be null"), + .incorrect_alignment => i += fmtBuf(buffer[i..], "incorrect alignment"), + .invalid_error_code => i += fmtBuf(buffer[i..], "invalid error code"), + .cast_truncated_data => i += fmtBuf(buffer[i..], "integer cast truncated bits"), + .negative_to_unsigned => i += fmtBuf(buffer[i..], "attempt to cast negative value to unsigned integer"), + .integer_overflow => i += fmtBuf(buffer[i..], "integer overflow"), + .shl_overflow => i += fmtBuf(buffer[i..], "left shift overflowed bits"), + .shr_overflow => i += fmtBuf(buffer[i..], "right shift overflowed bits"), + .divide_by_zero => i += fmtBuf(buffer[i..], "division by zero"), + .exact_division_remainder => i += fmtBuf(buffer[i..], "exact division produced remainder"), + .inactive_union_field => |info| { + i += fmtBuf(buffer[i..], "access of union field '"); + i += fmtBuf(buffer[i..], info.accessed); + i += fmtBuf(buffer[i..], "' while field '"); + i += fmtBuf(buffer[i..], info.active); + i += fmtBuf(buffer[i..], "' is active"); + }, + .integer_part_out_of_bounds => i += fmtBuf(buffer[i..], "integer part of floating point value out of bounds"), + .corrupt_switch => i += fmtBuf(buffer[i..], "switch on corrupt value"), + .shift_rhs_too_big => i += fmtBuf(buffer[i..], "shift amount is greater than the type size"), + .invalid_enum_value => i += fmtBuf(buffer[i..], "invalid enum value"), + .sentinel_mismatch_usize => |mm| { + i += fmtBuf(buffer[i..], "sentinel mismatch: expected "); + i += fmtInt10(buffer[i..], mm.expected); + i += fmtBuf(buffer[i..], ", found "); + i += fmtInt10(buffer[i..], mm.found); + }, + .sentinel_mismatch_other => i += fmtBuf(buffer[i..], "sentinel mismatch"), + .unwrap_error => |err| { + i += fmtBuf(buffer[i..], "attempt to unwrap error: "); + i += fmtBuf(buffer[i..], @errorName(err)); + }, + .index_out_of_bounds => |oob| { + i += fmtBuf(buffer[i..], "index "); + i += fmtInt10(buffer[i..], oob.index); + i += fmtBuf(buffer[i..], " exceeds length "); + i += fmtInt10(buffer[i..], oob.len); + }, + .start_index_greater_than_end => |oob| { + i += fmtBuf(buffer[i..], "start index "); + i += fmtInt10(buffer[i..], oob.start); + i += fmtBuf(buffer[i..], " exceeds end index "); + i += fmtInt10(buffer[i..], oob.end); + }, + .for_len_mismatch => i += fmtBuf(buffer[i..], "for loop over objects with non-equal lengths"), + .memcpy_len_mismatch => i += fmtBuf(buffer[i..], "@memcpy arguments have non-equal lengths"), + .memcpy_alias => i += fmtBuf(buffer[i..], "@memcpy arguments alias"), + .noreturn_returned => i += fmtBuf(buffer[i..], "'noreturn' function returned"), + .explicit_call => |msg| i += fmtBuf(buffer[i..], msg), + } + + return i; +} + +fn fmtBuf(out_buf: []u8, s: []const u8) usize { + @memcpy(out_buf[0..s.len], s); + return s.len; +} + +fn fmtInt10(out_buf: []u8, integer_value: usize) usize { + var tmp_buf: [50]u8 = undefined; + var i: usize = tmp_buf.len; + var a: usize = integer_value; + + while (true) { + i -= 1; + tmp_buf[i] = '0' + (a % 10); + a /= 10; + if (a == 0) break; + } + + const result = tmp_buf[i..]; + @memcpy(out_buf[0..result.len], result); + return result.len; +} + /// Must be called only after adding 1 to `panicking`. There are three callsites. fn waitForOtherThreadToFinishPanicking() void { if (panicking.fetchSub(1, .seq_cst) != 1) { @@ -1157,7 +1336,7 @@ pub const default_enable_segfault_handler = runtime_safety and have_segfault_han pub fn maybeEnableSegfaultHandler() void { if (enable_segfault_handler) { - std.debug.attachSegfaultHandler(); + attachSegfaultHandler(); } } @@ -1289,46 +1468,29 @@ fn handleSegfaultWindows(info: *windows.EXCEPTION_POINTERS) callconv(windows.WIN } } -fn handleSegfaultWindowsExtra( - info: *windows.EXCEPTION_POINTERS, - msg: u8, - label: ?[]const u8, -) noreturn { - const exception_address = @intFromPtr(info.ExceptionRecord.ExceptionAddress); - if (windows.CONTEXT != void) { - nosuspend switch (panic_stage) { - 0 => { - panic_stage = 1; - _ = panicking.fetchAdd(1, .seq_cst); - - { - lockStdErr(); - defer unlockStdErr(); - - dumpSegfaultInfoWindows(info, msg, label); - } +fn handleSegfaultWindowsExtra(info: *windows.EXCEPTION_POINTERS, msg: u8, label: ?[]const u8) noreturn { + comptime assert(windows.CONTEXT != void); + nosuspend switch (panic_stage) { + 0 => { + panic_stage = 1; + _ = panicking.fetchAdd(1, .seq_cst); + + { + lockStdErr(); + defer unlockStdErr(); - waitForOtherThreadToFinishPanicking(); - }, - else => { - // panic mutex already locked dumpSegfaultInfoWindows(info, msg, label); - }, - }; - posix.abort(); - } else { - switch (msg) { - 0 => panicImpl(null, exception_address, "{s}", label.?), - 1 => { - const format_item = "Segmentation fault at address 0x{x}"; - var buf: [format_item.len + 64]u8 = undefined; // 64 is arbitrary, but sufficiently large - const to_print = std.fmt.bufPrint(buf[0..buf.len], format_item, .{info.ExceptionRecord.ExceptionInformation[1]}) catch unreachable; - panicImpl(null, exception_address, to_print); - }, - 2 => panicImpl(null, exception_address, "Illegal Instruction"), - else => unreachable, - } - } + } + + waitForOtherThreadToFinishPanicking(); + }, + 1 => { + panic_stage = 2; + io.getStdErr().writeAll("aborting due to recursive panic\n") catch {}; + }, + else => {}, + }; + posix.abort(); } fn dumpSegfaultInfoWindows(info: *windows.EXCEPTION_POINTERS, msg: u8, label: ?[]const u8) void { @@ -1347,7 +1509,7 @@ pub fn dumpStackPointerAddr(prefix: []const u8) void { const sp = asm ("" : [argc] "={rsp}" (-> usize), ); - std.debug.print("{s} sp = 0x{x}\n", .{ prefix, sp }); + print("{s} sp = 0x{x}\n", .{ prefix, sp }); } test "manage resources correctly" { |
