diff --git a/Makefile.objs b/Makefile.objs
index 5f8ea2d..4fb565b 100644
--- a/Makefile.objs
+++ b/Makefile.objs
@@ -94,6 +94,7 @@ common-obj-y += disas/
 # instrumentation
 
 tools-obj-y += instrument/
+target-obj-y += instrument/
 
 ######################################################################
 # guest agent
@@ -114,5 +115,6 @@ nested-vars += \
 	qga-obj-y \
 	block-obj-y \
 	tools-obj-y \
+	target-obj-y \
 	common-obj-y
 dummy := $(call unnest-vars)
diff --git a/configure b/configure
index 41089cc..d2e0b90 100755
--- a/configure
+++ b/configure
@@ -3925,7 +3925,10 @@ fi
 if test "$trace_instrument" = "dynamic"; then
     echo "#define QI_TYPE_DYNAMIC 1" >> $config_qi
     echo "CONFIG_TRACE_INSTRUMENT_DYNAMIC=y" >> $config_host_mak
+    libs_qga="-ldl $libs_qga"
 fi
+# code requiring it is always compiled-in
+LIBS="-ldl $LIBS"
 ##########################################
 
 echo "TOOLS=$tools" >> $config_host_mak
diff --git a/instrument/Makefile.objs b/instrument/Makefile.objs
index cae520d..e571c71 100644
--- a/instrument/Makefile.objs
+++ b/instrument/Makefile.objs
@@ -60,3 +60,9 @@ $(LIBTRACE_INSTRUMENT): $(dir $(LIBTRACE_INSTRUMENT))/Makefile force
 		TARGET_DIR=$(TARGET_DIR)$(dir $@)/ VPATH=$(VPATH)          \
 		SRC_PATH=$(SRC_PATH) V="$(V)" $(notdir $@))
 endif
