diff --git a/.gitignore b/.gitignore
index ee2768cb05..d3aa8286b0 100644
--- a/.gitignore
+++ b/.gitignore
@@ -9,6 +9,7 @@
 /instrument-root.c
 /instrument/generated-tcg-tracers.h
 /instrument/generated-tcg-tracers.c
+/instrument/qemu-instr/events.h
 /trace-events-all
 /trace/generated-events.h
 /trace/generated-events.c
diff --git a/Makefile b/Makefile
index fb226bf54b..c8be326790 100644
--- a/Makefile
+++ b/Makefile
@@ -193,6 +193,9 @@ trace-dtrace-root.o: trace-dtrace-root.dtrace
 
 INSTRUMENT_HEADERS = instrument-root.h $(trace-events-subdirs:%=%/instrument.h)
 INSTRUMENT_HEADERS += instrument/generated-tcg-tracers.h
+ifeq ($(TRACE_INSTRUMENT_BACKEND),instr-dynamic)
+INSTRUMENT_HEADERS += instrument/qemu-instr/events.h
+endif
 INSTRUMENT_SOURCES = instrument-root.c $(trace-events-subdirs:%=%/instrument.c)
 INSTRUMENT_SOURCES += instrument/generated-tcg-tracers.c
 
@@ -644,8 +647,13 @@ ifneq (,$(findstring qemu-ga,$(TOOLS)))
 endif
 endif
 
+install-includedir:
+	$(INSTALL_DIR) "$(DESTDIR)$(includedir)"
 
 install: all $(if $(BUILD_DOCS),install-doc) install-datadir install-localstatedir
+ifeq ($(TRACE_INSTRUMENT_BACKEND),instr-dynamic)
+install: install-includedir
+endif
 ifneq ($(TOOLS),)
 	$(call install-prog,$(subst qemu-ga,qemu-ga$(EXESUF),$(TOOLS)),$(DESTDIR)$(bindir))
 endif
@@ -676,6 +684,12 @@ endif
 	for d in $(TARGET_DIRS); do \
 	$(MAKE) $(SUBDIR_MAKEFLAGS) TARGET_DIR=$$d/ -C $$d $@ || exit 1 ; \
         done
+ifeq ($(TRACE_INSTRUMENT_BACKEND),instr-dynamic)
+	$(INSTALL_DIR) "$(DESTDIR)$(includedir)/qemu-instr"
+	$(INSTALL_DATA) $(SRC_PATH)/instrument/qemu-instr/types.h "$(DESTDIR)$(includedir)/qemu-instr/"
+	$(INSTALL_DATA) $(SRC_PATH)/instrument/qemu-instr/visibility-internal.h "$(DESTDIR)$(includedir)/qemu-instr/"
+	$(INSTALL_DATA) $(BUILD_DIR)/instrument/qemu-instr/events.h "$(DESTDIR)$(includedir)/qemu-instr/"
+endif
 
 # various test targets
 test speed: all
diff --git a/configure b/configure
index 8ab2a36130..ca61665874 100755
--- a/configure
+++ b/configure
@@ -489,6 +489,8 @@ QEMU_CFLAGS="-Wall -Wundef -Wwrite-strings -Wmissing-prototypes $QEMU_CFLAGS"
 QEMU_CFLAGS="-Wstrict-prototypes -Wredundant-decls $QEMU_CFLAGS"
 QEMU_CFLAGS="-D_GNU_SOURCE -D_FILE_OFFSET_BITS=64 -D_LARGEFILE_SOURCE $QEMU_CFLAGS"
 QEMU_INCLUDES="-I. -I\$(SRC_PATH) -I\$(SRC_PATH)/accel/tcg -I\$(SRC_PATH)/include"
+QEMU_INCLUDES="$QEMU_INCLUDES -I\$(SRC_PATH)/instrument"
+QEMU_INCLUDES="$QEMU_INCLUDES -I\$(BUILD_DIR)/instrument"
 if test "$debug_info" = "yes"; then
     CFLAGS="-g $CFLAGS"
     LDFLAGS="-g $LDFLAGS"
