Implemented the `qemu_plugin_vcpu_int_cb` callback to allow plugins to observe hardware and architecture-specific interrupts. Modified `cpu-exec.c` to invoke the callback on hard interrupts, and added a test plugin (`countint`) to demonstrate its functionality. A similar approach was previously proposed (https://lists.gnu.org/archive/html/qemu-devel/2023-10/msg07359.html) but was not merged.
Signed-off-by: marko1616 <marko1...@outlook.com> --- accel/tcg/cpu-exec.c | 9 ++++ contrib/plugins/countint.c | 90 +++++++++++++++++++++++++++++++++++++ contrib/plugins/meson.build | 2 +- include/qemu/plugin-event.h | 1 + include/qemu/plugin.h | 5 +++ include/qemu/qemu-plugin.h | 17 +++++++ plugins/core.c | 23 ++++++++++ 7 files changed, 146 insertions(+), 1 deletion(-) create mode 100644 contrib/plugins/countint.c diff --git a/accel/tcg/cpu-exec.c b/accel/tcg/cpu-exec.c index cc5f362305..829f4c26f0 100644 --- a/accel/tcg/cpu-exec.c +++ b/accel/tcg/cpu-exec.c @@ -34,6 +34,9 @@ #include "tcg/tcg.h" #include "qemu/atomic.h" #include "qemu/rcu.h" +#ifdef CONFIG_PLUGIN +#include "qemu/plugin.h" +#endif #include "exec/log.h" #include "qemu/main-loop.h" #include "exec/icount.h" @@ -818,7 +821,13 @@ static inline bool cpu_handle_interrupt(CPUState *cpu, * True when it is, and we should restart on a new TB, * and via longjmp via cpu_loop_exit. */ + const int prev_interrupt_request = interrupt_request; if (tcg_ops->cpu_exec_interrupt(cpu, interrupt_request)) { +#ifdef CONFIG_PLUGIN + if (interrupt_request & CPU_INTERRUPT_HARD) { + qemu_plugin_vcpu_int_cb(cpu, prev_interrupt_request); + } +#endif /* CONFIG_PLUGIN */ if (!tcg_ops->need_replay_interrupt || tcg_ops->need_replay_interrupt(interrupt_request)) { replay_interrupt(); diff --git a/contrib/plugins/countint.c b/contrib/plugins/countint.c new file mode 100644 index 0000000000..601b7f30d5 --- /dev/null +++ b/contrib/plugins/countint.c @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2025 marko1616 + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include <stdio.h> +#include <glib.h> + +#include <qemu-plugin.h> + +QEMU_PLUGIN_EXPORT int qemu_plugin_version = QEMU_PLUGIN_VERSION; + +typedef struct { + unsigned int vcpu_index; + struct qemu_plugin_scoreboard *int_count; +} IntStat; + +static GHashTable *vcpu_int_table; +static GMutex lock; + +static guint intstat_hash(gconstpointer key) +{ + const IntStat *s = key; + return s->vcpu_index; +} + +static gboolean intstat_equal(gconstpointer a, gconstpointer b) +{ + return ((IntStat *)a)->vcpu_index == ((IntStat *)b)->vcpu_index; +} + +static void intstat_free(gpointer key, gpointer value, gpointer user_data) +{ + IntStat *s = value; + qemu_plugin_scoreboard_free(s->int_count); + g_free(s); +} + +static void vcpu_hardint(qemu_plugin_id_t id, + unsigned int vcpu_index, + uint32_t int_req) +{ + IntStat *stat = NULL; + + g_mutex_lock(&lock); + { + IntStat key = { .vcpu_index = vcpu_index }; + stat = g_hash_table_lookup(vcpu_int_table, &key); + + if (!stat) { + stat = g_new0(IntStat, 1); + stat->vcpu_index = vcpu_index; + stat->int_count = qemu_plugin_scoreboard_new(sizeof(uint64_t)); + g_hash_table_insert(vcpu_int_table, stat, stat); + } + } + g_mutex_unlock(&lock); + + qemu_plugin_u64_add( + qemu_plugin_scoreboard_u64(stat->int_count), + vcpu_index, + 1 + ); +} + +static void plugin_exit(qemu_plugin_id_t id, void *p) +{ + GList *stats = g_hash_table_get_values(vcpu_int_table); + for (GList *it = stats; it != NULL; it = it->next) { + IntStat *stat = (IntStat *)it->data; + uint64_t count = qemu_plugin_u64_sum( + qemu_plugin_scoreboard_u64(stat->int_count) + ); + printf("vCPU %u: %" PRIu64 " interrupts\n", stat->vcpu_index, count); + } + g_list_free(stats); + g_hash_table_foreach(vcpu_int_table, intstat_free, NULL); + g_hash_table_destroy(vcpu_int_table); +} + +QEMU_PLUGIN_EXPORT +int qemu_plugin_install(qemu_plugin_id_t id, const qemu_info_t *info, + int argc, char **argv) +{ + vcpu_int_table = g_hash_table_new(intstat_hash, intstat_equal); + qemu_plugin_register_vcpu_int_cb(id, vcpu_hardint); + qemu_plugin_register_atexit_cb(id, plugin_exit, NULL); + return 0; +} diff --git a/contrib/plugins/meson.build b/contrib/plugins/meson.build index fa8a426c8b..17e3b0ec27 100644 --- a/contrib/plugins/meson.build +++ b/contrib/plugins/meson.build @@ -1,4 +1,4 @@ -contrib_plugins = ['bbv', 'cache', 'cflow', 'drcov', 'execlog', 'hotblocks', +contrib_plugins = ['bbv', 'cache', 'cflow', 'drcov', 'execlog', 'countint', 'hotblocks', 'hotpages', 'howvec', 'hwprofile', 'ips', 'stoptrigger'] if host_os != 'windows' # lockstep uses socket.h diff --git a/include/qemu/plugin-event.h b/include/qemu/plugin-event.h index 7056d8427b..cd9f9eb25b 100644 --- a/include/qemu/plugin-event.h +++ b/include/qemu/plugin-event.h @@ -16,6 +16,7 @@ enum qemu_plugin_event { QEMU_PLUGIN_EV_VCPU_TB_TRANS, QEMU_PLUGIN_EV_VCPU_IDLE, QEMU_PLUGIN_EV_VCPU_RESUME, + QEMU_PLUGIN_EV_VCPU_INT, QEMU_PLUGIN_EV_VCPU_SYSCALL, QEMU_PLUGIN_EV_VCPU_SYSCALL_RET, QEMU_PLUGIN_EV_FLUSH, diff --git a/include/qemu/plugin.h b/include/qemu/plugin.h index 9726a9ebf3..f1d5d91141 100644 --- a/include/qemu/plugin.h +++ b/include/qemu/plugin.h @@ -61,6 +61,7 @@ union qemu_plugin_cb_sig { qemu_plugin_vcpu_udata_cb_t vcpu_udata; qemu_plugin_vcpu_tb_trans_cb_t vcpu_tb_trans; qemu_plugin_vcpu_mem_cb_t vcpu_mem; + qemu_plugin_vcpu_int_cb_t vcpu_hardint; qemu_plugin_vcpu_syscall_cb_t vcpu_syscall; qemu_plugin_vcpu_syscall_ret_cb_t vcpu_syscall_ret; void *generic; @@ -160,6 +161,7 @@ void qemu_plugin_vcpu_exit_hook(CPUState *cpu); void qemu_plugin_tb_trans_cb(CPUState *cpu, struct qemu_plugin_tb *tb); void qemu_plugin_vcpu_idle_cb(CPUState *cpu); void qemu_plugin_vcpu_resume_cb(CPUState *cpu); +void qemu_plugin_vcpu_int_cb(CPUState *cpu, int interrupt_request); void qemu_plugin_vcpu_syscall(CPUState *cpu, int64_t num, uint64_t a1, uint64_t a2, uint64_t a3, uint64_t a4, uint64_t a5, @@ -242,6 +244,9 @@ static inline void qemu_plugin_vcpu_idle_cb(CPUState *cpu) static inline void qemu_plugin_vcpu_resume_cb(CPUState *cpu) { } +static inline void qemu_plugin_vcpu_int_cb(CPUState *cpu, int interrupt_request) +{ } + static inline void qemu_plugin_vcpu_syscall(CPUState *cpu, int64_t num, uint64_t a1, uint64_t a2, uint64_t a3, uint64_t a4, uint64_t a5, uint64_t a6, diff --git a/include/qemu/qemu-plugin.h b/include/qemu/qemu-plugin.h index 3a850aa216..65096c4c64 100644 --- a/include/qemu/qemu-plugin.h +++ b/include/qemu/qemu-plugin.h @@ -728,6 +728,23 @@ const void *qemu_plugin_request_time_control(void); QEMU_PLUGIN_API void qemu_plugin_update_ns(const void *handle, int64_t time); +typedef void +(*qemu_plugin_vcpu_int_cb_t)(qemu_plugin_id_t id, + unsigned int vcpu_index, + uint32_t interrupt_request); + +/** + * qemu_plugin_register_vcpu_int_cb() - + * register a vCPU hardware interruption callback + * @id: plugin ID + * @cb: callback function + * + * The @cb function is called every time a vCPU gets hardware interruption. + */ +QEMU_PLUGIN_API +void qemu_plugin_register_vcpu_int_cb(qemu_plugin_id_t id, + qemu_plugin_vcpu_int_cb_t cb); + typedef void (*qemu_plugin_vcpu_syscall_cb_t)(qemu_plugin_id_t id, unsigned int vcpu_index, int64_t num, uint64_t a1, uint64_t a2, diff --git a/plugins/core.c b/plugins/core.c index eb9281fe54..b6fbfe20e9 100644 --- a/plugins/core.c +++ b/plugins/core.c @@ -539,6 +539,23 @@ void qemu_plugin_vcpu_resume_cb(CPUState *cpu) } } +/* + * Disable CFI checks. + * The callback function has been loaded from an external library so we do not + * have type information + */ +QEMU_DISABLE_CFI +void qemu_plugin_vcpu_int_cb(CPUState *cpu, int interrupt_request) +{ + struct qemu_plugin_cb *cb, *next; + enum qemu_plugin_event ev = QEMU_PLUGIN_EV_VCPU_INT; + + QLIST_FOREACH_SAFE_RCU(cb, &plugin.cb_lists[ev], entry, next) { + qemu_plugin_vcpu_int_cb_t func = cb->f.vcpu_hardint; + func(cb->ctx->id, cpu->cpu_index, interrupt_request); + } +} + void qemu_plugin_register_vcpu_idle_cb(qemu_plugin_id_t id, qemu_plugin_vcpu_simple_cb_t cb) { @@ -551,6 +568,12 @@ void qemu_plugin_register_vcpu_resume_cb(qemu_plugin_id_t id, plugin_register_cb(id, QEMU_PLUGIN_EV_VCPU_RESUME, cb); } +void qemu_plugin_register_vcpu_int_cb(qemu_plugin_id_t id, + qemu_plugin_vcpu_int_cb_t cb) +{ + plugin_register_cb(id, QEMU_PLUGIN_EV_VCPU_INT, cb); +} + void qemu_plugin_register_flush_cb(qemu_plugin_id_t id, qemu_plugin_simple_cb_t cb) { -- 2.39.5