diff mbox

[v4,03/20] instrument: Add generic library loader

Message ID 150471928780.24907.14047559834166839201.stgit@frigg.lan (mailing list archive)
State New, archived
Headers show

Commit Message

Lluís Vilanova Sept. 6, 2017, 5:34 p.m. UTC
Signed-off-by: Lluís Vilanova <vilanova@ac.upc.edu>
---
 MAINTAINERS              |    1 
 Makefile.objs            |    4 +
 configure                |    2 +
 instrument/Makefile.objs |    4 +
 instrument/cmdline.c     |  124 ++++++++++++++++++++++++++++++++
 instrument/cmdline.h     |   49 +++++++++++++
 instrument/load.c        |  176 ++++++++++++++++++++++++++++++++++++++++++++++
 instrument/load.h        |   83 ++++++++++++++++++++++
 8 files changed, 443 insertions(+)
 create mode 100644 instrument/Makefile.objs
 create mode 100644 instrument/cmdline.c
 create mode 100644 instrument/cmdline.h
 create mode 100644 instrument/load.c
 create mode 100644 instrument/load.h

Comments

Emilio Cota Sept. 6, 2017, 9:20 p.m. UTC | #1
On Wed, Sep 06, 2017 at 20:34:48 +0300, Lluís Vilanova wrote:
> Signed-off-by: Lluís Vilanova <vilanova@ac.upc.edu>
> ---
(snip)
> diff --git a/configure b/configure
> index 80dcc91c98..05bd7b1950 100755
> --- a/configure
> +++ b/configure
> @@ -6034,6 +6034,8 @@ fi
>  echo "CONFIG_TRACE_FILE=$trace_file" >> $config_host_mak
>  
>  if test "$instrument" = "yes"; then
> +  LDFLAGS="-rdynamic $LDFLAGS"          # limit symbols available to clients

-rdynamic exports all objects to the instrumenters, not the other way around.
You can see it with nm(1).

BTW I'm using ebf447b5b from your qemu-dbi repo. Is that the same as this
patchset? It doesn't compile with --enable-instrument:
$ make
  CC      x86_64-linux-user/instrument/trace.o
/data/src/qemu2/instrument/trace.c:12:30: fatal error: qemu-instr/trace.h: No such file or directory
compilation terminated.
/data/src/qemu2/rules.mak:66: recipe for target 'instrument/trace.o' failed
make[1]: *** [instrument/trace.o] Error 1
Makefile:326: recipe for target 'subdir-x86_64-linux-user' failed
make: *** [subdir-x86_64-linux-user] Error 2

		Emilio
Lluís Vilanova Sept. 10, 2017, 5:41 p.m. UTC | #2
Emilio G Cota writes:

> On Wed, Sep 06, 2017 at 20:34:48 +0300, Lluís Vilanova wrote:
>> Signed-off-by: Lluís Vilanova <vilanova@ac.upc.edu>
>> ---
> (snip)
>> diff --git a/configure b/configure
>> index 80dcc91c98..05bd7b1950 100755
>> --- a/configure
>> +++ b/configure
>> @@ -6034,6 +6034,8 @@ fi
>> echo "CONFIG_TRACE_FILE=$trace_file" >> $config_host_mak
>> 
>> if test "$instrument" = "yes"; then
>> +  LDFLAGS="-rdynamic $LDFLAGS"          # limit symbols available to clients

> -rdynamic exports all objects to the instrumenters, not the other way around.
> You can see it with nm(1).

I'll send a minor fix for this in next version.


> BTW I'm using ebf447b5b from your qemu-dbi repo. Is that the same as this
> patchset? It doesn't compile with --enable-instrument:
> $ make
>   CC      x86_64-linux-user/instrument/trace.o
> /data/src/qemu2/instrument/trace.c:12:30: fatal error: qemu-instr/trace.h: No such file or directory
> compilation terminated.
> /data/src/qemu2/rules.mak:66: recipe for target 'instrument/trace.o' failed
> make[1]: *** [instrument/trace.o] Error 1
> Makefile:326: recipe for target 'subdir-x86_64-linux-user' failed
> make: *** [subdir-x86_64-linux-user] Error 2

No, I started using a separate branch to keep the old patches around in the
meantime.


> 		Emilio


Lluis
diff mbox

Patch

diff --git a/MAINTAINERS b/MAINTAINERS
index edb313c632..edd2c49078 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -1485,6 +1485,7 @@  M: Lluís Vilanova <vilanova@ac.upc.edu>
 M: Stefan Hajnoczi <stefanha@redhat.com>
 S: Maintained
 F: docs/instrument.txt
+F: instrument/
 
 Checkpatch
 S: Odd Fixes