diff --git a/instrument/Makefile.objs b/instrument/Makefile.objs
index c548bbdd8a..c2289aba85 100644
--- a/instrument/Makefile.objs
+++ b/instrument/Makefile.objs
@@ -24,3 +24,17 @@ $(obj)/generated-tcg-tracers.c-timestamp: $(SRC_PATH)/trace-events $(BUILD_DIR)/
 		$< > $@,"GEN","$(patsubst %-timestamp,%,$@)")
 
 target-obj-$(CONFIG_INSTRUMENT) += generated-tcg-tracers.o
+
+######################################################################
+# User interface
+
+$(obj)/qemu-instr/events.h: $(obj)/qemu-instr/events.h-timestamp
+	@cmp -s $< $@ || cp $< $@
+$(obj)/qemu-instr/events.h-timestamp: $(BUILD_DIR)/trace-events-all $(BUILD_DIR)/config-host.mak $(tracetool-y)
+	@mkdir -p $(dir $@)
+	$(call quiet-command,$(TRACETOOL) \
+		--group=root \
+		--format=instr-api-h \
+		--backend=$(TRACE_INSTRUMENT_BACKEND) \
+		$(TRACETOOL_INSTR_STATIC) \
+		$< > $@,"GEN","$(patsubst %-timestamp,%,$@)")
diff --git a/instrument/qemu-instr/types.h b/instrument/qemu-instr/types.h
new file mode 100644
index 0000000000..d3d26bbf73
--- /dev/null
+++ b/instrument/qemu-instr/types.h
@@ -0,0 +1,73 @@
+/*
+ * QI-specific types for instrumentation clients.
+ *
+ * Copyright (C) 2012-2017 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.
+ */
+
+#ifndef QI__TYPES_H
+#define QI__TYPES_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * SECTION: types
+ * @section_id: qi-types
+ * @title: Common types
+ *
+ * Data of architecture-specific length is always passed as an #int64_t to
+ * provide binary compatibility between the instrumentation library and QEMU,
+ * regardless of the guest architecture being instrumented.
+ */
+
+/**
+ * QICPU:
+ *
+ * Opaque guest CPU pointer.
+ */
+typedef struct QICPU QICPU;
+
+/**
+ * QITCGv_cpu:
+ *
+ * TCG register with QICPU.
+ */
+typedef struct QITCGv_cpu_d *QITCGv_cpu;
+
+/**
+ * QITCGv:
+ *
+ * TCG register with data of architecture-specific length.
+ */
+typedef struct QITCGv_d *QITCGv;
+
+/**
+ * QITCGv_i32:
+ *
+ * TCG register with 32-bit data.
+ */
+typedef struct QITCGv_i32_d *QITCGv_i32;
+
+/**
+ * QITCGv_i64:
+ *
+ * TCG register with 64-bit data.
+ */
+typedef struct QITCGv_i64_d *QITCGv_i64;
+
+/**
+ * QITCGv_ptr:
+ *
+ * TCG register with pointer of architecture-specific length.
+ */
+typedef struct QITCGv_ptr_d *QITCGv_ptr;
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif  /* QI__TYPES_H */
diff --git a/instrument/qemu-instr/visibility-internal.h b/instrument/qemu-instr/visibility-internal.h
new file mode 100644
index 0000000000..3bbbdc34cd
--- /dev/null
+++ b/instrument/qemu-instr/visibility-internal.h
@@ -0,0 +1,58 @@
+/*
+ * Macros for symbol visibility.
+ *
+ * Copyright (C) 2012-2017 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 of QEMU.
+ */
+
+#ifndef QI__VISIBILITY_INTERNAL_H
+#define QI__VISIBILITY_INTERNAL_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * SECTION:visibility
+ * @section_id: qi-visibility
+ * @title: Symbol visibility
+ *
+ * This code is taken from http://gcc.gnu.org/wiki/Visibility.
+ */
+
+/**
+ * QI_VPUBLIC:
+ *
+ * Make an element public to user's instrumentation code.
+ */
+
+/**
+ * QI_VLOCAL:
+ *
+ * Make an element not visible to user's instrumentation code.
+ */
+
+#if defined _WIN32 || defined __CYGWIN__
+  #ifdef __GNUC__
+    #define QI_VPUBLIC __attribute__ ((dllimport))
+  #else
+    #define QI_VPUBLIC __declspec(dllimport)
+  #endif
+  #define QI_VLOCAL
+#else
+  #if __GNUC__ >= 4
+    #define QI_VPUBLIC __attribute__ ((visibility ("default")))
+    #define QI_VLOCAL  __attribute__ ((visibility ("hidden")))
+  #else
+    #define QI_VPUBLIC
+    #define QI_VLOCAL
+  #endif
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif  /* QI__VISIBILITY_INTERNAL_H */
diff --git a/scripts/tracetool/__init__.py b/scripts/tracetool/__init__.py
index b9ddf8fbf9..7c19dc8c94 100644
--- a/scripts/tracetool/__init__.py
+++ b/scripts/tracetool/__init__.py
@@ -277,6 +277,10 @@ class Event(object):
 
     QI_TRACE_INSTRUMENT      = "qi_event_%(name)s"
     QI_TRACE_INSTRUMENT_TCG  = QI_TRACE_INSTRUMENT + "_tcg"
