diff --git a/Makefile.objs b/Makefile.objs
index dac2c02..c268acb 100644
--- a/Makefile.objs
+++ b/Makefile.objs
@@ -103,6 +103,7 @@ version-lobj-$(CONFIG_WIN32) += $(BUILD_DIR)/version.lo
 # tracing
 util-obj-y +=  trace/
 target-obj-y += trace/
+stub-obj-y += trace/
 
 ######################################################################
 # guest agent
diff --git a/bsd-user/main.c b/bsd-user/main.c
index 1ecaeb5..b57f9a9 100644
--- a/bsd-user/main.c
+++ b/bsd-user/main.c
@@ -31,6 +31,7 @@
 /* For tb_lock */
 #include "cpu.h"
 #include "tcg.h"
+#include "trace/control.h"
 #include "qemu/timer.h"
 #include "qemu/envlist.h"
 
@@ -1121,6 +1122,7 @@ int main(int argc, char **argv)
         gdbserver_start (gdbstub_port);
         gdb_handlesig(cpu, 0);
     }
+    trace_init_vcpu_events();
     cpu_loop(env);
     /* never exits */
     return 0;
diff --git a/include/qom/cpu.h b/include/qom/cpu.h
index 40962e0..d1cbd7a 100644
--- a/include/qom/cpu.h
+++ b/include/qom/cpu.h
@@ -29,6 +29,7 @@
 #include "qemu/queue.h"
 #include "qemu/thread.h"
 #include "qemu/typedefs.h"
+#include "trace/generated-events.h"
 
 typedef int (*WriteCoreDumpFunction)(const void *buf, size_t size,
                                      void *opaque);
@@ -319,6 +320,15 @@ struct CPUState {
     unsigned int tb_phys_idx;
     unsigned int tb_phys_idx_req;
 
+    /*
+     * Ensure 'tb_phys_idx' can encode event states as a bitmask.
+     *
+     * This limits the number of events with the 'tcg' property and *without*
+     * the 'disabled' property to 'sizeof(tb_phys_idx)*8'.
+     */
+    bool too_many_tcg_vcpu_events[
+        TRACE_CPU_EVENT_COUNT > sizeof(unsigned int)*8 ? -1 : 0];
+
     /* TODO Move common fields from CPUArchState here. */
     int cpu_index; /* used by alpha TCG */
     uint32_t halted; /* used by alpha, cris, ppc TCG */
diff --git a/linux-user/main.c b/linux-user/main.c
index ee12035..44f0536 100644
--- a/linux-user/main.c
+++ b/linux-user/main.c
@@ -30,6 +30,7 @@
 #include "qemu-common.h"
 #include "cpu.h"
 #include "tcg.h"
+#include "trace/control.h"
 #include "qemu/timer.h"
 #include "qemu/envlist.h"
 #include "elf.h"
@@ -4654,6 +4655,7 @@ int main(int argc, char **argv, char **envp)
         }
         gdb_handlesig(cpu, 0);
     }
+    trace_init_vcpu_events();
     cpu_loop(env);
     /* never exits */
     return 0;
diff --git a/monitor.c b/monitor.c
index c53a453..7c42e78 100644
--- a/monitor.c
+++ b/monitor.c
@@ -888,7 +888,7 @@ static void hmp_trace_event(Monitor *mon, const QDict *qdict)
     bool new_state = qdict_get_bool(qdict, "option");
     Error *local_err = NULL;
 
-    qmp_trace_event_set_state(tp_name, new_state, true, true, &local_err);
+    qmp_trace_event_set_state(tp_name, new_state, true, true, false, 0, &local_err);
     if (local_err) {
         error_report_err(local_err);
     }
