diff --git a/bsd-user/bsd-proc.h b/bsd-user/bsd-proc.h
index 0e1d461c4c..84b52b399a 100644
--- a/bsd-user/bsd-proc.h
+++ b/bsd-user/bsd-proc.h
@@ -21,12 +21,14 @@
 #define BSD_PROC_H_
 
 #include <sys/resource.h>
+#include "tcg/tb-stats.h"
 
 /* exit(2) */
 static inline abi_long do_bsd_exit(void *cpu_env, abi_long arg1)
 {
     gdb_exit(arg1);
     qemu_plugin_user_exit();
+    tb_stats_dump_atexit();
     _exit(arg1);
 
     return 0;
diff --git a/include/tcg/tb-stats.h b/include/tcg/tb-stats.h
index edee73b63b..d3cca94f84 100644
--- a/include/tcg/tb-stats.h
+++ b/include/tcg/tb-stats.h
@@ -41,11 +41,12 @@ extern uint32_t tb_stats_enabled;
 /**
  * tb_stats_init:
  * @flags: TB_STATS_* flags to enable.
+ * @atexit: count of hottest tbs to log.
  *
  * Initialize translation block statistics, enabling @flags.
  * If @flags is 0, disable all statistics.
  */
-void tb_stats_init(uint32_t flags);
+void tb_stats_init(uint32_t flags, uint32_t atexit);
 
 /*
  * This struct stores statistics such as execution count of the
@@ -154,4 +155,11 @@ gint tb_stats_sort_by_hg(gconstpointer, gconstpointer);
  */
 GString *tb_stats_dump(TBStatistics *s, unsigned index);
 
+/**
+ * tb_stats_dump_atexit:
+ *
+ * Log any requested TBs at end of execution.
+ */
+void tb_stats_dump_atexit(void);
+
 #endif /* TCG_TB_STATS_H */
diff --git a/accel/tcg/monitor.c b/accel/tcg/monitor.c
index 1be3218715..7719583654 100644
--- a/accel/tcg/monitor.c
+++ b/accel/tcg/monitor.c
@@ -245,7 +245,7 @@ static void tb_stats_init_safe(CPUState *cpu, run_on_cpu_data icmd)
 {
     uint32_t flags = icmd.host_int;
 
-    tb_stats_init(flags);
+    tb_stats_init(flags, 0);
     tb_flush(cpu);
 }
 
@@ -335,8 +335,10 @@ static void hmp_info_tblist(Monitor *mon, const QDict *qdict)
         return;
     }
 
-    g_ptr_array_unref(tb_ctx.last_search);
-    tb_ctx.last_search = NULL;
+    if (tb_ctx.last_search) {
+        g_ptr_array_unref(tb_ctx.last_search);
+        tb_ctx.last_search = NULL;
+    }
 
     array = tb_stats_collect(max, sort);
     max = array->len;
diff --git a/accel/tcg/tb-stats.c b/accel/tcg/tb-stats.c
index 0f84c14a88..62a6228799 100644
--- a/accel/tcg/tb-stats.c
+++ b/accel/tcg/tb-stats.c
@@ -8,10 +8,12 @@
 
 #include "qemu/osdep.h"
 #include "qemu/xxhash.h"
+#include "qemu/log.h"
 #include "tcg/tb-stats.h"
 #include "tb-context.h"
 
 uint32_t tb_stats_enabled;
+static uint32_t tb_stats_atexit;
 
 static bool tb_stats_cmp(const void *ap, const void *bp)
 {
@@ -34,7 +36,7 @@ static void tb_stats_free(void *p, uint32_t hash, void *userp)
     g_free(s);
 }
 
-void tb_stats_init(uint32_t flags)
+void tb_stats_init(uint32_t flags, uint32_t atexit)
 {
     tb_stats_enabled = flags;
     if (flags) {
@@ -48,6 +50,14 @@ void tb_stats_init(uint32_t flags)
         qht_iter(&tb_ctx.stats, tb_stats_free, NULL);
         qht_destroy(&tb_ctx.stats);
     }
+
+    /*
+     * This function is also used by HMP, when atexit is 0.
+     * Preserve the value set from the command-line.
+     */
+    if (atexit) {
+        tb_stats_atexit = atexit;
+    }
 }
 
 static void tb_stats_reset(void *p, uint32_t hash, void *userp)
@@ -204,3 +214,18 @@ GString *tb_stats_dump(TBStatistics *s, unsigned index)
     }
     return buf;
 }