+    QI_TRACE_NOP             = QI_TRACE_INSTRUMENT + "_nop"
+    QI_TRACE_TRACE           = QI_TRACE_INSTRUMENT + "_trace"
+    QI_TRACE_GEN             = QI_TRACE_INSTRUMENT + "_gen_exec"
+    QI_TRACE_TRACEGEN        = QI_TRACE_INSTRUMENT + "_trace_and_gen_exec"
 
     def api(self, fmt=None):
         if fmt is None:
diff --git a/scripts/tracetool/backend/instr_dynamic.py b/scripts/tracetool/backend/instr_dynamic.py
index f42d3afa8f..6f9f8e49fb 100644
--- a/scripts/tracetool/backend/instr_dynamic.py
+++ b/scripts/tracetool/backend/instr_dynamic.py
@@ -65,3 +65,87 @@ def generate_instr_tcg_h(event, group):
         args=event.args,
         argnames=", ".join(event.args.names()),
         argtypes=argtypes)
+
+
+##################################################
+# instr-tcg-c
+
+def generate_instr_tcg_c_begin(events, group):
+    out('#include "qemu-instr/events.h"',
+        '')
+
+def generate_instr_tcg_c(event, group):
+    if "tcg" in event.properties:
+        event = event.event_trans
+
+    iargs = tracetool.vcpu.transform_args("tcg_h", event.original)
+    api_args = iargs.transform(TCG_2_QI_TCG)
+
+    qtargs = ["(%s)%s" % (arg[0], arg[1])
+              for arg in event.args]
+    gargs = tracetool.vcpu.transform_args(
+        "tcg_helper_c", event.original, "wrapper")
+    qgargs = ["(%s)%s" % (arg[0], arg[1])
+              for arg in gargs]
+    qargs = ["(%s)%s" % (arg[0], arg[1])
+             for arg in iargs]
+
+    out('QI_VPUBLIC void %(nop)s(%(api_args)s)',
+        '{',
+        '}',
+        'QI_VPUBLIC void %(trace)s(%(api_args)s)',
+        '{',
+        '     %(b_trace)s(%(qtargs)s);',
+        '}',
+        'QI_VPUBLIC void %(gen)s(%(api_args)s)',
+        '{',
+        '     %(b_gen)s(%(qgargs)s);',
+        '}',
+        'QI_VPUBLIC void %(trace_gen)s(%(api_args)s)',
+        '{',
+        '     %(b_trace_gen)s(%(qargs)s);',
+        '}',
+        'void *%(qi_cb)s_cb = %(trace_gen)s;',
+        '',
+        api_args=api_args,
+        nop=event.api(event.QI_TRACE_NOP),
+        trace=event.api(event.QI_TRACE_TRACE),
+        b_trace=event.api(event.QEMU_TRACE_BACKEND),
+        gen=event.api(event.QI_TRACE_GEN),
+        b_gen="gen_helper_" + event.original.event_exec.api(event.QEMU_TRACE_BACKEND),
+        trace_gen=event.api(event.QI_TRACE_TRACEGEN),
+        b_trace_gen=event.original.api(event.QEMU_TRACE_TCG_BACKEND),
+        iargs=iargs,
+        qargs=", ".join(qargs),
+        qtargs=", ".join(qtargs),
+        qgargs=", ".join(qgargs),
+        qi_cb=event.original.api(event.QI_TRACE_INSTRUMENT_TCG))
+
+
+##################################################
+# instr-c
+
+def generate_instr_c_begin(events, group):
+    out('#include "qemu-instr/events.h"',
+        '')
+
+def generate_instr_c(event, group):
+    args = event.args.transform(TCG_2_QI_TCG)
+    qargs = ["(%s)%s" % (arg[0], arg[1])
+             for arg in event.args]
+    out('QI_VPUBLIC void %(nop)s(%(args)s)',
+        '{',
+        '}',
+        'QI_VPUBLIC void %(trace)s(%(args)s)',
+        '{',
+        '     %(backend)s(%(qargs)s);',
+        '}',
+        'void *%(qi_cb)s_cb = %(qi_trace)s;',
+        '',
+        args=args,
+        nop=event.api(event.QI_TRACE_NOP),
+        trace=event.api(event.QI_TRACE_TRACE),
+        backend=event.api(event.QEMU_TRACE_BACKEND),
+        qargs=", ".join(qargs),
+        qi_cb=event.api(event.QI_TRACE_INSTRUMENT),
+        qi_trace=event.api(event.QI_TRACE_TRACE))
diff --git a/scripts/tracetool/backend/instr_none.py b/scripts/tracetool/backend/instr_none.py
index 56508cfcbe..95aed5e96f 100644
--- a/scripts/tracetool/backend/instr_none.py
+++ b/scripts/tracetool/backend/instr_none.py
@@ -32,3 +32,21 @@ def generate_instr_h(event, group):
     out('    %(backend)s(%(argnames)s);',
         backend=event.api(event.QEMU_TRACE_BACKEND),
         argnames=", ".join(event.args.names()))