+
+
+######################################################################
+# Control code
+
+target-obj-y += control.o
diff --git a/instrument/control-internal.h b/instrument/control-internal.h
new file mode 100644
index 0000000..19b342f
--- /dev/null
+++ b/instrument/control-internal.h
@@ -0,0 +1,38 @@
+/*
+ * Interface for controlling dynamic trace instrumentation.
+ *
+ * Copyright (C) 2012-2013 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 INSTRUMENT__CONTROL_INTERNAL_H
+#define INSTRUMENT__CONTROL_INTERNAL_H
+
+#include <assert.h>
+#include <stdlib.h>
+
+#include "config-host.h"
+
+
+static inline bool instr_dynamic(void)
+{
+#if defined(CONFIG_TRACE_INSTRUMENT_DYNAMIC)
+    return true;
+#else
+    return false;
+#endif
+}
+
+static inline bool instr_event_available(TraceEvent *ev)
+{
+    assert(ev != NULL);
+#if defined(CONFIG_TRACE_INSTRUMENT_NONE)
+    return false;
+#else
+    return ev->instr;
+#endif
+}
+
+#endif  /* INSTRUMENT__CONTROL_INTERNAL_H */
diff --git a/instrument/control.c b/instrument/control.c
new file mode 100644
index 0000000..c6ed36f
--- /dev/null
+++ b/instrument/control.c
@@ -0,0 +1,139 @@
+/*
+ * Interface for controlling dynamic trace instrumentation.
+ *
+ * Copyright (C) 2012-2013 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 "instrument/control.h"
+
+#include <dlfcn.h>
+
+#include "qemu-common.h"
+#include "trace/control.h"
+
+
+#if defined(CONFIG_TRACE_INSTRUMENT_DYNAMIC)
+static void *handle = NULL;
+#endif
+
+
+InstrLoadError instr_load(const char * path, int argc, const char ** argv)
+{
+    if (!instr_dynamic()) {
+        return INSTR_LOAD_UNAVAILABLE;
+    }
+
+#if defined(CONFIG_TRACE_INSTRUMENT_DYNAMIC)
+    if (handle != NULL) {
+        return INSTR_LOAD_LOADED;
+    }
+
+    handle = dlopen(path, RTLD_NOW);
+
+    if (handle == NULL) {
+        return INSTR_LOAD_DL;
+    }
+
+    void (*init)(int, const char **) = dlsym(handle, "qi_init");
+    if (init == NULL) {
+        return INSTR_LOAD_DL;
+    }
+    void (*fini)(void) = dlsym(handle, "qi_fini");
+    if (fini == NULL) {
+        return INSTR_LOAD_DL;
+    }
+
+    if (init != NULL) {
+        init(argc, argv);
+    }
+#endif
+
+    return INSTR_LOAD_OK;
+}
+
+InstrUnloadError instr_unload(void)
+{
+    if (!instr_dynamic()) {
+        return INSTR_UNLOAD_UNAVAILABLE;
+    }
+
+#if defined(CONFIG_TRACE_INSTRUMENT_DYNAMIC)
+    if (handle == NULL)  {
+        return INSTR_UNLOAD_UNLOADED;
+    }
+
+    void (*fini)(void) = dlsym(handle, "qi_fini");
+    assert(fini != NULL);
+    fini();
+
+    TraceEvent *ev = NULL;
+    while ((ev = trace_event_pattern("*", ev)) != NULL) {
+        if (instr_event_available(ev)) {
+            instr_event_set(ev, INSTR_CB_NOP);
+        }
+    }
+
+    /* this should never fail */
+    if (dlclose(handle) < 0) {
+        return INSTR_UNLOAD_DL;
+    }
+
+    handle = NULL;
+#endif
+
+    return INSTR_UNLOAD_OK;
+}
+
+#if defined(CONFIG_TRACE_INSTRUMENT_DYNAMIC)
+static void *get_event_symbol(TraceEvent *ev, const char *fmt)
+{
+    assert(ev != NULL);
+
+    char name[1024];
+    assert(strlen(trace_event_get_name(ev)) + strlen(fmt) < 1024);
+    sprintf(name, fmt, trace_event_get_name(ev));
+    return dlsym(handle, name);
+}
+
+static void event_set(TraceEvent *ev, void *cb)
+{
+    if (ev->instr_cb == cb) {
+        return;
+    }
+    assert(instr_event_available(ev));
+    *ev->instr_cb = cb;
+}
+#endif
+
+bool instr_event_set(TraceEvent *ev, void *cb)
+{
+    assert(instr_dynamic());
+    assert(ev != NULL);
+    assert(instr_event_available(ev));
+
+#if defined(CONFIG_TRACE_INSTRUMENT_DYNAMIC)
+    if (cb == INSTR_CB_NOP) {
+        event_set(ev, ev->instr_cb_nop);
+        return true;
+    } else if (cb == INSTR_CB_NEXT) {
+        event_set(ev, ev->instr_cb_backend);
+        return true;
+    } else if (cb == INSTR_CB_AUTO) {
+        void *ptr = get_event_symbol(ev, "qi_event_%s");
+        if (ptr != NULL) {
+            event_set(ev, ptr);
+            return true;
+        } else {
+            return false;
+        }
+    } else {
+        event_set(ev, cb);
+        return true;
+    }
+#else
+    assert(false);
+#endif
+}
diff --git a/instrument/control.h b/instrument/control.h
new file mode 100644
index 0000000..0924fc8
--- /dev/null
+++ b/instrument/control.h
@@ -0,0 +1,134 @@
+/*
+ * Interface for controlling dynamic trace instrumentation.
+ *
+ * Copyright (C) 2012-2013 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 INSTRUMENT__CONTROL_H
+#define INSTRUMENT__CONTROL_H
+
+#include "trace/generated-events.h"
+
+
+/**
+ * InstrLoadError:
+ * @INSTR_LOAD_OK: Correctly loaded.
+ * @INSTR_LOAD_UNAVAILABLE: Service not available.
+ * @INSTR_LOAD_LOADED: Already loaded.
+ * @INSTR_LOAD_DL: Error with libdl (see dlerror).
+ *
+ * Error codes for instr_load().
+ */
+typedef enum {
+    INSTR_LOAD_OK,
+    INSTR_LOAD_UNAVAILABLE,
+    INSTR_LOAD_LOADED,
+    INSTR_LOAD_DL,
+} InstrLoadError;
+
+/**
+ * InstrUnloadError:
+ * @INSTR_UNLOAD_OK: Correctly unloaded.
+ * @INSTR_UNLOAD_UNAVAILABLE: Service not available.
+ * @INSTR_UNLOAD_UNLOADED: Already unloaded.
+ * @INSTR_UNLOAD_DL: Error with libdl (see dlerror).
+ *
+ * Error codes for instr_unload().
+ */
+typedef enum {
+    INSTR_UNLOAD_OK,                   /*< Correctly unloaded             */
+    INSTR_UNLOAD_UNAVAILABLE,          /*< Service not available          */
+    INSTR_UNLOAD_UNLOADED,             /*< Already unloaded               */
+    INSTR_UNLOAD_DL,                   /*< Error with libdl (see dlerror) */
+} InstrUnloadError;
+
+/**
+ * instr_load:
+ * @path: Path to the shared library to load.
+ * @argc: Number of arguments passed to the initialization function of the library.
+ * @argv: Arguments passed to the initialization function of the library.
+ *
+ * Load a dynamic trace instrumentation library.
+ *
+ * Returns: Whether the library could be loaded.
+ */
+InstrLoadError instr_load(const char * path, int argc, const char ** argv);
+
+/**
+ * instr_unload:
+ *
+ * Unload the currently loaded instrumentation library.
+ *
+ * Returns: Whether the library could be unloaded.
+ */
+InstrUnloadError instr_unload(void);
+
+
+/**
+ * instr_dynamic:
+ *
+ * Returns: Whether dynamic trace instrumentation is available.
+ */
+static bool instr_dynamic(void);
+
+/**
+ * instr_event_available:
+ *
+ * Returns: Whether the given event has the 'instrument' property.
+ */
+static bool instr_event_available(TraceEvent *ev);
+
+
+/**
+ * INSTR_CB_NOP:
+ *
+ * Set callback to no-operation.
+ * (qi_event_${name}_nop).
+ */
+#define INSTR_CB_NOP ((void*)NULL)
+
+/**
+ * INSTR_CB_NEXT:
+ *
+ * Set callback to the "next logical step"
+ * (qi_event_${name}_trace).
+ */
+#define INSTR_CB_NEXT ((void*)1)
+
+/**
+ * INSTR_CB_AUTO:
+ *
+ * Automatically set callback to proper routine.
+ *
+ * Looks for a symbol name in the instrumentation library matching the event
+ * name (qi_event_${name}).
+ */
+#define INSTR_CB_AUTO    ((void*)2)
+
+/**
+ * instr_event_set:
+ * @ev: Tracing event descriptor.
+ * @cb: Pointer to instrumentation callback.
+ *
+ * Set the instrumentation callback for the given event.
+ *
+ * Argument cb can also be set to any of the INSTR_CB_* special values.
+ *
+ * A negative return value indicates that the instrumentation library does not
+ * export the appropriate symbol for the instrumentation routine.
+ *
+ * Pre-condition: instr_dynamic() == true
+ * Pre-condition: instr_event_available(ev) == true
+ *
+ * Returns: Whether the callback could be set (if cb == INSTR_CB_AUTO, always
+ *          true otherwise).
+ */
+bool instr_event_set(TraceEvent *ev, void *cb);
+
+
+#include "instrument/control-internal.h"
+
+#endif  /* INSTRUMENT__CONTROL_H */
diff --git a/rules.mak b/rules.mak
index edc2552..e0767a9 100644
--- a/rules.mak
+++ b/rules.mak
@@ -51,6 +51,9 @@ endif
 %.o: %.dtrace
 	$(call quiet-command,dtrace -o $@ -G -s $<, "  GEN   $(TARGET_DIR)$@")
 
