diff --git a/instrument/Makefile.objs b/instrument/Makefile.objs
index 5ea5c77245..13a8f60431 100644
--- a/instrument/Makefile.objs
+++ b/instrument/Makefile.objs
@@ -2,3 +2,4 @@
 
 target-obj-y += cmdline.o
 target-obj-$(CONFIG_INSTRUMENT) += load.o
+target-obj-y += qmp.o
diff --git a/instrument/load.h b/instrument/load.h
index 2ddb2c6c19..f8a02e6849 100644
--- a/instrument/load.h
+++ b/instrument/load.h
@@ -25,6 +25,8 @@
  * @INSTR_LOAD_DLERROR: Error with libdl (see dlerror).
  *
  * Error codes for instr_load().
+ *
+ * NOTE: Keep in sync with QAPI's #InstrLoadCode.
  */
 typedef enum {
     INSTR_LOAD_OK,
@@ -40,6 +42,8 @@ typedef enum {
  * @INSTR_UNLOAD_DLERROR: Error with libdl (see dlerror).
  *
  * Error codes for instr_unload().
+ *
+ * NOTE: Keep in sync with QAPI's #InstrUnloadCode.
  */
 typedef enum {
     INSTR_UNLOAD_OK,
diff --git a/instrument/qmp.c b/instrument/qmp.c
new file mode 100644
index 0000000000..c36960c12f
--- /dev/null
+++ b/instrument/qmp.c
@@ -0,0 +1,88 @@
+/*
+ * QMP interface for instrumentation control commands.
+ *
+ * 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.
+ */
+
+#include "qemu/osdep.h"
+#include "qemu-common.h"
+#include "qapi/qmp/qerror.h"
+#include "qmp-commands.h"
+
+#include <dlfcn.h>
+
+#include "instrument/load.h"
+
+
+
+InstrLoadResult *qmp_instr_load(const char * path,
+                                bool have_args, strList * args,
+                                Error **errp)
+{
+    InstrLoadResult *res = g_malloc0(sizeof(*res));
+
+#if defined(CONFIG_INSTRUMENT)
+    int argc = 0;
+    const char **argv = NULL;
+
+    strList *entry = have_args ? args : NULL;
+    while (entry != NULL) {
+        argv = realloc(argv, sizeof(*argv) * (argc + 1));
+        argv[argc] = entry->value;
+        argc++;
+        entry = entry->next;
+    }
+
+    InstrLoadError code = instr_load(path, argc, argv, &res->handle);
+    switch (code) {
+    case INSTR_LOAD_OK:
+        res->code = INSTR_LOAD_CODE_OK;
+        res->has_handle = true;
+        break;
+    case INSTR_LOAD_TOO_MANY:
+        res->code = INSTR_LOAD_CODE_TOO_MANY;
+        break;
+    case INSTR_LOAD_ERROR:
+        res->code = INSTR_LOAD_CODE_ERROR;
+        break;
+    case INSTR_LOAD_DLERROR:
+        res->has_msg = true;
+        res->msg = dlerror();
+        res->code = INSTR_LOAD_CODE_DLERROR;
+        break;
+    }
+#else
+    res->code = INSTR_LOAD_CODE_UNAVAILABLE;
+#endif
+
+    return res;
+}
+
+InstrUnloadResult *qmp_instr_unload(int64_t handle, Error **errp)
+{
+    InstrUnloadResult *res = g_malloc0(sizeof(*res));
+
+#if defined(CONFIG_INSTRUMENT)
+    InstrUnloadError code = instr_unload(handle);
+    switch (code) {
+    case INSTR_UNLOAD_OK:
+        res->code = INSTR_UNLOAD_CODE_OK;
+        break;
+    case INSTR_UNLOAD_INVALID:
+        res->code = INSTR_UNLOAD_CODE_INVALID;
+        break;
+    case INSTR_UNLOAD_DLERROR:
+        res->has_msg = true;
+        res->msg = dlerror();
+        break;
+        res->code = INSTR_UNLOAD_CODE_DLERROR;
+    }
+#else
+    res->code = INSTR_UNLOAD_CODE_UNAVAILABLE;
+#endif
+
+    return res;
+}
diff --git a/qapi-schema.json b/qapi-schema.json
index 802ea53d00..5e343be9ff 100644
--- a/qapi-schema.json
+++ b/qapi-schema.json
@@ -90,6 +90,9 @@
 # QAPI introspection
 { 'include': 'qapi/introspect.json' }
 
+# Instrumentation commands
+{ 'include': 'qapi/instrument.json' }
+
 ##
 # = QMP commands
 ##
diff --git a/qapi/instrument.json b/qapi/instrument.json
new file mode 100644
index 0000000000..ea63fae309
--- /dev/null
+++ b/qapi/instrument.json
@@ -0,0 +1,96 @@
+# *-*- Mode: Python -*-*
+#
+# QAPI instrumentation control commands.
+#
+# 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.
+
+##
+# @InstrLoadCode:
+#
+# Result code of an 'instr-load' command.
+#
+# @ok: Correctly loaded.
+# @too-many: Tried to load too many instrumentation libraries.
+# @error: The library's main() function returned a non-zero value.
+# @dlerror: Error with libdl (see 'msg').
+# @unavailable: Service not available.
+#
+# Since: 2.11
+##
+{ 'enum': 'InstrLoadCode',
+  'data': [ 'ok', 'too-many', 'error', 'dlerror', 'unavailable' ] }
+
+##
+# @InstrLoadResult:
+#
+# Result of an 'instr-load' command.
+#
+# @code: Result code.
+# @msg: Additional error message (for human consumption only; present only in
+#       case of error).
+# @handle: Instrumentation library identifier (present only if successful).
+#
+# Since: 2.11
+##
+{ 'struct': 'InstrLoadResult',
+  'data': { 'code': 'InstrLoadCode', '*msg': 'str', '*handle': 'int' } }
+
+##
+# @instr-load:
+#
+# Load an instrumentation library.
+#
+# @path: path to the dynamic instrumentation library
+# @args: arguments to the dynamic instrumentation library
+#
+# Since: 2.11
+##
+{ 'command': 'instr-load',
+  'data':    { 'path': 'str', '*args': ['str'] },
+  'returns': 'InstrLoadResult' }
+
+
+##
+# @InstrUnloadCode:
+#
+# Result code of an 'instr-unload' command.
+#
+# @ok: Correctly unloaded.
+# @invalid: Invalid handle.
+# @dlerror: Error with libdl (see 'msg').
+# @unavailable: Service not available.
+#
+# Since: 2.11
+##
+{ 'enum': 'InstrUnloadCode',
+  'data': [ 'ok', 'invalid', 'dlerror', 'unavailable' ] }
+
+##
+# @InstrUnloadResult:
+#
+# Result of an 'instr-unload' command.
+#
+# @code: Result code.
+# @msg: Additional error message (for human consumption only; present only in
+#       case of error).
+#
+# Since: 2.11
+##
+{ 'struct': 'InstrUnloadResult',
+  'data': { 'code': 'InstrUnloadCode', '*msg': 'str' } }
+
+##
+# @instr-unload:
+#
+# Unload an instrumentation library.
+#
+# @handle: Instrumentation library identifier (see #InstrLoadResult).
+#
+# Since: 2.11
+##
+{ 'command': 'instr-unload',
+  'data': { 'handle': 'int' },
+  'returns': 'InstrUnloadResult' }