+
+
+##################################################
+# instr-tcg-c
+
+def generate_instr_tcg_c(event, group):
+    out('void *%(qi_cb)s_cb = %(qi_trace)s;',
+        qi_cb=event.api(event.QI_TRACE_INSTRUMENT_TCG),
+        qi_trace=event.api(event.QEMU_TRACE_TCG_BACKEND))
+
+
+##################################################
+# instr-c
+
+def generate_instr_c(event, group):
+    out('void *%(qi_cb)s_cb = %(qi_trace)s;',
+        qi_cb=event.api(event.QI_TRACE_INSTRUMENT),
+        qi_trace=event.api(event.QEMU_TRACE_BACKEND))
diff --git a/scripts/tracetool/format/instr_api_h.py b/scripts/tracetool/format/instr_api_h.py
new file mode 100644
index 0000000000..d85f64df45
--- /dev/null
+++ b/scripts/tracetool/format/instr_api_h.py
@@ -0,0 +1,65 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+"""
+instrument/qemu-instr/events.h
+
+"""
+
+__author__     = "Lluís Vilanova <vilanova@ac.upc.edu>"
+__copyright__  = "Copyright 2012-2017, Lluís Vilanova <vilanova@ac.upc.edu>"
+__license__    = "GPL version 2 or (at your option) any later version"
+
+__maintainer__ = "Stefan Hajnoczi"
+__email__      = "stefanha@linux.vnet.ibm.com"
+
+
+from tracetool import out
+from tracetool.transform import *
+import tracetool.vcpu
+
+
+def generate(events, backend, group):
+    # We won't be using any backend, because this code is only compiled in when
+    # dynamic instrumentation mode is enabled
+
+    out('/* This file is autogenerated by tracetool, do not edit. */',
+        '',
+        '#ifndef QI__EVENTS_H',
+        '#define QI__EVENTS_H',
+        '',
+        '#ifdef __cplusplus',
+        'extern "C" {',
+        '#endif',
+        '',
+        '#include <stdbool.h>',
+        '#include <stdint.h>',
+        '#include <qemu-instr/types.h>',
+        '#include <qemu-instr/visibility-internal.h>',
+        '',
+        '')
+
+    for e in events:
+        if "tcg-trans" in e.properties:
+            args = tracetool.vcpu.transform_args("tcg_h", e.original)
+            args = args.transform(TCG_2_QI_TCG)
+        else:
+            args = e.args.transform(TCG_2_QI_TCG)
+        out('QI_VPUBLIC void %(nop)s(%(args)s);',
+            'QI_VPUBLIC void %(trace)s(%(args)s);',
+            args=args,
+            nop=e.api(e.QI_TRACE_NOP),
+            trace=e.api(e.QI_TRACE_TRACE))
+        if "tcg-trans" in e.properties:
+            out('QI_VPUBLIC void %(gen)s(%(args)s);',
+                'QI_VPUBLIC void %(trace_gen)s(%(args)s);',
+                args=args,
+                gen=e.api(e.QI_TRACE_GEN),
+                trace_gen=e.api(e.QI_TRACE_TRACEGEN))
+        out('')
+
+    out('#ifdef __cplusplus',
+        '}',
+        '#endif',
+        '',
+        '#endif  /* QI__EVENTS_H */')
diff --git a/scripts/tracetool/format/instr_c.py b/scripts/tracetool/format/instr_c.py
index 987ecbf5c2..35b7955641 100644
--- a/scripts/tracetool/format/instr_c.py
+++ b/scripts/tracetool/format/instr_c.py
@@ -21,8 +21,7 @@ from tracetool.transform import *
 
 def generate(events, backend, group):
     events = [e for e in events
-              if "tcg-trans" not in e.properties and
-              "instrument" in e.properties]
+              if "tcg-trans" not in e.properties]
 
     if group == "root":
         header = "trace-root.h"