+
+void tb_stats_dump_atexit(void)
+{
+    if (tb_stats_enabled && tb_stats_atexit) {
+        g_autoptr(GPtrArray) array =
+            tb_stats_collect(tb_stats_atexit, tb_stats_sort_by_coverage);
+
+        for (uint32_t i = 0, n = array->len; i < n; ++i) {
+            TBStatistics *s = g_ptr_array_index(array, i);
+            g_autoptr(GString) str = tb_stats_dump(s, i);
+
+            qemu_log("%s\n", str->str);
+        }
+    }
+}
diff --git a/linux-user/exit.c b/linux-user/exit.c
index 50266314e0..4487aaac7e 100644
--- a/linux-user/exit.c
+++ b/linux-user/exit.c
@@ -22,6 +22,7 @@
 #include "qemu.h"
 #include "user-internals.h"
 #include "qemu/plugin.h"
+#include "tcg/tb-stats.h"
 
 #ifdef CONFIG_GCOV
 extern void __gcov_dump(void);
@@ -30,9 +31,10 @@ extern void __gcov_dump(void);
 void preexit_cleanup(CPUArchState *env, int code)
 {
 #ifdef CONFIG_GCOV
-        __gcov_dump();
+    __gcov_dump();
 #endif
-        gdb_exit(code);
-        qemu_plugin_user_exit();
-        perf_exit();
+    gdb_exit(code);
+    qemu_plugin_user_exit();
+    perf_exit();
+    tb_stats_dump_atexit();
 }
diff --git a/softmmu/runstate.c b/softmmu/runstate.c
index 1652ed0439..2c6fb9bff1 100644
--- a/softmmu/runstate.c
+++ b/softmmu/runstate.c
@@ -59,6 +59,7 @@
 #include "sysemu/runstate-action.h"
 #include "sysemu/sysemu.h"
 #include "sysemu/tpm.h"
+#include "tcg/tb-stats.h"
 #include "trace.h"
 
 static NotifierList exit_notifiers =
@@ -846,6 +847,7 @@ void qemu_cleanup(void)
     /* No more vcpu or device emulation activity beyond this point */
     vm_shutdown();
     replay_finish();
+    tb_stats_dump_atexit();
 
     /*
      * We must cancel all block jobs while the block layer is drained,
diff --git a/stubs/tb-stats.c b/stubs/tb-stats.c
index ceaa1622ce..f9e4ef5d04 100644
--- a/stubs/tb-stats.c
+++ b/stubs/tb-stats.c
@@ -11,6 +11,10 @@
 #include "qemu/osdep.h"
 #include "tcg/tb-stats.h"
 
-void tb_stats_init(uint32_t flags)
+void tb_stats_init(uint32_t flags, uint32_t atexit)
+{
+}
+
+void tb_stats_dump_atexit(void)
 {
 }
diff --git a/util/log.c b/util/log.c
index 0cb987fb74..789b19a226 100644
--- a/util/log.c
+++ b/util/log.c
@@ -526,19 +526,31 @@ int qemu_str_to_log_mask(const char *str, Error **errp)
 #ifdef CONFIG_TCG
         } else if (g_str_has_prefix(t, "tb_stats:") && t[9] != '\0') {
             int flags = TB_STATS_NONE;
+            unsigned atexit = 0;
             char *v = t + 9;
+            char *e = strchr(v, ':');
+            size_t len;
 
-            if (g_str_equal(v, "all")) {
+            if (e) {
+                len = e - v;
+                if (qemu_strtoui(e + 1, NULL, 10, &atexit) < 0) {
+                    error_setg(errp, "Invalid -d option \"%s\"", t);
+                    goto error;
+                }
+            } else {
+                len = strlen(v);
+            }
+            if (strncmp(v, "all", len) == 0) {
                 flags = TB_STATS_ALL;
-            } else if (g_str_equal(v, "jit")) {
+            } else if (strncmp(v, "jit", len) == 0) {
                 flags = TB_STATS_JIT;
-            } else if (g_str_equal(v, "exec")) {
+            } else if (strncmp(v, "exec", len) == 0) {
                 flags = TB_STATS_EXEC;
             } else {
                 error_setg(errp, "Invalid -d option \"%s\"", t);
                 goto error;
             }
-            tb_stats_init(flags);
+            tb_stats_init(flags, atexit);
 #endif
         } else {
             for (item = qemu_log_items; item->mask != 0; item++) {
