diff --git a/accel/tcg/plugin-gen.c b/accel/tcg/plugin-gen.c
index 5c13615112..88dcbda651 100644
--- a/accel/tcg/plugin-gen.c
+++ b/accel/tcg/plugin-gen.c
@@ -180,6 +180,8 @@ static void plugin_gen_empty_callback(enum plugin_gen_from from)
 {
     switch (from) {
     case PLUGIN_GEN_AFTER_INSN:
+        gen_wrapped(from, PLUGIN_GEN_CB_UDATA, gen_empty_udata_cb);
+        gen_wrapped(from, PLUGIN_GEN_CB_INLINE, gen_empty_inline_cb);
         gen_wrapped(from, PLUGIN_GEN_DISABLE_MEM_HELPER,
                     gen_empty_mem_helper);
         break;
@@ -598,18 +600,21 @@ static void plugin_gen_tb_inline(const struct qemu_plugin_tb *ptb,
 }
 
 static void plugin_gen_insn_udata(const struct qemu_plugin_tb *ptb,
+                                  enum plugin_dyn_cb_type cb_type,
                                   TCGOp *begin_op, int insn_idx)
 {
+    g_assert(cb_type == PLUGIN_CB_INSN || cb_type == PLUGIN_CB_AFTER_INSN);
     struct qemu_plugin_insn *insn = g_ptr_array_index(ptb->insns, insn_idx);
-
-    inject_udata_cb(insn->cbs[PLUGIN_CB_INSN][PLUGIN_CB_REGULAR], begin_op);
+    inject_udata_cb(insn->cbs[cb_type][PLUGIN_CB_REGULAR], begin_op);
 }
 
 static void plugin_gen_insn_inline(const struct qemu_plugin_tb *ptb,
+                                   enum plugin_dyn_cb_type cb_type,
                                    TCGOp *begin_op, int insn_idx)
 {
+    g_assert(cb_type == PLUGIN_CB_INSN || cb_type == PLUGIN_CB_AFTER_INSN);
     struct qemu_plugin_insn *insn = g_ptr_array_index(ptb->insns, insn_idx);
-    inject_inline_cb(insn->cbs[PLUGIN_CB_INSN][PLUGIN_CB_INLINE],
+    inject_inline_cb(insn->cbs[cb_type][PLUGIN_CB_INLINE],
                      begin_op, op_ok);
 }
 
@@ -738,10 +743,12 @@ static void plugin_gen_inject(struct qemu_plugin_tb *plugin_tb)
 
                 switch (type) {
                 case PLUGIN_GEN_CB_UDATA:
-                    plugin_gen_insn_udata(plugin_tb, op, insn_idx);
+                    plugin_gen_insn_udata(plugin_tb, PLUGIN_CB_INSN,
+                                          op, insn_idx);
                     break;
                 case PLUGIN_GEN_CB_INLINE:
-                    plugin_gen_insn_inline(plugin_tb, op, insn_idx);
+                    plugin_gen_insn_inline(plugin_tb, PLUGIN_CB_INSN,
+                                           op, insn_idx);
                     break;
                 case PLUGIN_GEN_ENABLE_MEM_HELPER:
                     plugin_gen_enable_mem_helper(plugin_tb, op, insn_idx);
@@ -773,6 +780,14 @@ static void plugin_gen_inject(struct qemu_plugin_tb *plugin_tb)
                 g_assert(insn_idx >= 0);
 
                 switch (type) {
+                case PLUGIN_GEN_CB_UDATA:
+                    plugin_gen_insn_udata(plugin_tb, PLUGIN_CB_AFTER_INSN,
+                                          op, insn_idx);
+                    break;
+                case PLUGIN_GEN_CB_INLINE:
+                    plugin_gen_insn_inline(plugin_tb, PLUGIN_CB_AFTER_INSN,
+                                           op, insn_idx);
+                    break;
                 case PLUGIN_GEN_DISABLE_MEM_HELPER:
                     plugin_gen_disable_mem_helper(plugin_tb, op, insn_idx);
                     break;
diff --git a/accel/tcg/translator.c b/accel/tcg/translator.c
index 1a6a5448c8..5e57dc754e 100644
--- a/accel/tcg/translator.c
+++ b/accel/tcg/translator.c
@@ -180,6 +180,12 @@ void translator_loop(CPUState *cpu, TranslationBlock *tb, int *max_insns,
             ops->translate_insn(db, cpu);
         }
 
+
+        /* Stop translation if translate_insn so indicated.  */
+        if (db->is_jmp != DISAS_NEXT) {
+            break;
+        }
+
         /*
          * We can't instrument after instructions that change control
          * flow although this only really affects post-load operations.
@@ -193,11 +199,6 @@ void translator_loop(CPUState *cpu, TranslationBlock *tb, int *max_insns,
             plugin_gen_insn_end();
         }
 
-        /* Stop translation if translate_insn so indicated.  */
-        if (db->is_jmp != DISAS_NEXT) {
-            break;
-        }
-
         /* Stop translation if the output buffer is full,
            or we have executed all of the allowed instructions.  */
         if (tcg_op_buf_full() || db->num_insns >= db->max_insns) {
@@ -211,6 +212,13 @@ void translator_loop(CPUState *cpu, TranslationBlock *tb, int *max_insns,
     gen_tb_end(tb, cflags, icount_start_insn, db->num_insns);
 
     if (plugin_enabled) {
+        /*
+         * Last chance to call plugin_gen_insn_end() if is skipped in translation
+         * loop above.
+         */
+        if (db->is_jmp != DISAS_NEXT && tcg_ctx->exitreq_label == NULL) {
+            plugin_gen_insn_end();
+        }
         plugin_gen_tb_end(cpu);
     }
 
diff --git a/include/qemu/plugin.h b/include/qemu/plugin.h
index bc0781cab8..b221650281 100644
--- a/include/qemu/plugin.h
+++ b/include/qemu/plugin.h
@@ -68,6 +68,7 @@ union qemu_plugin_cb_sig {
 enum plugin_dyn_cb_type {
     PLUGIN_CB_INSN,
     PLUGIN_CB_MEM,
+    PLUGIN_CB_AFTER_INSN,
     PLUGIN_N_CB_TYPES,
 };
 
diff --git a/include/qemu/qemu-plugin.h b/include/qemu/qemu-plugin.h
index 50a9957279..21e25e895d 100644
--- a/include/qemu/qemu-plugin.h
+++ b/include/qemu/qemu-plugin.h
@@ -51,7 +51,7 @@ typedef uint64_t qemu_plugin_id_t;
 
 extern QEMU_PLUGIN_EXPORT int qemu_plugin_version;
 
-#define QEMU_PLUGIN_VERSION 1
+#define QEMU_PLUGIN_VERSION 2
 
 /**
  * struct qemu_info_t - system information for plugins
@@ -314,6 +314,21 @@ void qemu_plugin_register_vcpu_insn_exec_cb(struct qemu_plugin_insn *insn,
                                             enum qemu_plugin_cb_flags flags,
                                             void *userdata);
 
+/**
+ * qemu_plugin_register_vcpu_after_insn_exec_cb() - register cb
+ * after insn execution
+ * @insn: the opaque qemu_plugin_insn handle for an instruction
+ * @cb: callback function
+ * @flags: does the plugin read or write the CPU's registers?
+ * @userdata: any plugin data to pass to the @cb?
+ *
+ * The @cb function is called every time after a non-control-flow
+ * instruction is executed
+ */
+void qemu_plugin_register_vcpu_after_insn_exec_cb(
+    struct qemu_plugin_insn *insn, qemu_plugin_vcpu_udata_cb_t cb,
+    enum qemu_plugin_cb_flags flags, void *userdata);
+
 /**
  * qemu_plugin_register_vcpu_insn_exec_inline() - insn execution inline op
  * @insn: the opaque qemu_plugin_insn handle for an instruction
@@ -328,6 +343,22 @@ void qemu_plugin_register_vcpu_insn_exec_inline(struct qemu_plugin_insn *insn,
                                                 enum qemu_plugin_op op,
                                                 void *ptr, uint64_t imm);
 
+/**
+ * qemu_plugin_register_vcpu_after_insn_exec_inline() - after insn execution
+ * inline op
+ * @insn: the opaque qemu_plugin_insn handle for an instruction
+ * @op: the type of qemu_plugin_op (e.g. ADD_U64)
+ * @ptr: the target memory location for the op
+ * @imm: the op data (e.g. 1)
+ *
+ * Insert an inline op to every time after a non-control-flow
+ * instruction executes.
+ * Useful if you just want to increment a single counter somewhere in memory.
+ */
+void qemu_plugin_register_vcpu_after_insn_exec_inline(
+    struct qemu_plugin_insn *insn, enum qemu_plugin_op op,
+    void *ptr, uint64_t imm);
+
 /**
  * qemu_plugin_tb_n_insns() - query helper for number of insns in TB
  * @tb: opaque handle to TB passed to callback
diff --git a/plugins/api.c b/plugins/api.c
index 2078b16edb..5d4aedc0b5 100644
--- a/plugins/api.c
+++ b/plugins/api.c
@@ -114,16 +114,38 @@ void qemu_plugin_register_vcpu_insn_exec_cb(struct qemu_plugin_insn *insn,
     }
 }
 
+void qemu_plugin_register_vcpu_after_insn_exec_cb(
+    struct qemu_plugin_insn *insn, qemu_plugin_vcpu_udata_cb_t cb,
+    enum qemu_plugin_cb_flags flags, void *udata)
+{
+    if (!insn->mem_only) {
+        plugin_register_dyn_cb__udata(
+            &insn->cbs[PLUGIN_CB_AFTER_INSN][PLUGIN_CB_REGULAR],
+            cb, flags, udata);
+    }
+}
+
 void qemu_plugin_register_vcpu_insn_exec_inline(struct qemu_plugin_insn *insn,
                                                 enum qemu_plugin_op op,
                                                 void *ptr, uint64_t imm)
 {
     if (!insn->mem_only) {
-        plugin_register_inline_op(&insn->cbs[PLUGIN_CB_INSN][PLUGIN_CB_INLINE],
-                                  0, op, ptr, imm);
+        plugin_register_inline_op(
+            &insn->cbs[PLUGIN_CB_INSN][PLUGIN_CB_INLINE],
+            0, op, ptr, imm);
     }
 }
 
+void qemu_plugin_register_vcpu_after_insn_exec_inline(
+    struct qemu_plugin_insn *insn, enum qemu_plugin_op op,
+    void *ptr, uint64_t imm)
+{
+    if (!insn->mem_only) {
+        plugin_register_inline_op(
+            &insn->cbs[PLUGIN_CB_AFTER_INSN][PLUGIN_CB_INLINE],
+            0, op, ptr, imm);
+    }
+}
 
 /*
  * We always plant memory instrumentation because they don't finalise until
diff --git a/plugins/qemu-plugins.symbols b/plugins/qemu-plugins.symbols
index 71f6c90549..d6c25521d1 100644
--- a/plugins/qemu-plugins.symbols
+++ b/plugins/qemu-plugins.symbols
@@ -26,7 +26,9 @@
   qemu_plugin_register_vcpu_idle_cb;
   qemu_plugin_register_vcpu_init_cb;
   qemu_plugin_register_vcpu_insn_exec_cb;
+  qemu_plugin_register_vcpu_after_insn_exec_cb;
   qemu_plugin_register_vcpu_insn_exec_inline;
+  qemu_plugin_register_vcpu_after_insn_exec_inline;
   qemu_plugin_register_vcpu_mem_cb;
   qemu_plugin_register_vcpu_mem_inline;
   qemu_plugin_register_vcpu_resume_cb;
diff --git a/tcg/tcg-op.c b/tcg/tcg-op.c
index 7aadb37756..566da1cb04 100644
--- a/tcg/tcg-op.c
+++ b/tcg/tcg-op.c
@@ -2819,6 +2819,22 @@ void tcg_gen_exit_tb(const TranslationBlock *tb, unsigned idx)
         tcg_debug_assert(idx == TB_EXIT_REQUESTED);
     }
 
+#ifdef CONFIG_PLUGIN
+    /*
+     * Some of instruction generators insert exit_tb explicitelly to
+     * trigger early exit from translation block. On the other hand
+     * translation loop (translator_loop()) inserts plugin callbacks
+     * after instruction is generated, but it appears as dead code
+     * because of the explicit exit_tb insert.
+     *
+     * Calling plugin_gen_insn_end() here before the exit allows
+     * plugins to receive control before translation block exits.
+     */
+    if (tcg_ctx->plugin_insn) {
+        plugin_gen_insn_end();
+    }
+#endif
+
     tcg_gen_op1i(INDEX_op_exit_tb, val);
 }
 
