qemu-devel.nongnu.org archive mirror
 help / color / mirror / Atom feed
From: Pierrick Bouvier <pierrick.bouvier@linaro.org>
To: Simon Hamelin <simon.hamelin@grenoble-inp.org>, qemu-devel@nongnu.org
Cc: "Alexandre Iooss" <erdnaxe@crans.org>,
	"Alex Bennée" <alex.bennee@linaro.org>,
	"Mahmoud Mandour" <ma.mandourr@gmail.com>,
	"Richard Henderson" <richard.henderson@linaro.org>,
	"Paolo Bonzini" <pbonzini@redhat.com>
Subject: Re: [PATCH v4] plugins/stoptrigger: TCG plugin to stop execution under conditions
Date: Mon, 15 Jul 2024 16:12:17 -0700	[thread overview]
Message-ID: <3c9c625b-6442-4e2d-9f27-793d91e8d89c@linaro.org> (raw)
In-Reply-To: <20240715081521.19122-2-simon.hamelin@grenoble-inp.org>

On 7/15/24 01:15, Simon Hamelin wrote:
> This new plugin allows to stop emulation using conditions on the
> emulation state. By setting this plugin arguments, it is possible
> to set an instruction count limit and/or trigger address(es) to stop at.
> The code returned at emulation exit can be customized.
> 
> This plugin demonstrates how someone could stop QEMU execution.
> It could be used for research purposes to launch some code and
> deterministically stop it and understand where its execution flow went.
> 
> Co-authored-by: Alexandre Iooss <erdnaxe@crans.org>
> Signed-off-by: Simon Hamelin <simon.hamelin@grenoble-inp.org>
> Signed-off-by: Alexandre Iooss <erdnaxe@crans.org>
> ---
> v2:
>    - use a scoreboard for counting instructions
>    - no longer hook each instruction to exit at given address
>    - add `exit_emulation` function for future use case such as stopping the VM or triggering a gdbstub exception
> 
> v3:
>    - add missing glib include
>    - refactor code to print exit address when icount is reached
> 
> v4:
>    - remove unnecessary lock
> 
>   contrib/plugins/Makefile      |   1 +
>   contrib/plugins/stoptrigger.c | 151 ++++++++++++++++++++++++++++++++++
>   docs/devel/tcg-plugins.rst    |  22 +++++
>   3 files changed, 174 insertions(+)
>   create mode 100644 contrib/plugins/stoptrigger.c
> 
> diff --git a/contrib/plugins/Makefile b/contrib/plugins/Makefile
> index 449ead1130..98a89d5c40 100644
> --- a/contrib/plugins/Makefile
> +++ b/contrib/plugins/Makefile
> @@ -28,6 +28,7 @@ NAMES += hwprofile
>   NAMES += cache
>   NAMES += drcov
>   NAMES += ips
> +NAMES += stoptrigger
>   
>   ifeq ($(CONFIG_WIN32),y)
>   SO_SUFFIX := .dll
> diff --git a/contrib/plugins/stoptrigger.c b/contrib/plugins/stoptrigger.c
> new file mode 100644
> index 0000000000..03ee22f4c6
> --- /dev/null
> +++ b/contrib/plugins/stoptrigger.c
> @@ -0,0 +1,151 @@
> +/*
> + * Copyright (C) 2024, Simon Hamelin <simon.hamelin@grenoble-inp.org>
> + *
> + * Stop execution once a given address is reached or if the
> + * count of executed instructions reached a specified limit
> + *
> + * License: GNU GPL, version 2 or later.
> + *   See the COPYING file in the top-level directory.
> + */
> +
> +#include <assert.h>
> +#include <glib.h>
> +#include <inttypes.h>
> +#include <stdio.h>
> +#include <stdlib.h>
> +
> +#include <qemu-plugin.h>
> +
> +QEMU_PLUGIN_EXPORT int qemu_plugin_version = QEMU_PLUGIN_VERSION;
> +
> +/* Scoreboard to track executed instructions count */
> +typedef struct {
> +    uint64_t insn_count;
> +} InstructionsCount;
> +static struct qemu_plugin_scoreboard *insn_count_sb;
> +static qemu_plugin_u64 insn_count;
> +
> +static uint64_t icount;
> +static int icount_exit_code;
> +
> +static bool exit_on_icount;
> +static bool exit_on_address;
> +
> +/* Map trigger addresses to exit code */
> +static GHashTable *addrs_ht;
> +
> +static void exit_emulation(int return_code, char *message)
> +{
> +    qemu_plugin_outs(message);
> +    g_free(message);
> +    exit(return_code);
> +}
> +
> +static void exit_icount_reached(unsigned int cpu_index, void *udata)
> +{
> +    uint64_t insn_vaddr = GPOINTER_TO_UINT(udata);
> +    char *msg = g_strdup_printf("icount reached at 0x%" PRIx64 ", exiting\n",
> +                                insn_vaddr);
> +
> +    exit_emulation(icount_exit_code, msg);
> +}
> +
> +static void exit_address_reached(unsigned int cpu_index, void *udata)
> +{
> +    uint64_t insn_vaddr = GPOINTER_TO_UINT(udata);
> +    char *msg = g_strdup_printf("0x%" PRIx64 " reached, exiting\n", insn_vaddr);
> +    int exit_code;
> +
> +    exit_code = GPOINTER_TO_INT(
> +        g_hash_table_lookup(addrs_ht, GUINT_TO_POINTER(insn_vaddr)));
> +
> +    exit_emulation(exit_code, msg);
> +}
> +
> +static void vcpu_tb_trans(qemu_plugin_id_t id, struct qemu_plugin_tb *tb)
> +{
> +    size_t tb_n = qemu_plugin_tb_n_insns(tb);
> +    for (size_t i = 0; i < tb_n; i++) {
> +        struct qemu_plugin_insn *insn = qemu_plugin_tb_get_insn(tb, i);
> +        gpointer insn_vaddr = GUINT_TO_POINTER(qemu_plugin_insn_vaddr(insn));
> +
> +        if (exit_on_icount) {
> +            /* Increment and check scoreboard for each instruction */
> +            qemu_plugin_register_vcpu_insn_exec_inline_per_vcpu(
> +                insn, QEMU_PLUGIN_INLINE_ADD_U64, insn_count, 1);
> +            qemu_plugin_register_vcpu_insn_exec_cond_cb(
> +                insn, exit_icount_reached, QEMU_PLUGIN_CB_NO_REGS,
> +                QEMU_PLUGIN_COND_EQ, insn_count, icount + 1, insn_vaddr);
> +        }
> +
> +        if (exit_on_address) {
> +            if (g_hash_table_contains(addrs_ht, insn_vaddr)) {
> +                /* Exit triggered by address */
> +                qemu_plugin_register_vcpu_insn_exec_cb(
> +                    insn, exit_address_reached, QEMU_PLUGIN_CB_NO_REGS,
> +                    insn_vaddr);
> +            }
> +        }
> +    }
> +}
> +
> +static void plugin_exit(qemu_plugin_id_t id, void *p)
> +{
> +    g_hash_table_destroy(addrs_ht);
> +    qemu_plugin_scoreboard_free(insn_count_sb);
> +}
> +
> +QEMU_PLUGIN_EXPORT int qemu_plugin_install(qemu_plugin_id_t id,
> +                                           const qemu_info_t *info, int argc,
> +                                           char **argv)
> +{
> +    addrs_ht = g_hash_table_new(NULL, g_direct_equal);
> +
> +    insn_count_sb = qemu_plugin_scoreboard_new(sizeof(InstructionsCount));
> +    insn_count = qemu_plugin_scoreboard_u64_in_struct(
> +        insn_count_sb, InstructionsCount, insn_count);
> +
> +    for (int i = 0; i < argc; i++) {
> +        char *opt = argv[i];
> +        g_auto(GStrv) tokens = g_strsplit(opt, "=", 2);
> +        if (g_strcmp0(tokens[0], "icount") == 0) {
> +            g_auto(GStrv) icount_tokens = g_strsplit(tokens[1], ":", 2);
> +            icount = g_ascii_strtoull(icount_tokens[0], NULL, 0);
> +            if (icount < 1 || g_strrstr(icount_tokens[0], "-") != NULL) {
> +                fprintf(stderr,
> +                        "icount parsing failed: '%s' must be a positive "
> +                        "integer\n",
> +                        icount_tokens[0]);
> +                return -1;
> +            }
> +            if (icount_tokens[1]) {
> +                icount_exit_code = g_ascii_strtoull(icount_tokens[1], NULL, 0);
> +            }
> +            exit_on_icount = true;
> +        } else if (g_strcmp0(tokens[0], "addr") == 0) {
> +            g_auto(GStrv) addr_tokens = g_strsplit(tokens[1], ":", 2);
> +            uint64_t exit_addr = g_ascii_strtoull(addr_tokens[0], NULL, 0);
> +            int exit_code = 0;
> +            if (addr_tokens[1]) {
> +                exit_code = g_ascii_strtoull(addr_tokens[1], NULL, 0);
> +            }
> +            g_hash_table_insert(addrs_ht, GUINT_TO_POINTER(exit_addr),
> +                                GINT_TO_POINTER(exit_code));
> +            exit_on_address = true;
> +        } else {
> +            fprintf(stderr, "option parsing failed: %s\n", opt);
> +            return -1;
> +        }
> +    }
> +
> +    if (!exit_on_icount && !exit_on_address) {
> +        fprintf(stderr, "'icount' or 'addr' argument missing\n");
> +        return -1;
> +    }
> +
> +    /* Register translation block and exit callbacks */
> +    qemu_plugin_register_vcpu_tb_trans_cb(id, vcpu_tb_trans);
> +    qemu_plugin_register_atexit_cb(id, plugin_exit, NULL);
> +
> +    return 0;
> +}
> diff --git a/docs/devel/tcg-plugins.rst b/docs/devel/tcg-plugins.rst
> index f7d7b9e3a4..954623f9bf 100644
> --- a/docs/devel/tcg-plugins.rst
> +++ b/docs/devel/tcg-plugins.rst
> @@ -642,6 +642,28 @@ The plugin has a number of arguments, all of them are optional:
>     configuration arguments implies ``l2=on``.
>     (default: N = 2097152 (2MB), B = 64, A = 16)
>   
> +- contrib/plugins/stoptrigger.c
> +
> +The stoptrigger plugin allows to setup triggers to stop emulation.
> +It can be used for research purposes to launch some code and precisely stop it
> +and understand where its execution flow went.
> +
> +Two types of triggers can be configured: a count of instructions to stop at,
> +or an address to stop at. Multiple triggers can be set at once.
> +
> +By default, QEMU will exit with return code 0. A custom return code can be
> +configured for each trigger using ``:CODE`` syntax.
> +
> +For example, to stop at the 20-th instruction with return code 41, at address
> +0xd4 with return code 0 or at address 0xd8 with return code 42::
> +
> +  $ qemu-system-aarch64 $(QEMU_ARGS) \
> +    -plugin ./contrib/plugins/libstoptrigger.so,icount=20:41,addr=0xd4,addr=0xd8:42 -d plugin
> +
> +The plugin will log the reason of exit, for example::
> +
> +  0xd4 reached, exiting
> +
>   Plugin API
>   ==========
>   

Reviewed-by: Pierrick Bouvier <pierrick.bouvier@linaro.org>


  reply	other threads:[~2024-07-15 23:13 UTC|newest]

Thread overview: 3+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2024-07-15  8:15 [PATCH v4] plugins/stoptrigger: TCG plugin to stop execution under conditions Simon Hamelin
2024-07-15 23:12 ` Pierrick Bouvier [this message]
2024-07-16 12:51 ` Alex Bennée

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=3c9c625b-6442-4e2d-9f27-793d91e8d89c@linaro.org \
    --to=pierrick.bouvier@linaro.org \
    --cc=alex.bennee@linaro.org \
    --cc=erdnaxe@crans.org \
    --cc=ma.mandourr@gmail.com \
    --cc=pbonzini@redhat.com \
    --cc=qemu-devel@nongnu.org \
    --cc=richard.henderson@linaro.org \
    --cc=simon.hamelin@grenoble-inp.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).