diff mbox series

[RFC,v2,2/7] Add plugin support

Message ID 152819516675.30857.9162557650483931182.stgit@pasha-ThinkPad-T60
State New
Headers show
Series QEMU binary instrumentation prototype | expand

Commit Message

Pavel Dovgalyuk June 5, 2018, 10:39 a.m. UTC
This patch adds support for dynamically loaded plugins.
Every plugin is a dynamic library with a set of optional exported
functions that will be called from QEMU.

Signed-off-by: Pavel Dovgalyuk <Pavel.Dovgaluk@ispras.ru>
---
 Makefile.target           |    1 
 configure                 |   14 ++++++-
 include/qemu/plugins.h    |    8 ++++
 plugins/include/plugins.h |   12 ++++++
 plugins/plugins.c         |   91 +++++++++++++++++++++++++++++++++++++++++++++
 qemu-options.hx           |   10 +++++
 vl.c                      |    8 ++++
 7 files changed, 143 insertions(+), 1 deletion(-)
 create mode 100644 include/qemu/plugins.h
 create mode 100644 plugins/include/plugins.h
 create mode 100644 plugins/plugins.c

Comments

Alex Bennée Sept. 7, 2018, 10:11 a.m. UTC | #1
Pavel Dovgalyuk <Pavel.Dovgaluk@ispras.ru> writes:

> This patch adds support for dynamically loaded plugins.
> Every plugin is a dynamic library with a set of optional exported
> functions that will be called from QEMU.
>
> Signed-off-by: Pavel Dovgalyuk <Pavel.Dovgaluk@ispras.ru>
> ---
<snip>
>  qemu-options.hx           |   10 +++++
>  vl.c                      |    8 ++++

There are a couple of trivial conflicts vs master here, simple to fix.

>  7 files changed, 143 insertions(+), 1 deletion(-)
>  create mode 100644 include/qemu/plugins.h
>  create mode 100644 plugins/include/plugins.h
>  create mode 100644 plugins/plugins.c
>
> diff --git a/Makefile.target b/Makefile.target
> index dad2cf8..4cffd96 100644
> --- a/Makefile.target
> +++ b/Makefile.target
> @@ -93,6 +93,7 @@ all: $(PROGS) stap
>  # cpu emulator library
>  obj-y += exec.o
>  obj-y += accel/
> +obj-$(CONFIG_PLUGINS) += plugins/plugins.o
>  obj-$(CONFIG_TCG) += tcg/tcg.o tcg/tcg-op.o tcg/tcg-op-vec.o tcg/tcg-op-gvec.o
>  obj-$(CONFIG_TCG) += tcg/tcg-common.o tcg/optimize.o
>  obj-$(CONFIG_TCG_INTERPRETER) += tcg/tci.o
> diff --git a/configure b/configure
> index a71bf9b..34e6f00 100755
> --- a/configure
> +++ b/configure
> @@ -373,6 +373,7 @@ EXESUF=""
>  DSOSUF=".so"
>  LDFLAGS_SHARED="-shared"
>  modules="no"
> +plugins="no"
>  prefix="/usr/local"
>  mandir="\${prefix}/share/man"
>  datadir="\${prefix}/share"
> @@ -922,6 +923,12 @@ for opt do
>    --disable-modules)
>        modules="no"
>    ;;
> +  --enable-plugins)
> +      plugins="yes"
> +  ;;
> +  --disable-plugins)
> +      plugins="no"
> +  ;;
>    --cpu=*)
>    ;;
>    --target-list=*) target_list="$optarg"
> @@ -1567,6 +1574,7 @@ disabled with --disable-FEATURE, default is enabled if available:
>    guest-agent-msi build guest agent Windows MSI installation package
>    pie             Position Independent Executables
>    modules         modules support
> +  plugins         plugins support
>    debug-tcg       TCG debugging (default is disabled)
>    debug-info      debugging information
>    sparse          sparse checker
> @@ -3392,7 +3400,7 @@ else
>      glib_req_ver=2.22
>  fi
>  glib_modules=gthread-2.0
> -if test "$modules" = yes; then
> +if test "$modules" = yes || test "$plugins" = yes; then
>      glib_modules="$glib_modules gmodule-export-2.0"
>  fi
>
> @@ -5777,6 +5785,7 @@ if test "$slirp" = "yes" ; then
>      echo "smbd              $smbd"
>  fi
>  echo "module support    $modules"
> +echo "plugin support    $plugins"
>  echo "host CPU          $cpu"
>  echo "host big endian   $bigendian"
>  echo "target list       $target_list"
> @@ -6111,6 +6120,9 @@ if test "$modules" = "yes"; then
>    echo "CONFIG_STAMP=_$( (echo $qemu_version; echo $pkgversion; cat $0) | $shacmd - | cut -f1 -d\ )" >> $config_host_mak
>    echo "CONFIG_MODULES=y" >> $config_host_mak
>  fi
> +if test "$plugins" = "yes"; then
> +  echo "CONFIG_PLUGINS=y" >> $config_host_mak
> +fi
>  if test "$have_x11" = "yes" -a "$need_x11" = "yes"; then
>    echo "CONFIG_X11=y" >> $config_host_mak
>    echo "X11_CFLAGS=$x11_cflags" >> $config_host_mak
> diff --git a/include/qemu/plugins.h b/include/qemu/plugins.h
> new file mode 100644
> index 0000000..4464822
> --- /dev/null
> +++ b/include/qemu/plugins.h
> @@ -0,0 +1,8 @@
> +#ifndef PLUGINS_H
> +#define PLUGINS_H
> +
> +void qemu_plugin_parse_cmd_args(const char *optarg);
> +void qemu_plugin_load(const char *filename, const char *args);
> +void qemu_plugins_init(void);
> +
> +#endif /* PLUGINS_H */
> diff --git a/plugins/include/plugins.h b/plugins/include/plugins.h
> new file mode 100644
> index 0000000..100a786
> --- /dev/null
> +++ b/plugins/include/plugins.h
> @@ -0,0 +1,12 @@
> +#ifndef PLUGINS_INTERFACE_H
> +#define PLUGINS_INTERFACE_H
> +
> +#include <stdbool.h>
> +
> +/* Plugin interface */
> +
> +bool plugin_init(const char *args);
> +bool plugin_needs_before_insn(uint64_t pc, void *cpu);
> +void plugin_before_insn(uint64_t pc, void *cpu);
> +
> +#endif /* PLUGINS_INTERFACE_H */
> diff --git a/plugins/plugins.c b/plugins/plugins.c
> new file mode 100644
> index 0000000..eabc931
> --- /dev/null
> +++ b/plugins/plugins.c
> @@ -0,0 +1,91 @@
> +#include "qemu/osdep.h"
> +#include "qemu-common.h"
> +#include "qemu/error-report.h"
> +#include "qemu/plugins.h"
> +#include "qemu/queue.h"
> +#include <gmodule.h>
> +
> +typedef bool (*PluginInitFunc)(const char *);
> +typedef bool (*PluginNeedsBeforeInsnFunc)(uint64_t, void *);
> +typedef void (*PluginBeforeInsnFunc)(uint64_t, void *);
> +
> +typedef struct QemuPluginInfo {
> +    const char *filename;
> +    const char *args;
> +    GModule *g_module;
> +
> +    PluginInitFunc init;
> +    PluginNeedsBeforeInsnFunc needs_before_insn;

It seems a bit heavyweight to have a query function here. Is this
dynamic state the plugin might change during execution? If not could we
not better report plugin requirements during initialisation?

> +    PluginBeforeInsnFunc before_insn;
> +
> +    QLIST_ENTRY(QemuPluginInfo) next;
> +} QemuPluginInfo;
> +
> +static QLIST_HEAD(, QemuPluginInfo) qemu_plugins
> +                                = QLIST_HEAD_INITIALIZER(qemu_plugins);
> +
> +static QemuOptsList qemu_plugin_opts = {
> +    .name = "plugin",
> +    .head = QTAILQ_HEAD_INITIALIZER(qemu_plugin_opts.head),
> +    .desc = {
> +        {
> +            .name = "file",
> +            .type = QEMU_OPT_STRING,
> +        },{
> +            .name = "args",
> +            .type = QEMU_OPT_STRING,
> +        },
> +        { /* end of list */ }
> +    },
> +};
> +
> +void qemu_plugin_parse_cmd_args(const char *optarg)
> +{
> +    QemuOpts *opts = qemu_opts_parse_noisily(&qemu_plugin_opts, optarg, false);
> +    qemu_plugin_load(qemu_opt_get(opts, "file"),
> +        qemu_opt_get(opts, "args"));
> +}
> +
> +void qemu_plugin_load(const char *filename, const char *args)
> +{
> +    GModule *g_module;
> +    QemuPluginInfo *info = NULL;
> +    if (!filename) {
> +        error_report("plugin name was not specified");
> +        return;
> +    }
> +    g_module = g_module_open(filename,
> +        G_MODULE_BIND_LAZY | G_MODULE_BIND_LOCAL);
> +    if (!g_module) {
> +        error_report("can't load plugin '%s'", filename);
> +        return;
> +    }
> +    info = g_new0(QemuPluginInfo, 1);
> +    info->filename = g_strdup(filename);
> +    info->g_module = g_module;
> +    if (args) {
> +        info->args = g_strdup(args);
> +    }
> +
> +    g_module_symbol(g_module, "plugin_init", (gpointer*)&info->init);
> +
> +    /* Get the instrumentation callbacks */
> +    g_module_symbol(g_module, "plugin_needs_before_insn",
> +        (gpointer*)&info->needs_before_insn);
> +    g_module_symbol(g_module, "plugin_before_insn",
> +        (gpointer*)&info->before_insn);

In fact could the presence of an "optional" symbol imply it's need here?

> +
> +    QLIST_INSERT_HEAD(&qemu_plugins, info, next);
> +
> +    return;
> +}
> +
> +void qemu_plugins_init(void)
> +{
> +    QemuPluginInfo *info;
> +    QLIST_FOREACH(info, &qemu_plugins, next) {
> +        if (info->init) {
> +            info->init(info->args);
> +        }
> +    }
> +}
> diff --git a/qemu-options.hx b/qemu-options.hx
> index c0d3951..d171544 100644
> --- a/qemu-options.hx
> +++ b/qemu-options.hx
> @@ -3950,6 +3950,16 @@ Dump json-encoded vmstate information for current machine type to file
>  in @var{file}
>  ETEXI
>
> +#ifdef CONFIG_PLUGINS
> +DEF("plugin", HAS_ARG, QEMU_OPTION_plugin, \
> +           "-plugin file=<file>[,args=<args>] load <dso> plugin with <args>\n", QEMU_ARCH_ALL)
> +STEXI
> +@item -plugin file=@var{file}[,args=@var{args}]
> +@findex -plugin
> +Load @var{file} plugin passing @var{args} arguments.
> +ETEXI
> +#endif
> +
>  STEXI
>  @end table
>  ETEXI
> diff --git a/vl.c b/vl.c
> index 0603171..05420bf 100644
> --- a/vl.c
> +++ b/vl.c
> @@ -129,6 +129,7 @@ int main(int argc, char **argv)
>  #include "qapi/qapi-commands-run-state.h"
>  #include "qapi/qmp/qerror.h"
>  #include "sysemu/iothread.h"
> +#include "qemu/plugins.h"
>
>  #define MAX_VIRTIO_CONSOLES 1
>
> @@ -3925,6 +3926,11 @@ int main(int argc, char **argv, char **envp)
>                      exit(1);
>                  }
>                  break;
> +#ifdef CONFIG_PLUGINS
> +            case QEMU_OPTION_plugin:
> +                qemu_plugin_parse_cmd_args(optarg);
> +                break;
> +#endif
>              case QEMU_OPTION_nodefconfig:
>              case QEMU_OPTION_nouserconfig:
>                  /* Nothing to be parsed here. Especially, do not error out below. */
> @@ -4470,6 +4476,8 @@ int main(int argc, char **argv, char **envp)
>      }
>      parse_numa_opts(current_machine);
>
> +    qemu_plugins_init();
> +
>      /* do monitor/qmp handling at preconfig state if requested */
>      main_loop();
>


