diff --git a/accel/tcg/monitor.c b/accel/tcg/monitor.c
index 60b66f16ff..0ce5a1cb45 100644
--- a/accel/tcg/monitor.c
+++ b/accel/tcg/monitor.c
@@ -8,18 +8,22 @@
 
 #include "qemu/osdep.h"
 #include "qemu/accel.h"
+#include "qemu/log.h"
 #include "qapi/error.h"
 #include "qapi/type-helpers.h"
 #include "qapi/qapi-commands-machine.h"
 #include "qapi/qmp/qdict.h"
 #include "monitor/monitor.h"
 #include "monitor/hmp.h"
+#include "monitor/hmp-target.h"
 #include "sysemu/cpus.h"
 #include "sysemu/cpu-timers.h"
 #include "sysemu/tcg.h"
 #include "tcg/tcg.h"
 #include "exec/tb-stats.h"
 #include "exec/tb-flush.h"
+#include "disas/disas.h"
+#include "tb-context.h"
 #include "internal.h"
 #include "tb-context.h"
 
@@ -185,6 +189,114 @@ void hmp_tbstats(Monitor *mon, const QDict *qdict)
 
 }
 
+struct tblist_dump_info {
+    int count;
+    int sortedby;
+    Monitor *mon;
+};
+
+static void do_dump_tblist_info_safe(CPUState *cpu, run_on_cpu_data info)
+{
+    struct tblist_dump_info *tbdi = info.host_ptr;
+    g_autoptr(GString) buf = g_string_new("");
+
+    dump_tblist_info(buf, tbdi->count, tbdi->sortedby);
+    monitor_printf(tbdi->mon, "%s", buf->str);
+
+    g_free(tbdi);
+}
+
+void hmp_info_tblist(Monitor *mon, const QDict *qdict)
+{
+    int number_int;
+    const char *sortedby_str = NULL;
+
+    if (!tcg_enabled()) {
+        monitor_printf(mon, "Only available with accel=tcg\n");
+        return;
+    }
+    if (!tb_ctx.tb_stats.map) {
+        monitor_printf(mon, "no TB information recorded\n");
+        return;
+    }
+
+    number_int = qdict_get_try_int(qdict, "number", 10);
+    sortedby_str = qdict_get_try_str(qdict, "sortedby");
+
+    int sortedby = SORT_BY_HOTNESS;
+    if (sortedby_str == NULL || strcmp(sortedby_str, "hotness") == 0) {
+        sortedby = SORT_BY_HOTNESS;
+    } else if (strcmp(sortedby_str, "hg") == 0) {
+        sortedby = SORT_BY_HG;
+    } else if (strcmp(sortedby_str, "spills") == 0) {
+        sortedby = SORT_BY_SPILLS;
+    } else {
+        monitor_printf(mon, "valid sort options are: hotness hg spills\n");
+        return;
+    }
+
+    struct tblist_dump_info *tbdi = g_new(struct tblist_dump_info, 1);
+    tbdi->count = number_int;
+    tbdi->sortedby = sortedby;
+    tbdi->mon = mon;
+    async_safe_run_on_cpu(first_cpu, do_dump_tblist_info_safe,
+                          RUN_ON_CPU_HOST_PTR(tbdi));
+}
+
+struct tb_dump_info {
+    int id;
+    Monitor *mon;
+};
+
+static void do_dump_tb_info_safe(CPUState *cpu, run_on_cpu_data info)
+{
+    struct tb_dump_info *tbdi = info.host_ptr;
+    int id = tbdi->id;
+    Monitor *mon = tbdi->mon;
+    g_autoptr(GString) buf = g_string_new("");
+
+    TBStatistics *tbs = get_tbstats_by_id(id);
+    if (tbs == NULL) {
+        monitor_printf(mon, "TB %d information is not recorded\n", id);
+        return;
+    }
+
+    monitor_printf(mon, "\n------------------------------\n\n");
+
+    int valid_tb_num = dump_tb_info(buf, tbs, id);
+    monitor_printf(mon, "%s", buf->str);
+
+    if (valid_tb_num > 0) {
+        for (int i = tbs->tbs->len - 1; i >= 0; --i) {
+            TranslationBlock *tb = g_ptr_array_index(tbs->tbs, i);
+            if (!(tb->cflags & CF_INVALID)) {
+                monitor_disas(mon, mon_get_cpu(mon), tbs->phys_pc, tb->icount,
+                              DISAS_GRA);
+                break;
+            }
+        }
+    }
+    monitor_printf(mon, "\n------------------------------\n\n");
+
+    g_free(tbdi);
+}
+
+void hmp_info_tb(Monitor *mon, const QDict *qdict)
+{
+    const int id = qdict_get_int(qdict, "id");
+
+    if (!tcg_enabled()) {
+        monitor_printf(mon, "Only available with accel=tcg\n");
+        return;
+    }
+
+    struct tb_dump_info *tbdi = g_new(struct tb_dump_info, 1);
+    tbdi->id = id;
+    tbdi->mon = mon;
+    async_safe_run_on_cpu(first_cpu, do_dump_tb_info_safe,
+                          RUN_ON_CPU_HOST_PTR(tbdi));
+}
+
 static void hmp_tcg_register(void)
 {
     monitor_register_hmp_info_hrt("jit", qmp_x_query_jit);
diff --git a/accel/tcg/tb-stats.c b/accel/tcg/tb-stats.c
index 7c7f700c89..73f0a25781 100644
--- a/accel/tcg/tb-stats.c
+++ b/accel/tcg/tb-stats.c
@@ -11,12 +11,16 @@
 #include "disas/disas.h"
 #include "exec/exec-all.h"
 #include "tcg/tcg.h"
+#include "qapi/error.h"
 
 #include "qemu/qemu-print.h"
+#include "qemu/log.h"
 
 #include "exec/tb-stats.h"
 #include "tb-context.h"
 
+#include "internal.h"
+
 /* TBStatistic collection controls */
 enum TBStatsStatus {
     TB_STATS_STOPPED = 0,
@@ -26,6 +30,8 @@ enum TBStatsStatus {
 static enum TBStatsStatus tcg_collect_tb_stats;
 static uint32_t tbstats_flag;
 
+static GPtrArray *last_search;
+
 struct jit_profile_info {
     uint64_t translations;
     uint64_t aborted;
@@ -42,6 +48,18 @@ struct jit_profile_info {
 #define stat_per_translation(stat, name) \
     (stat->translations.total ? stat->name / stat->translations.total : 0)
 
+TBStatistics *get_tbstats_by_id(int id)
+{
+    if (!last_search) {
+        return NULL;
+    }
+
+    if (id < 0 || id >= last_search->len) {
+        return NULL;
+    }
+    return g_ptr_array_index(last_search, id);
+}
+
 /* accumulate the statistics from all TBs */
 static void collect_jit_profile_info(void *p, uint32_t hash, void *userp)
 {
@@ -98,6 +116,11 @@ static void free_tbstats(void *p, uint32_t hash, void *userp)
 
 void clean_tbstats(void)
 {
+    if (last_search) {
+        g_ptr_array_free(last_search, true);
+        last_search = NULL;
+    }
+
     /* remove all tb_stats */
     qht_iter(&tb_ctx.tb_stats, free_tbstats, NULL);
     qht_destroy(&tb_ctx.tb_stats);
@@ -129,6 +152,154 @@ void init_tb_stats_htable(void)
     }
 }
 
+static void collect_tb_stats(void *p, uint32_t hash, void *userp)
+{
+    int *count = userp;
+
+    g_ptr_array_add(last_search, p);
+    (*count)++;
+}
+
+static void count_invalid_tbs(gpointer data, gpointer user_data)
+{
+    TranslationBlock *tb = (TranslationBlock *) data;
+    unsigned *counter = (unsigned *) user_data;
+    if (tb->cflags & CF_INVALID) {
+        *counter = *counter + 1;
+    }
+}
+
+int dump_tb_info(GString *buf, TBStatistics *tbs, int id)
+{
+    unsigned g = stat_per_translation(tbs, code.num_guest_inst);
+    unsigned ops = stat_per_translation(tbs, code.num_tcg_ops);
+    unsigned ops_opt = stat_per_translation(tbs, code.num_tcg_ops_opt);
+    unsigned spills = stat_per_translation(tbs, code.spills);
+    unsigned h = stat_per_translation(tbs, code.out_len);
+    unsigned act = tbs->tbs->len;
+    unsigned invalid = 0;
+
+    float guest_host_prop = g ? ((float) h / g) : 0;
+
+    g_ptr_array_foreach(tbs->tbs, &count_invalid_tbs, &invalid);
+
+    g_string_append_printf(buf,
+            "TB id:%d | phys:0x"TB_PAGE_ADDR_FMT" virt:0x"TARGET_FMT_lx
+            " flags:0x%08x %d inv/%d\n",
+            id, tbs->phys_pc, tbs->pc, tbs->flags, invalid, act);
+
+    if (tbs_stats_enabled(tbs, TB_EXEC_STATS)) {
+        g_string_append_printf(buf,
+                "\t| exec:%lu/%lu guest inst cov:%.2f%%\n",
+                tbs->executions.normal,
+                tbs->executions.atomic, tbs->executions.coverage / 100.0f);
+    }
+
+    if (tbs_stats_enabled(tbs, TB_JIT_STATS)) {
+        g_string_append_printf(buf,
+                "\t| trans:%lu inst: g:%u op:%u op_opt:%u spills:%d"
+                "\n\t| h/g (host bytes / guest insts): %f\n",
+                tbs->translations.total, g, ops, ops_opt, spills,
+                guest_host_prop);
+    }
+
+    g_string_append_printf(buf, "\n");
+
+    return act - invalid;
+}
+
+static gint
+inverse_sort_tbs_spills(gconstpointer p1, gconstpointer p2)
+{
+    const TBStatistics *tbs1 = *(TBStatistics **) p1;
+    const TBStatistics *tbs2 = *(TBStatistics **) p2;
+    unsigned long c1 = stat_per_translation(tbs1, code.spills);
+    unsigned long c2 = stat_per_translation(tbs2, code.spills);
+    return c1 < c2 ? 1 : c1 == c2 ? 0 : -1;
+}
+
+static gint
+inverse_sort_tbs_hotness(gconstpointer p1, gconstpointer p2)
+{
+    const TBStatistics *tbs1 = *(TBStatistics **) p1;
+    const TBStatistics *tbs2 = *(TBStatistics **) p2;
+    unsigned long c1 = stat_per_translation(tbs1, executions.normal);
+    unsigned long c2 = stat_per_translation(tbs2, executions.normal);
+    return c1 < c2 ? 1 : c1 == c2 ? 0 : -1;
+}
+
+static gint
+inverse_sort_tbs_hg(gconstpointer p1, gconstpointer p2)
+{
+    const TBStatistics *tbs1 = *(TBStatistics **) p1;
+    const TBStatistics *tbs2 = *(TBStatistics **) p2;
+
+    if (tbs1->code.num_guest_inst == 0) {
+        return -1;
+    }
+    if (tbs2->code.num_guest_inst == 0) {
+        return 1;
+    }
+
+    unsigned long c1 = tbs1->code.out_len / tbs1->code.num_guest_inst;
+    unsigned long c2 = tbs2->code.out_len / tbs2->code.num_guest_inst;
+    return c1 < c2 ? 1 : c1 == c2 ? 0 : -1;
+}
+
+static void calculate_last_search_coverages(void)
+{
+    uint64_t total_exec_count = 0;
+
+    /* Compute total execution count for all tbs */
+    for (int i = 0; i < last_search->len; ++i) {
+        TBStatistics *tbs = g_ptr_array_index(last_search, i);
+        total_exec_count +=
+            (tbs->executions.atomic + tbs->executions.normal)
+                * stat_per_translation(tbs, code.num_guest_inst);
+    }
+
+    for (int i = 0; i < last_search->len; ++i) {
+        TBStatistics *tbs = g_ptr_array_index(last_search, i);
+        uint64_t tb_total_execs =
+            (tbs->executions.atomic + tbs->executions.normal)
+                * stat_per_translation(tbs, code.num_guest_inst);
+        tbs->executions.coverage =
+            (10000 * tb_total_execs) / (total_exec_count + 1);
+    }
+}
+
+void dump_tblist_info(GString *buf, int total, int sort_by)
+{
+    int array_size = 0;
+
+    if (last_search) {
+        g_ptr_array_free(last_search, true);
+    }
+    last_search = g_ptr_array_new();
+
+    qht_iter(&tb_ctx.tb_stats, collect_tb_stats, &array_size);
+
+    calculate_last_search_coverages();
+
+    if (sort_by == SORT_BY_HOTNESS) {
+        g_ptr_array_sort(last_search, (GCompareFunc)inverse_sort_tbs_hotness);
+    } else if (sort_by == SORT_BY_SPILLS) {
+        g_ptr_array_sort(last_search, (GCompareFunc)inverse_sort_tbs_spills);
+    } else if (sort_by == SORT_BY_HG) {
+        g_ptr_array_sort(last_search, (GCompareFunc)inverse_sort_tbs_hg);
+    } else {
+        return;
+    }
+
+    array_size = (array_size > total) ? total : array_size;
+    g_ptr_array_set_size(last_search, array_size);
+
+    for (int i = 0; i < last_search->len; ++i) {
+        TBStatistics *tbs = g_ptr_array_index(last_search, i);
+        dump_tb_info(buf, tbs, i);
+    }
+}
+
 void enable_collect_tb_stats(void)
 {
     tcg_collect_tb_stats = TB_STATS_RUNNING;
@@ -166,3 +337,9 @@ bool tb_stats_enabled(TranslationBlock *tb, uint32_t flag)
            tb->tb_stats &&
            (tbstats_flag & flag);
 }
+
+bool tbs_stats_enabled(struct TBStatistics *tbs, uint32_t flag)
+{
+    return tb_stats_collection_enabled() &&
+           (tbstats_flag & flag);
+}
diff --git a/disas/disas-mon.c b/disas/disas-mon.c
index 48ac492c6c..e4125b4493 100644
--- a/disas/disas-mon.c
+++ b/disas/disas-mon.c
@@ -23,9 +23,18 @@ physical_read_memory(bfd_vma memaddr, bfd_byte *myaddr, int length,
     return res == MEMTX_OK ? 0 : EIO;
 }
 
+static int
+ram_addr_read_memory(bfd_vma memaddr, bfd_byte *myaddr, int length,
+                     struct disassemble_info *info)
+{
+    void *p = qemu_map_ram_ptr(0, memaddr);
+    memcpy(myaddr, p, length);
+    return 0;
+}
+
 /* Disassembler for the monitor.  */
 void monitor_disas(Monitor *mon, CPUState *cpu, uint64_t pc,
-                   int nb_insn, bool is_physical)
+                   int nb_insn, int addr_kind)
 {
     int count, i;
     CPUDebug s;
@@ -35,8 +44,10 @@ void monitor_disas(Monitor *mon, CPUState *cpu, uint64_t pc,
     s.info.fprintf_func = disas_gstring_printf;
     s.info.stream = (FILE *)ds;  /* abuse this slot */
 
-    if (is_physical) {
+    if (addr_kind == DISAS_GPA) {
         s.info.read_memory_func = physical_read_memory;
+    } else if (addr_kind == DISAS_GRA) {
+        s.info.read_memory_func = ram_addr_read_memory;
     }
     s.info.buffer_vma = pc;
 
diff --git a/disas/disas.c b/disas/disas.c
index 0d2d06c2ec..3b19b51fca 100644
--- a/disas/disas.c
+++ b/disas/disas.c
@@ -8,6 +8,8 @@
 #include "hw/core/cpu.h"
 #include "exec/memory.h"
 
+#include "qemu/log-for-trace.h"
+
 /* Filled in by elfload.c.  Simplistic, but will do for now. */
 struct syminfo *syminfos = NULL;
 
diff --git a/hmp-commands-info.hx b/hmp-commands-info.hx
index f5b37eb74a..ace1ecc3c5 100644
--- a/hmp-commands-info.hx
+++ b/hmp-commands-info.hx
@@ -262,6 +262,22 @@ ERST
         .params     = "",
         .help       = "show dynamic compiler info",
     },
+    {
+        .name       = "tb-list",
+        .args_type  = "number:i?,sortedby:s?",
+        .params     = "[number sortedby]",
+        .help       = "show a [number] translated blocks sorted by [sortedby]"
+                      "sortedby opts: hotness hg spills",
+        .cmd        = hmp_info_tblist,
+    },
+    {
+        .name       = "tb",
+        .args_type  = "id:i,flags:s?",
+        .params     = "id [flag1,flag2,...]",
+        .help       = "show information about one translated block by id."
+                      "dump flags can be used to set dump code level: out_asm in_asm op",
+        .cmd        = hmp_info_tb,
+    },
 #endif
 
 SRST
diff --git a/include/disas/disas.h b/include/disas/disas.h
index 176775eff7..95854abca2 100644
--- a/include/disas/disas.h
+++ b/include/disas/disas.h
@@ -1,12 +1,18 @@
 #ifndef QEMU_DISAS_H
 #define QEMU_DISAS_H
 
+enum {
+    DISAS_GVA = 0,
+    DISAS_GPA,
+    DISAS_GRA,  /* guest ram addr */
+};
+
 /* Disassemble this for me please... (debugging). */
 void disas(FILE *out, const void *code, size_t size);
 void target_disas(FILE *out, CPUState *cpu, uint64_t code, size_t size);
 
 void monitor_disas(Monitor *mon, CPUState *cpu, uint64_t pc,
-                   int nb_insn, bool is_physical);
+                   int nb_insn, int addr_kind);
 
 char *plugin_disas(CPUState *cpu, uint64_t addr, size_t size);
 
diff --git a/include/exec/tb-stats.h b/include/exec/tb-stats.h
index cef177bc69..0caffdf32b 100644
--- a/include/exec/tb-stats.h
+++ b/include/exec/tb-stats.h
@@ -54,6 +54,8 @@ struct TBStatistics {
     struct {
         unsigned long normal;
         unsigned long atomic;
+        /* filled only when dumping x% cover set */
+        uint16_t coverage;
     } executions;
 
     /* JIT Stats - protected by lock */
@@ -89,6 +91,7 @@ bool tb_stats_cmp(const void *ap, const void *bp);
 
 void init_tb_stats_htable(void);
 bool tb_stats_enabled(TranslationBlock *tb, uint32_t flag);
+bool tbs_stats_enabled(struct TBStatistics *tbs, uint32_t flag);
 
 void dump_jit_profile_info(GString *buf);
 
@@ -102,4 +105,26 @@ void clean_tbstats(void);
  */
 void tbstats_reset_tbs(void);
 
+/**
+ * dump_tbs_info: report the hottest blocks
+ *
+ * @buf: output buffer
+ * @total: the limit of hotblocks
+ * @sort_by: property in which the dump will be sorted
+ *
+ * Report the hottest blocks to either the log or monitor
+ */
+void dump_tblist_info(GString *buf, int total, int sort_by);
+
+/**
+ * dump_tb_info: dump information about one TB
+ *
+ * @buf: output buffer
+ * @tbs: the tbs to dump
+ * @id: the display id of the block (from previous search)
+ */
+int dump_tb_info(GString *buf, TBStatistics *tbs, int id);
+
+TBStatistics *get_tbstats_by_id(int id);
+
 #endif
diff --git a/include/monitor/hmp.h b/include/monitor/hmp.h
index 2e7f141754..acdd6f1561 100644
--- a/include/monitor/hmp.h
+++ b/include/monitor/hmp.h
@@ -182,5 +182,7 @@ void hmp_boot_set(Monitor *mon, const QDict *qdict);
 void hmp_info_mtree(Monitor *mon, const QDict *qdict);
 void hmp_info_cryptodev(Monitor *mon, const QDict *qdict);
 void hmp_tbstats(Monitor *mon, const QDict *qdict);
+void hmp_info_tblist(Monitor *mon, const QDict *qdict);
+void hmp_info_tb(Monitor *mon, const QDict *qdict);
 
 #endif
diff --git a/monitor/hmp-cmds-target.c b/monitor/hmp-cmds-target.c
index 0d3e84d960..7d1433336d 100644
--- a/monitor/hmp-cmds-target.c
+++ b/monitor/hmp-cmds-target.c
@@ -133,7 +133,8 @@ static void memory_dump(Monitor *mon, int count, int format, int wsize,
     }
 
     if (format == 'i') {
-        monitor_disas(mon, cs, addr, count, is_physical);
+        monitor_disas(mon, cs, addr, count,
+                      is_physical ? DISAS_GPA : DISAS_GVA);
         return;
     }
 
