authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2024-01-08 16:25:15-08:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2024-01-08 16:25:15-08:00
logdeed19496a98a22ed39025721d448ce2f47642ea
tree6c42e2d891058c8f0cc5896c8738d8c6d8a85bbc
parent4cf08932b51ab433933f5b3059d0cab90acc9696
parent25d2e7fce04d5cbe63331cc56ab7bafe89c249c4
signaturebadge-question-mark Signed by PGP key 4AEE18F83AFDEB23

Merge pull request #16650 from wooster0/hexdump

general-use std.debug.hexdump for printing hexdumps

2 files changed, 101 insertions(+), 33 deletions(-)

lib/std/debug.zig+65
...@@ -104,6 +104,67 @@ pub fn getSelfDebugInfo() !*DebugInfo {...@@ -104,6 +104,67 @@ pub fn getSelfDebugInfo() !*DebugInfo {
104 }104 }
105}105}
106106
107/// Tries to print a hexadecimal view of the bytes, unbuffered, and ignores any error returned.
108/// Obtains the stderr mutex while dumping.
109pub fn dump_hex(bytes: []const u8) void {
110 stderr_mutex.lock();
111 defer stderr_mutex.unlock();
112 dump_hex_fallible(bytes) catch {};
113}
114
115/// Prints a hexadecimal view of the bytes, unbuffered, returning any error that occurs.
116pub fn dump_hex_fallible(bytes: []const u8) !void {
117 const stderr = std.io.getStdErr();
118 const ttyconf = std.io.tty.detectConfig(stderr);
119 const writer = stderr.writer();
120 var chunks = mem.window(u8, bytes, 16, 16);
121 while (chunks.next()) |window| {
122 // 1. Print the address.
123 const address = (@intFromPtr(bytes.ptr) + 0x10 * (chunks.index orelse 0) / 16) - 0x10;
124 try ttyconf.setColor(writer, .dim);
125 // We print the address in lowercase and the bytes in uppercase hexadecimal to distinguish them more.
126 // Also, make sure all lines are aligned by padding the address.
127 try writer.print("{x:0>[1]} ", .{ address, @sizeOf(usize) * 2 });
128 try ttyconf.setColor(writer, .reset);
129
130 // 2. Print the bytes.
131 for (window, 0..) |byte, index| {
132 try writer.print("{X:0>2} ", .{byte});
133 if (index == 7) try writer.writeByte(' ');
134 }
135 try writer.writeByte(' ');
136 if (window.len < 16) {
137 var missing_columns = (16 - window.len) * 3;
138 if (window.len < 8) missing_columns += 1;
139 try writer.writeByteNTimes(' ', missing_columns);
140 }
141
142 // 3. Print the characters.
143 for (window) |byte| {
144 if (std.ascii.isPrint(byte)) {
145 try writer.writeByte(byte);
146 } else {
147 // Related: https://github.com/ziglang/zig/issues/7600
148 if (ttyconf == .windows_api) {
149 try writer.writeByte('.');
150 continue;
151 }
152
153 // Let's print some common control codes as graphical Unicode symbols.
154 // We don't want to do this for all control codes because most control codes apart from
155 // the ones that Zig has escape sequences for are likely not very useful to print as symbols.
156 switch (byte) {
157 '\n' => try writer.writeAll("␊"),
158 '\r' => try writer.writeAll("␍"),
159 '\t' => try writer.writeAll("␉"),
160 else => try writer.writeByte('.'),
161 }
162 }
163 }
164 try writer.writeByte('\n');
165 }
166}
167
107/// Tries to print the current stack trace to stderr, unbuffered, and ignores any error returned.168/// Tries to print the current stack trace to stderr, unbuffered, and ignores any error returned.
108/// TODO multithreaded awareness169/// TODO multithreaded awareness
109pub fn dumpCurrentStackTrace(start_addr: ?usize) void {170pub fn dumpCurrentStackTrace(start_addr: ?usize) void {
...@@ -2774,3 +2835,7 @@ pub fn ConfigurableTrace(comptime size: usize, comptime stack_frame_count: usize...@@ -2774,3 +2835,7 @@ pub fn ConfigurableTrace(comptime size: usize, comptime stack_frame_count: usize
2774 }2835 }
2775 };2836 };
2776}2837}
2838
2839test {
2840 _ = &dump_hex;
2841}
lib/std/testing.zig+36-33
...@@ -339,7 +339,8 @@ pub fn expectEqualSlices(comptime T: type, expected: []const T, actual: []const...@@ -339,7 +339,8 @@ pub fn expectEqualSlices(comptime T: type, expected: []const T, actual: []const
339 const actual_window = actual[window_start..@min(actual.len, window_start + max_window_size)];339 const actual_window = actual[window_start..@min(actual.len, window_start + max_window_size)];
340 const actual_truncated = window_start + actual_window.len < actual.len;340 const actual_truncated = window_start + actual_window.len < actual.len;
341341
342 const ttyconf = std.io.tty.detectConfig(std.io.getStdErr());342 const stderr = std.io.getStdErr();
343 const ttyconf = std.io.tty.detectConfig(stderr);
343 var differ = if (T == u8) BytesDiffer{344 var differ = if (T == u8) BytesDiffer{
344 .expected = expected_window,345 .expected = expected_window,
345 .actual = actual_window,346 .actual = actual_window,
...@@ -350,7 +351,6 @@ pub fn expectEqualSlices(comptime T: type, expected: []const T, actual: []const...@@ -350,7 +351,6 @@ pub fn expectEqualSlices(comptime T: type, expected: []const T, actual: []const
350 .actual = actual_window,351 .actual = actual_window,
351 .ttyconf = ttyconf,352 .ttyconf = ttyconf,
352 };353 };
353 const stderr = std.io.getStdErr();
354354
355 // Print indexes as hex for slices of u8 since it's more likely to be binary data where355 // Print indexes as hex for slices of u8 since it's more likely to be binary data where
356 // that is usually useful.356 // that is usually useful.
...@@ -432,16 +432,17 @@ const BytesDiffer = struct {...@@ -432,16 +432,17 @@ const BytesDiffer = struct {
432 ttyconf: std.io.tty.Config,432 ttyconf: std.io.tty.Config,
433433
434 pub fn write(self: BytesDiffer, writer: anytype) !void {434 pub fn write(self: BytesDiffer, writer: anytype) !void {
435 var expected_iterator = ChunkIterator{ .bytes = self.expected };435 var expected_iterator = std.mem.window(u8, self.expected, 16, 16);
436 var row: usize = 0;
436 while (expected_iterator.next()) |chunk| {437 while (expected_iterator.next()) |chunk| {
437 // to avoid having to calculate diffs twice per chunk438 // to avoid having to calculate diffs twice per chunk
438 var diffs: std.bit_set.IntegerBitSet(16) = .{ .mask = 0 };439 var diffs: std.bit_set.IntegerBitSet(16) = .{ .mask = 0 };
439 for (chunk, 0..) |byte, i| {440 for (chunk, 0..) |byte, col| {
440 const absolute_byte_index = (expected_iterator.index - chunk.len) + i;441 const absolute_byte_index = col + row * 16;
441 const diff = if (absolute_byte_index < self.actual.len) self.actual[absolute_byte_index] != byte else true;442 const diff = if (absolute_byte_index < self.actual.len) self.actual[absolute_byte_index] != byte else true;
442 if (diff) diffs.set(i);443 if (diff) diffs.set(col);
443 try self.writeByteDiff(writer, "{X:0>2} ", byte, diff);444 try self.writeDiff(writer, "{X:0>2} ", .{byte}, diff);
444 if (i == 7) try writer.writeByte(' ');445 if (col == 7) try writer.writeByte(' ');
445 }446 }
446 try writer.writeByte(' ');447 try writer.writeByte(' ');
447 if (chunk.len < 16) {448 if (chunk.len < 16) {
...@@ -449,33 +450,38 @@ const BytesDiffer = struct {...@@ -449,33 +450,38 @@ const BytesDiffer = struct {
449 if (chunk.len < 8) missing_columns += 1;450 if (chunk.len < 8) missing_columns += 1;
450 try writer.writeByteNTimes(' ', missing_columns);451 try writer.writeByteNTimes(' ', missing_columns);
451 }452 }
452 for (chunk, 0..) |byte, i| {453 for (chunk, 0..) |byte, col| {
453 const byte_to_print = if (std.ascii.isPrint(byte)) byte else '.';454 const diff = diffs.isSet(col);
454 try self.writeByteDiff(writer, "{c}", byte_to_print, diffs.isSet(i));455 if (std.ascii.isPrint(byte)) {
456 try self.writeDiff(writer, "{c}", .{byte}, diff);
457 } else {
458 // TODO: remove this `if` when https://github.com/ziglang/zig/issues/7600 is fixed
459 if (self.ttyconf == .windows_api) {
460 try self.writeDiff(writer, ".", .{}, diff);
461 continue;
462 }
463
464 // Let's print some common control codes as graphical Unicode symbols.
465 // We don't want to do this for all control codes because most control codes apart from
466 // the ones that Zig has escape sequences for are likely not very useful to print as symbols.
467 switch (byte) {
468 '\n' => try self.writeDiff(writer, "␊", .{}, diff),
469 '\r' => try self.writeDiff(writer, "␍", .{}, diff),
470 '\t' => try self.writeDiff(writer, "␉", .{}, diff),
471 else => try self.writeDiff(writer, ".", .{}, diff),
472 }
473 }
455 }474 }
456 try writer.writeByte('\n');475 try writer.writeByte('\n');
476 row += 1;
457 }477 }
458 }478 }
459479
460 fn writeByteDiff(self: BytesDiffer, writer: anytype, comptime fmt: []const u8, byte: u8, diff: bool) !void {480 fn writeDiff(self: BytesDiffer, writer: anytype, comptime fmt: []const u8, args: anytype, diff: bool) !void {
461 if (diff) try self.ttyconf.setColor(writer, .red);481 if (diff) try self.ttyconf.setColor(writer, .red);
462 try writer.print(fmt, .{byte});482 try writer.print(fmt, args);
463 if (diff) try self.ttyconf.setColor(writer, .reset);483 if (diff) try self.ttyconf.setColor(writer, .reset);
464 }484 }
465
466 const ChunkIterator = struct {
467 bytes: []const u8,
468 index: usize = 0,
469
470 pub fn next(self: *ChunkIterator) ?[]const u8 {
471 if (self.index == self.bytes.len) return null;
472
473 const start_index = self.index;
474 const end_index = @min(self.bytes.len, start_index + 16);
475 self.index = end_index;
476 return self.bytes[start_index..end_index];
477 }
478 };
479};485};
480486
481test {487test {
...@@ -926,11 +932,8 @@ fn printIndicatorLine(source: []const u8, indicator_index: usize) void {...@@ -926,11 +932,8 @@ fn printIndicatorLine(source: []const u8, indicator_index: usize) void {
926 source.len;932 source.len;
927933
928 printLine(source[line_begin_index..line_end_index]);934 printLine(source[line_begin_index..line_end_index]);
929 {935 for (line_begin_index..indicator_index) |_|
930 var i: usize = line_begin_index;936 print(" ", .{});
931 while (i < indicator_index) : (i += 1)
932 print(" ", .{});
933 }
934 if (indicator_index >= source.len)937 if (indicator_index >= source.len)
935 print("^ (end of string)\n", .{})938 print("^ (end of string)\n", .{})
936 else939 else
...@@ -947,7 +950,7 @@ fn printWithVisibleNewlines(source: []const u8) void {...@@ -947,7 +950,7 @@ fn printWithVisibleNewlines(source: []const u8) void {
947950
948fn printLine(line: []const u8) void {951fn printLine(line: []const u8) void {
949 if (line.len != 0) switch (line[line.len - 1]) {952 if (line.len != 0) switch (line[line.len - 1]) {
950 ' ', '\t' => return print("{s}⏎\n", .{line}), // Carriage return symbol,953 ' ', '\t' => return print("{s}⏎\n", .{line}), // Return symbol
951 else => {},954 else => {},
952 };955 };
953 print("{s}\n", .{line});956 print("{s}\n", .{line});