From 6f5579c88a30b322cc604f65580f088b1e738d1c Mon Sep 17 00:00:00 2001 From: "Richard W.M. Jones" Date: Fri, 19 Dec 2025 13:41:43 +0000 Subject: [PATCH] server: Add nbdkit_debug_hexdump function This emits a buffer to debug output in the same format as 'hexdump -C'. --- .gitignore | 1 + docs/Makefile.am | 7 ++ docs/nbdkit-plugin.pod | 1 + docs/nbdkit_debug.pod | 1 + docs/nbdkit_debug_hexdump.pod | 54 +++++++++++ include/nbdkit-common.h | 3 + server/debug.c | 72 ++++++++++++++ server/nbdkit.syms | 1 + tests/Makefile.am | 26 +++++ tests/test-debug-hexdump-plugin.c | 85 +++++++++++++++++ tests/test-debug-hexdump.sh | 154 ++++++++++++++++++++++++++++++ 11 files changed, 405 insertions(+) create mode 100644 docs/nbdkit_debug_hexdump.pod create mode 100755 tests/test-debug-hexdump-plugin.c create mode 100755 tests/test-debug-hexdump.sh diff --git a/.gitignore b/.gitignore index ecb76feb..3562a665 100644 --- a/.gitignore +++ b/.gitignore @@ -28,6 +28,7 @@ __pycache__ docs/*.1 docs/nbdkit_debug.3 +docs/nbdkit_debug_hexdump.3 docs/nbdkit_error.3 docs/nbdkit_export_name.3 docs/nbdkit-filter.3 diff --git a/docs/Makefile.am b/docs/Makefile.am index 9e3a4d75..240d9e1d 100644 --- a/docs/Makefile.am +++ b/docs/Makefile.am @@ -38,6 +38,7 @@ EXTRA_DIST = \ nbdkit-captive.pod \ nbdkit-client.pod \ nbdkit_debug.pod \ + nbdkit_debug_hexdump.pod \ nbdkit_error.pod \ nbdkit_export_name.pod \ nbdkit-filter.pod \ @@ -126,6 +127,7 @@ generated_mans = \ nbdkit-captive.1 \ nbdkit-client.1 \ nbdkit_debug.3 \ + nbdkit_debug_hexdump.3 \ nbdkit_error.3 \ nbdkit_export_name.3 \ nbdkit-filter.3 \ @@ -206,6 +208,11 @@ nbdkit_debug.3: nbdkit_debug.pod $(top_builddir)/podwrapper.pl --html $(top_builddir)/html/$@.html \ $< +nbdkit_debug_hexdump.3: nbdkit_debug_hexdump.pod $(top_builddir)/podwrapper.pl + $(PODWRAPPER) --section=3 --man $@ \ + --html $(top_builddir)/html/$@.html \ + $< + nbdkit_error.3: nbdkit_error.pod $(top_builddir)/podwrapper.pl $(PODWRAPPER) --section=3 --man $@ \ --html $(top_builddir)/html/$@.html \ diff --git a/docs/nbdkit-plugin.pod b/docs/nbdkit-plugin.pod index 6310fff1..61deb63b 100644 --- a/docs/nbdkit-plugin.pod +++ b/docs/nbdkit-plugin.pod @@ -1624,6 +1624,7 @@ Utility functions provided by nbdkit for plugins and filters to use: L, L, +L, L, L, L, diff --git a/docs/nbdkit_debug.pod b/docs/nbdkit_debug.pod index 638f2726..43bb5a41 100644 --- a/docs/nbdkit_debug.pod +++ b/docs/nbdkit_debug.pod @@ -57,6 +57,7 @@ C was present in nbdkit 0.1.0. =head1 SEE ALSO L, +L, L, L, L. diff --git a/docs/nbdkit_debug_hexdump.pod b/docs/nbdkit_debug_hexdump.pod new file mode 100644 index 00000000..c48e8eae --- /dev/null +++ b/docs/nbdkit_debug_hexdump.pod @@ -0,0 +1,54 @@ +=head1 NAME + +nbdkit_debug_hexdump - display buffer in hexdump format + +=head1 SYNOPSIS + + #include + + void nbdkit_debug_hexdump (const void *buf, size_t len, + const char *prefix, uint64_t start); + +=head1 DESCRIPTION + +This function displays a buffer of binary data in canonical hexdump +format, sending the output to the same place as L. +For example: + + char buf[33] = "12345678123456781234567812345678"; + nbdkit_debug_hexdump (buf, 32, "data: ", 0); + +would produce output similar to this: + + data: 00000000: 31 32 33 34 35 36 37 38 31 32 33 34 35 36 37 38 |1234567812345678| + data: 00000010: 31 32 33 34 35 36 37 38 31 32 33 34 35 36 37 38 |1234567812345678| + +An optional C may be given which prefixes the string on each +line of output. (This may be C or C<""> for no prefix). + +An optional C may be given which changes the first offset +displayed in the output. + +=head1 LANGUAGE BINDINGS + +(There are no language bindings of this function in the current version.) + +=head1 HISTORY + +C was added in nbdkit 1.46. + +=head1 SEE ALSO + +L, +L, +L, +L, +L. + +=head1 AUTHORS + +Richard W.M. Jones + +=head1 COPYRIGHT + +Copyright Red Hat diff --git a/include/nbdkit-common.h b/include/nbdkit-common.h index bb5e3e55..eeb60b8b 100644 --- a/include/nbdkit-common.h +++ b/include/nbdkit-common.h @@ -104,6 +104,9 @@ NBDKIT_EXTERN_DECL (void, nbdkit_vdebug, ATTRIBUTE_FORMAT_PRINTF (1, 0)); NBDKIT_EXTERN_DECL (char *, nbdkit_absolute_path, (const char *path)); +NBDKIT_EXTERN_DECL (void, nbdkit_debug_hexdump, + (const void *buf, size_t len, + const char *prefix, uint64_t start)); NBDKIT_EXTERN_DECL (int64_t, nbdkit_parse_size, (const char *str)); NBDKIT_EXTERN_DECL (int, nbdkit_parse_probability, (const char *what, const char *str, double *r)); diff --git a/server/debug.c b/server/debug.c index fb13af6a..bd9b4ddb 100644 --- a/server/debug.c +++ b/server/debug.c @@ -35,13 +35,18 @@ #include #include #include +#include #include +#include #include #include #include #include "ansi-colours.h" +#include "ascii-ctype.h" +#include "isaligned.h" #include "open_memstream.h" +#include "rounding.h" #include "utils.h" #include "internal.h" @@ -166,3 +171,70 @@ debug_in_server (const char *fs, ...) errno = err; } + +/* Usually this function would be part of server/public.c, but in this + * case we want to use nbdkit_debug instead of debug_in_server. + */ +NBDKIT_DLL_PUBLIC void +nbdkit_debug_hexdump (const void *vbuf, size_t len, + const char *prefix, uint64_t start) +{ + const uint8_t *buf = vbuf; + uint64_t offset = start, skip; + size_t i; + char str_offset[16+1]; /* The offset field */ + char hex[16*3+1]; /* Sixteen pairs of hex digits (or spaces) */ + char chars[16+1]; /* The printable chars (or spaces) */ + +#define HEXDUMP_RESET_STRS() \ + snprintf (str_offset, sizeof str_offset, "%08" PRIx64, \ + ROUND_DOWN (offset, 16)); \ + memset (hex, ' ', sizeof hex); \ + hex[sizeof hex - 1] = '\0'; \ + memset (chars, ' ', sizeof chars - 1); \ + chars[sizeof chars - 1] = '\0'; + +#define HEXDUMP_SET_BYTE(i, b) \ + sprintf (&hex[(i)*3], "%02x", (b)); \ + hex[(i)*3+2] = ' '; \ + chars[i] = ascii_isprint ((b)) ? (b) : '.'; + +/* Send the final string to nbdkit_debug. Start by splitting up the + * hex digits into two groups of 8. + */ +#define HEXDUMP_EMIT_DEBUG() \ + hex[8*3-1] = '\0'; \ + nbdkit_debug ("%s%s: %s %s |%s|", \ + prefix ? : "", \ + str_offset, hex, &hex[8*3], chars); + + /* Unaligned head. */ + if (! IS_ALIGNED (offset, 16)) { + HEXDUMP_RESET_STRS (); + skip = offset % 16; + for (i = skip; i < 16 && len > 0; ++i) { + HEXDUMP_SET_BYTE (i, *buf); + buf++; + offset++; + len--; + } + HEXDUMP_EMIT_DEBUG (); + } + + /* Aligned body and unaligned tail. */ + while (len > 0) { + assert (IS_ALIGNED (offset, 16)); + HEXDUMP_RESET_STRS (); + for (i = 0; i < 16 && len > 0; ++i) { + HEXDUMP_SET_BYTE (i, *buf); + buf++; + offset++; + len--; + } + HEXDUMP_EMIT_DEBUG (); + } + +#undef HEXDUMP_RESET_STRS +#undef HEXDUMP_SET_BYTE +#undef HEXDUMP_EMIT_DEBUG +} diff --git a/server/nbdkit.syms b/server/nbdkit.syms index 1393f1ea..239e6db1 100644 --- a/server/nbdkit.syms +++ b/server/nbdkit.syms @@ -44,6 +44,7 @@ nbdkit_context_get_backend; nbdkit_context_set_next; nbdkit_debug; + nbdkit_debug_hexdump; nbdkit_disconnect; nbdkit_error; nbdkit_export_name; diff --git a/tests/Makefile.am b/tests/Makefile.am index 75183b92..759a5237 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -327,6 +327,7 @@ TESTS += \ test-log-to-file.sh \ test-log-to-file-append.sh \ test-at-file.sh \ + test-debug-hexdump.sh \ $(NULL) if !IS_WINDOWS TESTS += \ @@ -358,6 +359,8 @@ EXTRA_DIST += \ test-export-handshake.sh \ test-export-handshake-tls.sh \ test-debug-flags.sh \ + test-debug-hexdump.sh \ + test-debug-hexdump-plugin.c \ test-disconnect-tls.sh \ test-disconnect.sh \ test-dump-plugin-and-single.sh \ @@ -531,6 +534,29 @@ test_shutdown_plugin_la_LDFLAGS = \ $(NULL) test_shutdown_plugin_la_LIBADD = $(IMPORT_LIBRARY_ON_WINDOWS) +# check_LTLIBRARIES won't build a shared library (see automake manual). +# So we have to do this and add a dependency. +noinst_LTLIBRARIES += \ + test-debug-hexdump-plugin.la \ + $(NULL) +test-debug-hexdump.sh: test-debug-hexdump-plugin.la + +test_debug_hexdump_plugin_la_SOURCES = \ + test-debug-hexdump-plugin.c \ + $(top_srcdir)/include/nbdkit-plugin.h \ + $(NULL) +test_debug_hexdump_plugin_la_CPPFLAGS = \ + -I$(top_srcdir)/include \ + -I$(top_builddir)/include \ + $(NULL) +test_debug_hexdump_plugin_la_CFLAGS = $(WARNINGS_CFLAGS) +# For use of the -rpath option, see: +# https://lists.gnu.org/archive/html/libtool/2007-07/msg00067.html +test_debug_hexdump_plugin_la_LDFLAGS = \ + -module -avoid-version -shared $(NO_UNDEFINED_ON_WINDOWS) -rpath /nowhere \ + $(NULL) +test_debug_hexdump_plugin_la_LIBADD = $(IMPORT_LIBRARY_ON_WINDOWS) + endif HAVE_PLUGINS # Test the header files can be included on their own. diff --git a/tests/test-debug-hexdump-plugin.c b/tests/test-debug-hexdump-plugin.c new file mode 100755 index 00000000..138edb04 --- /dev/null +++ b/tests/test-debug-hexdump-plugin.c @@ -0,0 +1,85 @@ +/* nbdkit + * Copyright Red Hat + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of Red Hat nor the names of its contributors may be + * used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY RED HAT AND CONTRIBUTORS ''AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL RED HAT OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/* Plugin for testing nbdkit_debug_hexdump. See nbdkit-debug-hexdump.sh */ + +#include + +#include +#include +#include + +#define NBDKIT_API_VERSION 2 +#include + +static void * +password_open (int readonly) +{ + return NBDKIT_HANDLE_NOT_NEEDED; +} + +static int64_t +password_get_size (void *handle) +{ + return INT64_MAX; +} + +#define THREAD_MODEL NBDKIT_THREAD_MODEL_SERIALIZE_ALL_REQUESTS + +/* Never actually called, but return zeroes. */ +static int +password_pread (void *handle, void *buf, uint32_t count, uint64_t offset, + uint32_t flags) +{ + memset (buf, 0, count); + return 0; +} + +/* We hexdump the buffer, with a prefix string, and discard the data. */ +static int +password_pwrite (void *handle, const void *buf, uint32_t count, uint64_t offset, + uint32_t flags) +{ + nbdkit_debug_hexdump (buf, count, "DUMP: ", offset); + return 0; +} + +static struct nbdkit_plugin plugin = { + .name = "hexdump-debug", + .version = PACKAGE_VERSION, + .open = password_open, + .get_size = password_get_size, + .pread = password_pread, + .pwrite = password_pwrite, +}; + +NBDKIT_REGISTER_PLUGIN (plugin) diff --git a/tests/test-debug-hexdump.sh b/tests/test-debug-hexdump.sh new file mode 100755 index 00000000..85b8429c --- /dev/null +++ b/tests/test-debug-hexdump.sh @@ -0,0 +1,154 @@ +#!/usr/bin/env bash +# nbdkit +# Copyright Red Hat +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# * Neither the name of Red Hat nor the names of its contributors may be +# used to endorse or promote products derived from this software without +# specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY RED HAT AND CONTRIBUTORS ''AS IS'' AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +# PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL RED HAT OR +# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF +# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +# OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +# SUCH DAMAGE. + +# Test nbdkit_debug_hexdump function. + +source ./functions.sh +set -e +set -x +set -u + +requires_run +requires_nbdsh_uri +requires diff --version +requires $SED --version + +plugin=.libs/test-debug-hexdump-plugin.$SOEXT +requires test -f $plugin + +out=debug-shutdown.out +hexout=debug-shutdown.hexout +files="$out $hexout" +rm -f $files +cleanup_fn rm -f $files + +define script <<'EOF' +import os + +offset = int(os.getenv("offset"), 0) +count = int(os.getenv("count"), 0) + +data = os.getenv("data") +b = bytearray(data, encoding='utf8') +if len(b) < count: + b += bytearray(count - len(b)) +assert(len(b) == count) + +h.pwrite(b, offset) +EOF +export script + +do_test () +{ + count="$1" + offset="$2" + data="$3" + export offset count data + + # Run nbdkit with the plugin and debug enabled. Capture the full + # output including stderr so we can find the hexdump output. + fail= + nbdkit -f -v $plugin --run 'nbdsh -u "$uri" -c "$script"' \ + >$out 2>&1 || fail=1 + cat $out + if test "$fail"; then exit 1; fi + + # Get the hexdump lines from the output. + grep "DUMP: " < $out | $SED 's/.*DUMP: //' > $hexout + cat $hexout + + # Compare it to the expected output (in $expected variable). + diff -u $hexout <(echo -n "$expected") +} + +define expected <<'EOF' +00000000: 31 |1 | +EOF +do_test 1 0 '1' + +define expected <<'EOF' +00000000: 31 | 1 | +EOF +do_test 1 1 '1' + +define expected <<'EOF' +00000000: 31 32 33 | 123 | +EOF +do_test 3 1 '123' + +define expected <<'EOF' +00000000: 31 32 33 00 00 00 00 00 00 00 00 00 00 00 00 | 123............| +00000010: 00 |. | +EOF +do_test 16 1 '123' + +define expected <<'EOF' +00000000: 31 32 33 61 62 63 01 00 00 00 00 00 00 00 00 00 |123abc..........| +EOF +do_test 16 0 $'123abc\x01' + +define expected <<'EOF' +00000000: 31 32 33 00 00 00 00 00 00 00 00 00 00 00 00 00 |123.............| +00000010: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| +EOF +do_test 32 0 '123' + +define expected <<'EOF' +00000010: 31 32 33 00 00 00 00 00 00 00 00 00 00 00 00 00 |123.............| +EOF +do_test 16 16 '123' + +define expected <<'EOF' +00000000: 31 32 33 00 00 00 00 00 | 123.....| +00000010: 00 00 00 00 00 00 00 00 |........ | +EOF +do_test 16 8 '123' + +define expected <<'EOF' +00000000: 31 32 33 00 00 00 00 00 00 00 | 123.......| +00000010: 00 00 00 00 00 00 00 00 |........ | +EOF +do_test 18 6 '123' + +define expected <<'EOF' +ffff00000000: 31 32 33 00 00 00 00 00 00 00 | 123.......| +ffff00000010: 00 00 00 00 00 00 00 00 |........ | +EOF +do_test 18 0xffff00000006 '123' + +# XXX In future we might make hexdump choose the longest format for +# the offset field across all lines of output. +define expected <<'EOF' +fffffffffff0: 31 32 33 00 00 00 00 00 00 00 | 123.......| +1000000000000: 00 00 00 00 00 00 00 00 |........ | +EOF +do_test 18 0xfffffffffff6 '123' -- 2.47.3