+ifdef CONFIG_TRACE_INSTRUMENT_DYNAMIC
+%$(EXESUF): LDFLAGS+=-rdynamic
+endif
 %$(EXESUF): %.o
 	$(call LINK,$^)
 
diff --git a/scripts/tracetool/backend/instr_dynamic.py b/scripts/tracetool/backend/instr_dynamic.py
index a07ee64..12d0503 100644
--- a/scripts/tracetool/backend/instr_dynamic.py
+++ b/scripts/tracetool/backend/instr_dynamic.py
@@ -40,6 +40,9 @@ def qemu_h(events):
 def api_h(events):
     out('#include <qemu-instr/visibility-internal.h>',
         '',
+        'QI_VPUBLIC void qi_init(int argc, const char **argv);',
+        'QI_VPUBLIC void qi_fini(void);',
+        '',
         )
 
     for e in events:
diff --git a/scripts/tracetool/backend/instr_static.py b/scripts/tracetool/backend/instr_static.py
index b010596..ecebbda 100644
--- a/scripts/tracetool/backend/instr_static.py
+++ b/scripts/tracetool/backend/instr_static.py
@@ -46,6 +46,9 @@ def qemu_h(events):
 def api_h(events):
     out('#include "trace/generated-tracers.h"',
         '',
+        'void qi_init(int argc, const char **argv);',
+        'void qi_fini(void);',
+        '',
         )
 
     for e in events:
