Commit dfc4d618dd

Wooster <wooster0@proton.me>
2023-08-03 09:02:24
general-use std.debug.hexdump for printing hexdumps
Recently, when I've been working with structures of data that is not directly in RAM but rather laid out in bytes somewhere else, it was always very useful to print out maybe the next 50 bytes or the previous 50 bytes or so to see what's ahead or before me. I would usually do this with a quick `std.debug.print("{any}\n", .{bytes});` or something but the output is not as nice obviously.
1 parent f597818
Changed files (1)
lib
lib/std/debug.zig
@@ -104,6 +104,63 @@ pub fn getSelfDebugInfo() !*DebugInfo {
     }
 }
 
+/// Tries to print a hexadecimal view of the bytes, unbuffered, and ignores any error returned.
+pub fn hexdump(bytes: []const u8) void {
+    hexdump_internal(bytes) catch {};
+}
+
+fn hexdump_internal(bytes: []const u8) !void {
+    const stderr = std.io.getStdErr();
+    const ttyconf = std.io.tty.detectConfig(stderr);
+    const writer = stderr.writer();
+    var chunks = mem.window(u8, bytes, 16, 16);
+    while (chunks.next()) |window| {
+        // 1. Print the address.
+        const address = (@intFromPtr(bytes.ptr) + 0x10 * (chunks.index orelse 0) / 16) - 0x10;
+        try ttyconf.setColor(writer, .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);
+
+        // 2. Print the bytes.
+        for (window, 0..) |byte, index| {
+            try writer.print("{X:0>2} ", .{byte});
+            if (index == 7) try writer.writeByte(' ');
+        }
+        try writer.writeByte(' ');
+        if (window.len < 16) {
+            var missing_columns = (16 - window.len) * 3;
+            if (window.len < 8) missing_columns += 1;
+            try writer.writeByteNTimes(' ', missing_columns);
+        }
+
+        // 3. Print the characters.
+        for (window) |byte| {
+            if (std.ascii.isPrint(byte)) {
+                try writer.writeByte(byte);
+            } else {
+                // TODO: remove this `if` when https://github.com/ziglang/zig/issues/7600 is fixed
+                if (ttyconf == .windows_api) {
+                    try writer.writeByte('.');
+                    continue;
+                }
+
+                // Let's print some common control codes as graphical Unicode symbols.
+                // 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('.'),
+                }
+            }
+        }
+        try writer.writeByte('\n');
+    }
+}
+
 /// 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 {