| 1 | const std = @import("std.zig"); |
| 2 | const Io = std.Io; |
| 3 | const Writer = std.Io.Writer; |
| 4 | const math = std.math; |
| 5 | const mem = std.mem; |
| 6 | const posix = std.posix; |
| 7 | const fs = std.fs; |
| 8 | const testing = std.testing; |
| 9 | const Allocator = mem.Allocator; |
| 10 | const File = std.Io.File; |
| 11 | const windows = std.os.windows; |
| 12 | |
| 13 | const builtin = @import("builtin"); |
| 14 | const native_arch = builtin.cpu.arch; |
| 15 | const native_os = builtin.os.tag; |
| 16 | |
| 17 | const root = @import("root"); |
| 18 | |
| 19 | pub const Dwarf = @import("debug/Dwarf.zig"); |
| 20 | pub const Pdb = @import("debug/Pdb.zig"); |
| 21 | pub const ElfFile = @import("debug/ElfFile.zig"); |
| 22 | pub const MachOFile = @import("debug/MachOFile.zig"); |
| 23 | pub const Info = @import("debug/Info.zig"); |
| 24 | pub const Coverage = @import("debug/Coverage.zig"); |
| 25 | pub const cpu_context = @import("debug/cpu_context.zig"); |
| 26 | |
| 27 | /// This type abstracts the target-specific implementation of accessing this process' own debug |
| 28 | /// information behind a generic interface which supports looking up source locations associated |
| 29 | /// with addresses, as well as unwinding the stack where a safe mechanism to do so exists. |
| 30 | /// |
| 31 | /// The Zig Standard Library provides default implementations of `SelfInfo` for common targets, but |
| 32 | /// the implementation can be overriden by exposing `root.debug.SelfInfo`. Setting `SelfInfo` to |
| 33 | /// `void` indicates that the `SelfInfo` API is not supported. |
| 34 | /// |
| 35 | /// This type must expose the following declarations: |
| 36 | /// |
| 37 | /// ``` |
| 38 | /// pub const init: SelfInfo; |
| 39 | /// pub fn deinit(si: *SelfInfo, io: Io) void; |
| 40 | /// |
| 41 | /// /// Appends the symbols for the instruction at `address` to `symbols`. |
| 42 | /// pub fn getSymbols(si: *SelfInfo, io: Io, symbol_allocator: Allocator, text_arena: Allocator, address: usize, include_inline_callers: bool, symbols: *std.ArrayList(Symbol)) SelfInfoError!void; |
| 43 | /// /// Returns a name for the "module" (e.g. shared library or executable image) containing `address`. |
| 44 | /// pub fn getModuleName(si: *SelfInfo, io: Io, address: usize) SelfInfoError![]const u8; |
| 45 | /// pub fn getModuleSlide(si: *SelfInfo, io: Io, address: usize) SelfInfoError!usize; |
| 46 | /// |
| 47 | /// /// Whether a reliable stack unwinding strategy, such as DWARF unwinding, is available. |
| 48 | /// pub const can_unwind: bool; |
| 49 | /// /// Only required if `can_unwind == true`. |
| 50 | /// pub const UnwindContext = struct { |
| 51 | /// /// An address representing the instruction pointer in the last frame. |
| 52 | /// pc: usize, |
| 53 | /// |
| 54 | /// pub fn init(ctx: *cpu_context.Native) Allocator.Error!UnwindContext; |
| 55 | /// pub fn deinit(ctx: *UnwindContext) void; |
| 56 | /// /// Returns the frame pointer associated with the last unwound stack frame. |
| 57 | /// /// If the frame pointer is unknown, 0 may be returned instead. |
| 58 | /// pub fn getFp(uc: *UnwindContext) usize; |
| 59 | /// }; |
| 60 | /// /// Only required if `can_unwind == true`. Unwinds a single stack frame, returning the frame's |
| 61 | /// /// return address, or 0 if the end of the stack has been reached. |
| 62 | /// pub fn unwindFrame(si: *SelfInfo, io: Io, context: *UnwindContext) SelfInfoError!usize; |
| 63 | /// ``` |
| 64 | pub const SelfInfo = if (@hasDecl(root, "debug") and @hasDecl(root.debug, "SelfInfo")) |
| 65 | root.debug.SelfInfo |
| 66 | else |
| 67 | TargetInfo(native_os, native_arch); |
| 68 | |
| 69 | /// Returns the default `SelfInfo` for the given `os` and `arch`. |
| 70 | pub fn TargetInfo(os: std.Target.Os.Tag, arch: std.Target.Cpu.Arch) type { |
| 71 | return switch (std.Target.ObjectFormat.default(os, arch)) { |
| 72 | .coff => if (os == .windows) @import("debug/SelfInfo/Windows.zig") else void, |
| 73 | .elf => switch (os) { |
| 74 | .freestanding, .other => void, |
| 75 | else => @import("debug/SelfInfo/Elf.zig"), |
| 76 | }, |
| 77 | .macho => @import("debug/SelfInfo/MachO.zig"), |
| 78 | .plan9, .spirv, .wasm, .raw, .hex => void, |
| 79 | .c => unreachable, |
| 80 | }; |
| 81 | } |
| 82 | |
| 83 | pub const SelfInfoError = error{ |
| 84 | /// The required debug info is invalid or corrupted. |
| 85 | InvalidDebugInfo, |
| 86 | /// The required debug info could not be found. |
| 87 | MissingDebugInfo, |
| 88 | /// The required debug info was found, and may be valid, but is not supported by this implementation. |
| 89 | UnsupportedDebugInfo, |
| 90 | /// The required debug info could not be read from disk due to some IO error. |
| 91 | ReadFailed, |
| 92 | OutOfMemory, |
| 93 | Canceled, |
| 94 | Unexpected, |
| 95 | }; |
| 96 | |
| 97 | pub const simple_panic = @import("debug/simple_panic.zig"); |
| 98 | pub const no_panic = @import("debug/no_panic.zig"); |
| 99 | |
| 100 | /// A fully-featured panic handler namespace which lowers all panics to calls to `panicFn`. |
| 101 | /// Safety panics will use formatted printing to provide a meaningful error message. |
| 102 | /// The signature of `panicFn` should match that of `defaultPanic`. |
| 103 | pub fn FullPanic(comptime panicFn: fn ([]const u8, ?usize) noreturn) type { |
| 104 | return struct { |
| 105 | pub const call = panicFn; |
| 106 | pub fn sentinelMismatch(expected: anytype, found: @TypeOf(expected)) noreturn { |
| 107 | @branchHint(.cold); |
| 108 | std.debug.panicExtra(@returnAddress(), "sentinel mismatch: expected {any}, found {any}", .{ |
| 109 | expected, found, |
| 110 | }); |
| 111 | } |
| 112 | pub fn unwrapError(err: anyerror) noreturn { |
| 113 | @branchHint(.cold); |
| 114 | std.debug.panicExtra(@returnAddress(), "attempt to unwrap error: {s}", .{@errorName(err)}); |
| 115 | } |
| 116 | pub fn outOfBounds(index: usize, len: usize) noreturn { |
| 117 | @branchHint(.cold); |
| 118 | std.debug.panicExtra(@returnAddress(), "index out of bounds: index {d}, len {d}", .{ index, len }); |
| 119 | } |
| 120 | pub fn startGreaterThanEnd(start: usize, end: usize) noreturn { |
| 121 | @branchHint(.cold); |
| 122 | std.debug.panicExtra(@returnAddress(), "start index {d} is larger than end index {d}", .{ start, end }); |
| 123 | } |
| 124 | pub fn inactiveUnionField(active: anytype, accessed: @TypeOf(active)) noreturn { |
| 125 | @branchHint(.cold); |
| 126 | std.debug.panicExtra(@returnAddress(), "access of union field '{s}' while field '{s}' is active", .{ |
| 127 | @tagName(accessed), @tagName(active), |
| 128 | }); |
| 129 | } |
| 130 | pub fn sliceCastLenRemainder(src_len: usize) noreturn { |
| 131 | @branchHint(.cold); |
| 132 | std.debug.panicExtra(@returnAddress(), "slice length '{d}' does not divide exactly into destination elements", .{src_len}); |
| 133 | } |
| 134 | pub fn reachedUnreachable() noreturn { |
| 135 | @branchHint(.cold); |
| 136 | call("reached unreachable code", @returnAddress()); |
| 137 | } |
| 138 | pub fn unwrapNull() noreturn { |
| 139 | @branchHint(.cold); |
| 140 | call("attempt to use null value", @returnAddress()); |
| 141 | } |
| 142 | pub fn castToNull() noreturn { |
| 143 | @branchHint(.cold); |
| 144 | call("cast causes pointer to be null", @returnAddress()); |
| 145 | } |
| 146 | pub fn incorrectAlignment() noreturn { |
| 147 | @branchHint(.cold); |
| 148 | call("incorrect alignment", @returnAddress()); |
| 149 | } |
| 150 | pub fn invalidErrorCode() noreturn { |
| 151 | @branchHint(.cold); |
| 152 | call("invalid error code", @returnAddress()); |
| 153 | } |
| 154 | pub fn unexpectedErrorCode(err: anyerror) noreturn { |
| 155 | @branchHint(.cold); |
| 156 | std.debug.panicExtra(@returnAddress(), "unexpected error code, found error.{s}", .{@errorName(err)}); |
| 157 | } |
| 158 | pub fn integerOutOfBounds() noreturn { |
| 159 | @branchHint(.cold); |
| 160 | call("integer does not fit in destination type", @returnAddress()); |
| 161 | } |
| 162 | pub fn integerOverflow() noreturn { |
| 163 | @branchHint(.cold); |
| 164 | call("integer overflow", @returnAddress()); |
| 165 | } |
| 166 | pub fn shlOverflow() noreturn { |
| 167 | @branchHint(.cold); |
| 168 | call("left shift overflowed bits", @returnAddress()); |
| 169 | } |
| 170 | pub fn shrOverflow() noreturn { |
| 171 | @branchHint(.cold); |
| 172 | call("right shift overflowed bits", @returnAddress()); |
| 173 | } |
| 174 | pub fn divideByZero() noreturn { |
| 175 | @branchHint(.cold); |
| 176 | call("division by zero", @returnAddress()); |
| 177 | } |
| 178 | pub fn exactDivisionRemainder() noreturn { |
| 179 | @branchHint(.cold); |
| 180 | call("exact division produced remainder", @returnAddress()); |
| 181 | } |
| 182 | pub fn integerPartOutOfBounds() noreturn { |
| 183 | @branchHint(.cold); |
| 184 | call("integer part of floating point value out of bounds", @returnAddress()); |
| 185 | } |
| 186 | pub fn corruptSwitch() noreturn { |
| 187 | @branchHint(.cold); |
| 188 | call("switch on corrupt value", @returnAddress()); |
| 189 | } |
| 190 | pub fn shiftRhsTooBig() noreturn { |
| 191 | @branchHint(.cold); |
| 192 | call("shift amount is greater than the type size", @returnAddress()); |
| 193 | } |
| 194 | pub fn invalidEnumValue() noreturn { |
| 195 | @branchHint(.cold); |
| 196 | call("invalid enum value", @returnAddress()); |
| 197 | } |
| 198 | pub fn forLenMismatch() noreturn { |
| 199 | @branchHint(.cold); |
| 200 | call("for loop over objects with non-equal lengths", @returnAddress()); |
| 201 | } |
| 202 | pub fn copyLenMismatch() noreturn { |
| 203 | @branchHint(.cold); |
| 204 | call("source and destination arguments have non-equal lengths", @returnAddress()); |
| 205 | } |
| 206 | pub fn memcpyAlias() noreturn { |
| 207 | @branchHint(.cold); |
| 208 | call("@memcpy arguments alias", @returnAddress()); |
| 209 | } |
| 210 | pub fn noreturnReturned() noreturn { |
| 211 | @branchHint(.cold); |
| 212 | call("'noreturn' function returned", @returnAddress()); |
| 213 | } |
| 214 | pub fn loadUninstantiableType() noreturn { |
| 215 | @branchHint(.cold); |
| 216 | call("attempt to load uninstantiable type", @returnAddress()); |
| 217 | } |
| 218 | }; |
| 219 | } |
| 220 | |
| 221 | /// Unresolved source locations can be represented with a single `usize` that |
| 222 | /// corresponds to a virtual memory address of the program counter. Combined |
| 223 | /// with debug information, those values can be converted into a resolved |
| 224 | /// source location, including file, line, and column. |
| 225 | pub const SourceLocation = struct { |
| 226 | line: u64, |
| 227 | column: u64, |
| 228 | file_name: []const u8, |
| 229 | |
| 230 | pub const invalid: SourceLocation = .{ |
| 231 | .line = 0, |
| 232 | .column = 0, |
| 233 | .file_name = &.{}, |
| 234 | }; |
| 235 | }; |
| 236 | |
| 237 | pub const Symbol = struct { |
| 238 | name: ?[]const u8, |
| 239 | compile_unit_name: ?[]const u8, |
| 240 | source_location: ?SourceLocation, |
| 241 | pub const unknown: Symbol = .{ |
| 242 | .name = null, |
| 243 | .compile_unit_name = null, |
| 244 | .source_location = null, |
| 245 | }; |
| 246 | }; |
| 247 | |
| 248 | /// Deprecated in favor of `std.lang.Optimize.runtimeSafety`, to be removed after 0.18.0 |
| 249 | /// |
| 250 | /// Returns whether the standard library has safety checks enabled. Callsites |
| 251 | /// likely would rather know whether their own module's optimization mode |
| 252 | /// (found via `@import("builtin").optimize`) has safety checks enabled. |
| 253 | pub const runtime_safety = builtin.mode.runtimeSafety(); |
| 254 | |
| 255 | /// Whether we can unwind the stack on this target, allowing capturing and/or printing the current |
| 256 | /// stack trace. It is still legal to call `captureCurrentStackTrace`, `writeCurrentStackTrace`, and |
| 257 | /// `dumpCurrentStackTrace` if this is `false`; it will just print an error / capture an empty |
| 258 | /// trace due to missing functionality. This value is just intended as a heuristic to avoid |
| 259 | /// pointless work e.g. capturing always-empty stack traces. |
| 260 | pub const sys_can_stack_trace = switch (builtin.cpu.arch) { |
| 261 | // `@returnAddress()` in LLVM 10 gives |
| 262 | // "Non-Emscripten WebAssembly hasn't implemented __builtin_return_address". |
| 263 | // On Emscripten, Zig only supports `@returnAddress()` in debug builds |
| 264 | // because Emscripten's implementation is very slow. |
| 265 | .wasm32, |
| 266 | .wasm64, |
| 267 | => native_os == .emscripten and builtin.mode == .debug, |
| 268 | |
| 269 | // `@returnAddress()` is unsupported in LLVM 21. |
| 270 | .bpfel, |
| 271 | .bpfeb, |
| 272 | => false, |
| 273 | |
| 274 | // https://codeberg.org/ziglang/zig/issues/31127 |
| 275 | .avr => false, |
| 276 | |
| 277 | else => true, |
| 278 | }; |
| 279 | |
| 280 | /// Allows the caller to freely write to stderr until `unlockStderr` is called. |
| 281 | /// |
| 282 | /// During the lock, any `std.Progress` information is cleared from the terminal. |
| 283 | /// |
| 284 | /// The lock is recursive, so it is valid for the same thread to call |
| 285 | /// `lockStderr` multiple times, allowing the panic handler to safely |
| 286 | /// dump the stack trace and panic message even if the mutex was held at the |
| 287 | /// panic site. |
| 288 | /// |
| 289 | /// The returned `Writer` does not need to be manually flushed: flushing is |
| 290 | /// performed automatically when the matching `unlockStderr` call occurs. |
| 291 | /// |
| 292 | /// This is a low-level debugging primitive that bypasses the `Io` interface, |
| 293 | /// writing directly to stderr using the most basic syscalls available. This |
| 294 | /// function does not switch threads, switch stacks, or suspend. |
| 295 | /// |
| 296 | /// Alternatively, use the higher-level `Io.lockStderr` to integrate with the |
| 297 | /// application's chosen `Io` implementation. |
| 298 | pub fn lockStderr(buffer: []u8) Io.LockedStderr { |
| 299 | const io = std.Options.debug_io; |
| 300 | const prev = io.swapCancelProtection(.blocked); |
| 301 | defer _ = io.swapCancelProtection(prev); |
| 302 | return io.lockStderr(buffer, null) catch |err| switch (err) { |
| 303 | error.Canceled => unreachable, // Cancel protection enabled above. |
| 304 | }; |
| 305 | } |
| 306 | |
| 307 | pub fn unlockStderr() void { |
| 308 | const io = std.Options.debug_io; |
| 309 | io.unlockStderr(); |
| 310 | } |
| 311 | |
| 312 | /// Writes to stderr, ignoring errors. |
| 313 | /// |
| 314 | /// This is a low-level debugging primitive that bypasses the `Io` interface, |
| 315 | /// writing directly to stderr using the most basic syscalls available. This |
| 316 | /// function does not switch threads, switch stacks, or suspend. |
| 317 | /// |
| 318 | /// Uses a 64-byte buffer for formatted printing which is flushed before this |
| 319 | /// function returns. |
| 320 | /// |
| 321 | /// Alternatively, use the higher-level `std.log` or `Io.lockStderr` to |
| 322 | /// integrate with the application's chosen `Io` implementation. |
| 323 | pub fn print(comptime fmt: []const u8, args: anytype) void { |
| 324 | const io = std.Options.debug_io; |
| 325 | const prev = io.swapCancelProtection(.blocked); |
| 326 | defer _ = io.swapCancelProtection(prev); |
| 327 | var buffer: [64]u8 = undefined; |
| 328 | const stderr = lockStderr(&buffer); |
| 329 | defer unlockStderr(); |
| 330 | stderr.file_writer.interface.print(fmt, args) catch return; |
| 331 | } |
| 332 | |
| 333 | /// Marked `inline` to propagate a comptime-known error to callers. |
| 334 | pub inline fn getSelfDebugInfo() !*SelfInfo { |
| 335 | if (SelfInfo == void) return error.UnsupportedTarget; |
| 336 | const S = struct { |
| 337 | var self_info: SelfInfo = .init; |
| 338 | }; |
| 339 | return &S.self_info; |
| 340 | } |
| 341 | |
| 342 | /// Tries to print a hexadecimal view of the bytes, unbuffered, and ignores any error returned. |
| 343 | /// Obtains the stderr mutex while dumping. |
| 344 | pub fn dumpHex(bytes: []const u8) void { |
| 345 | const io = std.Options.debug_io; |
| 346 | const prev = io.swapCancelProtection(.blocked); |
| 347 | defer _ = io.swapCancelProtection(prev); |
| 348 | const stderr = lockStderr(&.{}).terminal(); |
| 349 | defer unlockStderr(); |
| 350 | dumpHexFallible(stderr, bytes) catch {}; |
| 351 | } |
| 352 | |
| 353 | /// Prints a hexadecimal view of the bytes, returning any error that occurs. |
| 354 | pub fn dumpHexFallible(t: Io.Terminal, bytes: []const u8) !void { |
| 355 | const w = t.writer; |
| 356 | var chunks = mem.window(u8, bytes, 16, 16); |
| 357 | while (chunks.next()) |window| { |
| 358 | // 1. Print the address. |
| 359 | const address = (@intFromPtr(bytes.ptr) + 0x10 * @divCeil(chunks.index orelse bytes.len, 16) - 0x10); |
| 360 | try t.setColor(.dim); |
| 361 | // We print the address in lowercase and the bytes in uppercase hexadecimal to distinguish them more. |
| 362 | // Also, make sure all lines are aligned by padding the address. |
| 363 | try w.print("{x:0>[1]} ", .{ address, @sizeOf(usize) * 2 }); |
| 364 | try t.setColor(.reset); |
| 365 | |
| 366 | // 2. Print the bytes. |
| 367 | for (window, 0..) |byte, index| { |
| 368 | try w.print("{X:0>2} ", .{byte}); |
| 369 | if (index == 7) try w.writeByte(' '); |
| 370 | } |
| 371 | try w.writeByte(' '); |
| 372 | if (window.len < 16) { |
| 373 | var missing_columns = (16 - window.len) * 3; |
| 374 | if (window.len < 8) missing_columns += 1; |
| 375 | try w.splatByteAll(' ', missing_columns); |
| 376 | } |
| 377 | |
| 378 | // 3. Print the characters. |
| 379 | for (window) |byte| { |
| 380 | if (std.ascii.isPrint(byte)) { |
| 381 | try w.writeByte(byte); |
| 382 | } else { |
| 383 | // Related: https://github.com/ziglang/zig/issues/7600 |
| 384 | if (t.mode == .windows_api) { |
| 385 | try w.writeByte('.'); |
| 386 | continue; |
| 387 | } |
| 388 | |
| 389 | // Let's print some common control codes as graphical Unicode symbols. |
| 390 | // We don't want to do this for all control codes because most control codes apart from |
| 391 | // the ones that Zig has escape sequences for are likely not very useful to print as symbols. |
| 392 | switch (byte) { |
| 393 | '\n' => try w.writeAll("␊"), |
| 394 | '\r' => try w.writeAll("␍"), |
| 395 | '\t' => try w.writeAll("␉"), |
| 396 | else => try w.writeByte('.'), |
| 397 | } |
| 398 | } |
| 399 | } |
| 400 | try w.writeByte('\n'); |
| 401 | } |
| 402 | } |
| 403 | |
| 404 | test dumpHexFallible { |
| 405 | const gpa = testing.allocator; |
| 406 | const bytes: []const u8 = &.{ 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, 0x01, 0x12, 0x13 }; |
| 407 | var aw: Writer.Allocating = .init(gpa); |
| 408 | defer aw.deinit(); |
| 409 | |
| 410 | try dumpHexFallible(.{ .writer = &aw.writer, .mode = .no_color }, bytes); |
| 411 | const expected = try std.fmt.allocPrint(gpa, |
| 412 | \\{x:0>[2]} 00 11 22 33 44 55 66 77 88 99 AA BB CC DD EE FF .."3DUfw........ |
| 413 | \\{x:0>[2]} 01 12 13 ... |
| 414 | \\ |
| 415 | , .{ |
| 416 | @intFromPtr(bytes.ptr), |
| 417 | @intFromPtr(bytes.ptr) + 16, |
| 418 | @sizeOf(usize) * 2, |
| 419 | }); |
| 420 | defer gpa.free(expected); |
| 421 | try testing.expectEqualStrings(expected, aw.written()); |
| 422 | } |
| 423 | |
| 424 | /// The pointer through which a `cpu_context.Native` is received from callers of stack tracing logic. |
| 425 | pub const CpuContextPtr = if (cpu_context.Native == noreturn) noreturn else *const cpu_context.Native; |
| 426 | |
| 427 | /// Invokes detectable illegal behavior when `ok` is `false`. |
| 428 | /// |
| 429 | /// In debug and safe modes, calls to this function are always |
| 430 | /// generated, and the `unreachable` statement triggers a panic. |
| 431 | /// |
| 432 | /// In fast and small modes, calls to this function are optimized |
| 433 | /// away, and in fact the optimizer is able to use the assertion in its |
| 434 | /// heuristics. |
| 435 | /// |
| 436 | /// Inside a test block, it is best to use the `testing` module rather than |
| 437 | /// this function, because this function may not detect a test failure in |
| 438 | /// fast and small mode. Outside of a test block, this assert |
| 439 | /// function is the correct function to use. |
| 440 | pub fn assert(ok: bool) void { |
| 441 | @disableInstrumentation(); |
| 442 | if (!ok) unreachable; // assertion failure |
| 443 | } |
| 444 | |
| 445 | /// Invokes detectable illegal behavior when the provided slice is not mapped |
| 446 | /// or lacks read permissions. |
| 447 | pub fn assertReadable(slice: []const volatile u8) void { |
| 448 | if (!runtime_safety) return; |
| 449 | for (slice) |*byte| _ = byte.*; |
| 450 | } |
| 451 | |
| 452 | /// Invokes detectable illegal behavior when the provided array is not aligned |
| 453 | /// to the provided amount. |
| 454 | pub fn assertAligned(ptr: anytype, comptime alignment: std.mem.Alignment) void { |
| 455 | const aligned_ptr: *align(alignment.toByteUnits()) const anyopaque = @ptrCast(@alignCast(ptr)); |
| 456 | _ = aligned_ptr; |
| 457 | } |
| 458 | |
| 459 | /// Equivalent to `@panic` but with a formatted message. |
| 460 | pub fn panic(comptime format: []const u8, args: anytype) noreturn { |
| 461 | @branchHint(.cold); |
| 462 | panicExtra(@returnAddress(), format, args); |
| 463 | } |
| 464 | |
| 465 | /// Equivalent to `@panic` but with a formatted message and an explicitly provided return address |
| 466 | /// which will be the first address in the stack trace. |
| 467 | pub fn panicExtra( |
| 468 | ret_addr: ?usize, |
| 469 | comptime format: []const u8, |
| 470 | args: anytype, |
| 471 | ) noreturn { |
| 472 | @branchHint(.cold); |
| 473 | |
| 474 | const size = 0x1000; |
| 475 | const trunc_msg = "(msg truncated)"; |
| 476 | var buf: [size + trunc_msg.len]u8 = undefined; |
| 477 | var bw: Writer = .fixed(buf[0..size]); |
| 478 | // a minor annoyance with this is that it will result in the NoSpaceLeft |
| 479 | // error being part of the @panic stack trace (but that error should |
| 480 | // only happen rarely) |
| 481 | const msg = if (bw.print(format, args)) |_| bw.buffered() else |_| blk: { |
| 482 | @memcpy(buf[size..], trunc_msg); |
| 483 | break :blk &buf; |
| 484 | }; |
| 485 | std.builtin.panic.call(msg, ret_addr); |
| 486 | } |
| 487 | |
| 488 | /// Non-zero whenever the program triggered a panic. |
| 489 | /// The counter is incremented/decremented atomically. |
| 490 | var panicking = std.atomic.Value(u8).init(0); |
| 491 | |
| 492 | /// Counts how many times the panic handler is invoked by this thread. |
| 493 | /// This is used to catch and handle panics triggered by the panic handler. |
| 494 | threadlocal var panic_stage: usize = 0; |
| 495 | |
| 496 | /// For backends that cannot handle the language features depended on by the |
| 497 | /// default panic handler, we will use a simpler implementation. |
| 498 | const use_trap_panic = switch (builtin.zig_backend) { |
| 499 | .stage2_aarch64, |
| 500 | .stage2_arm, |
| 501 | .stage2_loongarch, |
| 502 | .stage2_powerpc, |
| 503 | .stage2_riscv64, |
| 504 | .stage2_spirv, |
| 505 | .stage2_x86, |
| 506 | => true, |
| 507 | else => false, |
| 508 | }; |
| 509 | |
| 510 | /// Dumps a stack trace to standard error, then aborts. |
| 511 | pub fn defaultPanic(msg: []const u8, first_trace_addr: ?usize) noreturn { |
| 512 | @branchHint(.cold); |
| 513 | |
| 514 | if (use_trap_panic) @trap(); |
| 515 | |
| 516 | switch (builtin.os.tag) { |
| 517 | .freestanding, |
| 518 | .other, |
| 519 | |
| 520 | .@"3ds", |
| 521 | .wiiu, |
| 522 | .@"switch", |
| 523 | .gba, |
| 524 | |
| 525 | .psx, |
| 526 | .psp, |
| 527 | .vita, |
| 528 | => { |
| 529 | @trap(); |
| 530 | }, |
| 531 | .uefi => { |
| 532 | const uefi = std.os.uefi; |
| 533 | |
| 534 | var utf16_buffer: [1000]u16 = undefined; |
| 535 | const len_minus_3 = std.unicode.utf8ToUtf16Le(&utf16_buffer, msg) catch 0; |
| 536 | utf16_buffer[len_minus_3..][0..3].* = .{ '\r', '\n', 0 }; |
| 537 | const len = len_minus_3 + 3; |
| 538 | const exit_msg = utf16_buffer[0 .. len - 1 :0]; |
| 539 | |
| 540 | // Output to both std_err and con_out, as std_err is easier |
| 541 | // to read in stuff like QEMU at times, but, unlike con_out, |
| 542 | // isn't visible on actual hardware if directly booted into |
| 543 | inline for ([_]?*uefi.protocol.SimpleTextOutput{ uefi.system_table.std_err, uefi.system_table.con_out }) |o| { |
| 544 | if (o) |out| { |
| 545 | out.setAttribute(.{ .foreground = .red }) catch {}; |
| 546 | _ = out.outputString(exit_msg) catch {}; |
| 547 | out.setAttribute(.{ .foreground = .white }) catch {}; |
| 548 | } |
| 549 | } |
| 550 | |
| 551 | if (uefi.system_table.boot_services) |bs| { |
| 552 | // ExitData buffer must be allocated using boot_services.allocatePool (spec: page 220) |
| 553 | const exit_data = uefi.raw_pool_allocator.dupeSentinel(u16, exit_msg, 0) catch @trap(); |
| 554 | bs.exit(uefi.handle, .aborted, exit_data) catch {}; |
| 555 | } |
| 556 | @trap(); |
| 557 | }, |
| 558 | .cuda, .amdhsa => std.process.abort(), |
| 559 | .plan9 => { |
| 560 | var status: [std.os.plan9.ERRMAX]u8 = undefined; |
| 561 | const len = @min(msg.len, status.len - 1); |
| 562 | @memcpy(status[0..len], msg[0..len]); |
| 563 | status[len] = 0; |
| 564 | std.os.plan9.exits(status[0..len :0]); |
| 565 | }, |
| 566 | else => {}, |
| 567 | } |
| 568 | |
| 569 | std.Options.debug_io.vtable.crashHandler(std.Options.debug_io.userdata); |
| 570 | |
| 571 | if (enable_segfault_handler) { |
| 572 | // If a segfault happens while panicking, we want it to actually segfault, not trigger |
| 573 | // the handler. |
| 574 | resetSegfaultHandler(); |
| 575 | } |
| 576 | |
| 577 | // There is very similar logic to the following in `handleSegfault`. |
| 578 | switch (panic_stage) { |
| 579 | 0 => { |
| 580 | panic_stage = 1; |
| 581 | _ = panicking.fetchAdd(1, .seq_cst); |
| 582 | |
| 583 | trace: { |
| 584 | const stderr = lockStderr(&.{}).terminal(); |
| 585 | defer unlockStderr(); |
| 586 | const writer = stderr.writer; |
| 587 | |
| 588 | if (builtin.single_threaded) { |
| 589 | writer.print("panic: ", .{}) catch break :trace; |
| 590 | } else { |
| 591 | const current_thread_id = std.Thread.getCurrentId(); |
| 592 | writer.print("thread {d} panic: ", .{current_thread_id}) catch break :trace; |
| 593 | } |
| 594 | writer.print("{s}\n", .{msg}) catch break :trace; |
| 595 | |
| 596 | if (@errorReturnTrace()) |t| if (t.index > 0) { |
| 597 | writer.writeAll("error return context:\n") catch break :trace; |
| 598 | writeErrorReturnTrace(t, stderr) catch break :trace; |
| 599 | writer.writeAll("\nstack trace:\n") catch break :trace; |
| 600 | }; |
| 601 | writeCurrentStackTrace(.{ |
| 602 | .first_address = first_trace_addr orelse @returnAddress(), |
| 603 | .allow_unsafe_unwind = true, // we're crashing anyway, give it our all! |
| 604 | }, stderr) catch break :trace; |
| 605 | } |
| 606 | |
| 607 | waitForOtherThreadToFinishPanicking(); |
| 608 | }, |
| 609 | 1 => { |
| 610 | panic_stage = 2; |
| 611 | // A panic happened while trying to print a previous panic message. |
| 612 | // We're still holding the mutex but that's fine as we're going to |
| 613 | // call abort(). |
| 614 | const stderr = lockStderr(&.{}).terminal(); |
| 615 | stderr.writer.writeAll("aborting due to recursive panic\n") catch {}; |
| 616 | }, |
| 617 | else => {}, // Panicked while printing the recursive panic message. |
| 618 | } |
| 619 | |
| 620 | std.process.abort(); |
| 621 | } |
| 622 | |
| 623 | /// Must be called only after adding 1 to `panicking`. There are three callsites. |
| 624 | fn waitForOtherThreadToFinishPanicking() void { |
| 625 | if (panicking.fetchSub(1, .seq_cst) != 1) { |
| 626 | // Another thread is panicking, wait for the last one to finish |
| 627 | // and call abort() |
| 628 | if (builtin.single_threaded) unreachable; |
| 629 | |
| 630 | // Sleep forever without hammering the CPU |
| 631 | var futex: u32 = 0; |
| 632 | while (true) std.Options.debug_io.futexWaitUncancelable(u32, &futex, 0); |
| 633 | unreachable; |
| 634 | } |
| 635 | } |
| 636 | |
| 637 | pub const StackTrace = struct { |
| 638 | /// Each element is the "return address" of a function call, meaning the instruction address |
| 639 | /// which control flow will return to when the function returns. |
| 640 | /// |
| 641 | /// The first slice element corresponds to the innermost stack frame, and the last element to |
| 642 | /// the outermost. |
| 643 | /// |
| 644 | /// Inlined function calls do not have meaningful return addresses and are therefore not |
| 645 | /// included in this slice. Instead, when printing the stack trace, the source locations of |
| 646 | /// inline calls should be read from debug information and the corresponding "inline frames" |
| 647 | /// printed in the appropriate locations. |
| 648 | return_addresses: []usize, |
| 649 | /// Indicates whether any stack frames were omitted from `return_addresses`. |
| 650 | skipped: SkippedAddresses, |
| 651 | }; |
| 652 | |
| 653 | /// Indicates how many addresses were skipped in a trace. |
| 654 | pub const SkippedAddresses = enum(usize) { |
| 655 | /// No addresses were omitted: `return_addresses` contains all stack frames, including the |
| 656 | /// outermost. |
| 657 | none = 0, |
| 658 | /// It is not known whether any frames were omitted. |
| 659 | unknown = std.math.maxInt(usize), |
| 660 | /// The full stack trace was available, but some frames are not included in |
| 661 | /// `return_addresses` due to buffer size limitations. The enum value is the exact number of |
| 662 | /// addresses which were omitted. |
| 663 | _, |
| 664 | }; |
| 665 | |
| 666 | pub const StackUnwindOptions = struct { |
| 667 | /// If not `null`, we will ignore all frames up until this return address. This is typically |
| 668 | /// used to omit intermediate handling code (for instance, a panic handler and its machinery) |
| 669 | /// from stack traces. |
| 670 | first_address: ?usize = null, |
| 671 | /// If not `null`, we will unwind from this `cpu_context.Native` instead of the current top of |
| 672 | /// the stack. The main use case here is printing stack traces from signal handlers, where the |
| 673 | /// kernel provides a `*const cpu_context.Native` of the state before the signal. |
| 674 | context: ?CpuContextPtr = null, |
| 675 | /// If `true`, stack unwinding strategies which may cause crashes are used as a last resort. |
| 676 | /// If `false`, only known-safe mechanisms will be attempted. |
| 677 | allow_unsafe_unwind: bool = false, |
| 678 | }; |
| 679 | |
| 680 | /// Capture and return the current stack trace. The returned `StackTrace` stores its addresses in |
| 681 | /// the given buffer, so `addr_buf` must have a lifetime at least equal to the `StackTrace`. |
| 682 | /// |
| 683 | /// See `writeCurrentStackTrace` to immediately print the trace instead of capturing it. |
| 684 | pub noinline fn captureCurrentStackTrace(options: StackUnwindOptions, addr_buf: []usize) StackTrace { |
| 685 | const empty_trace: StackTrace = .{ |
| 686 | .return_addresses = &.{}, |
| 687 | .skipped = .none, |
| 688 | }; |
| 689 | if (!std.options.allow_stack_tracing) return empty_trace; |
| 690 | var it: StackIterator = .init(options.context); |
| 691 | defer it.deinit(); |
| 692 | if (!it.stratOk(options.allow_unsafe_unwind)) return empty_trace; |
| 693 | |
| 694 | const io = std.Options.debug_io; |
| 695 | |
| 696 | var total_frames: usize = 0; |
| 697 | var index: usize = 0; |
| 698 | var wait_for = options.first_address; |
| 699 | // Ideally, we would iterate the whole stack so that the `index - min(buf.len, index)` would be |
| 700 | // indicative of how many frames were skipped. However, this has a significant runtime cost |
| 701 | // in some cases, so at least for now, we don't do that. |
| 702 | const skipped: SkippedAddresses = while (index < addr_buf.len) switch (it.next(io)) { |
| 703 | .switch_to_fp => if (!it.stratOk(options.allow_unsafe_unwind)) break .unknown, |
| 704 | .end => break .none, |
| 705 | .frame => |ret_addr| { |
| 706 | if (total_frames > 10_000) { |
| 707 | // Limit the number of frames in case of (e.g.) broken debug information which is |
| 708 | // getting unwinding stuck in a loop. |
| 709 | break .unknown; |
| 710 | } |
| 711 | total_frames += 1; |
| 712 | if (wait_for) |target| { |
| 713 | if (ret_addr != target) continue; |
| 714 | wait_for = null; |
| 715 | } |
| 716 | addr_buf[index] = ret_addr; |
| 717 | index += 1; |
| 718 | }, |
| 719 | } else .unknown; |
| 720 | return .{ |
| 721 | .return_addresses = addr_buf[0..index], |
| 722 | .skipped = skipped, |
| 723 | }; |
| 724 | } |
| 725 | /// Write the current stack trace to `writer`, annotated with source locations. |
| 726 | /// |
| 727 | /// See `captureCurrentStackTrace` to capture the trace addresses into a buffer instead of printing. |
| 728 | pub noinline fn writeCurrentStackTrace(options: StackUnwindOptions, t: Io.Terminal) Writer.Error!void { |
| 729 | const writer = t.writer; |
| 730 | |
| 731 | var text_arena: std.heap.ArenaAllocator = .init(getDebugInfoAllocator()); |
| 732 | defer text_arena.deinit(); |
| 733 | |
| 734 | if (!std.options.allow_stack_tracing) { |
| 735 | t.setColor(.dim) catch {}; |
| 736 | try writer.print("Cannot print stack trace: stack tracing is disabled\n", .{}); |
| 737 | t.setColor(.reset) catch {}; |
| 738 | return; |
| 739 | } |
| 740 | const di = getSelfDebugInfo() catch |err| switch (err) { |
| 741 | error.UnsupportedTarget => { |
| 742 | t.setColor(.dim) catch {}; |
| 743 | try writer.print("Cannot print stack trace: debug info unavailable for target\n", .{}); |
| 744 | t.setColor(.reset) catch {}; |
| 745 | return; |
| 746 | }, |
| 747 | }; |
| 748 | var it: StackIterator = .init(options.context); |
| 749 | defer it.deinit(); |
| 750 | if (!it.stratOk(options.allow_unsafe_unwind)) { |
| 751 | t.setColor(.dim) catch {}; |
| 752 | try writer.print("Cannot print stack trace: safe unwind unavailable for target\n", .{}); |
| 753 | t.setColor(.reset) catch {}; |
| 754 | return; |
| 755 | } |
| 756 | var total_frames: usize = 0; |
| 757 | var wait_for = options.first_address; |
| 758 | var printed_any_frame = false; |
| 759 | const io = std.Options.debug_io; |
| 760 | while (true) switch (it.next(io)) { |
| 761 | .switch_to_fp => |unwind_error| { |
| 762 | switch (StackIterator.fp_usability) { |
| 763 | .useless, .unsafe => {}, |
| 764 | .safe, .ideal => continue, // no need to even warn |
| 765 | } |
| 766 | const module_name = di.getModuleName(io, unwind_error.address) catch "???"; |
| 767 | const caption: []const u8 = switch (unwind_error.err) { |
| 768 | error.MissingDebugInfo => "unwind info unavailable", |
| 769 | error.InvalidDebugInfo => "unwind info invalid", |
| 770 | error.UnsupportedDebugInfo => "unwind info unsupported", |
| 771 | error.ReadFailed => "filesystem error", |
| 772 | error.OutOfMemory => "out of memory", |
| 773 | error.Canceled => "operation canceled", |
| 774 | error.Unexpected => "unexpected error", |
| 775 | }; |
| 776 | if (it.stratOk(options.allow_unsafe_unwind)) { |
| 777 | t.setColor(.dim) catch {}; |
| 778 | try writer.print( |
| 779 | "Unwind error at address `{s}:0x{x}` ({s}), remaining frames may be incorrect\n", |
| 780 | .{ module_name, unwind_error.address, caption }, |
| 781 | ); |
| 782 | t.setColor(.reset) catch {}; |
| 783 | } else { |
| 784 | t.setColor(.dim) catch {}; |
| 785 | try writer.print( |
| 786 | "Unwind error at address `{s}:0x{x}` ({s}), stopping trace early\n", |
| 787 | .{ module_name, unwind_error.address, caption }, |
| 788 | ); |
| 789 | t.setColor(.reset) catch {}; |
| 790 | return; |
| 791 | } |
| 792 | }, |
| 793 | .end => break, |
| 794 | .frame => |ret_addr| { |
| 795 | if (total_frames > 10_000) { |
| 796 | t.setColor(.dim) catch {}; |
| 797 | try writer.print( |
| 798 | "Stopping trace after {d} frames (large frame count may indicate broken debug info)\n", |
| 799 | .{total_frames}, |
| 800 | ); |
| 801 | t.setColor(.reset) catch {}; |
| 802 | return; |
| 803 | } |
| 804 | total_frames += 1; |
| 805 | if (wait_for) |target| { |
| 806 | if (ret_addr != target) continue; |
| 807 | wait_for = null; |
| 808 | } |
| 809 | // `ret_addr` is the return address, which is *after* the function call. |
| 810 | // Subtract 1 to get an address *in* the function call for a better source location. |
| 811 | try printSourceAtAddress(io, &text_arena, di, t, .{ |
| 812 | .address = ret_addr -| StackIterator.ra_call_offset, |
| 813 | .resolve_inline_callers = true, |
| 814 | }); |
| 815 | printed_any_frame = true; |
| 816 | }, |
| 817 | }; |
| 818 | if (!printed_any_frame) return writer.writeAll("(empty stack trace)\n"); |
| 819 | } |
| 820 | /// A thin wrapper around `writeCurrentStackTrace` which writes to stderr and ignores write errors. |
| 821 | pub fn dumpCurrentStackTrace(options: StackUnwindOptions) void { |
| 822 | const io = std.Options.debug_io; |
| 823 | const prev = io.swapCancelProtection(.blocked); |
| 824 | defer _ = io.swapCancelProtection(prev); |
| 825 | const stderr = lockStderr(&.{}).terminal(); |
| 826 | defer unlockStderr(); |
| 827 | writeCurrentStackTrace(.{ |
| 828 | .first_address = a: { |
| 829 | if (options.first_address) |a| break :a a; |
| 830 | if (options.context != null) break :a null; |
| 831 | break :a @returnAddress(); // don't include this frame in the trace |
| 832 | }, |
| 833 | .context = options.context, |
| 834 | .allow_unsafe_unwind = options.allow_unsafe_unwind, |
| 835 | }, stderr) catch |err| switch (err) { |
| 836 | error.WriteFailed => {}, |
| 837 | }; |
| 838 | } |
| 839 | |
| 840 | pub const FormatStackTrace = struct { |
| 841 | stack_trace: StackTrace, |
| 842 | terminal_mode: Io.Terminal.Mode = .no_color, |
| 843 | |
| 844 | pub fn format(fst: FormatStackTrace, writer: *Writer) Writer.Error!void { |
| 845 | try writer.writeByte('\n'); |
| 846 | try writeStackTrace(&fst.stack_trace, .{ .writer = writer, .mode = fst.terminal_mode }); |
| 847 | } |
| 848 | }; |
| 849 | |
| 850 | /// Write a previously captured error return trace to `writer`, annotated with source locations. |
| 851 | pub fn writeErrorReturnTrace(et: *const std.builtin.StackTrace, t: Io.Terminal) Writer.Error!void { |
| 852 | // We take the slice by value, preventing the length from being mutated if an error occurs while |
| 853 | // writing the stack trace. |
| 854 | const len = @min(et.instruction_addresses.len, et.index); |
| 855 | const skipped = et.index - len; |
| 856 | try writeTrace(et.instruction_addresses[0..len], @fromBackingInt(@intCast(skipped)), t, false); |
| 857 | } |
| 858 | |
| 859 | /// Write a previously captured stack trace to `writer`, annotated with source locations. |
| 860 | pub fn writeStackTrace(st: *const StackTrace, t: Io.Terminal) Writer.Error!void { |
| 861 | try writeTrace(st.return_addresses, st.skipped, t, true); |
| 862 | } |
| 863 | |
| 864 | fn writeTrace( |
| 865 | addresses: []const usize, |
| 866 | skipped: SkippedAddresses, |
| 867 | t: Io.Terminal, |
| 868 | resolve_inline_callers: bool, |
| 869 | ) Writer.Error!void { |
| 870 | var text_arena: std.heap.ArenaAllocator = .init(getDebugInfoAllocator()); |
| 871 | defer text_arena.deinit(); |
| 872 | |
| 873 | const writer = t.writer; |
| 874 | if (!std.options.allow_stack_tracing) { |
| 875 | t.setColor(.dim) catch {}; |
| 876 | try writer.print("Cannot print stack trace: stack tracing is disabled\n", .{}); |
| 877 | t.setColor(.reset) catch {}; |
| 878 | return; |
| 879 | } |
| 880 | |
| 881 | if (addresses.len == 0) return writer.writeAll("(empty stack trace)\n"); |
| 882 | const di = getSelfDebugInfo() catch |err| switch (err) { |
| 883 | error.UnsupportedTarget => { |
| 884 | t.setColor(.dim) catch {}; |
| 885 | try writer.print("Cannot print stack trace: debug info unavailable for target\n\n", .{}); |
| 886 | t.setColor(.reset) catch {}; |
| 887 | return; |
| 888 | }, |
| 889 | }; |
| 890 | const io = std.Options.debug_io; |
| 891 | for (addresses) |addr| { |
| 892 | // `addr` is the return address, which is *after* the function call. |
| 893 | // Subtract 1 to get an address *in* the function call for a better source location. |
| 894 | try printSourceAtAddress(io, &text_arena, di, t, .{ |
| 895 | .address = addr -| StackIterator.ra_call_offset, |
| 896 | .resolve_inline_callers = resolve_inline_callers, |
| 897 | }); |
| 898 | } |
| 899 | switch (skipped) { |
| 900 | .none => {}, |
| 901 | .unknown => { |
| 902 | t.setColor(.bold) catch {}; |
| 903 | try writer.writeAll("(additional stack frames may have been skipped...)\n"); |
| 904 | t.setColor(.reset) catch {}; |
| 905 | }, |
| 906 | else => |n| { |
| 907 | t.setColor(.bold) catch {}; |
| 908 | try writer.print("({d} additional stack frames skipped due to buffer size limitations...)\n", .{n}); |
| 909 | t.setColor(.reset) catch {}; |
| 910 | }, |
| 911 | } |
| 912 | } |
| 913 | /// A thin wrapper around `writeStackTrace` which writes to stderr and ignores write errors. |
| 914 | pub fn dumpStackTrace(st: *const StackTrace) void { |
| 915 | const io = std.Options.debug_io; |
| 916 | const prev = io.swapCancelProtection(.blocked); |
| 917 | defer _ = io.swapCancelProtection(prev); |
| 918 | const stderr = lockStderr(&.{}).terminal(); |
| 919 | defer unlockStderr(); |
| 920 | writeStackTrace(st, stderr) catch |err| switch (err) { |
| 921 | error.WriteFailed => {}, |
| 922 | }; |
| 923 | } |
| 924 | |
| 925 | /// A thin wrapper around `writeErrorReturnTrace` which writes to stderr and ignores write errors. |
| 926 | pub fn dumpErrorReturnTrace(et: *const std.builtin.StackTrace) void { |
| 927 | const io = std.Options.debug_io; |
| 928 | const prev = io.swapCancelProtection(.blocked); |
| 929 | defer _ = io.swapCancelProtection(prev); |
| 930 | const stderr = lockStderr(&.{}).terminal(); |
| 931 | defer unlockStderr(); |
| 932 | writeErrorReturnTrace(et, stderr) catch |err| switch (err) { |
| 933 | error.WriteFailed => {}, |
| 934 | }; |
| 935 | } |
| 936 | |
| 937 | const StackIterator = union(enum) { |
| 938 | /// We will first report the current PC of this `CpuContextPtr`, then we will switch to a |
| 939 | /// different strategy to actually unwind. |
| 940 | ctx_first: CpuContextPtr, |
| 941 | /// Unwinding using debug info (e.g. DWARF CFI). |
| 942 | di: if (SelfInfo != void and SelfInfo.can_unwind and fp_usability != .ideal) |
| 943 | SelfInfo.UnwindContext |
| 944 | else |
| 945 | noreturn, |
| 946 | /// Naive frame-pointer-based unwinding. Very simple, but typically unreliable. |
| 947 | fp: usize, |
| 948 | |
| 949 | /// It is important that this function is marked `inline` so that it can safely use |
| 950 | /// `@frameAddress` and `cpu_context.Native.current` as the caller's stack frame and |
| 951 | /// our own are one and the same. |
| 952 | /// |
| 953 | /// `opt_context_ptr` must remain valid while the `StackIterator` is used. |
| 954 | inline fn init(opt_context_ptr: ?CpuContextPtr) StackIterator { |
| 955 | if (opt_context_ptr) |context_ptr| { |
| 956 | // Use `ctx_first` here so we report the PC in the context before unwinding any further. |
| 957 | return .{ .ctx_first = context_ptr }; |
| 958 | } |
| 959 | |
| 960 | // Otherwise, we're going to capture the current context or frame address, so we don't need |
| 961 | // `ctx_first`, because the first PC is in `std.debug` and we need to unwind before reaching |
| 962 | // a frame we want to report. |
| 963 | |
| 964 | // Workaround the C backend being unable to use inline assembly on MSVC by disabling the |
| 965 | // call to `current`. This effectively constrains stack trace collection and dumping to FP |
| 966 | // unwinding when building with CBE for MSVC. |
| 967 | if (!(builtin.zig_backend == .stage2_c and builtin.target.abi == .msvc) and |
| 968 | SelfInfo != void and |
| 969 | SelfInfo.can_unwind and |
| 970 | cpu_context.Native != noreturn and |
| 971 | fp_usability != .ideal) |
| 972 | { |
| 973 | return .{ .di = .init(&.current()) }; |
| 974 | } |
| 975 | return .{ |
| 976 | // On SPARC, the frame pointer will point to the previous frame's save area, |
| 977 | // meaning we will read the previous return address and thus miss a frame. |
| 978 | // Instead, start at the stack pointer so we get the return address from the |
| 979 | // current frame's save area. The addition of the stack bias cannot fail here |
| 980 | // since we know we have a valid stack pointer. |
| 981 | .fp = if (native_arch.isSPARC()) sp: { |
| 982 | flushSparcWindows(); |
| 983 | break :sp asm ("" |
| 984 | : [_] "={o6}" (-> usize), |
| 985 | ) + stack_bias; |
| 986 | } else @frameAddress(), |
| 987 | }; |
| 988 | } |
| 989 | fn deinit(si: *StackIterator) void { |
| 990 | switch (si.*) { |
| 991 | .ctx_first => {}, |
| 992 | .fp => {}, |
| 993 | .di => |*unwind_context| unwind_context.deinit(), |
| 994 | } |
| 995 | } |
| 996 | |
| 997 | noinline fn flushSparcWindows() void { |
| 998 | // Flush all register windows except the current one (hence `noinline`). This ensures that |
| 999 | // we actually see meaningful data on the stack when we walk the frame chain. |
| 1000 | if (comptime builtin.target.cpu.has(.sparc, .v9)) |
| 1001 | asm volatile ("flushw" ::: .{ .memory = true }) |
| 1002 | else |
| 1003 | asm volatile ("ta 3" ::: .{ .memory = true }); // ST_FLUSH_WINDOWS |
| 1004 | } |
| 1005 | |
| 1006 | const FpUsability = enum { |
| 1007 | /// FP unwinding is impractical on this target. For example, due to its very silly ABI |
| 1008 | /// design decisions, it's not possible to do generic FP unwinding on MIPS without a |
| 1009 | /// complicated code scanning algorithm. |
| 1010 | useless, |
| 1011 | /// FP unwinding is unsafe on this target; we may crash when doing so. We will only perform |
| 1012 | /// FP unwinding in the case of crashes/panics, or if the user opts in. |
| 1013 | unsafe, |
| 1014 | /// FP unwinding is guaranteed to be safe on this target. We will do so if unwinding with |
| 1015 | /// debug info does not work, and if this compilation has frame pointers enabled. |
| 1016 | safe, |
| 1017 | /// FP unwinding is the best option on this target. This is usually because the ABI requires |
| 1018 | /// a backchain pointer, thus making it always available, safe, and fast. |
| 1019 | ideal, |
| 1020 | }; |
| 1021 | |
| 1022 | const fp_usability: FpUsability = switch (builtin.target.cpu.arch) { |
| 1023 | .alpha, |
| 1024 | .avr, |
| 1025 | .csky, |
| 1026 | .microblaze, |
| 1027 | .microblazeel, |
| 1028 | .mips, |
| 1029 | .mipsel, |
| 1030 | .mips64, |
| 1031 | .mips64el, |
| 1032 | .msp430, |
| 1033 | .sh, |
| 1034 | .sheb, |
| 1035 | .xcore, |
| 1036 | .xtensa, |
| 1037 | .xtensaeb, |
| 1038 | => .useless, |
| 1039 | .hexagon, |
| 1040 | // The PowerPC ABIs don't actually strictly require a backchain pointer; they allow omitting |
| 1041 | // it when full unwind info is present. Despite this, both GCC and Clang always enforce the |
| 1042 | // presence of the backchain pointer no matter what options they are given. This seems to be |
| 1043 | // a case of "the spec is only a polite suggestion", except it works in our favor this time! |
| 1044 | .powerpc, |
| 1045 | .powerpcle, |
| 1046 | .powerpc64, |
| 1047 | .powerpc64le, |
| 1048 | .sparc, |
| 1049 | .sparc64, |
| 1050 | => .ideal, |
| 1051 | // https://developer.apple.com/documentation/xcode/writing-arm64-code-for-apple-platforms#Respect-the-purpose-of-specific-CPU-registers |
| 1052 | .aarch64 => if (builtin.target.os.tag.isDarwin()) .safe else .unsafe, |
| 1053 | else => .unsafe, |
| 1054 | }; |
| 1055 | |
| 1056 | /// Whether the current unwind strategy is allowed given `allow_unsafe`. |
| 1057 | fn stratOk(it: *const StackIterator, allow_unsafe: bool) bool { |
| 1058 | return switch (it.*) { |
| 1059 | .ctx_first, .di => true, |
| 1060 | // If we omitted frame pointers from *this* compilation, FP unwinding would crash |
| 1061 | // immediately regardless of anything. But FPs could also be omitted from a different |
| 1062 | // linked object, so it's not guaranteed to be safe, unless the target specifically |
| 1063 | // requires it. |
| 1064 | .fp => switch (fp_usability) { |
| 1065 | .useless => false, |
| 1066 | .unsafe => allow_unsafe and !builtin.omit_frame_pointer, |
| 1067 | .safe => !builtin.omit_frame_pointer, |
| 1068 | .ideal => true, |
| 1069 | }, |
| 1070 | }; |
| 1071 | } |
| 1072 | |
| 1073 | const Result = union(enum) { |
| 1074 | /// A stack frame has been found; this is the corresponding return address. |
| 1075 | frame: usize, |
| 1076 | /// The end of the stack has been reached. |
| 1077 | end, |
| 1078 | /// We were using `SelfInfo.UnwindInfo`, but are now switching to FP unwinding due to this error. |
| 1079 | switch_to_fp: struct { |
| 1080 | address: usize, |
| 1081 | err: SelfInfoError, |
| 1082 | }, |
| 1083 | }; |
| 1084 | |
| 1085 | fn next(it: *StackIterator, io: Io) Result { |
| 1086 | switch (it.*) { |
| 1087 | .ctx_first => |context_ptr| { |
| 1088 | // After the first frame, start actually unwinding. |
| 1089 | if (SelfInfo != void and SelfInfo.can_unwind and fp_usability != .ideal) { |
| 1090 | it.* = .{ .di = .init(context_ptr) }; |
| 1091 | } else { |
| 1092 | const fp = applyOffset(context_ptr.getFp(), stack_bias) orelse return .end; |
| 1093 | it.* = .{ .fp = fp }; |
| 1094 | } |
| 1095 | |
| 1096 | // The caller expects *return* addresses, where they will subtract 1 to find the address of the call. |
| 1097 | // However, we have the actual current PC, which should not be adjusted. Compensate by adding 1. |
| 1098 | return .{ .frame = context_ptr.getPc() +| 1 }; |
| 1099 | }, |
| 1100 | .di => |*unwind_context| { |
| 1101 | const di = getSelfDebugInfo() catch unreachable; |
| 1102 | const ret_addr = di.unwindFrame(io, unwind_context) catch |err| { |
| 1103 | const pc = unwind_context.pc; |
| 1104 | const fp = applyOffset(unwind_context.getFp(), stack_bias) orelse return .end; |
| 1105 | unwind_context.deinit(); |
| 1106 | it.* = .{ .fp = fp }; |
| 1107 | return .{ .switch_to_fp = .{ |
| 1108 | .address = pc, |
| 1109 | .err = err, |
| 1110 | } }; |
| 1111 | }; |
| 1112 | if (ret_addr <= 1) return .end; |
| 1113 | return .{ .frame = ret_addr }; |
| 1114 | }, |
| 1115 | .fp => |fp| { |
| 1116 | if (fp == 0) return .end; // we reached the "sentinel" base pointer |
| 1117 | |
| 1118 | const bp_addr = applyOffset(fp, fp_to_bp_offset) orelse return .end; |
| 1119 | const ra_addr = applyOffset(fp, fp_to_ra_offset) orelse return .end; |
| 1120 | |
| 1121 | if (bp_addr == 0 or !mem.isAligned(bp_addr, @alignOf(usize)) or |
| 1122 | ra_addr == 0 or !mem.isAligned(ra_addr, @alignOf(usize))) |
| 1123 | { |
| 1124 | // This isn't valid, but it most likely indicates end of stack. |
| 1125 | return .end; |
| 1126 | } |
| 1127 | |
| 1128 | const bp_ptr: *const usize = @ptrFromInt(bp_addr); |
| 1129 | const ra_ptr: *const usize = @ptrFromInt(ra_addr); |
| 1130 | const bp = applyOffset(bp_ptr.*, stack_bias) orelse return .end; |
| 1131 | |
| 1132 | // If the stack grows downwards, `bp > fp` should always hold; conversely, if it |
| 1133 | // grows upwards, `bp < fp` should always hold. If that is not the case, this |
| 1134 | // frame is invalid, so we'll treat it as though we reached end of stack. The |
| 1135 | // exception is address 0, which is a graceful end-of-stack signal, in which case |
| 1136 | // *this* return address is valid and the *next* iteration will be the last. |
| 1137 | if (bp != 0 and switch (comptime builtin.target.stackGrowth()) { |
| 1138 | .down => bp <= fp, |
| 1139 | .up => bp >= fp, |
| 1140 | }) return .end; |
| 1141 | |
| 1142 | it.fp = bp; |
| 1143 | const ra = stripInstructionPtrAuthCode(ra_ptr.*); |
| 1144 | if (ra <= 1) return .end; |
| 1145 | return .{ .frame = ra }; |
| 1146 | }, |
| 1147 | } |
| 1148 | } |
| 1149 | |
| 1150 | /// Offset of the saved base pointer (previous frame pointer) wrt the frame pointer. |
| 1151 | const fp_to_bp_offset = off: { |
| 1152 | // On 32-bit PA-RISC, the base pointer is the final word of the frame marker. |
| 1153 | if (native_arch == .hppa) break :off -1 * @sizeOf(usize); |
| 1154 | // On 64-bit PA-RISC, the frame marker was shrunk significantly; now there's just the return |
| 1155 | // address followed by the base pointer. |
| 1156 | if (native_arch == .hppa64) break :off -1 * @sizeOf(usize); |
| 1157 | // On LoongArch and RISC-V, the frame pointer points to the top of the saved register area, |
| 1158 | // in which the base pointer is the first word. |
| 1159 | if (native_arch.isLoongArch() or native_arch.isRISCV()) break :off -2 * @sizeOf(usize); |
| 1160 | // On OpenRISC, the frame pointer is stored below the return address. |
| 1161 | if (native_arch == .or1k) break :off -2 * @sizeOf(usize); |
| 1162 | // On SPARC, the frame pointer points to the save area which holds 16 slots for the local |
| 1163 | // and incoming registers. The base pointer (i6) is stored in its customary save slot. |
| 1164 | if (native_arch.isSPARC()) break :off 14 * @sizeOf(usize); |
| 1165 | // Everywhere else, the frame pointer points directly to the location of the base pointer. |
| 1166 | break :off 0; |
| 1167 | }; |
| 1168 | |
| 1169 | /// Offset of the saved return address wrt the frame pointer. |
| 1170 | const fp_to_ra_offset = off: { |
| 1171 | // On 32-bit PA-RISC, the return address sits in the middle-ish of the frame marker. |
| 1172 | if (native_arch == .hppa) break :off -5 * @sizeOf(usize); |
| 1173 | // On 64-bit PA-RISC, the frame marker was shrunk significantly; now there's just the return |
| 1174 | // address followed by the base pointer. |
| 1175 | if (native_arch == .hppa64) break :off -2 * @sizeOf(usize); |
| 1176 | // On LoongArch and RISC-V, the frame pointer points to the top of the saved register area, |
| 1177 | // in which the return address is the second word. |
| 1178 | if (native_arch.isLoongArch() or native_arch.isRISCV()) break :off -1 * @sizeOf(usize); |
| 1179 | // On OpenRISC, the return address is stored below the stack parameter area. |
| 1180 | if (native_arch == .or1k) break :off -1 * @sizeOf(usize); |
| 1181 | if (native_arch.isPowerPC64()) break :off 2 * @sizeOf(usize); |
| 1182 | // On s390x, r14 is the link register and we need to grab it from its customary slot in the |
| 1183 | // register save area (ELF ABI s390x Supplement §1.2.2.2). |
| 1184 | if (native_arch == .s390x) break :off 14 * @sizeOf(usize); |
| 1185 | // On SPARC, the frame pointer points to the save area which holds 16 slots for the local |
| 1186 | // and incoming registers. The return address (i7) is stored in its customary save slot. |
| 1187 | if (native_arch.isSPARC()) break :off 15 * @sizeOf(usize); |
| 1188 | break :off @sizeOf(usize); |
| 1189 | }; |
| 1190 | |
| 1191 | /// Value to add to the stack pointer and frame/base pointers to get the real location being |
| 1192 | /// pointed to. Yes, SPARC really does this. |
| 1193 | const stack_bias = bias: { |
| 1194 | if (native_arch == .sparc64) break :bias 2047; |
| 1195 | break :bias 0; |
| 1196 | }; |
| 1197 | |
| 1198 | /// On some oddball architectures, a return address points to the call instruction rather than |
| 1199 | /// the instruction following it. |
| 1200 | const ra_call_offset = off: { |
| 1201 | if (native_arch.isSPARC()) break :off 0; |
| 1202 | break :off 1; |
| 1203 | }; |
| 1204 | |
| 1205 | fn applyOffset(addr: usize, comptime off: comptime_int) ?usize { |
| 1206 | if (off >= 0) return math.add(usize, addr, off) catch return null; |
| 1207 | return math.sub(usize, addr, -off) catch return null; |
| 1208 | } |
| 1209 | }; |
| 1210 | |
| 1211 | /// Some platforms use pointer authentication: the upper bits of instruction pointers contain a |
| 1212 | /// signature. This function clears those signature bits to make the pointer directly usable. |
| 1213 | pub inline fn stripInstructionPtrAuthCode(ptr: usize) usize { |
| 1214 | if (native_arch.isAARCH64()) { |
| 1215 | // `hint 0x07` maps to `xpaclri` (or `nop` if the hardware doesn't support it) |
| 1216 | // The save / restore is because `xpaclri` operates on x30 (LR) |
| 1217 | return asm ( |
| 1218 | \\mov x16, x30 |
| 1219 | \\mov x30, x15 |
| 1220 | \\hint 0x07 |
| 1221 | \\mov x15, x30 |
| 1222 | \\mov x30, x16 |
| 1223 | : [ret] "={x15}" (-> usize), |
| 1224 | : [ptr] "{x15}" (ptr), |
| 1225 | : .{ .x16 = true }); |
| 1226 | } |
| 1227 | |
| 1228 | return ptr; |
| 1229 | } |
| 1230 | |
| 1231 | const PrintSourceAddressOptions = struct { |
| 1232 | address: usize, |
| 1233 | resolve_inline_callers: bool, |
| 1234 | }; |
| 1235 | |
| 1236 | fn printSourceAtAddress( |
| 1237 | io: Io, |
| 1238 | text_arena: *std.heap.ArenaAllocator, |
| 1239 | debug_info: *SelfInfo, |
| 1240 | t: Io.Terminal, |
| 1241 | options: PrintSourceAddressOptions, |
| 1242 | ) Writer.Error!void { |
| 1243 | defer _ = text_arena.reset(.retain_capacity); |
| 1244 | |
| 1245 | // Initialize the symbol array with space for at least one element, allocating this on the stack |
| 1246 | // in the common case where only one element is needed |
| 1247 | var buf: [1]Symbol = undefined; |
| 1248 | var bfa: std.heap.BufferFirstAllocator = .init(@ptrCast(&buf), getDebugInfoAllocator()); |
| 1249 | const symbol_allocator = bfa.allocator(); |
| 1250 | var symbols = std.ArrayList(Symbol).initCapacity(symbol_allocator, 1) catch unreachable; |
| 1251 | defer symbols.deinit(symbol_allocator); |
| 1252 | |
| 1253 | debug_info.getSymbols( |
| 1254 | io, |
| 1255 | symbol_allocator, |
| 1256 | text_arena.allocator(), |
| 1257 | options.address, |
| 1258 | options.resolve_inline_callers, |
| 1259 | &symbols, |
| 1260 | ) catch |err| { |
| 1261 | t.setColor(.dim) catch {}; |
| 1262 | defer t.setColor(.reset) catch {}; |
| 1263 | switch (err) { |
| 1264 | error.MissingDebugInfo, |
| 1265 | error.UnsupportedDebugInfo, |
| 1266 | error.InvalidDebugInfo, |
| 1267 | => {}, |
| 1268 | error.ReadFailed, error.Unexpected, error.Canceled => { |
| 1269 | try t.writer.print("Failed to read debug info from filesystem, trace may be incomplete\n\n", .{}); |
| 1270 | }, |
| 1271 | error.OutOfMemory => { |
| 1272 | t.setColor(.dim) catch {}; |
| 1273 | try t.writer.print("Ran out of memory loading debug info, trace may be incomplete\n\n", .{}); |
| 1274 | t.setColor(.reset) catch {}; |
| 1275 | }, |
| 1276 | } |
| 1277 | }; |
| 1278 | |
| 1279 | // If we failed to write any symbols, at least write the unknown symbol. Can't fail since we |
| 1280 | // initialized with a capacity of 1. |
| 1281 | if (symbols.items.len == 0) symbols.appendAssumeCapacity(.unknown); |
| 1282 | |
| 1283 | for (symbols.items) |symbol| { |
| 1284 | try printLineInfo(io, t, debug_info, options.address, symbol); |
| 1285 | } |
| 1286 | } |
| 1287 | fn printLineInfo( |
| 1288 | io: Io, |
| 1289 | t: Io.Terminal, |
| 1290 | debug_info: *SelfInfo, |
| 1291 | address: usize, |
| 1292 | symbol: Symbol, |
| 1293 | ) Writer.Error!void { |
| 1294 | const writer = t.writer; |
| 1295 | t.setColor(.bold) catch {}; |
| 1296 | |
| 1297 | if (symbol.source_location) |*sl| { |
| 1298 | if (sl.column == 0) { |
| 1299 | try writer.print("{s}:{d}", .{ sl.file_name, sl.line }); |
| 1300 | } else { |
| 1301 | try writer.print("{s}:{d}:{d}", .{ sl.file_name, sl.line, sl.column }); |
| 1302 | } |
| 1303 | } else { |
| 1304 | try writer.writeAll("???:?:?"); |
| 1305 | } |
| 1306 | |
| 1307 | t.setColor(.reset) catch {}; |
| 1308 | try writer.writeAll(": "); |
| 1309 | t.setColor(.dim) catch {}; |
| 1310 | try writer.print("0x{x} in {s} ({s})", .{ |
| 1311 | address, |
| 1312 | symbol.name orelse "???", |
| 1313 | symbol.compile_unit_name orelse debug_info.getModuleName(io, address) catch "???", |
| 1314 | }); |
| 1315 | t.setColor(.reset) catch {}; |
| 1316 | try writer.writeAll("\n"); |
| 1317 | |
| 1318 | // Show the matching source code line if possible |
| 1319 | if (symbol.source_location) |sl| { |
| 1320 | if (printLineFromFile(io, writer, sl)) { |
| 1321 | if (sl.column > 0) { |
| 1322 | // The caret already takes one char |
| 1323 | const space_needed = @as(usize, @intCast(sl.column - 1)); |
| 1324 | |
| 1325 | try writer.splatByteAll(' ', space_needed); |
| 1326 | t.setColor(.green) catch {}; |
| 1327 | try writer.writeAll("^"); |
| 1328 | t.setColor(.reset) catch {}; |
| 1329 | } |
| 1330 | try writer.writeAll("\n"); |
| 1331 | } else |_| { |
| 1332 | // Ignore all errors; it's a better UX to just print the source location without the |
| 1333 | // corresponding line number. The user can always open the source file themselves. |
| 1334 | } |
| 1335 | } |
| 1336 | } |
| 1337 | fn printLineFromFile(io: Io, writer: *Writer, source_location: SourceLocation) !void { |
| 1338 | // Allow overriding the target-agnostic source line printing logic by exposing `root.debug.printLineFromFile`. |
| 1339 | if (@hasDecl(root, "debug") and @hasDecl(root.debug, "printLineFromFile")) { |
| 1340 | return root.debug.printLineFromFile(io, writer, source_location); |
| 1341 | } |
| 1342 | |
| 1343 | // Need this to always block even in async I/O mode, because this could potentially |
| 1344 | // be called from e.g. the event loop code crashing. |
| 1345 | const cwd: Io.Dir = .cwd(); |
| 1346 | var file = try cwd.openFile(io, source_location.file_name, .{}); |
| 1347 | defer file.close(io); |
| 1348 | |
| 1349 | var buffer: [4096]u8 = undefined; |
| 1350 | var file_reader: File.Reader = .init(file, io, &buffer); |
| 1351 | var line_index: usize = 0; |
| 1352 | const r = &file_reader.interface; |
| 1353 | while (true) { |
| 1354 | line_index += 1; |
| 1355 | if (line_index == source_location.line) { |
| 1356 | // TODO delete hard tabs from the language |
| 1357 | _ = try r.streamDelimiterEnding(writer, '\n'); |
| 1358 | try writer.writeByte('\n'); |
| 1359 | return; |
| 1360 | } |
| 1361 | _ = try r.discardDelimiterInclusive('\n'); |
| 1362 | } |
| 1363 | } |
| 1364 | |
| 1365 | test printLineFromFile { |
| 1366 | const io = testing.io; |
| 1367 | const gpa = testing.allocator; |
| 1368 | |
| 1369 | var aw: Writer.Allocating = .init(gpa); |
| 1370 | defer aw.deinit(); |
| 1371 | const output_stream = &aw.writer; |
| 1372 | |
| 1373 | const join = std.fs.path.join; |
| 1374 | const expectError = testing.expectError; |
| 1375 | const expectEqualStrings = testing.expectEqualStrings; |
| 1376 | |
| 1377 | var test_dir = testing.tmpDir(.{}); |
| 1378 | defer test_dir.cleanup(); |
| 1379 | // Relies on testing.tmpDir internals which is not ideal, but SourceLocation requires paths. |
| 1380 | const test_dir_path = try join(gpa, &.{ ".zig-cache", "tmp", test_dir.sub_path[0..] }); |
| 1381 | defer gpa.free(test_dir_path); |
| 1382 | |
| 1383 | // Cases |
| 1384 | { |
| 1385 | const path = try join(gpa, &.{ test_dir_path, "one_line.zig" }); |
| 1386 | defer gpa.free(path); |
| 1387 | try test_dir.dir.writeFile(io, .{ .sub_path = "one_line.zig", .data = "no new lines in this file, but one is printed anyway" }); |
| 1388 | |
| 1389 | try expectError(error.EndOfStream, printLineFromFile(io, output_stream, .{ .file_name = path, .line = 2, .column = 0 })); |
| 1390 | |
| 1391 | try printLineFromFile(io, output_stream, .{ .file_name = path, .line = 1, .column = 0 }); |
| 1392 | try expectEqualStrings("no new lines in this file, but one is printed anyway\n", aw.written()); |
| 1393 | aw.clearRetainingCapacity(); |
| 1394 | } |
| 1395 | { |
| 1396 | const path = try fs.path.join(gpa, &.{ test_dir_path, "three_lines.zig" }); |
| 1397 | defer gpa.free(path); |
| 1398 | try test_dir.dir.writeFile(io, .{ |
| 1399 | .sub_path = "three_lines.zig", |
| 1400 | .data = |
| 1401 | \\1 |
| 1402 | \\2 |
| 1403 | \\3 |
| 1404 | , |
| 1405 | }); |
| 1406 | |
| 1407 | try printLineFromFile(io, output_stream, .{ .file_name = path, .line = 1, .column = 0 }); |
| 1408 | try expectEqualStrings("1\n", aw.written()); |
| 1409 | aw.clearRetainingCapacity(); |
| 1410 | |
| 1411 | try printLineFromFile(io, output_stream, .{ .file_name = path, .line = 3, .column = 0 }); |
| 1412 | try expectEqualStrings("3\n", aw.written()); |
| 1413 | aw.clearRetainingCapacity(); |
| 1414 | } |
| 1415 | { |
| 1416 | const file = try test_dir.dir.createFile(io, "line_overlaps_page_boundary.zig", .{}); |
| 1417 | defer file.close(io); |
| 1418 | const path = try fs.path.join(gpa, &.{ test_dir_path, "line_overlaps_page_boundary.zig" }); |
| 1419 | defer gpa.free(path); |
| 1420 | |
| 1421 | const overlap = 10; |
| 1422 | var buf: [16]u8 = undefined; |
| 1423 | var file_writer = file.writer(io, &buf); |
| 1424 | const writer = &file_writer.interface; |
| 1425 | try writer.splatByteAll('a', std.heap.page_size_min - overlap); |
| 1426 | try writer.writeByte('\n'); |
| 1427 | try writer.splatByteAll('a', overlap); |
| 1428 | try writer.flush(); |
| 1429 | |
| 1430 | try printLineFromFile(io, output_stream, .{ .file_name = path, .line = 2, .column = 0 }); |
| 1431 | try expectEqualStrings(&@as([overlap]u8, @splat('a')) ++ "\n", aw.written()); |
| 1432 | aw.clearRetainingCapacity(); |
| 1433 | } |
| 1434 | { |
| 1435 | const file = try test_dir.dir.createFile(io, "file_ends_on_page_boundary.zig", .{}); |
| 1436 | defer file.close(io); |
| 1437 | const path = try fs.path.join(gpa, &.{ test_dir_path, "file_ends_on_page_boundary.zig" }); |
| 1438 | defer gpa.free(path); |
| 1439 | |
| 1440 | var file_writer = file.writer(io, &.{}); |
| 1441 | const writer = &file_writer.interface; |
| 1442 | try writer.splatByteAll('a', std.heap.page_size_max); |
| 1443 | |
| 1444 | try printLineFromFile(io, output_stream, .{ .file_name = path, .line = 1, .column = 0 }); |
| 1445 | try expectEqualStrings(&@as([std.heap.page_size_max]u8, @splat('a')) ++ "\n", aw.written()); |
| 1446 | aw.clearRetainingCapacity(); |
| 1447 | } |
| 1448 | { |
| 1449 | const file = try test_dir.dir.createFile(io, "very_long_first_line_spanning_multiple_pages.zig", .{}); |
| 1450 | defer file.close(io); |
| 1451 | const path = try fs.path.join(gpa, &.{ test_dir_path, "very_long_first_line_spanning_multiple_pages.zig" }); |
| 1452 | defer gpa.free(path); |
| 1453 | |
| 1454 | var file_writer = file.writer(io, &.{}); |
| 1455 | const writer = &file_writer.interface; |
| 1456 | try writer.splatByteAll('a', 3 * std.heap.page_size_max); |
| 1457 | |
| 1458 | try expectError(error.EndOfStream, printLineFromFile(io, output_stream, .{ .file_name = path, .line = 2, .column = 0 })); |
| 1459 | |
| 1460 | const many_a: [3 * std.heap.page_size_max]u8 = @splat('a'); |
| 1461 | |
| 1462 | try printLineFromFile(io, output_stream, .{ .file_name = path, .line = 1, .column = 0 }); |
| 1463 | try expectEqualStrings(&many_a ++ "\n", aw.written()); |
| 1464 | aw.clearRetainingCapacity(); |
| 1465 | |
| 1466 | try writer.writeAll("a\na"); |
| 1467 | |
| 1468 | try printLineFromFile(io, output_stream, .{ .file_name = path, .line = 1, .column = 0 }); |
| 1469 | try expectEqualStrings(&many_a ++ "a\n", aw.written()); |
| 1470 | aw.clearRetainingCapacity(); |
| 1471 | |
| 1472 | try printLineFromFile(io, output_stream, .{ .file_name = path, .line = 2, .column = 0 }); |
| 1473 | try expectEqualStrings("a\n", aw.written()); |
| 1474 | aw.clearRetainingCapacity(); |
| 1475 | } |
| 1476 | { |
| 1477 | const file = try test_dir.dir.createFile(io, "file_of_newlines.zig", .{}); |
| 1478 | defer file.close(io); |
| 1479 | const path = try fs.path.join(gpa, &.{ test_dir_path, "file_of_newlines.zig" }); |
| 1480 | defer gpa.free(path); |
| 1481 | |
| 1482 | var file_writer = file.writer(io, &.{}); |
| 1483 | const writer = &file_writer.interface; |
| 1484 | const real_file_start = 3 * std.heap.page_size_min; |
| 1485 | try writer.splatByteAll('\n', real_file_start); |
| 1486 | try writer.writeAll("abc\ndef"); |
| 1487 | |
| 1488 | try printLineFromFile(io, output_stream, .{ .file_name = path, .line = real_file_start + 1, .column = 0 }); |
| 1489 | try expectEqualStrings("abc\n", aw.written()); |
| 1490 | aw.clearRetainingCapacity(); |
| 1491 | |
| 1492 | try printLineFromFile(io, output_stream, .{ .file_name = path, .line = real_file_start + 2, .column = 0 }); |
| 1493 | try expectEqualStrings("def\n", aw.written()); |
| 1494 | aw.clearRetainingCapacity(); |
| 1495 | } |
| 1496 | } |
| 1497 | |
| 1498 | /// The returned allocator should be thread-safe if the compilation is multi-threaded, because |
| 1499 | /// multiple threads could capture and/or print stack traces simultaneously. |
| 1500 | pub fn getDebugInfoAllocator() Allocator { |
| 1501 | // Allow overriding the debug info allocator by exposing `root.debug.getDebugInfoAllocator`. |
| 1502 | if (@hasDecl(root, "debug") and @hasDecl(root.debug, "getDebugInfoAllocator")) { |
| 1503 | return root.debug.getDebugInfoAllocator(); |
| 1504 | } |
| 1505 | // Otherwise, use a global arena backed by the page allocator |
| 1506 | const S = struct { |
| 1507 | var arena: std.heap.ArenaAllocator = .init(std.heap.page_allocator); |
| 1508 | }; |
| 1509 | return S.arena.allocator(); |
| 1510 | } |
| 1511 | |
| 1512 | /// Whether or not the current target can print useful debug information when a segfault occurs. |
| 1513 | pub const have_segfault_handling_support = switch (native_os) { |
| 1514 | .haiku, |
| 1515 | .linux, |
| 1516 | .serenity, |
| 1517 | |
| 1518 | .dragonfly, |
| 1519 | .freebsd, |
| 1520 | .netbsd, |
| 1521 | .openbsd, |
| 1522 | |
| 1523 | .driverkit, |
| 1524 | .ios, |
| 1525 | .maccatalyst, |
| 1526 | .macos, |
| 1527 | .tvos, |
| 1528 | .visionos, |
| 1529 | .watchos, |
| 1530 | |
| 1531 | .illumos, |
| 1532 | |
| 1533 | .windows, |
| 1534 | => true, |
| 1535 | |
| 1536 | else => false, |
| 1537 | }; |
| 1538 | |
| 1539 | const enable_segfault_handler = std.options.enable_segfault_handler; |
| 1540 | pub const default_enable_segfault_handler = runtime_safety and have_segfault_handling_support; |
| 1541 | |
| 1542 | pub fn maybeEnableSegfaultHandler() void { |
| 1543 | if (enable_segfault_handler) { |
| 1544 | attachSegfaultHandler(); |
| 1545 | } |
| 1546 | } |
| 1547 | |
| 1548 | var windows_segfault_handle: ?windows.HANDLE = null; |
| 1549 | |
| 1550 | pub fn updateSegfaultHandler(act: ?*const posix.Sigaction) void { |
| 1551 | posix.sigaction(.SEGV, act, null); |
| 1552 | posix.sigaction(.ILL, act, null); |
| 1553 | posix.sigaction(.BUS, act, null); |
| 1554 | posix.sigaction(.FPE, act, null); |
| 1555 | } |
| 1556 | |
| 1557 | /// Attaches a global handler for several signals which, when triggered, prints output to stderr |
| 1558 | /// similar to the default panic handler, with a message containing the type of signal and a stack |
| 1559 | /// trace if possible. This implementation does not just call the panic handler, because unwinding |
| 1560 | /// the stack (for a stack trace) when a signal is received requires special target-specific logic. |
| 1561 | /// |
| 1562 | /// On POSIX targets, the signal handler is configured to use the alternative signal stack. Such a |
| 1563 | /// stack is configured by the Zig Standard Library if `std.options.signal_stack_size` is set. |
| 1564 | /// |
| 1565 | /// The signals for which a handler is installed are: |
| 1566 | /// * SIGSEGV (segmentation fault) |
| 1567 | /// * SIGILL (illegal instruction) |
| 1568 | /// * SIGBUS (bus error) |
| 1569 | /// * SIGFPE (arithmetic exception) |
| 1570 | pub fn attachSegfaultHandler() void { |
| 1571 | if (!have_segfault_handling_support) { |
| 1572 | @compileError("segfault handler not supported for this target"); |
| 1573 | } |
| 1574 | if (native_os == .windows) { |
| 1575 | windows_segfault_handle = windows.ntdll.RtlAddVectoredExceptionHandler(0, handleSegfaultWindows); |
| 1576 | return; |
| 1577 | } |
| 1578 | const act: posix.Sigaction = .{ |
| 1579 | .handler = .{ .sigaction = handleSegfaultPosix }, |
| 1580 | .mask = posix.sigemptyset(), |
| 1581 | .flags = (posix.SA.SIGINFO | posix.SA.RESTART | posix.SA.RESETHAND | posix.SA.ONSTACK), |
| 1582 | }; |
| 1583 | updateSegfaultHandler(&act); |
| 1584 | } |
| 1585 | |
| 1586 | fn resetSegfaultHandler() void { |
| 1587 | if (native_os == .windows) { |
| 1588 | if (windows_segfault_handle) |handle| { |
| 1589 | assert(windows.ntdll.RtlRemoveVectoredExceptionHandler(handle) != 0); |
| 1590 | windows_segfault_handle = null; |
| 1591 | } |
| 1592 | return; |
| 1593 | } |
| 1594 | const act = posix.Sigaction{ |
| 1595 | .handler = .{ .handler = posix.SIG.DFL }, |
| 1596 | .mask = posix.sigemptyset(), |
| 1597 | .flags = 0, |
| 1598 | }; |
| 1599 | updateSegfaultHandler(&act); |
| 1600 | } |
| 1601 | |
| 1602 | fn handleSegfaultPosix(sig: posix.SIG, info: *const posix.siginfo_t, ctx_ptr: ?*anyopaque) callconv(.c) noreturn { |
| 1603 | if (use_trap_panic) @trap(); |
| 1604 | const addr: ?usize, const name: []const u8 = info: { |
| 1605 | if (native_os == .linux and native_arch == .x86_64) { |
| 1606 | // x86_64 doesn't have a full 64-bit virtual address space. |
| 1607 | // Addresses outside of that address space are non-canonical |
| 1608 | // and the CPU won't provide the faulting address to us. |
| 1609 | // This happens when accessing memory addresses such as 0xaaaaaaaaaaaaaaaa |
| 1610 | // but can also happen when no addressable memory is involved; |
| 1611 | // for example when reading/writing model-specific registers |
| 1612 | // by executing `rdmsr` or `wrmsr` in user-space (unprivileged mode). |
| 1613 | const SI_KERNEL = 0x80; |
| 1614 | if (sig == .SEGV and info.code == SI_KERNEL) { |
| 1615 | break :info .{ null, "General protection exception" }; |
| 1616 | } |
| 1617 | } |
| 1618 | const addr: usize = switch (native_os) { |
| 1619 | .serenity, |
| 1620 | .dragonfly, |
| 1621 | .freebsd, |
| 1622 | .driverkit, |
| 1623 | .ios, |
| 1624 | .maccatalyst, |
| 1625 | .macos, |
| 1626 | .tvos, |
| 1627 | .visionos, |
| 1628 | .watchos, |
| 1629 | .haiku, |
| 1630 | => @intFromPtr(info.addr), |
| 1631 | .linux, |
| 1632 | => @intFromPtr(info.fields.sigfault.addr), |
| 1633 | .netbsd, |
| 1634 | => @intFromPtr(info.info.reason.fault.addr), |
| 1635 | .openbsd, |
| 1636 | => @intFromPtr(info.data.fault.addr), |
| 1637 | .illumos, |
| 1638 | => @intFromPtr(info.reason.fault.addr), |
| 1639 | else => comptime unreachable, |
| 1640 | }; |
| 1641 | const name = switch (sig) { |
| 1642 | .SEGV => "Segmentation fault", |
| 1643 | .ILL => "Illegal instruction", |
| 1644 | .BUS => "Bus error", |
| 1645 | .FPE => "Arithmetic exception", |
| 1646 | else => unreachable, |
| 1647 | }; |
| 1648 | break :info .{ addr, name }; |
| 1649 | }; |
| 1650 | const opt_cpu_context: ?cpu_context.Native = cpu_context.fromPosixSignalContext(ctx_ptr); |
| 1651 | |
| 1652 | handleSegfault(addr, name, if (opt_cpu_context) |*ctx| ctx else null); |
| 1653 | } |
| 1654 | |
| 1655 | fn handleSegfaultWindows(info: *windows.EXCEPTION_POINTERS) callconv(.winapi) c_long { |
| 1656 | if (use_trap_panic) @trap(); |
| 1657 | const name: []const u8, const addr: ?usize = switch (info.ExceptionRecord.ExceptionCode) { |
| 1658 | windows.EXCEPTION_DATATYPE_MISALIGNMENT => .{ "Unaligned memory access", null }, |
| 1659 | windows.EXCEPTION_ACCESS_VIOLATION => .{ "Segmentation fault", info.ExceptionRecord.ExceptionInformation[1] }, |
| 1660 | windows.EXCEPTION_ILLEGAL_INSTRUCTION => .{ "Illegal instruction", info.ContextRecord.getRegs().ip }, |
| 1661 | windows.EXCEPTION_STACK_OVERFLOW => .{ "Stack overflow", null }, |
| 1662 | else => return windows.EXCEPTION_CONTINUE_SEARCH, |
| 1663 | }; |
| 1664 | handleSegfault(addr, name, &cpu_context.fromWindowsContext(info.ContextRecord)); |
| 1665 | } |
| 1666 | |
| 1667 | fn handleSegfault(addr: ?usize, name: []const u8, opt_ctx: ?CpuContextPtr) noreturn { |
| 1668 | // Allow overriding the target-agnostic segfault handler by exposing `root.debug.handleSegfault`. |
| 1669 | if (@hasDecl(root, "debug") and @hasDecl(root.debug, "handleSegfault")) { |
| 1670 | return root.debug.handleSegfault(addr, name, opt_ctx); |
| 1671 | } |
| 1672 | return defaultHandleSegfault(addr, name, opt_ctx); |
| 1673 | } |
| 1674 | |
| 1675 | pub fn defaultHandleSegfault(addr: ?usize, name: []const u8, opt_ctx: ?CpuContextPtr) noreturn { |
| 1676 | std.Options.debug_io.vtable.crashHandler(std.Options.debug_io.userdata); |
| 1677 | |
| 1678 | // There is very similar logic to the following in `defaultPanic`. |
| 1679 | switch (panic_stage) { |
| 1680 | 0 => { |
| 1681 | panic_stage = 1; |
| 1682 | _ = panicking.fetchAdd(1, .seq_cst); |
| 1683 | |
| 1684 | trace: { |
| 1685 | const stderr = lockStderr(&.{}).terminal(); |
| 1686 | defer unlockStderr(); |
| 1687 | |
| 1688 | if (addr) |a| { |
| 1689 | stderr.writer.print("{s} at address 0x{x}\n", .{ name, a }) catch break :trace; |
| 1690 | } else { |
| 1691 | stderr.writer.print("{s} (no address available)\n", .{name}) catch break :trace; |
| 1692 | } |
| 1693 | if (opt_ctx) |context| { |
| 1694 | writeCurrentStackTrace(.{ |
| 1695 | .context = context, |
| 1696 | .allow_unsafe_unwind = true, // we're crashing anyway, give it our all! |
| 1697 | }, stderr) catch break :trace; |
| 1698 | } |
| 1699 | } |
| 1700 | }, |
| 1701 | 1 => { |
| 1702 | panic_stage = 2; |
| 1703 | // A segfault happened while trying to print a previous panic message. |
| 1704 | // We're still holding the mutex but that's fine as we're going to |
| 1705 | // call abort(). |
| 1706 | const stderr = lockStderr(&.{}).terminal(); |
| 1707 | stderr.writer.writeAll("aborting due to recursive panic\n") catch {}; |
| 1708 | }, |
| 1709 | else => {}, // Panicked while printing the recursive panic message. |
| 1710 | } |
| 1711 | |
| 1712 | // We cannot allow the signal handler to return because when it runs the original instruction |
| 1713 | // again, the memory may be mapped and undefined behavior would occur rather than repeating |
| 1714 | // the segfault. So we simply abort here. |
| 1715 | std.process.abort(); |
| 1716 | } |
| 1717 | |
| 1718 | pub fn dumpStackPointerAddr(prefix: []const u8) void { |
| 1719 | const sp = asm ("" |
| 1720 | : [argc] "={rsp}" (-> usize), |
| 1721 | ); |
| 1722 | print("{s} sp = 0x{x}\n", .{ prefix, sp }); |
| 1723 | } |
| 1724 | |
| 1725 | test "manage resources correctly" { |
| 1726 | if (SelfInfo == void) return error.SkipZigTest; |
| 1727 | if (builtin.zig_backend == .stage2_c) { |
| 1728 | // The C backend emits an extremely large C source file, meaning it has a huge |
| 1729 | // amount of debug information. Parsing this debug information makes this test |
| 1730 | // take too long to be worth running. |
| 1731 | return error.SkipZigTest; |
| 1732 | } |
| 1733 | |
| 1734 | const S = struct { |
| 1735 | noinline fn showMyTrace() usize { |
| 1736 | return @returnAddress(); |
| 1737 | } |
| 1738 | }; |
| 1739 | const io = testing.io; |
| 1740 | |
| 1741 | var discarding: Writer.Discarding = .init(&.{}); |
| 1742 | var di: SelfInfo = .init; |
| 1743 | defer di.deinit(io); |
| 1744 | const t: Io.Terminal = .{ .writer = &discarding.writer, .mode = .no_color }; |
| 1745 | var text_arena: std.heap.ArenaAllocator = .init(std.testing.allocator); |
| 1746 | defer text_arena.deinit(); |
| 1747 | try printSourceAtAddress(io, &text_arena, &di, t, .{ |
| 1748 | .address = S.showMyTrace(), |
| 1749 | .resolve_inline_callers = true, |
| 1750 | }); |
| 1751 | } |
| 1752 | |
| 1753 | /// This API helps you track where a value originated and where it was mutated, |
| 1754 | /// or any other points of interest. |
| 1755 | /// In debug mode, it adds a small size penalty (104 bytes on 64-bit architectures) |
| 1756 | /// to the aggregate that you add it to. |
| 1757 | /// In release mode, it is size 0 and all methods are no-ops. |
| 1758 | /// This is a pre-made type with default settings. |
| 1759 | /// For more advanced usage, see `ConfigurableTrace`. |
| 1760 | pub const Trace = ConfigurableTrace(2, 4, builtin.mode == .debug); |
| 1761 | |
| 1762 | pub fn ConfigurableTrace(comptime size: usize, comptime stack_frame_count: usize, comptime is_enabled: bool) type { |
| 1763 | return struct { |
| 1764 | addrs: [actual_size][stack_frame_count]usize, |
| 1765 | notes: [actual_size][]const u8, |
| 1766 | index: Index, |
| 1767 | |
| 1768 | const actual_size = if (enabled) size else 0; |
| 1769 | const Index = if (enabled) usize else u0; |
| 1770 | |
| 1771 | pub const init: @This() = .{ |
| 1772 | .addrs = undefined, |
| 1773 | .notes = undefined, |
| 1774 | .index = 0, |
| 1775 | }; |
| 1776 | |
| 1777 | pub const enabled = is_enabled; |
| 1778 | |
| 1779 | pub const add = if (enabled) addNoInline else addNoOp; |
| 1780 | |
| 1781 | pub noinline fn addNoInline(t: *@This(), note: []const u8) void { |
| 1782 | comptime assert(enabled); |
| 1783 | return addAddr(t, @returnAddress(), note); |
| 1784 | } |
| 1785 | |
| 1786 | pub inline fn addNoOp(t: *@This(), note: []const u8) void { |
| 1787 | _ = t; |
| 1788 | _ = note; |
| 1789 | comptime assert(!enabled); |
| 1790 | } |
| 1791 | |
| 1792 | pub fn addAddr(t: *@This(), addr: usize, note: []const u8) void { |
| 1793 | if (!enabled) return; |
| 1794 | |
| 1795 | if (t.index < size) { |
| 1796 | t.notes[t.index] = note; |
| 1797 | const addrs = &t.addrs[t.index]; |
| 1798 | const st = captureCurrentStackTrace(.{ .first_address = addr }, addrs); |
| 1799 | if (st.return_addresses.len < addrs.len) { |
| 1800 | @memset(addrs[st.return_addresses.len..], 0); // zero unused frames to indicate end of trace |
| 1801 | } |
| 1802 | } |
| 1803 | // Keep counting even if the end is reached so that the |
| 1804 | // user can find out how much more size they need. |
| 1805 | t.index += 1; |
| 1806 | } |
| 1807 | |
| 1808 | pub fn dump(t: @This()) void { |
| 1809 | if (!enabled) return; |
| 1810 | |
| 1811 | const stderr = lockStderr(&.{}).terminal(); |
| 1812 | defer unlockStderr(); |
| 1813 | const end = @min(t.index, size); |
| 1814 | for (t.addrs[0..end], 0..) |frames_array, i| { |
| 1815 | stderr.writer.print("{s}:\n", .{t.notes[i]}) catch return; |
| 1816 | var frames_array_mutable = frames_array; |
| 1817 | const frames = mem.sliceTo(frames_array_mutable[0..], 0); |
| 1818 | const len = @min(t.index, frames.len); |
| 1819 | const stack_trace: StackTrace = .{ |
| 1820 | .return_addresses = frames[0..len], |
| 1821 | .skipped = if (len < frames.len) .none else .unknown, |
| 1822 | }; |
| 1823 | writeStackTrace(&stack_trace, stderr) catch return; |
| 1824 | } |
| 1825 | if (t.index > end) { |
| 1826 | stderr.writer.print("{d} more traces not shown; consider increasing trace size\n", .{ |
| 1827 | t.index - end, |
| 1828 | }) catch return; |
| 1829 | } |
| 1830 | } |
| 1831 | |
| 1832 | pub fn format( |
| 1833 | t: @This(), |
| 1834 | comptime fmt: []const u8, |
| 1835 | options: std.fmt.Options, |
| 1836 | writer: *Writer, |
| 1837 | ) !void { |
| 1838 | if (fmt.len != 0) std.fmt.invalidFmtError(fmt, t); |
| 1839 | _ = options; |
| 1840 | if (enabled) { |
| 1841 | try writer.writeAll("\n"); |
| 1842 | t.dump(); |
| 1843 | try writer.writeAll("\n"); |
| 1844 | } else { |
| 1845 | return writer.writeAll("(value tracing disabled)"); |
| 1846 | } |
| 1847 | } |
| 1848 | }; |
| 1849 | } |
| 1850 | |
| 1851 | pub const SafetyLock = struct { |
| 1852 | state: State = if (runtime_safety) .unlocked else .unknown, |
| 1853 | |
| 1854 | pub const State = if (runtime_safety) enum { unlocked, locked } else enum { unknown }; |
| 1855 | |
| 1856 | pub fn lock(l: *SafetyLock) void { |
| 1857 | if (!runtime_safety) return; |
| 1858 | assert(l.state == .unlocked); |
| 1859 | l.state = .locked; |
| 1860 | } |
| 1861 | |
| 1862 | pub fn unlock(l: *SafetyLock) void { |
| 1863 | if (!runtime_safety) return; |
| 1864 | assert(l.state == .locked); |
| 1865 | l.state = .unlocked; |
| 1866 | } |
| 1867 | |
| 1868 | pub fn assertUnlocked(l: SafetyLock) void { |
| 1869 | if (!runtime_safety) return; |
| 1870 | assert(l.state == .unlocked); |
| 1871 | } |
| 1872 | |
| 1873 | pub fn assertLocked(l: SafetyLock) void { |
| 1874 | if (!runtime_safety) return; |
| 1875 | assert(l.state == .locked); |
| 1876 | } |
| 1877 | }; |
| 1878 | |
| 1879 | test SafetyLock { |
| 1880 | var safety_lock: SafetyLock = .{}; |
| 1881 | safety_lock.assertUnlocked(); |
| 1882 | safety_lock.lock(); |
| 1883 | safety_lock.assertLocked(); |
| 1884 | safety_lock.unlock(); |
| 1885 | safety_lock.assertUnlocked(); |
| 1886 | } |
| 1887 | |
| 1888 | /// Detect whether the program is being executed in the Valgrind virtual machine. |
| 1889 | /// |
| 1890 | /// When Valgrind integrations are disabled, this returns comptime-known false. |
| 1891 | /// Otherwise, the result is runtime-known. |
| 1892 | pub inline fn inValgrind() bool { |
| 1893 | if (@inComptime()) return false; |
| 1894 | if (!builtin.valgrind_support) return false; |
| 1895 | return std.valgrind.runningOnValgrind() > 0; |
| 1896 | } |
| 1897 | |
| 1898 | test { |
| 1899 | _ = &Dwarf; |
| 1900 | _ = &Pdb; |
| 1901 | _ = &SelfInfo; |
| 1902 | _ = &dumpHex; |
| 1903 | } |