--
Alex Bennée
Alex Bennée Sept. 7, 2018, 12:34 p.m. UTC | #2
Pavel Dovgalyuk <Pavel.Dovgaluk@ispras.ru> writes:

> This patch adds support for dynamically loaded plugins.
> Every plugin is a dynamic library with a set of optional exported
> functions that will be called from QEMU.
>
> Signed-off-by: Pavel Dovgalyuk <Pavel.Dovgaluk@ispras.ru>
> ---
>  Makefile.target           |    1
>  configure                 |   14 ++++++-
>  include/qemu/plugins.h    |    8 ++++
>  plugins/include/plugins.h |   12 ++++++
>  plugins/plugins.c         |   91 +++++++++++++++++++++++++++++++++++++++++++++
>  qemu-options.hx           |   10 +++++
>  vl.c                      |    8 ++++
>  7 files changed, 143 insertions(+), 1 deletion(-)
>  create mode 100644 include/qemu/plugins.h
>  create mode 100644 plugins/include/plugins.h
>  create mode 100644 plugins/plugins.c
>
> diff --git a/Makefile.target b/Makefile.target
> index dad2cf8..4cffd96 100644
> --- a/Makefile.target
> +++ b/Makefile.target
> @@ -93,6 +93,7 @@ all: $(PROGS) stap
>  # cpu emulator library
>  obj-y += exec.o
>  obj-y += accel/
> +obj-$(CONFIG_PLUGINS) += plugins/plugins.o
>  obj-$(CONFIG_TCG) += tcg/tcg.o tcg/tcg-op.o tcg/tcg-op-vec.o tcg/tcg-op-gvec.o
>  obj-$(CONFIG_TCG) += tcg/tcg-common.o tcg/optimize.o
>  obj-$(CONFIG_TCG_INTERPRETER) += tcg/tci.o
> diff --git a/configure b/configure
> index a71bf9b..34e6f00 100755
> --- a/configure
> +++ b/configure
> @@ -373,6 +373,7 @@ EXESUF=""
>  DSOSUF=".so"
>  LDFLAGS_SHARED="-shared"
>  modules="no"
> +plugins="no"
>  prefix="/usr/local"
>  mandir="\${prefix}/share/man"
>  datadir="\${prefix}/share"
> @@ -922,6 +923,12 @@ for opt do
>    --disable-modules)
>        modules="no"
>    ;;
> +  --enable-plugins)
> +      plugins="yes"
> +  ;;
> +  --disable-plugins)
> +      plugins="no"
> +  ;;
>    --cpu=*)
>    ;;
>    --target-list=*) target_list="$optarg"
> @@ -1567,6 +1574,7 @@ disabled with --disable-FEATURE, default is enabled if available:
>    guest-agent-msi build guest agent Windows MSI installation package
>    pie             Position Independent Executables
>    modules         modules support
> +  plugins         plugins support
>    debug-tcg       TCG debugging (default is disabled)
>    debug-info      debugging information
>    sparse          sparse checker
> @@ -3392,7 +3400,7 @@ else
>      glib_req_ver=2.22
>  fi
>  glib_modules=gthread-2.0
> -if test "$modules" = yes; then
> +if test "$modules" = yes || test "$plugins" = yes; then
>      glib_modules="$glib_modules gmodule-export-2.0"
>  fi
>
> @@ -5777,6 +5785,7 @@ if test "$slirp" = "yes" ; then
>      echo "smbd              $smbd"
>  fi
>  echo "module support    $modules"
> +echo "plugin support    $plugins"
>  echo "host CPU          $cpu"
>  echo "host big endian   $bigendian"
>  echo "target list       $target_list"
> @@ -6111,6 +6120,9 @@ if test "$modules" = "yes"; then
>    echo "CONFIG_STAMP=_$( (echo $qemu_version; echo $pkgversion; cat $0) | $shacmd - | cut -f1 -d\ )" >> $config_host_mak
>    echo "CONFIG_MODULES=y" >> $config_host_mak
>  fi
> +if test "$plugins" = "yes"; then
> +  echo "CONFIG_PLUGINS=y" >> $config_host_mak
> +fi
>  if test "$have_x11" = "yes" -a "$need_x11" = "yes"; then
>    echo "CONFIG_X11=y" >> $config_host_mak
>    echo "X11_CFLAGS=$x11_cflags" >> $config_host_mak
> diff --git a/include/qemu/plugins.h b/include/qemu/plugins.h
> new file mode 100644
> index 0000000..4464822
> --- /dev/null
> +++ b/include/qemu/plugins.h
> @@ -0,0 +1,8 @@
> +#ifndef PLUGINS_H
> +#define PLUGINS_H
> +
> +void qemu_plugin_parse_cmd_args(const char *optarg);
> +void qemu_plugin_load(const char *filename, const char *args);
> +void qemu_plugins_init(void);

