From patchwork Wed Sep 6 17:34:48 2017 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Llu=C3=ADs_Vilanova?= X-Patchwork-Id: 9941129 Return-Path: Received: from mail.wl.linuxfoundation.org (pdx-wl-mail.web.codeaurora.org [172.30.200.125]) by pdx-korg-patchwork.web.codeaurora.org (Postfix) with ESMTP id 51B48602CC for ; Wed, 6 Sep 2017 17:35:44 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id 3C412203C0 for ; Wed, 6 Sep 2017 17:35:44 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id 30E0228BFF; Wed, 6 Sep 2017 17:35:44 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on pdx-wl-mail.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-6.9 required=2.0 tests=BAYES_00,RCVD_IN_DNSWL_HI autolearn=ham version=3.3.1 Received: from lists.gnu.org (lists.gnu.org [208.118.235.17]) (using TLSv1 with cipher AES256-SHA (256/256 bits)) (No client certificate requested) by mail.wl.linuxfoundation.org (Postfix) with ESMTPS id 07D81203C0 for ; Wed, 6 Sep 2017 17:35:42 +0000 (UTC) Received: from localhost ([::1]:37288 helo=lists.gnu.org) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1dpeEo-0003PI-2e for patchwork-qemu-devel@patchwork.kernel.org; Wed, 06 Sep 2017 13:35:42 -0400 Received: from eggs.gnu.org ([2001:4830:134:3::10]:38410) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1dpeEB-0003Ox-IR for qemu-devel@nongnu.org; Wed, 06 Sep 2017 13:35:05 -0400 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1dpeE8-0006YW-Ch for qemu-devel@nongnu.org; Wed, 06 Sep 2017 13:35:03 -0400 Received: from roura.ac.upc.es ([147.83.33.10]:40325) by eggs.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1dpeE7-0006Y2-OF for qemu-devel@nongnu.org; Wed, 06 Sep 2017 13:35:00 -0400 Received: from correu-1.ac.upc.es (correu-1.ac.upc.es [147.83.30.91]) by roura.ac.upc.es (8.13.8/8.13.8) with ESMTP id v86HYteU003993; Wed, 6 Sep 2017 19:34:55 +0200 Received: from localhost (unknown [31.210.187.58]) by correu-1.ac.upc.es (Postfix) with ESMTPSA id D7E7D8AF; Wed, 6 Sep 2017 19:34:49 +0200 (CEST) From: =?utf-8?b?TGx1w61z?= Vilanova To: qemu-devel@nongnu.org Date: Wed, 6 Sep 2017 20:34:48 +0300 Message-Id: <150471928780.24907.14047559834166839201.stgit@frigg.lan> X-Mailer: git-send-email 2.14.1 In-Reply-To: <150471856141.24907.274176769201097378.stgit@frigg.lan> References: <150471856141.24907.274176769201097378.stgit@frigg.lan> User-Agent: StGit/0.17.1-dirty MIME-Version: 1.0 X-MIME-Autoconverted: from 8bit to quoted-printable by roura.ac.upc.es id v86HYteU003993 X-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.6.x [fuzzy] X-Received-From: 147.83.33.10 Subject: [Qemu-devel] [PATCH v4 03/20] instrument: Add generic library loader X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.21 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: "Emilio G. Cota" , =?UTF-8?q?Llu=C3=ADs=20Vilanova?= , Stefan Hajnoczi Errors-To: qemu-devel-bounces+patchwork-qemu-devel=patchwork.kernel.org@nongnu.org Sender: "Qemu-devel" X-Virus-Scanned: ClamAV using ClamSMTP Signed-off-by: Lluís Vilanova --- 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 diff --git a/MAINTAINERS b/MAINTAINERS index edb313c632..edd2c49078 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1485,6 +1485,7 @@ M: Lluís Vilanova M: Stefan Hajnoczi 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 + * + * 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 +#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 + * + * 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 + * + * 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 +#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 + * + * 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 */