diff --git a/scripts/tracetool/format/events_c.py b/scripts/tracetool/format/events_c.py
index 7531c66..d7b4f86 100644
--- a/scripts/tracetool/format/events_c.py
+++ b/scripts/tracetool/format/events_c.py
@@ -44,14 +44,19 @@ def begin(events):
     out('TraceEvent trace_events[TRACE_EVENT_COUNT] = {')
 
     for e in events:
+        instr = "false"
         cb = cb_nop = cb_backend = "NULL"
 
         if "instrument" in e.properties:
+            instr = "true"
             cb = "&%s_cb" % e.api(e.QI_TRACE_INSTRUMENT)
             cb_nop = e.api(e.QI_TRACE_NOP)
             cb_backend = e.api(e.QI_TRACE_BACKEND)
 
         out('    { .id = %(id)s, .name = \"%(name)s\", .sstate = %(sstate)s, .dstate = 0,',
+            '#if !defined(CONFIG_TRACE_INSTRUMENT_NONE)',
+            '      .instr = %(instr)s,',
+            '#endif',
             '#if defined(CONFIG_TRACE_INSTRUMENT_DYNAMIC)',
             '      .instr_cb = %(cb)s,',
             '      .instr_cb_nop = %(cb_nop)s,',
@@ -59,6 +64,7 @@ def begin(events):
             '#endif',
             '    },',
             id = "TRACE_" + e.name.upper(),
+            instr = instr,
             cb = cb,
             cb_nop = cb_nop,
             cb_backend = cb_backend,
diff --git a/trace/event-internal.h b/trace/event-internal.h
index aed4050..415aec6 100644
--- a/trace/event-internal.h
+++ b/trace/event-internal.h
@@ -20,6 +20,7 @@
  * @name: Event name.
  * @sstate: Static tracing state.
  * @dstate: Dynamic tracing state.
+ * @instr: Whether the event is instrumentable.
  * @instr_cb: Instrumentation callback pointer.
  * @instr_cb_nop: Instrumentation callback pointer to no-operation.
  * @instr_cb_backend: Instrumentation callback pointer to tracing backend.
@@ -32,6 +33,10 @@ typedef struct TraceEvent {
     const bool sstate;
     bool dstate;
 
+#if !defined(CONFIG_TRACE_INSTRUMENT_NONE)
+    const bool instr;
+#endif
+
 #if defined(CONFIG_TRACE_INSTRUMENT_DYNAMIC)
     void **instr_cb;
     void *instr_cb_nop;