I think you want to have an #ifdef CONFIG_PLUGINS here and some empty
inlines for the non CONFIG_PLUGINS case so you don't need to ifdef so
much later.

> +
> +#endif /* PLUGINS_H */
> diff --git a/plugins/include/plugins.h b/plugins/include/plugins.h
> new file mode 100644
> index 0000000..100a786
> --- /dev/null
> +++ b/plugins/include/plugins.h
> @@ -0,0 +1,12 @@
> +#ifndef PLUGINS_INTERFACE_H
> +#define PLUGINS_INTERFACE_H
> +
> +#include <stdbool.h>
> +
> +/* Plugin interface */
> +
> +bool plugin_init(const char *args);
> +bool plugin_needs_before_insn(uint64_t pc, void *cpu);
> +void plugin_before_insn(uint64_t pc, void *cpu);
> +
> +#endif /* PLUGINS_INTERFACE_H */
> diff --git a/plugins/plugins.c b/plugins/plugins.c
> new file mode 100644
> index 0000000..eabc931
> --- /dev/null
> +++ b/plugins/plugins.c
> @@ -0,0 +1,91 @@
> +#include "qemu/osdep.h"
> +#include "qemu-common.h"
> +#include "qemu/error-report.h"
> +#include "qemu/plugins.h"
> +#include "qemu/queue.h"
> +#include <gmodule.h>
> +
> +typedef bool (*PluginInitFunc)(const char *);
> +typedef bool (*PluginNeedsBeforeInsnFunc)(uint64_t, void *);
> +typedef void (*PluginBeforeInsnFunc)(uint64_t, void *);
> +
> +typedef struct QemuPluginInfo {
> +    const char *filename;
> +    const char *args;
> +    GModule *g_module;
> +
> +    PluginInitFunc init;
> +    PluginNeedsBeforeInsnFunc needs_before_insn;
> +    PluginBeforeInsnFunc before_insn;
> +
> +    QLIST_ENTRY(QemuPluginInfo) next;
> +} QemuPluginInfo;
> +
> +static QLIST_HEAD(, QemuPluginInfo) qemu_plugins
> +                                = QLIST_HEAD_INITIALIZER(qemu_plugins);
> +
> +static QemuOptsList qemu_plugin_opts = {
> +    .name = "plugin",
> +    .head = QTAILQ_HEAD_INITIALIZER(qemu_plugin_opts.head),
> +    .desc = {
> +        {
> +            .name = "file",
> +            .type = QEMU_OPT_STRING,
> +        },{
> +            .name = "args",
> +            .type = QEMU_OPT_STRING,
> +        },
> +        { /* end of list */ }
> +    },
> +};
> +
> +void qemu_plugin_parse_cmd_args(const char *optarg)
> +{
> +    QemuOpts *opts = qemu_opts_parse_noisily(&qemu_plugin_opts, optarg, false);
> +    qemu_plugin_load(qemu_opt_get(opts, "file"),
> +        qemu_opt_get(opts, "args"));
> +}
> +
> +void qemu_plugin_load(const char *filename, const char *args)
> +{
> +    GModule *g_module;
> +    QemuPluginInfo *info = NULL;
> +    if (!filename) {
> +        error_report("plugin name was not specified");
> +        return;
> +    }
> +    g_module = g_module_open(filename,
> +        G_MODULE_BIND_LAZY | G_MODULE_BIND_LOCAL);
> +    if (!g_module) {
> +        error_report("can't load plugin '%s'", filename);
> +        return;
> +    }
> +    info = g_new0(QemuPluginInfo, 1);
> +    info->filename = g_strdup(filename);
> +    info->g_module = g_module;
> +    if (args) {
> +        info->args = g_strdup(args);
> +    }
> +
> +    g_module_symbol(g_module, "plugin_init", (gpointer*)&info->init);
> +
> +    /* Get the instrumentation callbacks */
> +    g_module_symbol(g_module, "plugin_needs_before_insn",
> +        (gpointer*)&info->needs_before_insn);
> +    g_module_symbol(g_module, "plugin_before_insn",
> +        (gpointer*)&info->before_insn);
> +
> +    QLIST_INSERT_HEAD(&qemu_plugins, info, next);
> +
> +    return;
> +}
> +
> +void qemu_plugins_init(void)
> +{
> +    QemuPluginInfo *info;
> +    QLIST_FOREACH(info, &qemu_plugins, next) {
> +        if (info->init) {
> +            info->init(info->args);
> +        }
> +    }
> +}
> diff --git a/qemu-options.hx b/qemu-options.hx
> index c0d3951..d171544 100644
> --- a/qemu-options.hx
> +++ b/qemu-options.hx
> @@ -3950,6 +3950,16 @@ Dump json-encoded vmstate information for current machine type to file
>  in @var{file}
>  ETEXI
>
> +#ifdef CONFIG_PLUGINS
> +DEF("plugin", HAS_ARG, QEMU_OPTION_plugin, \
> +           "-plugin file=<file>[,args=<args>] load <dso> plugin with <args>\n", QEMU_ARCH_ALL)
> +STEXI
> +@item -plugin file=@var{file}[,args=@var{args}]
> +@findex -plugin
> +Load @var{file} plugin passing @var{args} arguments.
> +ETEXI
> +#endif
> +
>  STEXI
>  @end table
>  ETEXI
> diff --git a/vl.c b/vl.c
> index 0603171..05420bf 100644
> --- a/vl.c
> +++ b/vl.c
> @@ -129,6 +129,7 @@ int main(int argc, char **argv)
>  #include "qapi/qapi-commands-run-state.h"
>  #include "qapi/qmp/qerror.h"
>  #include "sysemu/iothread.h"
> +#include "qemu/plugins.h"
>
>  #define MAX_VIRTIO_CONSOLES 1
>
> @@ -3925,6 +3926,11 @@ int main(int argc, char **argv, char **envp)
>                      exit(1);
>                  }
>                  break;
> +#ifdef CONFIG_PLUGINS
> +            case QEMU_OPTION_plugin:
> +                qemu_plugin_parse_cmd_args(optarg);
> +                break;
> +#endif
>              case QEMU_OPTION_nodefconfig:
>              case QEMU_OPTION_nouserconfig:
>                  /* Nothing to be parsed here. Especially, do not error out below. */
> @@ -4470,6 +4476,8 @@ int main(int argc, char **argv, char **envp)
>      }
>      parse_numa_opts(current_machine);
>
> +    qemu_plugins_init();
> +

