diff --git a/cpus.c b/cpus.c
index cd25c8c..738b96f 100644
--- a/cpus.c
+++ b/cpus.c
@@ -1116,7 +1116,7 @@ static void qemu_cpu_kick_thread(CPUState *cpu)
 void qemu_cpu_kick(CPUState *cpu)
 {
     qemu_cond_broadcast(cpu->halt_cond);
-    if (!tcg_enabled() && !cpu->thread_kicked) {
+    if (!CPU_HOOK(cpu, tcg_enabled)() && !cpu->thread_kicked) {
         qemu_cpu_kick_thread(cpu);
         cpu->thread_kicked = true;
     }
@@ -1310,7 +1310,7 @@ void qemu_init_vcpu(CPUState *cpu)
     cpu->stopped = true;
     if (kvm_enabled()) {
         qemu_kvm_start_vcpu(cpu);
-    } else if (tcg_enabled()) {
+    } else if (CPU_HOOK(cpu, tcg_enabled)()) {
         qemu_tcg_init_vcpu(cpu);
     } else {
         qemu_dummy_start_vcpu(cpu);
@@ -1393,7 +1393,7 @@ static int tcg_cpu_exec(CPUState *cpu)
         cpu->icount_decr.u16.low = decr;
         cpu->icount_extra = count;
     }
-    ret = cpu_exec(cpu);
+    ret = CPU_HOOK(cpu, cpu_exec)(cpu);
 #ifdef CONFIG_PROFILER
     tcg_time += profile_getclock() - ti;
 #endif
diff --git a/exec.c b/exec.c
index c37475a..4a722ce 100644
--- a/exec.c
+++ b/exec.c
@@ -445,7 +445,7 @@ static int cpu_common_post_load(void *opaque, int version_id)
     /* 0x01 was CPU_INTERRUPT_EXIT. This line can be removed when the
        version_id is increased. */
     cpu->interrupt_request &= ~0x01;
-    tlb_flush(cpu, 1);
+    CPU_HOOK(cpu, tlb_flush)(cpu, 1);
 
     return 0;
 }
@@ -463,7 +463,7 @@ static bool cpu_common_exception_index_needed(void *opaque)
 {
     CPUState *cpu = opaque;
 
-    return tcg_enabled() && cpu->exception_index != -1;
+    return CPU_HOOK(cpu, tcg_enabled)() && cpu->exception_index != -1;
 }
 
 static const VMStateDescription vmstate_cpu_common_exception_index = {
@@ -621,8 +621,8 @@ static void breakpoint_invalidate(CPUState *cpu, target_ulong pc)
 {
     hwaddr phys = cpu_get_phys_page_debug(cpu, pc);
     if (phys != -1) {
-        tb_invalidate_phys_addr(cpu->as,
-                                phys | (pc & ~TARGET_PAGE_MASK));
+        CPU_HOOK(cpu, tb_invalidate_phys_addr)(cpu->as,
+                                               phys | (pc & ~TARGET_PAGE_MASK));
     }
 }
 #endif
@@ -674,7 +674,7 @@ int cpu_watchpoint_insert(CPUState *cpu, vaddr addr, vaddr len,
         QTAILQ_INSERT_TAIL(&cpu->watchpoints, wp, entry);
     }
 
-    tlb_flush_page(cpu, addr);
+    CPU_HOOK(cpu, tlb_flush_page)(cpu, addr);
 
     if (watchpoint)
         *watchpoint = wp;
@@ -702,7 +702,7 @@ void cpu_watchpoint_remove_by_ref(CPUState *cpu, CPUWatchpoint *watchpoint)
 {
     QTAILQ_REMOVE(&cpu->watchpoints, watchpoint, entry);
 
-    tlb_flush_page(cpu, watchpoint->vaddr);
+    CPU_HOOK(cpu, tlb_flush_page)(cpu, watchpoint->vaddr);
 
     g_free(watchpoint);
 }
@@ -814,7 +814,7 @@ void cpu_single_step(CPUState *cpu, int enabled)
         } else {
             /* must flush all the translated code to avoid inconsistencies */
             /* XXX: only flush what is necessary */
-            tb_flush(cpu);
+            CPU_HOOK(cpu,tb_flush)(cpu);
         }
     }
 }
@@ -906,7 +906,7 @@ static void tlb_reset_dirty_range_all(ram_addr_t start, ram_addr_t length)
     assert(block == qemu_get_ram_block(end - 1));
     start1 = (uintptr_t)ramblock_ptr(block, start - block->offset);
     CPU_FOREACH(cpu) {
-        tlb_reset_dirty(cpu, start1, length);
+        CPU_HOOK(cpu, tlb_reset_dirty)(cpu, start1, length);
     }
     rcu_read_unlock();
 }