@@ -37,8 +36,6 @@ def generate(events, backend, group):
     backend.generate_begin(events, group)
 
     for e in events:
-        out('void *%(qi_cb)s_cb = %(qi_trace)s;',
-            qi_cb=e.api(e.QI_TRACE_INSTRUMENT),
-            qi_trace=e.api(e.QEMU_TRACE_BACKEND))
+        backend.generate(e, group)
 
     backend.generate_end(events, group)
diff --git a/scripts/tracetool/format/instr_tcg_c.py b/scripts/tracetool/format/instr_tcg_c.py
index 382709d0bd..61407a68c0 100644
--- a/scripts/tracetool/format/instr_tcg_c.py
+++ b/scripts/tracetool/format/instr_tcg_c.py
@@ -20,8 +20,7 @@ from tracetool.transform import *
 
 def generate(events, backend, group):
     events = [e.original for e in events
-              if "tcg-trans" in e.properties and
-              "instrument" in e.properties]
+              if "tcg-trans" in e.properties]
 
     out('/* This file is autogenerated by tracetool, do not edit. */',
         '',
@@ -33,8 +32,6 @@ def generate(events, backend, group):
     backend.generate_begin(events, group)
 
     for e in events:
-        out('void *%(qi_cb)s_cb = %(qi_trace)s;',
-            qi_cb=e.api(e.QI_TRACE_INSTRUMENT_TCG),
-            qi_trace=e.api(e.QEMU_TRACE_TCG_BACKEND))
+        backend.generate(e, group)
 
     backend.generate_end(events, group)
diff --git a/scripts/tracetool/format/tcg_helper_c.py b/scripts/tracetool/format/tcg_helper_c.py
index e9ba62a136..db431797d9 100644
--- a/scripts/tracetool/format/tcg_helper_c.py
+++ b/scripts/tracetool/format/tcg_helper_c.py
@@ -75,7 +75,7 @@ def generate(events, backend, group):
             '    %(name)s(%(args_call)s);',
             '}',
             name_tcg="helper_%s_proxy" % e.api(e.QEMU_TRACE_BACKEND),
-            name=e.api(e.QEMU_TRACE_NOCHECK),
+            name=e.api(e.QEMU_TRACE),
             args_api=e_args_api,
             args_call=", ".join(e_args_call.casted()),
             )
diff --git a/scripts/tracetool/transform.py b/scripts/tracetool/transform.py
index e18b05315e..400d36a6ed 100644
--- a/scripts/tracetool/transform.py
+++ b/scripts/tracetool/transform.py
@@ -137,6 +137,19 @@ TCG_2_TCG_HELPER_DECL = {
 
 
 ##################################################
+# host/tcg -> instrumentation-client tcg type
+
+def TCG_2_QI_TCG(type_):
+    if type_ == "TCGv_env":
+        return "QITCGv_cpu"
+    elif type_.startswith("TCGv"):
+        return "QI" + type_
+    elif type_.startswith("CPUState"):
+        return type_.replace("CPUState", "QICPU", 1)
+    else:
+        return type_
+
+##################################################
 # host/tcg -> tcg temporal constant allocation
 
 def _host_2_tcg_tmp_new(type_):