For example this fails to build currently.

>      /* do monitor/qmp handling at preconfig state if requested */
>      main_loop();
>


--
Alex Bennée
Alex Bennée Sept. 7, 2018, 2:14 p.m. UTC | #3
Pavel Dovgalyuk <Pavel.Dovgaluk@ispras.ru> writes:

> This patch adds support for dynamically loaded plugins.
> Every plugin is a dynamic library with a set of optional exported
> functions that will be called from QEMU.
>
<snip>
> +
> +static QLIST_HEAD(, QemuPluginInfo) qemu_plugins
> +                                = QLIST_HEAD_INITIALIZER(qemu_plugins);
> +
> +static QemuOptsList qemu_plugin_opts = {
> +    .name = "plugin",
> +    .head = QTAILQ_HEAD_INITIALIZER(qemu_plugin_opts.head),
> +    .desc = {
> +        {
> +            .name = "file",
> +            .type = QEMU_OPT_STRING,
> +        },{
> +            .name = "args",
> +            .type = QEMU_OPT_STRING,
> +        },
> +        { /* end of list */ }
> +    },
> +};
> +
> +void qemu_plugin_parse_cmd_args(const char *optarg)
> +{
> +    QemuOpts *opts = qemu_opts_parse_noisily(&qemu_plugin_opts, optarg, false);
> +    qemu_plugin_load(qemu_opt_get(opts, "file"),
> +        qemu_opt_get(opts, "args"));
> +}

Currently this is only available to system mode emulation. Can it be
extended to include linux-user as well?

> +
> +void qemu_plugin_load(const char *filename, const char *args)
> +{
> +    GModule *g_module;
> +    QemuPluginInfo *info = NULL;
> +    if (!filename) {
> +        error_report("plugin name was not specified");
> +        return;
> +    }
> +    g_module = g_module_open(filename,
> +        G_MODULE_BIND_LAZY | G_MODULE_BIND_LOCAL);
> +    if (!g_module) {
> +        error_report("can't load plugin '%s'", filename);
> +        return;
> +    }
> +    info = g_new0(QemuPluginInfo, 1);
> +    info->filename = g_strdup(filename);
> +    info->g_module = g_module;
> +    if (args) {
> +        info->args = g_strdup(args);
> +    }
> +
> +    g_module_symbol(g_module, "plugin_init", (gpointer*)&info->init);
> +
> +    /* Get the instrumentation callbacks */
> +    g_module_symbol(g_module, "plugin_needs_before_insn",
> +        (gpointer*)&info->needs_before_insn);
> +    g_module_symbol(g_module, "plugin_before_insn",
> +        (gpointer*)&info->before_insn);
> +
> +    QLIST_INSERT_HEAD(&qemu_plugins, info, next);
> +
> +    return;
> +}
> +
> +void qemu_plugins_init(void)
> +{
> +    QemuPluginInfo *info;
> +    QLIST_FOREACH(info, &qemu_plugins, next) {
> +        if (info->init) {
> +            info->init(info->args);
> +        }
> +    }
> +}
> diff --git a/qemu-options.hx b/qemu-options.hx
> index c0d3951..d171544 100644
> --- a/qemu-options.hx
> +++ b/qemu-options.hx
> @@ -3950,6 +3950,16 @@ Dump json-encoded vmstate information for current machine type to file
>  in @var{file}
>  ETEXI
>
> +#ifdef CONFIG_PLUGINS
> +DEF("plugin", HAS_ARG, QEMU_OPTION_plugin, \
> +           "-plugin file=<file>[,args=<args>] load <dso> plugin with <args>\n", QEMU_ARCH_ALL)
> +STEXI
> +@item -plugin file=@var{file}[,args=@var{args}]
> +@findex -plugin
> +Load @var{file} plugin passing @var{args} arguments.
> +ETEXI
> +#endif
> +
>  STEXI
>  @end table
>  ETEXI
> diff --git a/vl.c b/vl.c
> index 0603171..05420bf 100644
> --- a/vl.c
> +++ b/vl.c
> @@ -129,6 +129,7 @@ int main(int argc, char **argv)
>  #include "qapi/qapi-commands-run-state.h"
>  #include "qapi/qmp/qerror.h"
>  #include "sysemu/iothread.h"
> +#include "qemu/plugins.h"
>
>  #define MAX_VIRTIO_CONSOLES 1
>
> @@ -3925,6 +3926,11 @@ int main(int argc, char **argv, char **envp)
>                      exit(1);
>                  }
>                  break;
> +#ifdef CONFIG_PLUGINS
> +            case QEMU_OPTION_plugin:
> +                qemu_plugin_parse_cmd_args(optarg);
> +                break;
> +#endif
>              case QEMU_OPTION_nodefconfig:
>              case QEMU_OPTION_nouserconfig:
>                  /* Nothing to be parsed here. Especially, do not error out below. */
> @@ -4470,6 +4476,8 @@ int main(int argc, char **argv, char **envp)
>      }
>      parse_numa_opts(current_machine);
>
> +    qemu_plugins_init();
> +
>      /* do monitor/qmp handling at preconfig state if requested */
>      main_loop();
>