@@ -1047,7 +1047,7 @@ static void hmp_info_cpustats(Monitor *mon, const QDict *qdict)
 
 static void hmp_info_trace_events(Monitor *mon, const QDict *qdict)
 {
-    TraceEventInfoList *events = qmp_trace_event_get_state("*", NULL);
+    TraceEventInfoList *events = qmp_trace_event_get_state("*", false, 0, NULL);
     TraceEventInfoList *elem;
 
     for (elem = events; elem != NULL; elem = elem->next) {
diff --git a/qapi/trace.json b/qapi/trace.json
index 84c34dd..8708dba 100644
--- a/qapi/trace.json
+++ b/qapi/trace.json
@@ -42,13 +42,18 @@
 # Query the state of events.
 #
 # @name: Event name pattern (case-sensitive glob).
+# @vcpu: #optional The vCPU to check (any by default; since 2.6).
 #
 # Returns: a list of @TraceEventInfo for the matching events
 #
+# For any event without the "vcpu" property:
+# - If @name is a pattern and @vcpu is set, events are ignored.
+# - If @name is not a pattern and @vcpu is set, an error is raised.
+#
 # Since 2.2
 ##
 { 'command': 'trace-event-get-state',
-  'data': {'name': 'str'},
+  'data': {'name': 'str', '*vcpu': 'int'},
   'returns': ['TraceEventInfo'] }
 
 ##
@@ -59,8 +64,10 @@
 # @name: Event name pattern (case-sensitive glob).
 # @enable: Whether to enable tracing.
 # @ignore-unavailable: #optional Do not match unavailable events with @name.
+# @vcpu: #optional The vCPU to act upon (all by default; since 2.6).
 #
 # Since 2.2
 ##
 { 'command': 'trace-event-set-state',
-  'data': {'name': 'str', 'enable': 'bool', '*ignore-unavailable': 'bool'} }
+  'data': {'name': 'str', 'enable': 'bool', '*ignore-unavailable': 'bool',
+           '*vcpu': 'int'} }
diff --git a/qmp-commands.hx b/qmp-commands.hx
index db072a6..ce6a419 100644
--- a/qmp-commands.hx
+++ b/qmp-commands.hx
@@ -4566,7 +4566,7 @@ EQMP
 
     {
         .name       = "trace-event-get-state",
-        .args_type  = "name:s",
+        .args_type  = "name:s,vcpu:i?",
         .mhandler.cmd_new = qmp_marshal_trace_event_get_state,
     },
 
@@ -4576,6 +4576,11 @@ trace-event-get-state
 
 Query the state of events.
 
+Arguments:
+
+- "name": Event name pattern (json-string).
+- "vcpu": Specific vCPU to query, any vCPU by default (json-int, optional).
+
 Example:
 
 -> { "execute": "trace-event-get-state", "arguments": { "name": "qemu_memalign" } }
@@ -4584,7 +4589,7 @@ EQMP
 
     {
         .name       = "trace-event-set-state",
-        .args_type  = "name:s,enable:b,ignore-unavailable:b?",
+        .args_type  = "name:s,enable:b,ignore-unavailable:b?,vcpu:i?",
         .mhandler.cmd_new = qmp_marshal_trace_event_set_state,
     },
 
@@ -4594,6 +4599,13 @@ trace-event-set-state
 
 Set the state of events.
 
+Arguments:
+
+- "name": Event name pattern (json-string).
+- "enable": Whether to enable or disable the event (json-bool).
+- "ignore-unavailable": Whether to ignore errors for events that cannot be changed (json-bool, optional).
+- "vcpu": Specific vCPU to set, all vCPUs by default (json-int, optional).
+
 Example:
 
 -> { "execute": "trace-event-set-state", "arguments": { "name": "qemu_memalign", "enable": "true" } }
@@ -4662,7 +4674,6 @@ Move mouse pointer to absolute coordinates (20000, 400).
                { "type": "abs", "data" : { "axis": "X", "value" : 20000 } },
                { "type": "abs", "data" : { "axis": "Y", "value" : 400 } } ] } }
 <- { "return": {} }
-
 EQMP
 
     {
diff --git a/scripts/tracetool/format/h.py b/scripts/tracetool/format/h.py
index 2bd68a2..f98f26b 100644
--- a/scripts/tracetool/format/h.py
+++ b/scripts/tracetool/format/h.py
@@ -36,6 +36,7 @@ def generate(events, backend):
             args=e.args)
 
         if "disable" not in e.properties:
