From 731ff0a5ac9196371f4882a543bfa0801dcd39c9 Mon Sep 17 00:00:00 2001 From: Brian Behlendorf Date: Fri, 30 Jan 2026 13:14:51 -0800 Subject: [PATCH] zhack: add -G option to dump debug buffer Add a -G option to zhack to dump the internal debug buffer on exit. We were able to use the same code from zdb for this which was nice. Signed-off-by: Brian Behlendorf Reviewed-by: Tony Hutter Reviewed-by: Olaf Faaland Reviewed-by: Akash B --- cmd/zhack.c | 76 ++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 72 insertions(+), 4 deletions(-) diff --git a/cmd/zhack.c b/cmd/zhack.c index 536e38807..cddfe9fb5 100644 --- a/cmd/zhack.c +++ b/cmd/zhack.c @@ -52,6 +52,7 @@ #include #include #include +#include #include #include #include @@ -60,6 +61,7 @@ static importargs_t g_importargs; static char *g_pool; static boolean_t g_readonly; +static boolean_t g_dump_dbgmsg; typedef enum { ZHACK_REPAIR_OP_UNKNOWN = 0, @@ -71,12 +73,19 @@ static __attribute__((noreturn)) void usage(void) { (void) fprintf(stderr, - "Usage: zhack [-o tunable] [-c cachefile] [-d dir] " - " ...\n" - "where is one of the following:\n" + "Usage: zhack [-o tunable] [-c cachefile] [-d dir] [-G] " + " ...\n" + " where is one of the following:\n" "\n"); (void) fprintf(stderr, + " global options:\n" + " -c reads config from the given cachefile\n" + " -d directory with vdevs for import\n" + " -o var=value... set global variable to an unsigned " + "32-bit integer\n" + " -G dump zfs_dbgmsg buffer before exiting\n" + "\n" " feature stat \n" " print information about enabled features\n" " feature enable [-r] [-d desc] \n" @@ -103,6 +112,39 @@ usage(void) exit(1); } +static void +dump_debug_buffer(void) +{ + ssize_t ret __attribute__((unused)); + + if (!g_dump_dbgmsg) + return; + + /* + * We use write() instead of printf() so that this function + * is safe to call from a signal handler. + */ + ret = write(STDERR_FILENO, "\n", 1); + zfs_dbgmsg_print(STDERR_FILENO, "zhack"); +} + +static void sig_handler(int signo) +{ + struct sigaction action; + + libspl_backtrace(STDERR_FILENO); + dump_debug_buffer(); + + /* + * Restore default action and re-raise signal so SIGSEGV and + * SIGABRT can trigger a core dump. + */ + action.sa_handler = SIG_DFL; + sigemptyset(&action.sa_mask); + action.sa_flags = 0; + (void) sigaction(signo, &action, NULL); + raise(signo); +} static __attribute__((format(printf, 3, 4))) __attribute__((noreturn)) void fatal(spa_t *spa, const void *tag, const char *fmt, ...) @@ -120,6 +162,8 @@ fatal(spa_t *spa, const void *tag, const char *fmt, ...) va_end(ap); (void) fputc('\n', stderr); + dump_debug_buffer(); + exit(1); } @@ -1183,17 +1227,35 @@ zhack_do_label(int argc, char **argv) int main(int argc, char **argv) { + struct sigaction action; char *path[MAX_NUM_PATHS]; const char *subcommand; int rv = 0; int c; + /* + * Set up signal handlers, so if we crash due to bad on-disk data we + * can get more info. Unlike ztest, we don't bail out if we can't set + * up signal handlers, because zhack is very useful without them. + */ + action.sa_handler = sig_handler; + sigemptyset(&action.sa_mask); + action.sa_flags = 0; + if (sigaction(SIGSEGV, &action, NULL) < 0) { + (void) fprintf(stderr, "zhack: cannot catch SIGSEGV: %s\n", + strerror(errno)); + } + if (sigaction(SIGABRT, &action, NULL) < 0) { + (void) fprintf(stderr, "zhack: cannot catch SIGABRT: %s\n", + strerror(errno)); + } + g_importargs.path = path; dprintf_setup(&argc, argv); zfs_prop_init(); - while ((c = getopt(argc, argv, "+c:d:o:")) != -1) { + while ((c = getopt(argc, argv, "+c:d:Go:")) != -1) { switch (c) { case 'c': g_importargs.cachefile = optarg; @@ -1202,6 +1264,9 @@ main(int argc, char **argv) assert(g_importargs.paths < MAX_NUM_PATHS); g_importargs.path[g_importargs.paths++] = optarg; break; + case 'G': + g_dump_dbgmsg = B_TRUE; + break; case 'o': if (handle_tunable_option(optarg, B_FALSE) != 0) exit(1); @@ -1240,6 +1305,9 @@ main(int argc, char **argv) "changes may not be committed to disk\n"); } + if (g_dump_dbgmsg) + dump_debug_buffer(); + kernel_fini(); return (rv);