--
Alex Bennée
Pavel Dovgalyuk Sept. 10, 2018, 8:30 a.m. UTC | #4
> From: Alex Bennée [mailto:alex.bennee@linaro.org]
> Pavel Dovgalyuk <Pavel.Dovgaluk@ispras.ru> writes:
> 
> > This patch adds support for dynamically loaded plugins.
> > Every plugin is a dynamic library with a set of optional exported
> > functions that will be called from QEMU.
> >
> > Signed-off-by: Pavel Dovgalyuk <Pavel.Dovgaluk@ispras.ru>
> > ---
> >  Makefile.target           |    1
> >  configure                 |   14 ++++++-
> >  include/qemu/plugins.h    |    8 ++++
> >  plugins/include/plugins.h |   12 ++++++
> >  plugins/plugins.c         |   91 +++++++++++++++++++++++++++++++++++++++++++++
> >  qemu-options.hx           |   10 +++++
> >  vl.c                      |    8 ++++
> >  7 files changed, 143 insertions(+), 1 deletion(-)
> >  create mode 100644 include/qemu/plugins.h
> >  create mode 100644 plugins/include/plugins.h
> >  create mode 100644 plugins/plugins.c
> >
> > diff --git a/Makefile.target b/Makefile.target
> > index dad2cf8..4cffd96 100644
> > --- a/Makefile.target
> > +++ b/Makefile.target
> > @@ -93,6 +93,7 @@ all: $(PROGS) stap
> >  # cpu emulator library
> >  obj-y += exec.o
> >  obj-y += accel/
> > +obj-$(CONFIG_PLUGINS) += plugins/plugins.o
> >  obj-$(CONFIG_TCG) += tcg/tcg.o tcg/tcg-op.o tcg/tcg-op-vec.o tcg/tcg-op-gvec.o
> >  obj-$(CONFIG_TCG) += tcg/tcg-common.o tcg/optimize.o
> >  obj-$(CONFIG_TCG_INTERPRETER) += tcg/tci.o
> > diff --git a/configure b/configure
> > index a71bf9b..34e6f00 100755
> > --- a/configure
> > +++ b/configure
> > @@ -373,6 +373,7 @@ EXESUF=""
> >  DSOSUF=".so"
> >  LDFLAGS_SHARED="-shared"
> >  modules="no"
> > +plugins="no"
> >  prefix="/usr/local"
> >  mandir="\${prefix}/share/man"
> >  datadir="\${prefix}/share"
> > @@ -922,6 +923,12 @@ for opt do
> >    --disable-modules)
> >        modules="no"
> >    ;;
> > +  --enable-plugins)
> > +      plugins="yes"
> > +  ;;
> > +  --disable-plugins)
> > +      plugins="no"
> > +  ;;
> >    --cpu=*)
> >    ;;
> >    --target-list=*) target_list="$optarg"
> > @@ -1567,6 +1574,7 @@ disabled with --disable-FEATURE, default is enabled if available:
> >    guest-agent-msi build guest agent Windows MSI installation package
> >    pie             Position Independent Executables
> >    modules         modules support
> > +  plugins         plugins support
> >    debug-tcg       TCG debugging (default is disabled)
> >    debug-info      debugging information
> >    sparse          sparse checker
> > @@ -3392,7 +3400,7 @@ else
> >      glib_req_ver=2.22
> >  fi
> >  glib_modules=gthread-2.0
> > -if test "$modules" = yes; then
> > +if test "$modules" = yes || test "$plugins" = yes; then
> >      glib_modules="$glib_modules gmodule-export-2.0"
> >  fi
> >
> > @@ -5777,6 +5785,7 @@ if test "$slirp" = "yes" ; then
> >      echo "smbd              $smbd"
> >  fi
> >  echo "module support    $modules"
> > +echo "plugin support    $plugins"
> >  echo "host CPU          $cpu"
> >  echo "host big endian   $bigendian"
> >  echo "target list       $target_list"
> > @@ -6111,6 +6120,9 @@ if test "$modules" = "yes"; then
> >    echo "CONFIG_STAMP=_$( (echo $qemu_version; echo $pkgversion; cat $0) | $shacmd - | cut -
> f1 -d\ )" >> $config_host_mak
> >    echo "CONFIG_MODULES=y" >> $config_host_mak
> >  fi
> > +if test "$plugins" = "yes"; then
> > +  echo "CONFIG_PLUGINS=y" >> $config_host_mak
> > +fi
> >  if test "$have_x11" = "yes" -a "$need_x11" = "yes"; then
> >    echo "CONFIG_X11=y" >> $config_host_mak
> >    echo "X11_CFLAGS=$x11_cflags" >> $config_host_mak
> > diff --git a/include/qemu/plugins.h b/include/qemu/plugins.h
> > new file mode 100644
> > index 0000000..4464822
> > --- /dev/null
> > +++ b/include/qemu/plugins.h
> > @@ -0,0 +1,8 @@
> > +#ifndef PLUGINS_H
> > +#define PLUGINS_H
> > +
> > +void qemu_plugin_parse_cmd_args(const char *optarg);
> > +void qemu_plugin_load(const char *filename, const char *args);
> > +void qemu_plugins_init(void);
> 
> I think you want to have an #ifdef CONFIG_PLUGINS here and some empty
> inlines for the non CONFIG_PLUGINS case so you don't need to ifdef so
> much later.

Thanks.
I'll better fix init, because all other invocations are command-line
dependent and still should be put under ifdef.

> > @@ -4470,6 +4476,8 @@ int main(int argc, char **argv, char **envp)
> >      }
> >      parse_numa_opts(current_machine);
> >
> > +    qemu_plugins_init();
> > +
> 
> For example this fails to build currently.
> 


Pavel Dovgalyuk
Pavel Dovgalyuk Sept. 10, 2018, 11:41 a.m. UTC | #5
> From: Alex Bennée [mailto:alex.bennee@linaro.org]
> Pavel Dovgalyuk <Pavel.Dovgaluk@ispras.ru> writes:
> 
> > This patch adds support for dynamically loaded plugins.
> > Every plugin is a dynamic library with a set of optional exported
> > functions that will be called from QEMU.
> >
> <snip>
> > +
> > +static QLIST_HEAD(, QemuPluginInfo) qemu_plugins
> > +                                = QLIST_HEAD_INITIALIZER(qemu_plugins);
> > +
> > +static QemuOptsList qemu_plugin_opts = {
> > +    .name = "plugin",
> > +    .head = QTAILQ_HEAD_INITIALIZER(qemu_plugin_opts.head),
> > +    .desc = {
> > +        {
> > +            .name = "file",
> > +            .type = QEMU_OPT_STRING,
> > +        },{
> > +            .name = "args",
> > +            .type = QEMU_OPT_STRING,
> > +        },
> > +        { /* end of list */ }
> > +    },
> > +};
> > +
> > +void qemu_plugin_parse_cmd_args(const char *optarg)
> > +{
> > +    QemuOpts *opts = qemu_opts_parse_noisily(&qemu_plugin_opts, optarg, false);
> > +    qemu_plugin_load(qemu_opt_get(opts, "file"),
> > +        qemu_opt_get(opts, "args"));
> > +}
> 
> Currently this is only available to system mode emulation. Can it be
> extended to include linux-user as well?

Why not? I'm not familiar with linux-user, so I'll need some time to try.

