Skip to content

Commit

Permalink
[sw, lib] Add a hexdump function in the style of xxd
Browse files Browse the repository at this point in the history
Signed-off-by: Miguel Young de la Sota <[email protected]>
  • Loading branch information
mcy committed Mar 28, 2022
1 parent 8d507ad commit d2d5fe5
Show file tree
Hide file tree
Showing 3 changed files with 219 additions and 0 deletions.
104 changes: 104 additions & 0 deletions sw/device/lib/runtime/print.c
Original file line number Diff line number Diff line change
Expand Up @@ -512,3 +512,107 @@ size_t base_vfprintf(buffer_sink_t out, const char *format, va_list args) {
va_end(args_copy);
return bytes_written;
}

const char kBaseHexdumpDefaultFmtAlphabet[256] =
// clang-format off
// First 32 characters are not printable.
"................................"
// Printable ASCII.
" !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~"
// The rest of the range is also not printable (129 characters).
"................................................................"
"................................................................"
".";
// clang-format on

static const base_hexdump_fmt_t kBaseHexdumpDefaultFmt = {
.bytes_per_word = 2,
.words_per_line = 8,
.alphabet = &kBaseHexdumpDefaultFmtAlphabet,
};

size_t base_hexdump(const char *buf, size_t len) {
return base_hexdump_with(kBaseHexdumpDefaultFmt, buf, len);
}

size_t base_snhexdump(char *out, size_t out_len, const char *buf, size_t len) {
return base_snhexdump_with(out, out_len, kBaseHexdumpDefaultFmt, buf, len);
}

size_t base_fhexdump(buffer_sink_t out, const char *buf, size_t len) {
return base_fhexdump_with(out, kBaseHexdumpDefaultFmt, buf, len);
}

size_t base_hexdump_with(base_hexdump_fmt_t fmt, const char *buf, size_t len) {
return base_fhexdump_with(base_stdout, fmt, buf, len);
}

size_t base_snhexdump_with(char *out, size_t out_len, base_hexdump_fmt_t fmt,
const char *buf, size_t len) {
snprintf_captures_t data = {
.buf = out,
.bytes_left = out_len,
};
buffer_sink_t sink = {
.data = &data,
.sink = &snprintf_sink,
};
return base_fhexdump_with(sink, fmt, buf, len);
}

size_t base_fhexdump_with(buffer_sink_t out, base_hexdump_fmt_t fmt,
const char *buf, size_t len) {
size_t bytes_written = 0;
size_t bytes_per_line = fmt.bytes_per_word * fmt.words_per_line;

for (size_t line = 0; line < len; line += bytes_per_line) {
bytes_written += base_fprintf(out, "%08x:", line);

size_t chars_per_line = bytes_per_line * 2 + fmt.words_per_line;
size_t line_bytes_written = 0;
for (size_t word = 0; word < bytes_per_line; word += fmt.bytes_per_word) {
if (len < line + word) {
char spaces[16] = " ";
while (line_bytes_written < chars_per_line) {
size_t to_print = chars_per_line - line_bytes_written;
if (to_print > sizeof(spaces)) {
to_print = sizeof(spaces);
}
line_bytes_written += base_fprintf(out, "%!s", to_print, spaces);
}
break;
}

size_t bytes_left = len - line - word;
if (bytes_left > fmt.bytes_per_word) {
bytes_left = fmt.bytes_per_word;
}
line_bytes_written += base_fprintf(out, " ");
line_bytes_written +=
hex_dump(out, buf + line + word, bytes_left, bytes_left, '0',
/*big_endian=*/false, kDigitsLow);
}
bytes_written += line_bytes_written;

bytes_written += base_fprintf(out, " ");
size_t buffered = 0;
char glyph_buffer[16];
for (size_t byte = 0; byte < bytes_per_line; ++byte) {
if (buffered == sizeof(glyph_buffer)) {
bytes_written += base_fprintf(out, "%!s", buffered, glyph_buffer);
buffered = 0;
}
if (line + byte >= len) {
break;
}
glyph_buffer[buffered++] =
(*fmt.alphabet)[(size_t)(uint8_t)buf[line + byte]];
}
if (buffered > 0) {
bytes_written += base_fprintf(out, "%!s", buffered, glyph_buffer);
}
bytes_written += base_fprintf(out, "\n");
}

return bytes_written;
}
92 changes: 92 additions & 0 deletions sw/device/lib/runtime/print.h
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,98 @@ size_t base_fprintf(buffer_sink_t out, const char *format, ...);
*/
size_t base_vfprintf(buffer_sink_t out, const char *format, va_list args);

