std.fmt: breaking API changes
added adapter to AnyWriter and GenericWriter to help bridge the gap
between old and new API
make std.testing.expectFmt work at compile-time
std.fmt no longer has a dependency on std.unicode. Formatted printing
was never properly unicode-aware. Now it no longer pretends to be.
Breakage/deprecations:
* std.fs.File.reader -> std.fs.File.deprecatedReader
* std.fs.File.writer -> std.fs.File.deprecatedWriter
* std.io.GenericReader -> std.io.Reader
* std.io.GenericWriter -> std.io.Writer
* std.io.AnyReader -> std.io.Reader
* std.io.AnyWriter -> std.io.Writer
* std.fmt.format -> std.fmt.deprecatedFormat
* std.fmt.fmtSliceEscapeLower -> std.ascii.hexEscape
* std.fmt.fmtSliceEscapeUpper -> std.ascii.hexEscape
* std.fmt.fmtSliceHexLower -> {x}
* std.fmt.fmtSliceHexUpper -> {X}
* std.fmt.fmtIntSizeDec -> {B}
* std.fmt.fmtIntSizeBin -> {Bi}
* std.fmt.fmtDuration -> {D}
* std.fmt.fmtDurationSigned -> {D}
* {} -> {f} when there is a format method
* format method signature
- anytype -> *std.io.Writer
- inferred error set -> error{WriteFailed}
- options -> (deleted)
* std.fmt.Formatted
- now takes context type explicitly
- no fmt string
This commit is contained in:
@@ -12,6 +12,7 @@ const windows = std.os.windows;
|
||||
const native_arch = builtin.cpu.arch;
|
||||
const native_os = builtin.os.tag;
|
||||
const native_endian = native_arch.endian();
|
||||
const Writer = std.io.Writer;
|
||||
|
||||
pub const MemoryAccessor = @import("debug/MemoryAccessor.zig");
|
||||
pub const FixedBufferReader = @import("debug/FixedBufferReader.zig");
|
||||
@@ -204,13 +205,26 @@ pub fn unlockStdErr() void {
|
||||
std.Progress.unlockStdErr();
|
||||
}
|
||||
|
||||
/// Allows the caller to freely write to stderr until `unlockStdErr` is called.
|
||||
///
|
||||
/// During the lock, any `std.Progress` information is cleared from the terminal.
|
||||
///
|
||||
/// Returns a `Writer` with empty buffer, meaning that it is
|
||||
/// in fact unbuffered and does not need to be flushed.
|
||||
pub fn lockStderrWriter(buffer: []u8) *Writer {
|
||||
return std.Progress.lockStderrWriter(buffer);
|
||||
}
|
||||
|
||||
pub fn unlockStderrWriter() void {
|
||||
std.Progress.unlockStderrWriter();
|
||||
}
|
||||
|
||||
/// Print to stderr, unbuffered, and silently returning on failure. Intended
|
||||
/// for use in "printf debugging." Use `std.log` functions for proper logging.
|
||||
/// for use in "printf debugging". Use `std.log` functions for proper logging.
|
||||
pub fn print(comptime fmt: []const u8, args: anytype) void {
|
||||
lockStdErr();
|
||||
defer unlockStdErr();
|
||||
const stderr = fs.File.stderr().writer();
|
||||
nosuspend stderr.print(fmt, args) catch return;
|
||||
const bw = lockStderrWriter(&.{});
|
||||
defer unlockStderrWriter();
|
||||
nosuspend bw.print(fmt, args) catch return;
|
||||
}
|
||||
|
||||
pub fn getStderrMutex() *std.Thread.Mutex {
|
||||
@@ -232,50 +246,44 @@ pub fn getSelfDebugInfo() !*SelfInfo {
|
||||
/// Tries to print a hexadecimal view of the bytes, unbuffered, and ignores any error returned.
|
||||
/// Obtains the stderr mutex while dumping.
|
||||
pub fn dumpHex(bytes: []const u8) void {
|
||||
lockStdErr();
|
||||
defer unlockStdErr();
|
||||
dumpHexFallible(bytes) catch {};
|
||||
const bw = lockStderrWriter(&.{});
|
||||
defer unlockStderrWriter();
|
||||
const ttyconf = std.io.tty.detectConfig(.stderr());
|
||||
dumpHexFallible(bw, ttyconf, bytes) catch {};
|
||||
}
|
||||
|
||||
/// Prints a hexadecimal view of the bytes, unbuffered, returning any error that occurs.
|
||||
pub fn dumpHexFallible(bytes: []const u8) !void {
|
||||
const stderr: fs.File = .stderr();
|
||||
const ttyconf = std.io.tty.detectConfig(stderr);
|
||||
const writer = stderr.writer();
|
||||
try dumpHexInternal(bytes, ttyconf, writer);
|
||||
}
|
||||
|
||||
fn dumpHexInternal(bytes: []const u8, ttyconf: std.io.tty.Config, writer: anytype) !void {
|
||||
/// Prints a hexadecimal view of the bytes, returning any error that occurs.
|
||||
pub fn dumpHexFallible(bw: *Writer, ttyconf: std.io.tty.Config, bytes: []const u8) !void {
|
||||
var chunks = mem.window(u8, bytes, 16, 16);
|
||||
while (chunks.next()) |window| {
|
||||
// 1. Print the address.
|
||||
const address = (@intFromPtr(bytes.ptr) + 0x10 * (std.math.divCeil(usize, chunks.index orelse bytes.len, 16) catch unreachable)) - 0x10;
|
||||
try ttyconf.setColor(writer, .dim);
|
||||
try ttyconf.setColor(bw, .dim);
|
||||
// We print the address in lowercase and the bytes in uppercase hexadecimal to distinguish them more.
|
||||
// Also, make sure all lines are aligned by padding the address.
|
||||
try writer.print("{x:0>[1]} ", .{ address, @sizeOf(usize) * 2 });
|
||||
try ttyconf.setColor(writer, .reset);
|
||||
try bw.print("{x:0>[1]} ", .{ address, @sizeOf(usize) * 2 });
|
||||
try ttyconf.setColor(bw, .reset);
|
||||
|
||||
// 2. Print the bytes.
|
||||
for (window, 0..) |byte, index| {
|
||||
try writer.print("{X:0>2} ", .{byte});
|
||||
if (index == 7) try writer.writeByte(' ');
|
||||
try bw.print("{X:0>2} ", .{byte});
|
||||
if (index == 7) try bw.writeByte(' ');
|
||||
}
|
||||
try writer.writeByte(' ');
|
||||
try bw.writeByte(' ');
|
||||
if (window.len < 16) {
|
||||
var missing_columns = (16 - window.len) * 3;
|
||||
if (window.len < 8) missing_columns += 1;
|
||||
try writer.writeByteNTimes(' ', missing_columns);
|
||||
try bw.splatByteAll(' ', missing_columns);
|
||||
}
|
||||
|
||||
// 3. Print the characters.
|
||||
for (window) |byte| {
|
||||
if (std.ascii.isPrint(byte)) {
|
||||
try writer.writeByte(byte);
|
||||
try bw.writeByte(byte);
|
||||
} else {
|
||||
// Related: https://github.com/ziglang/zig/issues/7600
|
||||
if (ttyconf == .windows_api) {
|
||||
try writer.writeByte('.');
|
||||
try bw.writeByte('.');
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -283,22 +291,23 @@ fn dumpHexInternal(bytes: []const u8, ttyconf: std.io.tty.Config, writer: anytyp
|
||||
// We don't want to do this for all control codes because most control codes apart from
|
||||
// the ones that Zig has escape sequences for are likely not very useful to print as symbols.
|
||||
switch (byte) {
|
||||
'\n' => try writer.writeAll("␊"),
|
||||
'\r' => try writer.writeAll("␍"),
|
||||
'\t' => try writer.writeAll("␉"),
|
||||
else => try writer.writeByte('.'),
|
||||
'\n' => try bw.writeAll("␊"),
|
||||
'\r' => try bw.writeAll("␍"),
|
||||
'\t' => try bw.writeAll("␉"),
|
||||
else => try bw.writeByte('.'),
|
||||
}
|
||||
}
|
||||
}
|
||||
try writer.writeByte('\n');
|
||||
try bw.writeByte('\n');
|
||||
}
|
||||
}
|
||||
|
||||
test dumpHexInternal {
|
||||
test dumpHexFallible {
|
||||
const bytes: []const u8 = &.{ 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, 0x01, 0x12, 0x13 };
|
||||
var output = std.ArrayList(u8).init(std.testing.allocator);
|
||||
defer output.deinit();
|
||||
try dumpHexInternal(bytes, .no_color, output.writer());
|
||||
var aw: std.io.Writer.Allocating = .init(std.testing.allocator);
|
||||
defer aw.deinit();
|
||||
|
||||
try dumpHexFallible(&aw.interface, .no_color, bytes);
|
||||
const expected = try std.fmt.allocPrint(std.testing.allocator,
|
||||
\\{x:0>[2]} 00 11 22 33 44 55 66 77 88 99 AA BB CC DD EE FF .."3DUfw........
|
||||
\\{x:0>[2]} 01 12 13 ...
|
||||
@@ -309,34 +318,36 @@ test dumpHexInternal {
|
||||
@sizeOf(usize) * 2,
|
||||
});
|
||||
defer std.testing.allocator.free(expected);
|
||||
try std.testing.expectEqualStrings(expected, output.items);
|
||||
try std.testing.expectEqualStrings(expected, aw.getWritten());
|
||||
}
|
||||
|
||||
/// Tries to print the current stack trace to stderr, unbuffered, and ignores any error returned.
|
||||
/// TODO multithreaded awareness
|
||||
pub fn dumpCurrentStackTrace(start_addr: ?usize) void {
|
||||
nosuspend {
|
||||
if (builtin.target.cpu.arch.isWasm()) {
|
||||
if (native_os == .wasi) {
|
||||
const stderr = fs.File.stderr().writer();
|
||||
stderr.print("Unable to dump stack trace: not implemented for Wasm\n", .{}) catch return;
|
||||
}
|
||||
return;
|
||||
const stderr = lockStderrWriter(&.{});
|
||||
defer unlockStderrWriter();
|
||||
nosuspend dumpCurrentStackTraceToWriter(start_addr, stderr) catch return;
|
||||
}
|
||||
|
||||
/// Prints the current stack trace to the provided writer.
|
||||
pub fn dumpCurrentStackTraceToWriter(start_addr: ?usize, writer: *Writer) !void {
|
||||
if (builtin.target.cpu.arch.isWasm()) {
|
||||
if (native_os == .wasi) {
|
||||
try writer.writeAll("Unable to dump stack trace: not implemented for Wasm\n");
|
||||
}
|
||||
const stderr = fs.File.stderr().writer();
|
||||
if (builtin.strip_debug_info) {
|
||||
stderr.print("Unable to dump stack trace: debug info stripped\n", .{}) catch return;
|
||||
return;
|
||||
}
|
||||
const debug_info = getSelfDebugInfo() catch |err| {
|
||||
stderr.print("Unable to dump stack trace: Unable to open debug info: {s}\n", .{@errorName(err)}) catch return;
|
||||
return;
|
||||
};
|
||||
writeCurrentStackTrace(stderr, debug_info, io.tty.detectConfig(fs.File.stderr()), start_addr) catch |err| {
|
||||
stderr.print("Unable to dump stack trace: {s}\n", .{@errorName(err)}) catch return;
|
||||
return;
|
||||
};
|
||||
return;
|
||||
}
|
||||
if (builtin.strip_debug_info) {
|
||||
try writer.writeAll("Unable to dump stack trace: debug info stripped\n");
|
||||
return;
|
||||
}
|
||||
const debug_info = getSelfDebugInfo() catch |err| {
|
||||
try writer.print("Unable to dump stack trace: Unable to open debug info: {s}\n", .{@errorName(err)});
|
||||
return;
|
||||
};
|
||||
writeCurrentStackTrace(writer, debug_info, io.tty.detectConfig(.stderr()), start_addr) catch |err| {
|
||||
try writer.print("Unable to dump stack trace: {s}\n", .{@errorName(err)});
|
||||
return;
|
||||
};
|
||||
}
|
||||
|
||||
pub const have_ucontext = posix.ucontext_t != void;
|
||||
@@ -402,16 +413,14 @@ pub inline fn getContext(context: *ThreadContext) bool {
|
||||
/// Tries to print the stack trace starting from the supplied base pointer to stderr,
|
||||
/// unbuffered, and ignores any error returned.
|
||||
/// TODO multithreaded awareness
|
||||
pub fn dumpStackTraceFromBase(context: *ThreadContext) void {
|
||||
pub fn dumpStackTraceFromBase(context: *ThreadContext, stderr: *Writer) void {
|
||||
nosuspend {
|
||||
if (builtin.target.cpu.arch.isWasm()) {
|
||||
if (native_os == .wasi) {
|
||||
const stderr = fs.File.stderr().writer();
|
||||
stderr.print("Unable to dump stack trace: not implemented for Wasm\n", .{}) catch return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
const stderr = fs.File.stderr().writer();
|
||||
if (builtin.strip_debug_info) {
|
||||
stderr.print("Unable to dump stack trace: debug info stripped\n", .{}) catch return;
|
||||
return;
|
||||
@@ -420,7 +429,7 @@ pub fn dumpStackTraceFromBase(context: *ThreadContext) void {
|
||||
stderr.print("Unable to dump stack trace: Unable to open debug info: {s}\n", .{@errorName(err)}) catch return;
|
||||
return;
|
||||
};
|
||||
const tty_config = io.tty.detectConfig(fs.File.stderr());
|
||||
const tty_config = io.tty.detectConfig(.stderr());
|
||||
if (native_os == .windows) {
|
||||
// On x86_64 and aarch64, the stack will be unwound using RtlVirtualUnwind using the context
|
||||
// provided by the exception handler. On x86, RtlVirtualUnwind doesn't exist. Instead, a new backtrace
|
||||
@@ -510,21 +519,23 @@ pub fn dumpStackTrace(stack_trace: std.builtin.StackTrace) void {
|
||||
nosuspend {
|
||||
if (builtin.target.cpu.arch.isWasm()) {
|
||||
if (native_os == .wasi) {
|
||||
const stderr = fs.File.stderr().writer();
|
||||
stderr.print("Unable to dump stack trace: not implemented for Wasm\n", .{}) catch return;
|
||||
const stderr = lockStderrWriter(&.{});
|
||||
defer unlockStderrWriter();
|
||||
stderr.writeAll("Unable to dump stack trace: not implemented for Wasm\n") catch return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
const stderr = fs.File.stderr().writer();
|
||||
const stderr = lockStderrWriter(&.{});
|
||||
defer unlockStderrWriter();
|
||||
if (builtin.strip_debug_info) {
|
||||
stderr.print("Unable to dump stack trace: debug info stripped\n", .{}) catch return;
|
||||
stderr.writeAll("Unable to dump stack trace: debug info stripped\n") catch return;
|
||||
return;
|
||||
}
|
||||
const debug_info = getSelfDebugInfo() catch |err| {
|
||||
stderr.print("Unable to dump stack trace: Unable to open debug info: {s}\n", .{@errorName(err)}) catch return;
|
||||
return;
|
||||
};
|
||||
writeStackTrace(stack_trace, stderr, debug_info, io.tty.detectConfig(fs.File.stderr())) catch |err| {
|
||||
writeStackTrace(stack_trace, stderr, debug_info, io.tty.detectConfig(.stderr())) catch |err| {
|
||||
stderr.print("Unable to dump stack trace: {s}\n", .{@errorName(err)}) catch return;
|
||||
return;
|
||||
};
|
||||
@@ -573,14 +584,13 @@ pub fn panicExtra(
|
||||
const size = 0x1000;
|
||||
const trunc_msg = "(msg truncated)";
|
||||
var buf: [size + trunc_msg.len]u8 = undefined;
|
||||
var bw: Writer = .fixed(buf[0..size]);
|
||||
// a minor annoyance with this is that it will result in the NoSpaceLeft
|
||||
// error being part of the @panic stack trace (but that error should
|
||||
// only happen rarely)
|
||||
const msg = std.fmt.bufPrint(buf[0..size], format, args) catch |err| switch (err) {
|
||||
error.NoSpaceLeft => blk: {
|
||||
@memcpy(buf[size..], trunc_msg);
|
||||
break :blk &buf;
|
||||
},
|
||||
const msg = if (bw.print(format, args)) |_| bw.buffered() else |_| blk: {
|
||||
@memcpy(buf[size..], trunc_msg);
|
||||
break :blk &buf;
|
||||
};
|
||||
std.builtin.panic.call(msg, ret_addr);
|
||||
}
|
||||
@@ -675,10 +685,9 @@ pub fn defaultPanic(
|
||||
_ = panicking.fetchAdd(1, .seq_cst);
|
||||
|
||||
{
|
||||
lockStdErr();
|
||||
defer unlockStdErr();
|
||||
const stderr = lockStderrWriter(&.{});
|
||||
defer unlockStderrWriter();
|
||||
|
||||
const stderr = fs.File.stderr().writer();
|
||||
if (builtin.single_threaded) {
|
||||
stderr.print("panic: ", .{}) catch posix.abort();
|
||||
} else {
|
||||
@@ -688,7 +697,7 @@ pub fn defaultPanic(
|
||||
stderr.print("{s}\n", .{msg}) catch posix.abort();
|
||||
|
||||
if (@errorReturnTrace()) |t| dumpStackTrace(t.*);
|
||||
dumpCurrentStackTrace(first_trace_addr orelse @returnAddress());
|
||||
dumpCurrentStackTraceToWriter(first_trace_addr orelse @returnAddress(), stderr) catch {};
|
||||
}
|
||||
|
||||
waitForOtherThreadToFinishPanicking();
|
||||
@@ -723,7 +732,7 @@ fn waitForOtherThreadToFinishPanicking() void {
|
||||
|
||||
pub fn writeStackTrace(
|
||||
stack_trace: std.builtin.StackTrace,
|
||||
out_stream: anytype,
|
||||
writer: *Writer,
|
||||
debug_info: *SelfInfo,
|
||||
tty_config: io.tty.Config,
|
||||
) !void {
|
||||
@@ -736,15 +745,15 @@ pub fn writeStackTrace(
|
||||
frame_index = (frame_index + 1) % stack_trace.instruction_addresses.len;
|
||||
}) {
|
||||
const return_address = stack_trace.instruction_addresses[frame_index];
|
||||
try printSourceAtAddress(debug_info, out_stream, return_address - 1, tty_config);
|
||||
try printSourceAtAddress(debug_info, writer, return_address - 1, tty_config);
|
||||
}
|
||||
|
||||
if (stack_trace.index > stack_trace.instruction_addresses.len) {
|
||||
const dropped_frames = stack_trace.index - stack_trace.instruction_addresses.len;
|
||||
|
||||
tty_config.setColor(out_stream, .bold) catch {};
|
||||
try out_stream.print("({d} additional stack frames skipped...)\n", .{dropped_frames});
|
||||
tty_config.setColor(out_stream, .reset) catch {};
|
||||
tty_config.setColor(writer, .bold) catch {};
|
||||
try writer.print("({d} additional stack frames skipped...)\n", .{dropped_frames});
|
||||
tty_config.setColor(writer, .reset) catch {};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -954,7 +963,7 @@ pub const StackIterator = struct {
|
||||
};
|
||||
|
||||
pub fn writeCurrentStackTrace(
|
||||
out_stream: anytype,
|
||||
writer: *Writer,
|
||||
debug_info: *SelfInfo,
|
||||
tty_config: io.tty.Config,
|
||||
start_addr: ?usize,
|
||||
@@ -962,7 +971,7 @@ pub fn writeCurrentStackTrace(
|
||||
if (native_os == .windows) {
|
||||
var context: ThreadContext = undefined;
|
||||
assert(getContext(&context));
|
||||
return writeStackTraceWindows(out_stream, debug_info, tty_config, &context, start_addr);
|
||||
return writeStackTraceWindows(writer, debug_info, tty_config, &context, start_addr);
|
||||
}
|
||||
var context: ThreadContext = undefined;
|
||||
const has_context = getContext(&context);
|
||||
@@ -973,7 +982,7 @@ pub fn writeCurrentStackTrace(
|
||||
defer it.deinit();
|
||||
|
||||
while (it.next()) |return_address| {
|
||||
printLastUnwindError(&it, debug_info, out_stream, tty_config);
|
||||
printLastUnwindError(&it, debug_info, writer, tty_config);
|
||||
|
||||
// On arm64 macOS, the address of the last frame is 0x0 rather than 0x1 as on x86_64 macOS,
|
||||
// therefore, we do a check for `return_address == 0` before subtracting 1 from it to avoid
|
||||
@@ -981,8 +990,8 @@ pub fn writeCurrentStackTrace(
|
||||
// condition on the subsequent iteration and return `null` thus terminating the loop.
|
||||
// same behaviour for x86-windows-msvc
|
||||
const address = return_address -| 1;
|
||||
try printSourceAtAddress(debug_info, out_stream, address, tty_config);
|
||||
} else printLastUnwindError(&it, debug_info, out_stream, tty_config);
|
||||
try printSourceAtAddress(debug_info, writer, address, tty_config);
|
||||
} else printLastUnwindError(&it, debug_info, writer, tty_config);
|
||||
}
|
||||
|
||||
pub noinline fn walkStackWindows(addresses: []usize, existing_context: ?*const windows.CONTEXT) usize {
|
||||
@@ -1042,7 +1051,7 @@ pub noinline fn walkStackWindows(addresses: []usize, existing_context: ?*const w
|
||||
}
|
||||
|
||||
pub fn writeStackTraceWindows(
|
||||
out_stream: anytype,
|
||||
writer: *Writer,
|
||||
debug_info: *SelfInfo,
|
||||
tty_config: io.tty.Config,
|
||||
context: *const windows.CONTEXT,
|
||||
@@ -1058,14 +1067,14 @@ pub fn writeStackTraceWindows(
|
||||
return;
|
||||
} else 0;
|
||||
for (addrs[start_i..]) |addr| {
|
||||
try printSourceAtAddress(debug_info, out_stream, addr - 1, tty_config);
|
||||
try printSourceAtAddress(debug_info, writer, addr - 1, tty_config);
|
||||
}
|
||||
}
|
||||
|
||||
fn printUnknownSource(debug_info: *SelfInfo, out_stream: anytype, address: usize, tty_config: io.tty.Config) !void {
|
||||
fn printUnknownSource(debug_info: *SelfInfo, writer: *Writer, address: usize, tty_config: io.tty.Config) !void {
|
||||
const module_name = debug_info.getModuleNameForAddress(address);
|
||||
return printLineInfo(
|
||||
out_stream,
|
||||
writer,
|
||||
null,
|
||||
address,
|
||||
"???",
|
||||
@@ -1075,38 +1084,38 @@ fn printUnknownSource(debug_info: *SelfInfo, out_stream: anytype, address: usize
|
||||
);
|
||||
}
|
||||
|
||||
fn printLastUnwindError(it: *StackIterator, debug_info: *SelfInfo, out_stream: anytype, tty_config: io.tty.Config) void {
|
||||
fn printLastUnwindError(it: *StackIterator, debug_info: *SelfInfo, writer: *Writer, tty_config: io.tty.Config) void {
|
||||
if (!have_ucontext) return;
|
||||
if (it.getLastError()) |unwind_error| {
|
||||
printUnwindError(debug_info, out_stream, unwind_error.address, unwind_error.err, tty_config) catch {};
|
||||
printUnwindError(debug_info, writer, unwind_error.address, unwind_error.err, tty_config) catch {};
|
||||
}
|
||||
}
|
||||
|
||||
fn printUnwindError(debug_info: *SelfInfo, out_stream: anytype, address: usize, err: UnwindError, tty_config: io.tty.Config) !void {
|
||||
fn printUnwindError(debug_info: *SelfInfo, writer: *Writer, address: usize, err: UnwindError, tty_config: io.tty.Config) !void {
|
||||
const module_name = debug_info.getModuleNameForAddress(address) orelse "???";
|
||||
try tty_config.setColor(out_stream, .dim);
|
||||
try tty_config.setColor(writer, .dim);
|
||||
if (err == error.MissingDebugInfo) {
|
||||
try out_stream.print("Unwind information for `{s}:0x{x}` was not available, trace may be incomplete\n\n", .{ module_name, address });
|
||||
try writer.print("Unwind information for `{s}:0x{x}` was not available, trace may be incomplete\n\n", .{ module_name, address });
|
||||
} else {
|
||||
try out_stream.print("Unwind error at address `{s}:0x{x}` ({}), trace may be incomplete\n\n", .{ module_name, address, err });
|
||||
try writer.print("Unwind error at address `{s}:0x{x}` ({}), trace may be incomplete\n\n", .{ module_name, address, err });
|
||||
}
|
||||
try tty_config.setColor(out_stream, .reset);
|
||||
try tty_config.setColor(writer, .reset);
|
||||
}
|
||||
|
||||
pub fn printSourceAtAddress(debug_info: *SelfInfo, out_stream: anytype, address: usize, tty_config: io.tty.Config) !void {
|
||||
pub fn printSourceAtAddress(debug_info: *SelfInfo, writer: *Writer, address: usize, tty_config: io.tty.Config) !void {
|
||||
const module = debug_info.getModuleForAddress(address) catch |err| switch (err) {
|
||||
error.MissingDebugInfo, error.InvalidDebugInfo => return printUnknownSource(debug_info, out_stream, address, tty_config),
|
||||
error.MissingDebugInfo, error.InvalidDebugInfo => return printUnknownSource(debug_info, writer, address, tty_config),
|
||||
else => return err,
|
||||
};
|
||||
|
||||
const symbol_info = module.getSymbolAtAddress(debug_info.allocator, address) catch |err| switch (err) {
|
||||
error.MissingDebugInfo, error.InvalidDebugInfo => return printUnknownSource(debug_info, out_stream, address, tty_config),
|
||||
error.MissingDebugInfo, error.InvalidDebugInfo => return printUnknownSource(debug_info, writer, address, tty_config),
|
||||
else => return err,
|
||||
};
|
||||
defer if (symbol_info.source_location) |sl| debug_info.allocator.free(sl.file_name);
|
||||
|
||||
return printLineInfo(
|
||||
out_stream,
|
||||
writer,
|
||||
symbol_info.source_location,
|
||||
address,
|
||||
symbol_info.name,
|
||||
@@ -1117,7 +1126,7 @@ pub fn printSourceAtAddress(debug_info: *SelfInfo, out_stream: anytype, address:
|
||||
}
|
||||
|
||||
fn printLineInfo(
|
||||
out_stream: anytype,
|
||||
writer: *Writer,
|
||||
source_location: ?SourceLocation,
|
||||
address: usize,
|
||||
symbol_name: []const u8,
|
||||
@@ -1126,34 +1135,34 @@ fn printLineInfo(
|
||||
comptime printLineFromFile: anytype,
|
||||
) !void {
|
||||
nosuspend {
|
||||
try tty_config.setColor(out_stream, .bold);
|
||||
try tty_config.setColor(writer, .bold);
|
||||
|
||||
if (source_location) |*sl| {
|
||||
try out_stream.print("{s}:{d}:{d}", .{ sl.file_name, sl.line, sl.column });
|
||||
try writer.print("{s}:{d}:{d}", .{ sl.file_name, sl.line, sl.column });
|
||||
} else {
|
||||
try out_stream.writeAll("???:?:?");
|
||||
try writer.writeAll("???:?:?");
|
||||
}
|
||||
|
||||
try tty_config.setColor(out_stream, .reset);
|
||||
try out_stream.writeAll(": ");
|
||||
try tty_config.setColor(out_stream, .dim);
|
||||
try out_stream.print("0x{x} in {s} ({s})", .{ address, symbol_name, compile_unit_name });
|
||||
try tty_config.setColor(out_stream, .reset);
|
||||
try out_stream.writeAll("\n");
|
||||
try tty_config.setColor(writer, .reset);
|
||||
try writer.writeAll(": ");
|
||||
try tty_config.setColor(writer, .dim);
|
||||
try writer.print("0x{x} in {s} ({s})", .{ address, symbol_name, compile_unit_name });
|
||||
try tty_config.setColor(writer, .reset);
|
||||
try writer.writeAll("\n");
|
||||
|
||||
// Show the matching source code line if possible
|
||||
if (source_location) |sl| {
|
||||
if (printLineFromFile(out_stream, sl)) {
|
||||
if (printLineFromFile(writer, sl)) {
|
||||
if (sl.column > 0) {
|
||||
// The caret already takes one char
|
||||
const space_needed = @as(usize, @intCast(sl.column - 1));
|
||||
|
||||
try out_stream.writeByteNTimes(' ', space_needed);
|
||||
try tty_config.setColor(out_stream, .green);
|
||||
try out_stream.writeAll("^");
|
||||
try tty_config.setColor(out_stream, .reset);
|
||||
try writer.splatByteAll(' ', space_needed);
|
||||
try tty_config.setColor(writer, .green);
|
||||
try writer.writeAll("^");
|
||||
try tty_config.setColor(writer, .reset);
|
||||
}
|
||||
try out_stream.writeAll("\n");
|
||||
try writer.writeAll("\n");
|
||||
} else |err| switch (err) {
|
||||
error.EndOfFile, error.FileNotFound => {},
|
||||
error.BadPathName => {},
|
||||
@@ -1164,7 +1173,7 @@ fn printLineInfo(
|
||||
}
|
||||
}
|
||||
|
||||
fn printLineFromFileAnyOs(out_stream: anytype, source_location: SourceLocation) !void {
|
||||
fn printLineFromFileAnyOs(writer: *Writer, source_location: SourceLocation) !void {
|
||||
// Need this to always block even in async I/O mode, because this could potentially
|
||||
// be called from e.g. the event loop code crashing.
|
||||
var f = try fs.cwd().openFile(source_location.file_name, .{});
|
||||
@@ -1197,31 +1206,31 @@ fn printLineFromFileAnyOs(out_stream: anytype, source_location: SourceLocation)
|
||||
if (mem.indexOfScalar(u8, slice, '\n')) |pos| {
|
||||
const line = slice[0 .. pos + 1];
|
||||
mem.replaceScalar(u8, line, '\t', ' ');
|
||||
return out_stream.writeAll(line);
|
||||
return writer.writeAll(line);
|
||||
} else { // Line is the last inside the buffer, and requires another read to find delimiter. Alternatively the file ends.
|
||||
mem.replaceScalar(u8, slice, '\t', ' ');
|
||||
try out_stream.writeAll(slice);
|
||||
try writer.writeAll(slice);
|
||||
while (amt_read == buf.len) {
|
||||
amt_read = try f.read(buf[0..]);
|
||||
if (mem.indexOfScalar(u8, buf[0..amt_read], '\n')) |pos| {
|
||||
const line = buf[0 .. pos + 1];
|
||||
mem.replaceScalar(u8, line, '\t', ' ');
|
||||
return out_stream.writeAll(line);
|
||||
return writer.writeAll(line);
|
||||
} else {
|
||||
const line = buf[0..amt_read];
|
||||
mem.replaceScalar(u8, line, '\t', ' ');
|
||||
try out_stream.writeAll(line);
|
||||
try writer.writeAll(line);
|
||||
}
|
||||
}
|
||||
// Make sure printing last line of file inserts extra newline
|
||||
try out_stream.writeByte('\n');
|
||||
try writer.writeByte('\n');
|
||||
}
|
||||
}
|
||||
|
||||
test printLineFromFileAnyOs {
|
||||
var output = std.ArrayList(u8).init(std.testing.allocator);
|
||||
defer output.deinit();
|
||||
const output_stream = output.writer();
|
||||
var aw: Writer.Allocating = .init(std.testing.allocator);
|
||||
defer aw.deinit();
|
||||
const output_stream = &aw.interface;
|
||||
|
||||
const allocator = std.testing.allocator;
|
||||
const join = std.fs.path.join;
|
||||
@@ -1243,8 +1252,8 @@ test printLineFromFileAnyOs {
|
||||
try expectError(error.EndOfFile, printLineFromFileAnyOs(output_stream, .{ .file_name = path, .line = 2, .column = 0 }));
|
||||
|
||||
try printLineFromFileAnyOs(output_stream, .{ .file_name = path, .line = 1, .column = 0 });
|
||||
try expectEqualStrings("no new lines in this file, but one is printed anyway\n", output.items);
|
||||
output.clearRetainingCapacity();
|
||||
try expectEqualStrings("no new lines in this file, but one is printed anyway\n", aw.getWritten());
|
||||
aw.clearRetainingCapacity();
|
||||
}
|
||||
{
|
||||
const path = try fs.path.join(allocator, &.{ test_dir_path, "three_lines.zig" });
|
||||
@@ -1259,12 +1268,12 @@ test printLineFromFileAnyOs {
|
||||
});
|
||||
|
||||
try printLineFromFileAnyOs(output_stream, .{ .file_name = path, .line = 1, .column = 0 });
|
||||
try expectEqualStrings("1\n", output.items);
|
||||
output.clearRetainingCapacity();
|
||||
try expectEqualStrings("1\n", aw.getWritten());
|
||||
aw.clearRetainingCapacity();
|
||||
|
||||
try printLineFromFileAnyOs(output_stream, .{ .file_name = path, .line = 3, .column = 0 });
|
||||
try expectEqualStrings("3\n", output.items);
|
||||
output.clearRetainingCapacity();
|
||||
try expectEqualStrings("3\n", aw.getWritten());
|
||||
aw.clearRetainingCapacity();
|
||||
}
|
||||
{
|
||||
const file = try test_dir.dir.createFile("line_overlaps_page_boundary.zig", .{});
|
||||
@@ -1273,14 +1282,15 @@ test printLineFromFileAnyOs {
|
||||
defer allocator.free(path);
|
||||
|
||||
const overlap = 10;
|
||||
var writer = file.writer();
|
||||
try writer.writeByteNTimes('a', std.heap.page_size_min - overlap);
|
||||
var file_writer = file.writer(&.{});
|
||||
const writer = &file_writer.interface;
|
||||
try writer.splatByteAll('a', std.heap.page_size_min - overlap);
|
||||
try writer.writeByte('\n');
|
||||
try writer.writeByteNTimes('a', overlap);
|
||||
try writer.splatByteAll('a', overlap);
|
||||
|
||||
try printLineFromFileAnyOs(output_stream, .{ .file_name = path, .line = 2, .column = 0 });
|
||||
try expectEqualStrings(("a" ** overlap) ++ "\n", output.items);
|
||||
output.clearRetainingCapacity();
|
||||
try expectEqualStrings(("a" ** overlap) ++ "\n", aw.getWritten());
|
||||
aw.clearRetainingCapacity();
|
||||
}
|
||||
{
|
||||
const file = try test_dir.dir.createFile("file_ends_on_page_boundary.zig", .{});
|
||||
@@ -1288,12 +1298,13 @@ test printLineFromFileAnyOs {
|
||||
const path = try fs.path.join(allocator, &.{ test_dir_path, "file_ends_on_page_boundary.zig" });
|
||||
defer allocator.free(path);
|
||||
|
||||
var writer = file.writer();
|
||||
try writer.writeByteNTimes('a', std.heap.page_size_max);
|
||||
var file_writer = file.writer(&.{});
|
||||
const writer = &file_writer.interface;
|
||||
try writer.splatByteAll('a', std.heap.page_size_max);
|
||||
|
||||
try printLineFromFileAnyOs(output_stream, .{ .file_name = path, .line = 1, .column = 0 });
|
||||
try expectEqualStrings(("a" ** std.heap.page_size_max) ++ "\n", output.items);
|
||||
output.clearRetainingCapacity();
|
||||
try expectEqualStrings(("a" ** std.heap.page_size_max) ++ "\n", aw.getWritten());
|
||||
aw.clearRetainingCapacity();
|
||||
}
|
||||
{
|
||||
const file = try test_dir.dir.createFile("very_long_first_line_spanning_multiple_pages.zig", .{});
|
||||
@@ -1301,24 +1312,25 @@ test printLineFromFileAnyOs {
|
||||
const path = try fs.path.join(allocator, &.{ test_dir_path, "very_long_first_line_spanning_multiple_pages.zig" });
|
||||
defer allocator.free(path);
|
||||
|
||||
var writer = file.writer();
|
||||
try writer.writeByteNTimes('a', 3 * std.heap.page_size_max);
|
||||
var file_writer = file.writer(&.{});
|
||||
const writer = &file_writer.interface;
|
||||
try writer.splatByteAll('a', 3 * std.heap.page_size_max);
|
||||
|
||||
try expectError(error.EndOfFile, printLineFromFileAnyOs(output_stream, .{ .file_name = path, .line = 2, .column = 0 }));
|
||||
|
||||
try printLineFromFileAnyOs(output_stream, .{ .file_name = path, .line = 1, .column = 0 });
|
||||
try expectEqualStrings(("a" ** (3 * std.heap.page_size_max)) ++ "\n", output.items);
|
||||
output.clearRetainingCapacity();
|
||||
try expectEqualStrings(("a" ** (3 * std.heap.page_size_max)) ++ "\n", aw.getWritten());
|
||||
aw.clearRetainingCapacity();
|
||||
|
||||
try writer.writeAll("a\na");
|
||||
|
||||
try printLineFromFileAnyOs(output_stream, .{ .file_name = path, .line = 1, .column = 0 });
|
||||
try expectEqualStrings(("a" ** (3 * std.heap.page_size_max)) ++ "a\n", output.items);
|
||||
output.clearRetainingCapacity();
|
||||
try expectEqualStrings(("a" ** (3 * std.heap.page_size_max)) ++ "a\n", aw.getWritten());
|
||||
aw.clearRetainingCapacity();
|
||||
|
||||
try printLineFromFileAnyOs(output_stream, .{ .file_name = path, .line = 2, .column = 0 });
|
||||
try expectEqualStrings("a\n", output.items);
|
||||
output.clearRetainingCapacity();
|
||||
try expectEqualStrings("a\n", aw.getWritten());
|
||||
aw.clearRetainingCapacity();
|
||||
}
|
||||
{
|
||||
const file = try test_dir.dir.createFile("file_of_newlines.zig", .{});
|
||||
@@ -1326,18 +1338,19 @@ test printLineFromFileAnyOs {
|
||||
const path = try fs.path.join(allocator, &.{ test_dir_path, "file_of_newlines.zig" });
|
||||
defer allocator.free(path);
|
||||
|
||||
var writer = file.writer();
|
||||
var file_writer = file.writer(&.{});
|
||||
const writer = &file_writer.interface;
|
||||
const real_file_start = 3 * std.heap.page_size_min;
|
||||
try writer.writeByteNTimes('\n', real_file_start);
|
||||
try writer.splatByteAll('\n', real_file_start);
|
||||
try writer.writeAll("abc\ndef");
|
||||
|
||||
try printLineFromFileAnyOs(output_stream, .{ .file_name = path, .line = real_file_start + 1, .column = 0 });
|
||||
try expectEqualStrings("abc\n", output.items);
|
||||
output.clearRetainingCapacity();
|
||||
try expectEqualStrings("abc\n", aw.getWritten());
|
||||
aw.clearRetainingCapacity();
|
||||
|
||||
try printLineFromFileAnyOs(output_stream, .{ .file_name = path, .line = real_file_start + 2, .column = 0 });
|
||||
try expectEqualStrings("def\n", output.items);
|
||||
output.clearRetainingCapacity();
|
||||
try expectEqualStrings("def\n", aw.getWritten());
|
||||
aw.clearRetainingCapacity();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1461,7 +1474,8 @@ fn handleSegfaultPosix(sig: i32, info: *const posix.siginfo_t, ctx_ptr: ?*anyopa
|
||||
}
|
||||
|
||||
fn dumpSegfaultInfoPosix(sig: i32, code: i32, addr: usize, ctx_ptr: ?*anyopaque) void {
|
||||
const stderr = fs.File.stderr().writer();
|
||||
const stderr = lockStderrWriter(&.{});
|
||||
defer unlockStderrWriter();
|
||||
_ = switch (sig) {
|
||||
posix.SIG.SEGV => if (native_arch == .x86_64 and native_os == .linux and code == 128) // SI_KERNEL
|
||||
// x86_64 doesn't have a full 64-bit virtual address space.
|
||||
@@ -1471,7 +1485,7 @@ fn dumpSegfaultInfoPosix(sig: i32, code: i32, addr: usize, ctx_ptr: ?*anyopaque)
|
||||
// but can also happen when no addressable memory is involved;
|
||||
// for example when reading/writing model-specific registers
|
||||
// by executing `rdmsr` or `wrmsr` in user-space (unprivileged mode).
|
||||
stderr.print("General protection exception (no address available)\n", .{})
|
||||
stderr.writeAll("General protection exception (no address available)\n")
|
||||
else
|
||||
stderr.print("Segmentation fault at address 0x{x}\n", .{addr}),
|
||||
posix.SIG.ILL => stderr.print("Illegal instruction at address 0x{x}\n", .{addr}),
|
||||
@@ -1509,7 +1523,7 @@ fn dumpSegfaultInfoPosix(sig: i32, code: i32, addr: usize, ctx_ptr: ?*anyopaque)
|
||||
}, @ptrCast(ctx)).__mcontext_data;
|
||||
}
|
||||
relocateContext(&new_ctx);
|
||||
dumpStackTraceFromBase(&new_ctx);
|
||||
dumpStackTraceFromBase(&new_ctx, stderr);
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
@@ -1539,10 +1553,10 @@ fn handleSegfaultWindowsExtra(info: *windows.EXCEPTION_POINTERS, msg: u8, label:
|
||||
_ = panicking.fetchAdd(1, .seq_cst);
|
||||
|
||||
{
|
||||
lockStdErr();
|
||||
defer unlockStdErr();
|
||||
const stderr = lockStderrWriter(&.{});
|
||||
defer unlockStderrWriter();
|
||||
|
||||
dumpSegfaultInfoWindows(info, msg, label);
|
||||
dumpSegfaultInfoWindows(info, msg, label, stderr);
|
||||
}
|
||||
|
||||
waitForOtherThreadToFinishPanicking();
|
||||
@@ -1556,8 +1570,7 @@ fn handleSegfaultWindowsExtra(info: *windows.EXCEPTION_POINTERS, msg: u8, label:
|
||||
posix.abort();
|
||||
}
|
||||
|
||||
fn dumpSegfaultInfoWindows(info: *windows.EXCEPTION_POINTERS, msg: u8, label: ?[]const u8) void {
|
||||
const stderr = fs.File.stderr().writer();
|
||||
fn dumpSegfaultInfoWindows(info: *windows.EXCEPTION_POINTERS, msg: u8, label: ?[]const u8, stderr: *Writer) void {
|
||||
_ = switch (msg) {
|
||||
0 => stderr.print("{s}\n", .{label.?}),
|
||||
1 => stderr.print("Segmentation fault at address 0x{x}\n", .{info.ExceptionRecord.ExceptionInformation[1]}),
|
||||
@@ -1565,7 +1578,7 @@ fn dumpSegfaultInfoWindows(info: *windows.EXCEPTION_POINTERS, msg: u8, label: ?[
|
||||
else => unreachable,
|
||||
} catch posix.abort();
|
||||
|
||||
dumpStackTraceFromBase(info.ContextRecord);
|
||||
dumpStackTraceFromBase(info.ContextRecord, stderr);
|
||||
}
|
||||
|
||||
pub fn dumpStackPointerAddr(prefix: []const u8) void {
|
||||
@@ -1588,10 +1601,10 @@ test "manage resources correctly" {
|
||||
// self-hosted debug info is still too buggy
|
||||
if (builtin.zig_backend != .stage2_llvm) return error.SkipZigTest;
|
||||
|
||||
const writer = std.io.null_writer;
|
||||
var writer: std.io.Writer = .discarding(&.{});
|
||||
var di = try SelfInfo.open(testing.allocator);
|
||||
defer di.deinit();
|
||||
try printSourceAtAddress(&di, writer, showMyTrace(), io.tty.detectConfig(std.fs.File.stderr()));
|
||||
try printSourceAtAddress(&di, &writer, showMyTrace(), io.tty.detectConfig(.stderr()));
|
||||
}
|
||||
|
||||
noinline fn showMyTrace() usize {
|
||||
@@ -1657,8 +1670,9 @@ pub fn ConfigurableTrace(comptime size: usize, comptime stack_frame_count: usize
|
||||
pub fn dump(t: @This()) void {
|
||||
if (!enabled) return;
|
||||
|
||||
const tty_config = io.tty.detectConfig(std.fs.File.stderr());
|
||||
const stderr = fs.File.stderr().writer();
|
||||
const tty_config = io.tty.detectConfig(.stderr());
|
||||
const stderr = lockStderrWriter(&.{});
|
||||
defer unlockStderrWriter();
|
||||
const end = @min(t.index, size);
|
||||
const debug_info = getSelfDebugInfo() catch |err| {
|
||||
stderr.print(
|
||||
@@ -1688,7 +1702,7 @@ pub fn ConfigurableTrace(comptime size: usize, comptime stack_frame_count: usize
|
||||
t: @This(),
|
||||
comptime fmt: []const u8,
|
||||
options: std.fmt.FormatOptions,
|
||||
writer: anytype,
|
||||
writer: *Writer,
|
||||
) !void {
|
||||
if (fmt.len != 0) std.fmt.invalidFmtError(fmt, t);
|
||||
_ = options;
|
||||
|
||||
Reference in New Issue
Block a user