Pavel Dovgalyuk
Pavel Dovgalyuk Sept. 13, 2018, 6:40 a.m. UTC | #6
> From: Alex Bennée [mailto:alex.bennee@linaro.org]
> Pavel Dovgalyuk <Pavel.Dovgaluk@ispras.ru> writes:
> 
> > This patch adds support for dynamically loaded plugins.
> > Every plugin is a dynamic library with a set of optional exported
> > functions that will be called from QEMU.
> >
> > Signed-off-by: Pavel Dovgalyuk <Pavel.Dovgaluk@ispras.ru>
> > ---
> <snip>
> >  qemu-options.hx           |   10 +++++
> >  vl.c                      |    8 ++++
> 
> There are a couple of trivial conflicts vs master here, simple to fix.
> 
> >  7 files changed, 143 insertions(+), 1 deletion(-)
> >  create mode 100644 include/qemu/plugins.h
> >  create mode 100644 plugins/include/plugins.h
> >  create mode 100644 plugins/plugins.c
> >
> > diff --git a/Makefile.target b/Makefile.target
> > index dad2cf8..4cffd96 100644
> > --- a/Makefile.target
> > +++ b/Makefile.target
> > @@ -93,6 +93,7 @@ all: $(PROGS) stap
> >  # cpu emulator library
> >  obj-y += exec.o
> >  obj-y += accel/
> > +obj-$(CONFIG_PLUGINS) += plugins/plugins.o
> >  obj-$(CONFIG_TCG) += tcg/tcg.o tcg/tcg-op.o tcg/tcg-op-vec.o tcg/tcg-op-gvec.o
> >  obj-$(CONFIG_TCG) += tcg/tcg-common.o tcg/optimize.o
> >  obj-$(CONFIG_TCG_INTERPRETER) += tcg/tci.o
> > diff --git a/configure b/configure
> > index a71bf9b..34e6f00 100755
> > --- a/configure
> > +++ b/configure
> > @@ -373,6 +373,7 @@ EXESUF=""
> >  DSOSUF=".so"
> >  LDFLAGS_SHARED="-shared"
> >  modules="no"
> > +plugins="no"
> >  prefix="/usr/local"
> >  mandir="\${prefix}/share/man"
> >  datadir="\${prefix}/share"
> > @@ -922,6 +923,12 @@ for opt do
> >    --disable-modules)
> >        modules="no"
> >    ;;
> > +  --enable-plugins)
> > +      plugins="yes"
> > +  ;;
> > +  --disable-plugins)
> > +      plugins="no"
> > +  ;;
> >    --cpu=*)
> >    ;;
> >    --target-list=*) target_list="$optarg"
> > @@ -1567,6 +1574,7 @@ disabled with --disable-FEATURE, default is enabled if available:
> >    guest-agent-msi build guest agent Windows MSI installation package
> >    pie             Position Independent Executables
> >    modules         modules support
> > +  plugins         plugins support
> >    debug-tcg       TCG debugging (default is disabled)
> >    debug-info      debugging information
> >    sparse          sparse checker
> > @@ -3392,7 +3400,7 @@ else
> >      glib_req_ver=2.22
> >  fi
> >  glib_modules=gthread-2.0
> > -if test "$modules" = yes; then
> > +if test "$modules" = yes || test "$plugins" = yes; then
> >      glib_modules="$glib_modules gmodule-export-2.0"
> >  fi
> >
> > @@ -5777,6 +5785,7 @@ if test "$slirp" = "yes" ; then
> >      echo "smbd              $smbd"
> >  fi
> >  echo "module support    $modules"
> > +echo "plugin support    $plugins"
> >  echo "host CPU          $cpu"
> >  echo "host big endian   $bigendian"
> >  echo "target list       $target_list"
> > @@ -6111,6 +6120,9 @@ if test "$modules" = "yes"; then
> >    echo "CONFIG_STAMP=_$( (echo $qemu_version; echo $pkgversion; cat $0) | $shacmd - | cut -
> f1 -d\ )" >> $config_host_mak
> >    echo "CONFIG_MODULES=y" >> $config_host_mak
> >  fi
> > +if test "$plugins" = "yes"; then
> > +  echo "CONFIG_PLUGINS=y" >> $config_host_mak
> > +fi
> >  if test "$have_x11" = "yes" -a "$need_x11" = "yes"; then
> >    echo "CONFIG_X11=y" >> $config_host_mak
> >    echo "X11_CFLAGS=$x11_cflags" >> $config_host_mak
> > diff --git a/include/qemu/plugins.h b/include/qemu/plugins.h
> > new file mode 100644
> > index 0000000..4464822
> > --- /dev/null
> > +++ b/include/qemu/plugins.h
> > @@ -0,0 +1,8 @@
> > +#ifndef PLUGINS_H
> > +#define PLUGINS_H
> > +
> > +void qemu_plugin_parse_cmd_args(const char *optarg);
> > +void qemu_plugin_load(const char *filename, const char *args);
> > +void qemu_plugins_init(void);
> > +
> > +#endif /* PLUGINS_H */
> > diff --git a/plugins/include/plugins.h b/plugins/include/plugins.h
> > new file mode 100644
> > index 0000000..100a786
> > --- /dev/null
> > +++ b/plugins/include/plugins.h
> > @@ -0,0 +1,12 @@
> > +#ifndef PLUGINS_INTERFACE_H
> > +#define PLUGINS_INTERFACE_H
> > +
> > +#include <stdbool.h>
> > +
> > +/* Plugin interface */
> > +
> > +bool plugin_init(const char *args);
> > +bool plugin_needs_before_insn(uint64_t pc, void *cpu);
> > +void plugin_before_insn(uint64_t pc, void *cpu);
> > +
> > +#endif /* PLUGINS_INTERFACE_H */
> > diff --git a/plugins/plugins.c b/plugins/plugins.c
> > new file mode 100644
> > index 0000000..eabc931
> > --- /dev/null
> > +++ b/plugins/plugins.c
> > @@ -0,0 +1,91 @@
> > +#include "qemu/osdep.h"
> > +#include "qemu-common.h"
> > +#include "qemu/error-report.h"
> > +#include "qemu/plugins.h"
> > +#include "qemu/queue.h"
> > +#include <gmodule.h>
> > +
> > +typedef bool (*PluginInitFunc)(const char *);
> > +typedef bool (*PluginNeedsBeforeInsnFunc)(uint64_t, void *);
> > +typedef void (*PluginBeforeInsnFunc)(uint64_t, void *);
> > +
> > +typedef struct QemuPluginInfo {
> > +    const char *filename;
> > +    const char *args;
> > +    GModule *g_module;
> > +
> > +    PluginInitFunc init;
> > +    PluginNeedsBeforeInsnFunc needs_before_insn;
> 
> It seems a bit heavyweight to have a query function here. Is this
> dynamic state the plugin might change during execution? If not could we
> not better report plugin requirements during initialisation?

Yes, it is dynamic.
For example, we created a plugin, which detects dynamic module loading
in the guest. Then it can add tracepoints to the entry addresses of the loaded
functions. These addresses are not known in advance.


Pavel Dovgalyuk
Aaron Lindsay Sept. 17, 2018, 7:29 p.m. UTC | #7
On Sep 10 14:41, Pavel Dovgalyuk wrote:
> From: Alex Bennée [mailto:alex.bennee@linaro.org]
> > Currently this is only available to system mode emulation. Can it be
> > extended to include linux-user as well?
> 
> Why not? I'm not familiar with linux-user, so I'll need some time to try.

Yes. In fact, your existing setup made it quite simple:


Subject: [PATCH] plugin: Add linux-user support

Signed-off-by: Aaron Lindsay <aclindsa@gmail.com>
---
 linux-user/main.c | 13 +++++++++++++
 1 file changed, 13 insertions(+)

diff --git a/linux-user/main.c b/linux-user/main.c
index 923cbb753a..4c70f9f8c5 100644
--- a/linux-user/main.c
+++ b/linux-user/main.c
@@ -37,6 +37,7 @@
 #include "trace/control.h"
 #include "target_elf.h"
 #include "cpu_loop-common.h"
+#include "qemu/plugins.h"
 
 char *exec_path;
 
@@ -385,6 +386,11 @@ static void handle_arg_trace(const char *arg)
     trace_file = trace_opt_parse(arg);
 }
 