diff --git a/Makefile.objs b/Makefile.objs
index 24a4ea08b8..81a9218e14 100644
--- a/Makefile.objs
+++ b/Makefile.objs
@@ -97,6 +97,10 @@  version-obj-$(CONFIG_WIN32) += $(BUILD_DIR)/version.o
 util-obj-y +=  trace/
 target-obj-y += trace/
 
+######################################################################
+# instrument
+target-obj-y += instrument/
+
 ######################################################################
 # guest agent
 
diff --git a/configure b/configure
index 80dcc91c98..05bd7b1950 100755
--- a/configure
+++ b/configure
@@ -6034,6 +6034,8 @@  fi
 echo "CONFIG_TRACE_FILE=$trace_file" >> $config_host_mak
 
 if test "$instrument" = "yes"; then
+  LDFLAGS="-rdynamic $LDFLAGS"          # limit symbols available to clients
+  LIBS="-ldl $LIBS"
   echo "CONFIG_INSTRUMENT=y" >> $config_host_mak
 fi
 
diff --git a/instrument/Makefile.objs b/instrument/Makefile.objs
new file mode 100644
index 0000000000..5ea5c77245
--- /dev/null
+++ b/instrument/Makefile.objs
@@ -0,0 +1,4 @@ 
+# -*- mode: makefile -*-
+
+target-obj-y += cmdline.o
+target-obj-$(CONFIG_INSTRUMENT) += load.o
diff --git a/instrument/cmdline.c b/instrument/cmdline.c
new file mode 100644
index 0000000000..ec87f96c72
--- /dev/null
+++ b/instrument/cmdline.c
@@ -0,0 +1,124 @@ 
+/*
+ * Control instrumentation during program (de)initialization.
+ *
+ * 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 <dlfcn.h>
+#include "instrument/cmdline.h"
+#include "instrument/load.h"
+#include "qemu/config-file.h"
+#include "qemu/error-report.h"
+
+
+QemuOptsList qemu_instr_opts = {
+    .name = "instrument",
+    .implied_opt_name = "file",
+    .merge_lists = true,
+    .head = QTAILQ_HEAD_INITIALIZER(qemu_instr_opts.head),
+    .desc = {
+        {
+            .name = "file",
+            .type = QEMU_OPT_STRING,
+        },{
+            .name = "arg",
+            .type = QEMU_OPT_STRING,
+        },
+        { /* end of list */ }
+    },
+};
+
+void instr_opt_parse(const char *optarg, char **path,
+                     int *argc, const char ***argv)
+{
+    const char *arg;
+    QemuOptsIter iter;
+    QemuOpts *opts = qemu_opts_parse_noisily(qemu_find_opts("instrument"),
+                                             optarg, true);
+    if (!opts) {
+        exit(1);
+    } else {
+#if !defined(CONFIG_INSTRUMENT)
+        error_report("instrumentation not enabled on this build");
+        exit(1);
+#endif
+    }
+
+
+    arg = qemu_opt_get(opts, "file");
+    if (arg != NULL) {
+        g_free(*path);
+        *path = g_strdup(arg);
+    }
+
+    qemu_opt_iter_init(&iter, opts, "arg");
+    while ((arg = qemu_opt_iter_next(&iter)) != NULL) {
+        *argv = realloc(*argv, sizeof(**argv) * (*argc + 1));
+        (*argv)[*argc] = g_strdup(arg);
+        (*argc)++;
+    }
+
+    qemu_opts_del(opts);
+}
+
+void instr_init(const char *path, int argc, const char **argv)
+{
+#if defined(CONFIG_INSTRUMENT)
+    InstrLoadError err;
+    int64_t handle;
+
+    if (path == NULL) {
+        return;
+    }
+
+    if (atexit(instr_fini) != 0) {
+        fprintf(stderr, "error: atexit: %s\n", strerror(errno));
+        abort();
+    }
+
+    err = instr_load(path, argc, argv, &handle);
+    switch (err) {
+    case INSTR_LOAD_OK:
+        return;
+    case INSTR_LOAD_TOO_MANY:
+        error_report("instrument: tried to load too many libraries");
+        break;
+    case INSTR_LOAD_ERROR:
+        error_report("instrument: library initialization returned non-zero");
+        break;
+    case INSTR_LOAD_DLERROR:
+        error_report("instrument: error loading library: %s", dlerror());
+        break;
+    }
+#else
+    error_report("instrument: not available");
+#endif
+
+    exit(1);
+}
+
+void instr_fini(void)
+{
+#if defined(CONFIG_INSTRUMENT)
+    InstrUnloadError err = instr_unload_all();
+
+    switch (err) {
+    case INSTR_UNLOAD_OK:
+        return;
+    case INSTR_UNLOAD_INVALID:
+        /* the user might have already unloaded it */
+        return;
+    case INSTR_UNLOAD_DLERROR:
+        error_report("instrument: error unloading library: %s", dlerror());
+        break;
+    }
+#else
+    error_report("instrument: not available");
+#endif
+
+    exit(1);
+}
diff --git a/instrument/cmdline.h b/instrument/cmdline.h
new file mode 100644
index 0000000000..e6ea08c3e3
--- /dev/null
+++ b/instrument/cmdline.h
@@ -0,0 +1,49 @@ 
+/*
+ * Control instrumentation during program (de)initialization.
+ *
+ * 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 INSTRUMENT__CMDLINE_H
+#define INSTRUMENT__CMDLINE_H
+
+
+/**
+ * Definition of QEMU options describing instrumentation subsystem
+ * configuration.
+ */
+extern QemuOptsList qemu_instr_opts;
+
+/**
+ * instr_opt_parse:
+ * @optarg: A string argument of --instrument command line argument
+ *
+ * Initialize instrument subsystem.
+ */
+void instr_opt_parse(const char *optarg, char **path,
+                     int *argc, const char ***argv);
+
+/**
+ * instr_init:
+ * @path: Path to dynamic trace instrumentation library.
+ * @argc: Number of arguments to the library's #qi_init routine.
+ * @argv: Arguments to the library's #qi_init routine.
+ *
+ * Load and initialize the given instrumentation library. Calls exit() if the
+ * library's initialization function returns a non-zero value.
+ *
+ * Installs instr_fini() as an atexit() callback.
+ */
+void instr_init(const char *path, int argc, const char **argv);
+
+/**
+ * instr_fini:
+ *
+ * Deinitialize and unload all instrumentation libraries.
+ */
+void instr_fini(void);
+
+#endif  /* INSTRUMENT__CMDLINE_H */
diff --git a/instrument/load.c b/instrument/load.c
new file mode 100644
index 0000000000..a57401102a
--- /dev/null
+++ b/instrument/load.c
@@ -0,0 +1,176 @@ 
+/*
+ * Interface for (un)loading instrumentation libraries.
+ *
+ * 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 <dlfcn.h>
+#include "instrument/load.h"
+#include "qemu/config-file.h"
+#include "qemu/error-report.h"
+
+
+typedef int64_t InstrHandleID;
+
+typedef struct InstrHandle
+{
+    InstrHandleID id;
+    void *dlhandle;
+    QSLIST_ENTRY(InstrHandle) list;
+} InstrHandle;
+
+
+static InstrHandleID handle_last_id;
+static QSLIST_HEAD(, InstrHandle) handles = QSLIST_HEAD_INITIALIZER(handles);
+static QemuMutex instr_lock;
+
+
+static InstrHandle *handle_get(void)
+{
+    InstrHandle *res = g_malloc0(sizeof(InstrHandle));
+    res->id = handle_last_id++;
+    QSLIST_INSERT_HEAD(&handles, res, list);
+    return res;
+}
+
+static bool handle_put(InstrHandleID id)
+{
+    InstrHandle *prev = NULL;
+    InstrHandle *handle;
+    QSLIST_FOREACH(handle, &handles, list) {
+        if (handle->id == id) {
+            break;
+        }
+        prev = handle;
+    }
+    if (handle == NULL) {
+        return false;
+    } else {
+        if (prev == NULL) {
+            QSLIST_REMOVE_HEAD(&handles, list);
+        } else {
+            QSLIST_REMOVE_AFTER(prev, list);
+        }
+        g_free(handle);
+        return true;
+    }
+}
+
+static InstrHandle *handle_find(InstrHandleID id)
+{
+    InstrHandle *handle;
+    QSLIST_FOREACH(handle, &handles, list) {
+        if (handle->id == id) {
+            return handle;
+        }
+    }
+    return NULL;
+}
+
+InstrLoadError instr_load(const char * path, int argc, const char ** argv,
+                          int64_t *handle_id)
+{
+    InstrLoadError res;
+    InstrHandle * handle;
+    int (*main_cb)(int, const char **);
+    int main_res;
+
+    qemu_rec_mutex_lock(&instr_lock);
+
+    *handle_id = -1;
+
+    if (!QSLIST_EMPTY(&handles) > 0) {
+        /* XXX: This is in fact a hard-coded limit, but there's no reason why a
+         *      real multi-library implementation should fail.
+         */
+        res = INSTR_LOAD_TOO_MANY;
+        goto out;
+    }
+
+    handle = handle_get();
+    handle->dlhandle = dlopen(path, RTLD_NOW);
+    if (handle->dlhandle == NULL) {
+        res = INSTR_LOAD_DLERROR;
+        goto err;
+    }
+
+    main_cb = dlsym(handle->dlhandle, "main");
+    if (main_cb == NULL) {
+        res = INSTR_LOAD_DLERROR;
+        goto err;
+    }
+
+    main_res = main_cb(argc, argv);
+
+    if (main_res != 0) {
+        res = INSTR_LOAD_ERROR;
+        goto err;
+    }
+
+    *handle_id = handle->id;
+    res = INSTR_LOAD_OK;
+    goto out;
+
+err:
+    handle_put(handle->id);
+out:
+    qemu_rec_mutex_unlock(&instr_lock);
+    return res;
+}
+
+InstrUnloadError instr_unload(int64_t handle_id)
+{
+    InstrLoadError res;
+
+    qemu_rec_mutex_lock(&instr_lock);
+
+    InstrHandle *handle = handle_find(handle_id);
+    if (handle == NULL) {
+        res = INSTR_UNLOAD_INVALID;
+        goto out;
+    }
+
+    /* this should never fail */
+    if (dlclose(handle->dlhandle) < 0) {
+        res = INSTR_UNLOAD_DLERROR;
+    } else {
+        res = INSTR_UNLOAD_OK;
+    }
+    handle_put(handle->id);
+
+out:
+    qemu_rec_mutex_unlock(&instr_lock);
+    return res;
+}
+
+InstrUnloadError instr_unload_all(void)
+{
+    InstrUnloadError res = INSTR_UNLOAD_OK;
+
+    qemu_rec_mutex_lock(&instr_lock);
+    while (true) {
+        InstrHandle *handle = QSLIST_FIRST(&handles);
+        if (handle == NULL) {
+            break;
+        } else {
+            res = instr_unload(handle->id);
+            if (res != INSTR_UNLOAD_OK) {
+                break;
+            }
+        }
+    }
+    qemu_rec_mutex_unlock(&instr_lock);
+
+    return res;
+}
+
+static void __attribute__((constructor)) instr_lock_init(void)
+{
+    qemu_rec_mutex_init(&instr_lock);
+}
diff --git a/instrument/load.h b/instrument/load.h
new file mode 100644
index 0000000000..2ddb2c6c19
--- /dev/null
+++ b/instrument/load.h
@@ -0,0 +1,83 @@ 
+/*
+ * Interface for (un)loading instrumentation libraries.
+ *
+ * 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 INSTRUMENT_LOAD_H
+#define INSTRUMENT_LOAD_H
+
+#include "qemu/osdep.h"
+
+#include "qemu/queue.h"
+#include "qemu/thread.h"
+
+
+/**
+ * InstrLoadError:
+ * @INSTR_LOAD_OK: Correctly loaded.
+ * @INSTR_LOAD_TOO_MANY: Tried to load too many instrumentation libraries.
+ * @INSTR_LOAD_ERROR: The library's main() function returned a non-zero value.
+ * @INSTR_LOAD_DLERROR: Error with libdl (see dlerror).
+ *
+ * Error codes for instr_load().
+ */
+typedef enum {
+    INSTR_LOAD_OK,
+    INSTR_LOAD_TOO_MANY,
+    INSTR_LOAD_ERROR,
+    INSTR_LOAD_DLERROR,
+} InstrLoadError;
+
+/**
+ * InstrUnloadError:
+ * @INSTR_UNLOAD_OK: Correctly unloaded.
+ * @INSTR_UNLOAD_INVALID: Invalid handle.
+ * @INSTR_UNLOAD_DLERROR: Error with libdl (see dlerror).
+ *
+ * Error codes for instr_unload().
+ */
+typedef enum {
+    INSTR_UNLOAD_OK,
+    INSTR_UNLOAD_INVALID,
+    INSTR_UNLOAD_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.
+ * @handle: Instrumentation library handle (undefined in case of error).
+ *
+ * Load a dynamic trace instrumentation library.
+ *
+ * Returns: Whether the library could be loaded.
+ */
+InstrLoadError instr_load(const char * path, int argc, const char ** argv,
+                          int64_t *handle);
+
+/**
+ * instr_unload:
+ * @handle: Instrumentation library handle returned by instr_load().
+ *
+ * Unload the given instrumentation library.
+ *
+ * Returns: Whether the library could be unloaded.
+ */
+InstrUnloadError instr_unload(int64_t handle);
+
+/**
+ * instr_unload_all:
+ *
+ * Unload all instrumentation libraries.
+ *
+ * Returns: Whether any library could not be unloaded.
+ */
+InstrUnloadError instr_unload_all(void);
+
+#endif  /* INSTRUMENT_LOAD_H */