@@ -1876,7 +1876,7 @@ static void notdirty_mem_write(void *opaque, hwaddr ram_addr,
                                uint64_t val, unsigned size)
 {
     if (!cpu_physical_memory_get_dirty_flag(ram_addr, DIRTY_MEMORY_CODE)) {
-        tb_invalidate_phys_page_fast(ram_addr, size);
+        CPU_HOOK(current_cpu, tb_invalidate_phys_page_fast)(ram_addr, size);
     }
     switch (size) {
     case 1:
@@ -1899,7 +1899,8 @@ static void notdirty_mem_write(void *opaque, hwaddr ram_addr,
     /* we remove the notdirty callback only if the code has been
        flushed */
     if (!cpu_physical_memory_is_clean(ram_addr)) {
-        tlb_set_dirty(current_cpu, current_cpu->mem_io_vaddr);
+        CPU_HOOK(current_cpu, tlb_set_dirty)(current_cpu,
+                                             current_cpu->mem_io_vaddr);
     }
 }
 
@@ -1945,13 +1946,13 @@ static void check_watchpoint(int offset, int len, MemTxAttrs attrs, int flags)
             wp->hitattrs = attrs;
             if (!cpu->watchpoint_hit) {
                 cpu->watchpoint_hit = wp;
-                tb_check_watchpoint(cpu);
+                CPU_HOOK(cpu, tb_check_watchpoint)(cpu);
                 if (wp->flags & BP_STOP_BEFORE_ACCESS) {
                     cpu->exception_index = EXCP_DEBUG;
                     cpu_loop_exit(cpu);
                 } else {
-                    cpu_get_tb_cpu_state(env, &pc, &cs_base, &cpu_flags);
-                    tb_gen_code(cpu, pc, cs_base, cpu_flags, 1);
+                    CPU_HOOK(cpu, cpu_get_tb_cpu_state)(env, &pc, &cs_base, &cpu_flags);
+                    CPU_HOOK(cpu, tb_gen_code)(cpu, pc, cs_base, cpu_flags, 1);
                     cpu_resume_from_signal(cpu, NULL);
                 }
             }
@@ -2344,7 +2345,7 @@ static void invalidate_and_set_dirty(MemoryRegion *mr, hwaddr addr,
             cpu_physical_memory_range_includes_clean(addr, length, dirty_log_mask);
     }
     if (dirty_log_mask & (1 << DIRTY_MEMORY_CODE)) {
-        tb_invalidate_phys_range(addr, addr + length);
+        CPU_HOOK(current_cpu, tb_invalidate_phys_range)(addr, addr + length);
         dirty_log_mask &= ~(1 << DIRTY_MEMORY_CODE);
     }
     cpu_physical_memory_set_dirty_range(addr, length, dirty_log_mask);
diff --git a/gdbstub.c b/gdbstub.c
index 92b2f81..2f280e6 100644
--- a/gdbstub.c
+++ b/gdbstub.c
@@ -1257,7 +1257,7 @@ static void gdb_vm_state_change(void *opaque, int running, RunState state)
             cpu->watchpoint_hit = NULL;
             goto send_packet;
         }
-        tb_flush(cpu);
+        CPU_HOOK(cpu, tb_flush)(cpu);
         ret = GDB_SIGNAL_TRAP;
         break;
     case RUN_STATE_PAUSED:
diff --git a/include/exec/cpu-common.h b/include/exec/cpu-common.h
index 47d416d..d35601e 100644
--- a/include/exec/cpu-common.h
+++ b/include/exec/cpu-common.h
@@ -53,9 +53,7 @@ typedef uintptr_t ram_addr_t;
 #  define RAM_ADDR_FMT "%" PRIxPTR
 #endif
 
-#ifndef CONFIG_USER_ONLY
 typedef ram_addr_t tb_page_addr_t;
-#endif
 
 extern ram_addr_t ram_size;
 ram_addr_t get_current_ram_size(void);
diff --git a/include/exec/exec-all.h b/include/exec/exec-all.h
index 8fd0540..0c70c17 100644
--- a/include/exec/exec-all.h
+++ b/include/exec/exec-all.h
@@ -363,4 +363,9 @@ extern volatile sig_atomic_t exit_request;
 #if !defined(CONFIG_USER_ONLY)
 void migration_bitmap_extend(ram_addr_t old, ram_addr_t new);
 #endif
+
+#ifdef NEED_CPU_H
+#include "translate-all.h"
+#endif
+
 #endif
diff --git a/include/qom/cpu.h b/include/qom/cpu.h
index eb12d26..9fbae16 100644
--- a/include/qom/cpu.h
+++ b/include/qom/cpu.h
@@ -212,6 +212,8 @@ struct kvm_run;
 #define TB_JMP_CACHE_BITS 12
 #define TB_JMP_CACHE_SIZE (1 << TB_JMP_CACHE_BITS)
 
+#define MAX_CPU_HOOKS 16
+
 /**
  * CPUState:
  * @cpu_index: CPU index (informative).
@@ -320,8 +322,85 @@ struct CPUState {
        (absolute value) offset as small as possible.  This reduces code
        size, especially for hosts without large memory offsets.  */
     volatile sig_atomic_t tcg_exit_req;
+
+    void (*cpu_get_tb_cpu_state)(void *env, void *pc, void *cs_base,
+                                 int *pflags);
+
+    union {
+        struct {
+#ifdef NEED_CPU_H
+            void (*tb_check_watchpoint)(CPUState *cpu);
+            void (*tb_flush)(CPUState *cpu);
+            struct TranslationBlock *(*tb_gen_code)(CPUState *cpu,
+                                                    target_ulong pc,
+                                                    target_ulong cs_base,
+                                                    int flags, int cflags);
+#ifndef CONFIG_USER_ONLY
+            void (*tlb_flush)(CPUState *cpu, int flush_global);
+            void (*tlb_flush_page)(CPUState *cpu, target_ulong addr);
+            void (*tlb_set_dirty)(CPUState *cpu, target_ulong addr);
+            void (*tlb_reset_dirty)(CPUState *cpu, ram_addr_t start,
+                  ram_addr_t length);
+
+            void (*tb_invalidate_phys_addr)(AddressSpace *as, hwaddr addr);
+            void (*tb_invalidate_phys_page_fast)(tb_page_addr_t start, int len);
+            void (*tb_invalidate_phys_range)(tb_page_addr_t start, tb_page_addr_t end);
+
+            void (*dump_exec_info)(FILE *f, fprintf_function cpu_fprintf);
+            void (*dump_opcount_info)(FILE *f, fprintf_function cpu_fprintf);
+#endif
+            int (*cpu_exec)(CPUState *cpu);
+
+            bool (*tcg_enabled)(void);
+#endif
+            void (*hooks_end_of_list)(void);
+        };
+        void (*hooks_dummy[MAX_CPU_HOOKS + 1])(void);
+    };
 };
 