+static void handle_arg_plugin(const char *arg)
+{
+    qemu_plugin_parse_cmd_args(arg);
+}
+
 struct qemu_argument {
     const char *argv;
     const char *env;
@@ -436,6 +442,10 @@ static const struct qemu_argument arg_table[] = {
      "",           "Seed for pseudo-random number generator"},
     {"trace",      "QEMU_TRACE",       true,  handle_arg_trace,
      "",           "[[enable=]<pattern>][,events=<file>][,file=<file>]"},
+#ifdef CONFIG_PLUGINS
+    {"plugin",     "QEMU_PLUGIN",      true,  handle_arg_plugin,
+     "",           "file=<plugin_so>[,args=<args>]"},
+#endif
     {"version",    "QEMU_VERSION",     false, handle_arg_version,
      "",           "display version information and exit"},
     {NULL, NULL, false, NULL, NULL, NULL}
@@ -816,6 +826,9 @@ int main(int argc, char **argv, char **envp)
         }
         gdb_handlesig(cpu, 0);
     }
+
+    qemu_plugins_init();
+
     cpu_loop(env);
     /* never exits */
     return 0;
Pavel Dovgalyuk Sept. 18, 2018, 5:34 a.m. UTC | #8
> From: Aaron Lindsay [mailto:aclindsa@gmail.com]
> On Sep 10 14:41, Pavel Dovgalyuk wrote:
> > From: Alex Bennée [mailto:alex.bennee@linaro.org]
> > > Currently this is only available to system mode emulation. Can it be
> > > extended to include linux-user as well?
> >
> > Why not? I'm not familiar with linux-user, so I'll need some time to try.
> 
> Yes. In fact, your existing setup made it quite simple:

Thank you.

Pavel Dovgalyuk
> 
> 
> Subject: [PATCH] plugin: Add linux-user support
> 
> Signed-off-by: Aaron Lindsay <aclindsa@gmail.com>
> ---
>  linux-user/main.c | 13 +++++++++++++
>  1 file changed, 13 insertions(+)
> 
> diff --git a/linux-user/main.c b/linux-user/main.c
> index 923cbb753a..4c70f9f8c5 100644
> --- a/linux-user/main.c
> +++ b/linux-user/main.c
> @@ -37,6 +37,7 @@
>  #include "trace/control.h"
>  #include "target_elf.h"
>  #include "cpu_loop-common.h"
> +#include "qemu/plugins.h"
> 
>  char *exec_path;
> 
> @@ -385,6 +386,11 @@ static void handle_arg_trace(const char *arg)
>      trace_file = trace_opt_parse(arg);
>  }
> 
> +static void handle_arg_plugin(const char *arg)
> +{
> +    qemu_plugin_parse_cmd_args(arg);
> +}
> +
>  struct qemu_argument {
>      const char *argv;
>      const char *env;
> @@ -436,6 +442,10 @@ static const struct qemu_argument arg_table[] = {
>       "",           "Seed for pseudo-random number generator"},
>      {"trace",      "QEMU_TRACE",       true,  handle_arg_trace,
>       "",           "[[enable=]<pattern>][,events=<file>][,file=<file>]"},
> +#ifdef CONFIG_PLUGINS
> +    {"plugin",     "QEMU_PLUGIN",      true,  handle_arg_plugin,
> +     "",           "file=<plugin_so>[,args=<args>]"},
> +#endif
>      {"version",    "QEMU_VERSION",     false, handle_arg_version,
>       "",           "display version information and exit"},
>      {NULL, NULL, false, NULL, NULL, NULL}
> @@ -816,6 +826,9 @@ int main(int argc, char **argv, char **envp)
>          }
>          gdb_handlesig(cpu, 0);
>      }
> +
> +    qemu_plugins_init();
> +
>      cpu_loop(env);
>      /* never exits */
>      return 0;
> --
> 2.19.0
diff mbox series

Patch

diff --git a/Makefile.target b/Makefile.target
index dad2cf8..4cffd96 100644
--- a/Makefile.target
+++ b/Makefile.target
@@ -93,6 +93,7 @@  all: $(PROGS) stap
 # cpu emulator library
 obj-y += exec.o
 obj-y += accel/
+obj-$(CONFIG_PLUGINS) += plugins/plugins.o
 obj-$(CONFIG_TCG) += tcg/tcg.o tcg/tcg-op.o tcg/tcg-op-vec.o tcg/tcg-op-gvec.o
 obj-$(CONFIG_TCG) += tcg/tcg-common.o tcg/optimize.o
 obj-$(CONFIG_TCG_INTERPRETER) += tcg/tci.o
diff --git a/configure b/configure
index a71bf9b..34e6f00 100755
--- a/configure
+++ b/configure
@@ -373,6 +373,7 @@  EXESUF=""
 DSOSUF=".so"
 LDFLAGS_SHARED="-shared"
 modules="no"
+plugins="no"
 prefix="/usr/local"
 mandir="\${prefix}/share/man"
 datadir="\${prefix}/share"
@@ -922,6 +923,12 @@  for opt do
   --disable-modules)
       modules="no"
   ;;
+  --enable-plugins)
+      plugins="yes"
+  ;;
+  --disable-plugins)
+      plugins="no"
+  ;;
   --cpu=*)
   ;;
   --target-list=*) target_list="$optarg"
@@ -1567,6 +1574,7 @@  disabled with --disable-FEATURE, default is enabled if available:
   guest-agent-msi build guest agent Windows MSI installation package
   pie             Position Independent Executables
   modules         modules support
+  plugins         plugins support
   debug-tcg       TCG debugging (default is disabled)
   debug-info      debugging information
   sparse          sparse checker
@@ -3392,7 +3400,7 @@  else
     glib_req_ver=2.22
 fi
 glib_modules=gthread-2.0
-if test "$modules" = yes; then
+if test "$modules" = yes || test "$plugins" = yes; then
     glib_modules="$glib_modules gmodule-export-2.0"
 fi
 
@@ -5777,6 +5785,7 @@  if test "$slirp" = "yes" ; then
     echo "smbd              $smbd"
 fi
 echo "module support    $modules"