/**
* Configuration options for `base_hexdump` and friends.
*/
typedef struct base_hexdump_fmt {
/** How many bytes to print per word of output. */
size_t bytes_per_word;
/** How many words (as defined above) per line of output. */
size_t words_per_line;
/**
* The alphabet to use for char-ifying a byte.
*
* These characters will be written as-is to the sink.
*/
const char (*alphabet)[256];
} base_hexdump_fmt_t;

/**
* The default alphabet used by `base_hexdump()` functions.
*/
extern const char kBaseHexdumpDefaultFmtAlphabet[256];

/**
* Dumps `hex` in an xxd-style hexdump to stdout, using default formatting
* options.
*
* @param buf the buffer to dump.
* @param len the number of bytes to dump from hex.
*/
size_t base_hexdump(const char *buf, size_t len);

/**
* Dumps `hex` in an xxd-style hexdump to the buffer `buf`, capped at the given
* length, using default formatting options.
*
* @param out a buffer to print to.
* @param out_len the length of the output buffer.
* @param buf the buffer to dump.
* @param len the number of bytes to dump from hex.
*/
size_t base_snhexdump(char *out, size_t out_len, const char *buf, size_t len);

/**
* Dumps `hex` in an xxd-style hexdump to `out`, using default formatting
* options.
*
* If `out.sink` is `NULL`, writes are treated as-if they were written to a
* UNIX-like /dev/null: writes succeed, but the actual bytes are not printed
* anywhere.
*
* @param out a sink to print to.
* @param buf the buffer to dump.
* @param len the number of bytes to dump from hex.
*/
size_t base_fhexdump(buffer_sink_t out, const char *buf, size_t len);

/**
* Dumps `hex` in an xxd-style hexdump to stdout.
*
* @param fmt the format for dumping.
* @param buf the buffer to dump.
* @param len the number of bytes to dump from hex.
*/
size_t base_hexdump_with(base_hexdump_fmt_t fmt, const char *buf, size_t len);

/**
* Dumps `hex` in an xxd-style hexdump to the buffer `buf`, capped at the given
* length.
*
* @param out a buffer to print to.
* @param out_len the length of the output buffer.
* @param fmt the format for dumping.
* @param buf the buffer to dump.
* @param len the number of bytes to dump from hex.
*/
size_t base_snhexdump_with(char *out, size_t out_len, base_hexdump_fmt_t fmt,
const char *buf, size_t len);

/**
* Dumps `hex` in an xxd-style hexdump to `out`.
*
* If `out.sink` is `NULL`, writes are treated as-if they were written to a
* UNIX-like /dev/null: writes succeed, but the actual bytes are not printed
* anywhere.
*
* @param out a sink to print to.
* @param fmt the format for dumping.
* @param buf the buffer to dump.
* @param len the number of bytes to dump from hex.
*/
size_t base_fhexdump_with(buffer_sink_t out, base_hexdump_fmt_t fmt,
const char *buf, size_t len);

/**
* Sets what the "stdout" sink is, which is used by `base_printf()`.
*
Expand Down
23 changes: 23 additions & 0 deletions sw/device/lib/runtime/print_unittest.cc
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,29 @@ TEST_F(PrintfTest, ManySpecifiers) {
EXPECT_THAT(buf_, StartsWith("2 + 8 == 10, also spelled 0xa"));
}

TEST_F(PrintfTest, HexDump) {
constexpr char kStuff[] =
"a very long string pot\x12\x02\xAA entially containing garbage";
base_hexdump(kStuff, sizeof(kStuff) - 1);
EXPECT_THAT(
buf_,
R"hex(00000000: 6120 7665 7279 206c 6f6e 6720 7374 7269 a very long stri
00000010: 6e67 2070 6f74 1202 aa20 656e 7469 616c ng pot... ential
00000020: 6c79 2063 6f6e 7461 696e 696e 6720 6761 ly containing ga
00000030: 7262 6167 65 rbage
)hex");

buf_.clear();
base_hexdump_with({3, 5, &kBaseHexdumpDefaultFmtAlphabet}, kStuff,
sizeof(kStuff) - 1);
EXPECT_THAT(
buf_, R"hex(00000000: 612076 657279 206c6f 6e6720 737472 a very long str
0000000f: 696e67 20706f 741202 aa2065 6e7469 ing pot... enti
0000001e: 616c6c 792063 6f6e74 61696e 696e67 ally containing
0000002d: 206761 726261 6765 garbage
)hex");
}

TEST(SnprintfTest, SimpleWrite) {
std::string buf(128, '\0');
auto len = base_snprintf(&buf[0], buf.size(), "Hello, World!\n");
Expand Down

0 comments on commit d2d5fe5

Please sign in to comment.