+QEMU_BUILD_BUG_ON(offsetof(CPUState, hooks_end_of_list) >
+                  offsetof(CPUState, hooks_dummy[MAX_CPU_HOOKS + 1]))
+
+#ifndef CONFIG_USER_ONLY
+#define IFN_USER_ONLY(a) a
+#else
+#define IFN_USER_ONLY(a)
+#endif
+
+#define CPU_SET_QOM_HOOKS(cpu) do {                                         \
+    cpu->cpu_get_tb_cpu_state           =                                   \
+         (void (*)(void *, void *, void *, int *))cpu_get_tb_cpu_state;     \
+                                                                            \
+    cpu->tb_check_watchpoint            = tb_check_watchpoint;              \
+    cpu->tb_flush                       = tb_flush;                         \
+    cpu->tb_gen_code                    = tb_gen_code;                      \
+                                                                            \
+    IFN_USER_ONLY(                                                          \
+    cpu->tlb_flush                      = tlb_flush;                        \
+    cpu->tlb_flush_page                 = tlb_flush_page;                   \
+    cpu->tlb_set_dirty                  = tlb_set_dirty;                    \
+    cpu->tlb_reset_dirty                = tlb_reset_dirty;                  \
+                                                                            \
+    cpu->tb_invalidate_phys_addr        = tb_invalidate_phys_addr;          \
+    cpu->tb_invalidate_phys_page_fast   = tb_invalidate_phys_page_fast;     \
+    cpu->tb_invalidate_phys_range       = tb_invalidate_phys_range;         \
+                                                                            \
+    cpu->dump_exec_info                 = dump_exec_info;                   \
+    cpu->dump_opcount_info              = dump_opcount_info;                \
+    )                                                                       \
+                                                                            \
+    cpu->cpu_exec                       = cpu_exec;                         \
+                                                                            \
+    cpu->tcg_enabled                    = tcg_enabled;                      \
+} while (0);
+
+#ifdef TARGET_MULTI
+#define CPU_HOOK(cpu, fn)(cpu->fn)
+#else
+#define CPU_HOOK(cpu, fn)(fn)
+#endif
+
 QTAILQ_HEAD(CPUTailQ, CPUState);
 extern struct CPUTailQ cpus;
 #define CPU_NEXT(cpu) QTAILQ_NEXT(cpu, node)
diff --git a/monitor.c b/monitor.c
index f283035..cb66692 100644
--- a/monitor.c
+++ b/monitor.c
@@ -962,13 +962,13 @@ static void hmp_info_registers(Monitor *mon, const QDict *qdict)
 
 static void hmp_info_jit(Monitor *mon, const QDict *qdict)
 {
-    dump_exec_info((FILE *)mon, monitor_fprintf);
+    CPU_HOOK(mon_get_cpu(), dump_exec_info)((FILE *)mon, monitor_fprintf);
     dump_drift_info((FILE *)mon, monitor_fprintf);
 }
 
 static void hmp_info_opcount(Monitor *mon, const QDict *qdict)
 {
-    dump_opcount_info((FILE *)mon, monitor_fprintf);
+    CPU_HOOK(mon_get_cpu(), dump_opcount_info)((FILE *)mon, monitor_fprintf);
 }
 
 static void hmp_info_history(Monitor *mon, const QDict *qdict)