+echo "plugin support    $plugins"
 echo "host CPU          $cpu"
 echo "host big endian   $bigendian"
 echo "target list       $target_list"
@@ -6111,6 +6120,9 @@  if test "$modules" = "yes"; then
   echo "CONFIG_STAMP=_$( (echo $qemu_version; echo $pkgversion; cat $0) | $shacmd - | cut -f1 -d\ )" >> $config_host_mak
   echo "CONFIG_MODULES=y" >> $config_host_mak
 fi
+if test "$plugins" = "yes"; then
+  echo "CONFIG_PLUGINS=y" >> $config_host_mak
+fi
 if test "$have_x11" = "yes" -a "$need_x11" = "yes"; then
   echo "CONFIG_X11=y" >> $config_host_mak
   echo "X11_CFLAGS=$x11_cflags" >> $config_host_mak
diff --git a/include/qemu/plugins.h b/include/qemu/plugins.h
new file mode 100644
index 0000000..4464822
--- /dev/null
+++ b/include/qemu/plugins.h
@@ -0,0 +1,8 @@ 
+#ifndef PLUGINS_H
+#define PLUGINS_H
+
+void qemu_plugin_parse_cmd_args(const char *optarg);
+void qemu_plugin_load(const char *filename, const char *args);
+void qemu_plugins_init(void);
+
+#endif /* PLUGINS_H */
diff --git a/plugins/include/plugins.h b/plugins/include/plugins.h
new file mode 100644
index 0000000..100a786
--- /dev/null
+++ b/plugins/include/plugins.h
@@ -0,0 +1,12 @@ 
+#ifndef PLUGINS_INTERFACE_H
+#define PLUGINS_INTERFACE_H
+
+#include <stdbool.h>
+
+/* Plugin interface */
+
+bool plugin_init(const char *args);
+bool plugin_needs_before_insn(uint64_t pc, void *cpu);
+void plugin_before_insn(uint64_t pc, void *cpu);
+
+#endif /* PLUGINS_INTERFACE_H */
diff --git a/plugins/plugins.c b/plugins/plugins.c
new file mode 100644
index 0000000..eabc931
--- /dev/null
+++ b/plugins/plugins.c
@@ -0,0 +1,91 @@ 
+#include "qemu/osdep.h"
+#include "qemu-common.h"
+#include "qemu/error-report.h"
+#include "qemu/plugins.h"
+#include "qemu/queue.h"
+#include <gmodule.h>
+
+typedef bool (*PluginInitFunc)(const char *);
+typedef bool (*PluginNeedsBeforeInsnFunc)(uint64_t, void *);
+typedef void (*PluginBeforeInsnFunc)(uint64_t, void *);
+
+typedef struct QemuPluginInfo {
+    const char *filename;
+    const char *args;
+    GModule *g_module;
+
+    PluginInitFunc init;
+    PluginNeedsBeforeInsnFunc needs_before_insn;
+    PluginBeforeInsnFunc before_insn;
+
+    QLIST_ENTRY(QemuPluginInfo) next;
+} QemuPluginInfo;
+
+static QLIST_HEAD(, QemuPluginInfo) qemu_plugins
+                                = QLIST_HEAD_INITIALIZER(qemu_plugins);
+
+static QemuOptsList qemu_plugin_opts = {
+    .name = "plugin",
+    .head = QTAILQ_HEAD_INITIALIZER(qemu_plugin_opts.head),
+    .desc = {
+        {
+            .name = "file",
+            .type = QEMU_OPT_STRING,
+        },{
+            .name = "args",
+            .type = QEMU_OPT_STRING,
+        },
+        { /* end of list */ }
+    },
+};
+
+void qemu_plugin_parse_cmd_args(const char *optarg)
+{
+    QemuOpts *opts = qemu_opts_parse_noisily(&qemu_plugin_opts, optarg, false);
+    qemu_plugin_load(qemu_opt_get(opts, "file"),
+        qemu_opt_get(opts, "args"));
+}
+
+void qemu_plugin_load(const char *filename, const char *args)
+{
+    GModule *g_module;
+    QemuPluginInfo *info = NULL;
+    if (!filename) {
+        error_report("plugin name was not specified");
+        return;
+    }
+    g_module = g_module_open(filename,
+        G_MODULE_BIND_LAZY | G_MODULE_BIND_LOCAL);
+    if (!g_module) {
+        error_report("can't load plugin '%s'", filename);
+        return;
+    }
+    info = g_new0(QemuPluginInfo, 1);
+    info->filename = g_strdup(filename);
+    info->g_module = g_module;
+    if (args) {
+        info->args = g_strdup(args);
+    }
+
+    g_module_symbol(g_module, "plugin_init", (gpointer*)&info->init);
+
+    /* Get the instrumentation callbacks */
+    g_module_symbol(g_module, "plugin_needs_before_insn",
+        (gpointer*)&info->needs_before_insn);
+    g_module_symbol(g_module, "plugin_before_insn",
+        (gpointer*)&info->before_insn);
+
+    QLIST_INSERT_HEAD(&qemu_plugins, info, next);
+
+    return;
+}
+
+void qemu_plugins_init(void)
+{
+    QemuPluginInfo *info;
+    QLIST_FOREACH(info, &qemu_plugins, next) {
+        if (info->init) {
+            info->init(info->args);
+        }
+    }
+}
diff --git a/qemu-options.hx b/qemu-options.hx
index c0d3951..d171544 100644
--- a/qemu-options.hx
+++ b/qemu-options.hx
@@ -3950,6 +3950,16 @@  Dump json-encoded vmstate information for current machine type to file
 in @var{file}
 ETEXI
 
+#ifdef CONFIG_PLUGINS
+DEF("plugin", HAS_ARG, QEMU_OPTION_plugin, \
+           "-plugin file=<file>[,args=<args>] load <dso> plugin with <args>\n", QEMU_ARCH_ALL)
+STEXI
+@item -plugin file=@var{file}[,args=@var{args}]
+@findex -plugin
+Load @var{file} plugin passing @var{args} arguments.
+ETEXI
+#endif
+
 STEXI
 @end table
 ETEXI
diff --git a/vl.c b/vl.c
index 0603171..05420bf 100644
--- a/vl.c
+++ b/vl.c
@@ -129,6 +129,7 @@  int main(int argc, char **argv)
 #include "qapi/qapi-commands-run-state.h"
 #include "qapi/qmp/qerror.h"
 #include "sysemu/iothread.h"
+#include "qemu/plugins.h"
 
 #define MAX_VIRTIO_CONSOLES 1
 
@@ -3925,6 +3926,11 @@  int main(int argc, char **argv, char **envp)
                     exit(1);
                 }
                 break;
+#ifdef CONFIG_PLUGINS
+            case QEMU_OPTION_plugin:
+                qemu_plugin_parse_cmd_args(optarg);
+                break;
+#endif
             case QEMU_OPTION_nodefconfig:
             case QEMU_OPTION_nouserconfig:
                 /* Nothing to be parsed here. Especially, do not error out below. */
@@ -4470,6 +4476,8 @@  int main(int argc, char **argv, char **envp)
     }
     parse_numa_opts(current_machine);
 
+    qemu_plugins_init();
+
     /* do monitor/qmp handling at preconfig state if requested */
     main_loop();