hexdump: add hexdump_memcmp()

Add a helper producing a hexdump(3)-like side-by-side dump of two memory
regions, marking the bytes that differ. Meant for assertion failure
messages.
This commit is contained in:
Yu Watanabe committed 2026-11-05 03:05:09 +09:00
1 parent 9b4509d2a6
commit b26ac01e42
3 files changed
+104

No files matched your search

+76
View File
@@ -5,6 +5,7 @@
#include <string.h>
#include "hexdump.h"
#include "memstream-util.h"
void hexdump(FILE *f, const void *p, size_t s) {
const uint8_t *b = p;
@@ -54,3 +55,78 @@ void hexdump(FILE *f, const void *p, size_t s) {
s -= 16;
}
}
static void dump_hex_line(FILE *f, char tag, const uint8_t *p, size_t size, size_t off) {
assert(f);
fprintf(f, "%c %04zx ", tag, off);
for (size_t i = 0; i < 16; i++) {
if (off + i < size)
fprintf(f, "%02x ", p[off + i]);
else
fputs(" ", f);
if (i == 7)
fputc(' ', f);
}
fputs(" |", f);
for (size_t i = 0; i < 16; i++) {
if (off + i < size) {
uint8_t c = p[off + i];
fputc(isprint(c) ? (char) c : '.', f);
} else
fputc(' ', f);
}
fputs("|\n", f);
}
char* hexdump_memcmp(const void *m1, size_t l1, const void *m2, size_t l2) {
_cleanup_(memstream_done) MemStream m = {};
const uint8_t *a = m1, *b = m2;
size_t mx = MAX(l1, l2);
FILE *f;
/* Returns a hexdump(3)-like side-by-side representation of two memory regions. For each 16 byte
* chunk that differs a marker line pointing at the diverging bytes ('<' is the first region, '>' the
* second) is appended. */
f = memstream_init(&m);
if (!f)
return NULL;
for (size_t off = 0; off < mx; off += 16) {
dump_hex_line(f, '<', a, l1, off);
dump_hex_line(f, '>', b, l2, off);
/* Figure out whether this chunk differs at all, and only then emit the marker line. */
bool any = false;
for (size_t i = 0; i < 16 && off + i < mx; i++) {
bool pa = off + i < l1, pb = off + i < l2;
if (pa != pb || (pa && pb && a[off + i] != b[off + i])) {
any = true;
break;
}
}
if (!any)
continue;
fputs(" ", f); /* align with the 8-char "%c %04zx " prefix: tag + space + 4 hex + 2 spaces */
for (size_t i = 0; i < 16; i++) {
bool pa = off + i < l1, pb = off + i < l2;
bool diff = pa != pb || (pa && pb && a[off + i] != b[off + i]);
fputs(diff ? "^^ " : " ", f);
if (i == 7)
fputc(' ', f);
}
fputc('\n', f);
}
char *ret;
if (memstream_finalize(&m, &ret, NULL) < 0)
return NULL;
return ret;
}
+4
View File
@@ -4,3 +4,7 @@
#include "forward.h"
void hexdump(FILE *f, const void *p, size_t s) _nonnull_if_nonzero_(2, 3);
/* Returns a hexdump(3)-like side-by-side dump of two memory regions, marking the differing bytes. Returns
* NULL on OOM. Meant for assertion failure messages, see ASSERT_MEMEQ() and friends. */
char* hexdump_memcmp(const void *m1, size_t l1, const void *m2, size_t l2);
+24
View File
@@ -1,5 +1,6 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include "alloc-util.h"
#include "hexdump.h"
#include "tests.h"
@@ -26,4 +27,27 @@ TEST(hexdump) {
hexdump(stdout, data, sizeof(data));
}
TEST(hexdump_memcmp) {
static const uint8_t a[] = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
0x10, 0x11 };
static const uint8_t b[] = { 0x00, 0x01, 0xff, 0x03, 0x04, 0x05, 0x06, 0x07,
0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
0x10 };
/* Equal regions: no marker lines, just the two sides. */
_cleanup_free_ char *s = ASSERT_NOT_NULL(hexdump_memcmp(a, sizeof(a), a, sizeof(a)));
log_info("equal:\n%s", s);
/* Differing byte and differing length. */
free(s);
s = ASSERT_NOT_NULL(hexdump_memcmp(a, sizeof(a), b, sizeof(b)));
log_info("differ:\n%s", s);
/* Empty regions are fine. */
free(s);
s = ASSERT_NOT_NULL(hexdump_memcmp(NULL, 0, NULL, 0));
ASSERT_STREQ(s, "");
}
DEFINE_TEST_MAIN(LOG_INFO);