+            # NOTE: See note on 'trace_event_set_cpu_state()'.
             backend.generate(e)
 
         out('}')
diff --git a/scripts/tracetool/format/tcg_helper_c.py b/scripts/tracetool/format/tcg_helper_c.py
index 96655a0..e5dd3fe 100644
--- a/scripts/tracetool/format/tcg_helper_c.py
+++ b/scripts/tracetool/format/tcg_helper_c.py
@@ -6,7 +6,7 @@ Generate trace/generated-helpers.c.
 """
 
 __author__     = "Lluís Vilanova <vilanova@ac.upc.edu>"
-__copyright__  = "Copyright 2012-2014, Lluís Vilanova <vilanova@ac.upc.edu>"
+__copyright__  = "Copyright 2012-2016, Lluís Vilanova <vilanova@ac.upc.edu>"
 __license__    = "GPL version 2 or (at your option) any later version"
 
 __maintainer__ = "Stefan Hajnoczi"
@@ -36,8 +36,13 @@ def generate(events, backend):
         # tracetool.generate always transforms types to host
         e_args = e.original.args
 
-        values = ["(%s)%s" % (t, n)
-                  for t, n in e.args.transform(TCG_2_TCG_HELPER_DEF)]
+        values = []
+        for (t_old, n), (t_new, _) in zip(
+                e.args, e.args.transform(TCG_2_TCG_HELPER_DEF)):
+            if t_old == "CPUState *":
+                values.append("ENV_GET_CPU((CPUArchState*)%s)" % n)
+            else:
+                values.append("(%s)%s" % (t_new, n))
 
         out('void %(name_tcg)s(%(args)s)',
             '{',
diff --git a/trace/Makefile.objs b/trace/Makefile.objs
index 5145b34..a179d02 100644
--- a/trace/Makefile.objs
+++ b/trace/Makefile.objs
@@ -152,4 +152,6 @@ util-obj-$(CONFIG_TRACE_SIMPLE) += simple.o generated-tracers.o
 util-obj-$(CONFIG_TRACE_FTRACE) += ftrace.o
 util-obj-$(CONFIG_TRACE_UST) += generated-ust.o
 util-obj-y += control.o
+target-obj-y += control-target.o
+stub-obj-y += control-stub.o
 util-obj-y += qmp.o
diff --git a/trace/control-internal.h b/trace/control-internal.h
index ab42396..81e757f 100644
--- a/trace/control-internal.h
+++ b/trace/control-internal.h
@@ -12,6 +12,9 @@
 
 #include <string.h>
 
+#include "qemu-common.h"
+#include "qom/cpu.h"
+
 
 extern TraceEvent trace_events[];
 
@@ -60,14 +63,16 @@ static inline bool trace_event_get_state_static(TraceEvent *ev)
 static inline bool trace_event_get_state_dynamic(TraceEvent *ev)
 {
     assert(ev != NULL);
-    return ev->dstate;
+    /* no need to iterate over vCPUs, since the global dynamic state is always set */
+    return ev->dstate > 0;
 }
 
-static inline void trace_event_set_state_dynamic(TraceEvent *ev, bool state)
+static inline bool trace_event_get_cpu_state_dynamic(CPUState *cpu,
+                                                     TraceEvent *ev)
 {
+    assert(cpu != NULL);
     assert(ev != NULL);
-    assert(trace_event_get_state_static(ev));
-    ev->dstate = state;
+    return cpu->tb_phys_idx & (((unsigned long)1) << ev->cpu_id);
 }
 
 #endif  /* TRACE__CONTROL_INTERNAL_H */
diff --git a/trace/control-stub.c b/trace/control-stub.c
new file mode 100644
index 0000000..784bc35
--- /dev/null
+++ b/trace/control-stub.c
@@ -0,0 +1,29 @@
+/*
+ * Interface for configuring and controlling the state of tracing events.
+ *
+ * Copyright (C) 2014-2016 Lluís Vilanova <vilanova@ac.upc.edu>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#include "trace/control.h"
+
+
+void trace_init_vcpu_tb_caches(void)
+{
+}
+
+void trace_event_set_state_dynamic(TraceEvent *ev, bool state)
+{
+    assert(ev != NULL);
+    assert(trace_event_get_state_static(ev));
+    ev->dstate = state;
+}
+
+void trace_event_set_cpu_state_dynamic(CPUState *cpu,
+                                       TraceEvent *ev, bool state)
+{
+    /* should never be called on non-target binaries */
+    abort();
+}
diff --git a/trace/control-target.c b/trace/control-target.c
new file mode 100644
index 0000000..c256f54
--- /dev/null
+++ b/trace/control-target.c
@@ -0,0 +1,69 @@
+/*
+ * Interface for configuring and controlling the state of tracing events.
+ *
+ * Copyright (C) 2014-2016 Lluís Vilanova <vilanova@ac.upc.edu>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#include "trace/control.h"
+#include "cpu.h"
+#include "translate-all.h"
+
+
+void trace_init_vcpu_tb_caches(void)
+{
+    unsigned int events = 1;
+    TraceEvent *ev = NULL;
+    while ((ev = trace_event_pattern("*", ev)) != NULL) {
+        if (trace_event_get_cpu_id(ev) == TRACE_CPU_EVENT_COUNT) {
+            continue;
+        }
+        events <<= 1;
+    }
+    tb_caches_set(events);
+    tb_caches_apply();
+}
+
+void trace_event_set_state_dynamic(TraceEvent *ev, bool state)
+{
+    CPUState *cpu;
+    assert(ev != NULL);
+    assert(trace_event_get_state_static(ev));
+    if (trace_event_get_cpu_id(ev) != TRACE_CPU_EVENT_COUNT) {
+        CPU_FOREACH(cpu) {
+            trace_event_set_cpu_state_dynamic(cpu, ev, state);
+        }
+    } else {
+        ev->dstate = state;
+    }
+}
+
+void trace_event_set_cpu_state_dynamic(CPUState *cpu,
+                                       TraceEvent *ev, bool state)
+{
+    /*
+     * NOTE: Does not immediately apply changes on all affected vCPUs, so
+     *       "tcg-exec" events might be generated after being disabled (until
+     *       the vCPU finishes a BBL and checks for event state changes).
+     */
+    unsigned int bit;
+    bool state_pre;
+    assert(cpu != NULL);
+    assert(ev != NULL);
+    assert(trace_event_get_state_static(ev));
+    assert(trace_event_get_cpu_id(ev) != TRACE_CPU_EVENT_COUNT);
+    bit = ((unsigned long)1) << ev->cpu_id;
+    /* must use the requested TB index in case it's not yet synchronized */
+    state_pre = cpu->tb_phys_idx_req & bit;
+    if ((state_pre == 0) != (state == 0)) {
+        if (state) {
+            cpu_tb_cache_set(cpu, (cpu->tb_phys_idx_req & ~bit) | bit);
+            ev->dstate++;
+        } else {
+            cpu_tb_cache_set(cpu, (cpu->tb_phys_idx_req & ~bit));
+            ev->dstate--;
+        }
+    }
+}
diff --git a/trace/control.c b/trace/control.c
index 995beb3..2bd5908 100644
--- a/trace/control.c
+++ b/trace/control.c
@@ -1,7 +1,7 @@
 /*
  * Interface for configuring and controlling the state of tracing events.
  *
- * Copyright (C) 2011-2014 Lluís Vilanova <vilanova@ac.upc.edu>
+ * Copyright (C) 2011-2016 Lluís Vilanova <vilanova@ac.upc.edu>
  *
  * This work is licensed under the terms of the GNU GPL, version 2 or later.
  * See the COPYING file in the top-level directory.
@@ -167,3 +167,18 @@ bool trace_init_backends(const char *events, const char *file)
     trace_init_events(events);
     return true;
 }
+
+void trace_init_vcpu_events(void)
+{
+    TraceEvent *ev = NULL;
+    /*
+     * Set per-vCPU tracing state according to initial event state. The calls
+     * here affect al vCPUs in the system.
+     */
+    while ((ev = trace_event_pattern("*", ev)) != NULL) {
+        if (trace_event_get_cpu_id(ev) != TRACE_CPU_EVENT_COUNT &&
+            trace_event_get_state_dynamic(ev)) {
+            trace_event_set_state_dynamic(ev, true);
+        }
+    }
+}
diff --git a/trace/control.h b/trace/control.h
index 860beed..1103a4c 100644
--- a/trace/control.h
+++ b/trace/control.h
@@ -115,6 +115,22 @@ static const char * trace_event_get_name(TraceEvent *ev);
     ((id ##_ENABLED) && trace_event_get_state_dynamic(trace_event_id(id)))
 
 /**
+ * trace_event_get_cpu_state:
+ * @id: Event identifier.
+ *
+ * Get the tracing state of an event (both static and dynamic) for the given
+ * vCPU.
+ *
+ * If the event has the disabled property, the check will have no performance
+ * impact.
+ *
+ * As a down side, you must always use an immediate #TraceEventID value.
+ */
+#define trace_event_get_cpu_state(cpu, id)                              \
+    ((id ##_ENABLED) && trace_event_get_cpu_state_dynamic(cpu,          \
+                                                          trace_event_id(id)))
+
+/**
  * trace_event_get_state_static:
  * @id: Event identifier.
  *
@@ -129,10 +145,19 @@ static bool trace_event_get_state_static(TraceEvent *ev);
  * trace_event_get_state_dynamic:
  *
  * Get the dynamic tracing state of an event.
+ *
+ * If the event has the 'vcpu' property, gets the OR'ed state of all vCPUs.
  */
 static bool trace_event_get_state_dynamic(TraceEvent *ev);
 
 /**
+ * trace_event_get_cpu_state_dynamic:
+ *
+ * Get the dynamic tracing state of an event for the given vCPU.
+ */
+static bool trace_event_get_cpu_state_dynamic(CPUState *cpu, TraceEvent *ev);
+
+/**
  * trace_event_set_state:
  *
  * Set the tracing state of an event (only if possible).
@@ -146,13 +171,38 @@ static bool trace_event_get_state_dynamic(TraceEvent *ev);
     } while (0)
 
 /**
+ * trace_event_set_cpu_state:
+ *
+ * Set the tracing state of an event for the given vCPU (only if possible).
+ */
+#define trace_event_set_cpu_state(cpu, id, state)               \
+    do {                                                        \
+        if ((id ##_ENABLED)) {                                  \
+            TraceEvent *_e = trace_event_id(id);                \
+            trace_event_set_cpu_state_dynamic(cpu, _e, state);  \
+        }                                                       \
+    } while (0)
+
+/**
  * trace_event_set_state_dynamic:
  *
  * Set the dynamic tracing state of an event.
  *
+ * If the event has the 'vcpu' property, sets the state on all vCPUs.
+ *
  * Pre-condition: trace_event_get_state_static(ev) == true
  */
-static void trace_event_set_state_dynamic(TraceEvent *ev, bool state);
+void trace_event_set_state_dynamic(TraceEvent *ev, bool state);
+
+/**
+ * trace_event_set_cpu_state_dynamic:
+ *
+ * Set the dynamic tracing state of an event for the given vCPU.
+ *
+ * Pre-condition: trace_event_get_cpu_state_static(ev) == true
+ */
+void trace_event_set_cpu_state_dynamic(CPUState *cpu,
+                                       TraceEvent *ev, bool state);
 
 
 
@@ -169,6 +219,9 @@ static void trace_event_set_state_dynamic(TraceEvent *ev, bool state);
  */
 bool trace_init_backends(const char *events, const char *file);
 
+void trace_init_vcpu_tb_caches(void);
+void trace_init_vcpu_events(void);
+
 
 #include "trace/control-internal.h"
 
diff --git a/trace/event-internal.h b/trace/event-internal.h
index 258bcde..b785017 100644
--- a/trace/event-internal.h
+++ b/trace/event-internal.h
@@ -28,7 +28,7 @@ typedef struct TraceEvent {
     TraceEventCPUID cpu_id;
     const char * name;
     const bool sstate;
-    bool dstate;
+    size_t dstate;
 } TraceEvent;
 
 
diff --git a/trace/qmp.c b/trace/qmp.c
index be99496..8ee9b70 100644
--- a/trace/qmp.c
+++ b/trace/qmp.c
@@ -12,64 +12,152 @@
 #include "trace/control.h"
 
 
-TraceEventInfoList *qmp_trace_event_get_state(const char *name, Error **errp)
+static bool get_cpu_state(bool has_index, int index, CPUState **cpu, Error **errp)
+{
+    if (has_index) {
+        *cpu = qemu_get_cpu(index);
+        if (*cpu == NULL) {
+            error_setg(errp, "invalid vCPU index %u", index);
+            return false;
+        }
+    } else {
+        *cpu = NULL;
+    }
+    return true;
+}
+
+TraceEventInfoList *qmp_trace_event_get_state(const char *name,
+                                              bool has_vcpu, int64_t vcpu,
+                                              Error **errp)
 {
     TraceEventInfoList *events = NULL;
-    bool found = false;
     TraceEvent *ev;
+    bool is_pattern = trace_event_is_pattern(name);
+    CPUState *cpu;
+
+    if (!get_cpu_state(has_vcpu, vcpu, &cpu, errp)) {
+        return NULL;
+    }
 
     ev = NULL;
     while ((ev = trace_event_pattern(name, ev)) != NULL) {
-        TraceEventInfoList *elem = g_new(TraceEventInfoList, 1);
+        TraceEventInfoList *elem;
+        bool is_vcpu = trace_event_get_cpu_id(ev) != TRACE_CPU_EVENT_COUNT;
+        if (has_vcpu && !is_vcpu) {
+            if (!is_pattern) {
+                error_setg(errp, "event \"%s\" is not vCPU-specific", name);
+            }
+            /* else: ignore */
+            continue;
+        }
+
+        elem = g_new(TraceEventInfoList, 1);
         elem->value = g_new(TraceEventInfo, 1);
-        elem->value->vcpu = trace_event_get_cpu_id(ev) != TRACE_CPU_EVENT_COUNT;
+        elem->value->vcpu = is_vcpu;
         elem->value->name = g_strdup(trace_event_get_name(ev));
+
         if (!trace_event_get_state_static(ev)) {
             elem->value->state = TRACE_EVENT_STATE_UNAVAILABLE;
-        } else if (!trace_event_get_state_dynamic(ev)) {
-            elem->value->state = TRACE_EVENT_STATE_DISABLED;
         } else {
-            elem->value->state = TRACE_EVENT_STATE_ENABLED;
+            if (has_vcpu) {
+                if (is_vcpu) {
+                    if (trace_event_get_cpu_state_dynamic(cpu, ev)) {
+                        elem->value->state = TRACE_EVENT_STATE_ENABLED;
+                    } else {
+                        elem->value->state = TRACE_EVENT_STATE_DISABLED;
+                    }
+                }
+                /* else: already handled above */
+            } else {
+                if (trace_event_get_state_dynamic(ev)) {
+                    elem->value->state = TRACE_EVENT_STATE_ENABLED;
+                } else {
+                    elem->value->state = TRACE_EVENT_STATE_DISABLED;
+                }
+            }
         }
         elem->next = events;
         events = elem;
-        found = true;
     }
 
-    if (!found && !trace_event_is_pattern(name)) {
+    if (events == NULL && !is_pattern) {
         error_setg(errp, "unknown event \"%s\"", name);
     }
 
     return events;
 }
 
+
+static void check_events_are_dynamic(const char *name, bool per_cpu,
+                                     bool *found, bool error_check,
+                                     bool *error_found, Error **errp)
+{
+    TraceEvent *ev = NULL;
+    *found = false;
+    *error_found = false;
+    while ((ev = trace_event_pattern(name, ev)) != NULL) {
+        if (per_cpu && trace_event_get_cpu_id(ev) == TRACE_CPU_EVENT_COUNT) {
+            if (error_check) {
+                Error *local_err = NULL;
+                error_setg(&local_err, "event \"%s\" is not vCPU-specific",
+                           trace_event_get_name(ev));
+                error_propagate(errp, local_err);
+                *error_found = true;
+            }
+            continue;
+        }
+        if (!trace_event_get_state_static(ev)) {
+            if (error_check) {
+                Error *local_err = NULL;
+                error_setg(&local_err, "cannot set dynamic tracing state for \"%s\"",
+                           trace_event_get_name(ev));
+                error_propagate(errp, local_err);
+                *error_found = true;
+            }
+            continue;
+        }
+        *found = true;
+    }
+}
+
 void qmp_trace_event_set_state(const char *name, bool enable,
-                               bool has_ignore_unavailable,
-                               bool ignore_unavailable, Error **errp)
+                               bool has_ignore_unavailable, bool ignore_unavailable,
+                               bool has_vcpu, int64_t vcpu,
+                               Error **errp)
 {
-    bool found = false;
-    TraceEvent *ev;
+    bool error, found;
+    TraceEvent *ev = NULL;
+    CPUState *cpu;
+
+    if (!get_cpu_state(has_vcpu, vcpu, &cpu, errp)) {
+        return;
+    }
 
     /* Check all selected events are dynamic */
-    ev = NULL;
-    while ((ev = trace_event_pattern(name, ev)) != NULL) {
-        found = true;
-        if (!(has_ignore_unavailable && ignore_unavailable) &&
-            !trace_event_get_state_static(ev)) {
-            error_setg(errp, "cannot set dynamic tracing state for \"%s\"",
-                       trace_event_get_name(ev));
-            return;
-        }
+    check_events_are_dynamic(name, has_vcpu, &found,
+                             !(has_ignore_unavailable && ignore_unavailable),
+                             &error, errp);
+    if (error) {
+        return;
     }
-    if (!found && !trace_event_is_pattern(name)) {
-        error_setg(errp, "unknown event \"%s\"", name);
+    if (!found) {
+        if (!trace_event_is_pattern(name)) {
+            error_setg(errp, "unknown event \"%s\"", name);
+        }
         return;
     }
 
     /* Apply changes */
     ev = NULL;
     while ((ev = trace_event_pattern(name, ev)) != NULL) {
-        if (trace_event_get_state_static(ev)) {
+        if (!trace_event_get_state_static(ev) ||
+            (has_vcpu && trace_event_get_cpu_id(ev) == TRACE_CPU_EVENT_COUNT)) {
+            /* if it were an error, it was catched by the check above */
+            continue;
+        }
+        if (has_vcpu) {
+            trace_event_set_cpu_state_dynamic(cpu, ev, enable);
+        } else {
             trace_event_set_state_dynamic(ev, enable);
         }
     }
diff --git a/translate-all.c b/translate-all.c
index 2395977..dab44ad 100644
--- a/translate-all.c
+++ b/translate-all.c
@@ -179,6 +179,7 @@ void cpu_gen_init(void)
     tcg_ctx.tb_ctx.tb_phys_hash_size_req = 1;
     tcg_ctx.tb_ctx.tb_phys_hash = NULL;
     tb_caches_apply();
+    trace_init_vcpu_tb_caches();
 }
 
 /* Encode VAL as a signed leb128 sequence at P.
diff --git a/vl.c b/vl.c
index b7a083e..dcba749 100644
--- a/vl.c
+++ b/vl.c
@@ -4659,6 +4659,7 @@ int main(int argc, char **argv, char **envp)
         }
     }
 
+    trace_init_vcpu_events();
     main_loop();
     replay_disable_events();
 
