* [PULL 00/12] Plugins update 2025-03-05
@ 2026-03-05 20:51 Pierrick Bouvier
2026-03-05 20:51 ` [PULL 01/12] plugins/core: clamp syscall arguments if target is 32-bit Pierrick Bouvier
` (12 more replies)
0 siblings, 13 replies; 14+ messages in thread
From: Pierrick Bouvier @ 2026-03-05 20:51 UTC (permalink / raw)
To: qemu-devel, peter.maydell, richard.henderson, pbonzini, stefanha
Cc: pierrick.bouvier
The following changes since commit 314ff2e07ddc6163554077d68aed5d76a50b8e3d:
Merge tag 'pull-request-2026-03-05' of https://gitlab.com/thuth/qemu into staging (2026-03-05 16:58:20 +0000)
are available in the Git repository at:
https://gitlab.com/pbo-linaro/qemu tags/pr-plugins-20260305
for you to fetch changes up to aca77dfd90c8a17a2b3526c5cc871cc410357a5f:
tests/tcg/plugins/patch: Free read_data in patch_hwaddr() (2026-03-05 10:54:09 -0800)
----------------------------------------------------------------
Changes:
- [PATCH v7 0/8] Enable PC diversion via the plugin API (Florian Hofhammer <florian.hofhammer@epfl.ch>)
Link: https://lore.kernel.org/qemu-devel/20260305-setpc-v5-v7-0-4c3adba52403@epfl.ch
- [PATCH trivial] plugins: add missing callbacks to version history (Florian Hofhammer <florian.hofhammer@epfl.ch>)
Link: https://lore.kernel.org/qemu-devel/c4ecefb4-8769-403f-8420-8bce42e43e13@epfl.ch
- [PATCH 0/3] tests/tcg/plugins: Fix sanitizer issues (Peter Maydell <peter.maydell@linaro.org>)
Link: https://lore.kernel.org/qemu-devel/20260305161531.1774895-1-peter.maydell@linaro.org
----------------------------------------------------------------
Florian Hofhammer (8):
plugins: add flag to specify whether PC is rw
linux-user: make syscall emulation interruptible
plugins: add PC diversion API function
tests/tcg: add tests for qemu_plugin_set_pc API
plugins: add read-only property for registers
plugins: prohibit writing to read-only registers
tests/tcg/plugins: test register accesses
plugins: add missing callbacks to version history
Peter Maydell (3):
tests/tcg/plugins/mem: Don't access unaligned memory
tests/tcg/plugins/mem: Correct hash iteration code in plugin_exit()
tests/tcg/plugins/patch: Free read_data in patch_hwaddr()
Pierrick Bouvier (1):
plugins/core: clamp syscall arguments if target is 32-bit
MAINTAINERS | 1 +
include/plugins/qemu-plugin.h | 22 ++++
linux-user/include/special-errno.h | 8 ++
linux-user/aarch64/cpu_loop.c | 2 +-
linux-user/alpha/cpu_loop.c | 2 +-
linux-user/arm/cpu_loop.c | 2 +-
linux-user/hexagon/cpu_loop.c | 2 +-
linux-user/hppa/cpu_loop.c | 1 +
linux-user/i386/cpu_loop.c | 8 +-
linux-user/loongarch64/cpu_loop.c | 5 +-
linux-user/m68k/cpu_loop.c | 2 +-
linux-user/microblaze/cpu_loop.c | 2 +-
linux-user/mips/cpu_loop.c | 9 +-
linux-user/or1k/cpu_loop.c | 2 +-
linux-user/ppc/cpu_loop.c | 10 +-
linux-user/riscv/cpu_loop.c | 2 +-
linux-user/s390x/cpu_loop.c | 2 +-
linux-user/sh4/cpu_loop.c | 2 +-
linux-user/sparc/cpu_loop.c | 4 +-
linux-user/syscall.c | 16 +++
linux-user/xtensa/cpu_loop.c | 1 +
plugins/api.c | 42 ++++++-
plugins/core.c | 50 ++++++--
.../{ => plugin}/test-plugin-mem-access.c | 0
tests/tcg/multiarch/plugin/test-plugin-set-pc.c | 134 +++++++++++++++++++++
tests/tcg/plugins/mem.c | 95 +++++++--------
tests/tcg/plugins/patch.c | 2 +-
tests/tcg/plugins/registers.c | 79 ++++++++++++
tests/tcg/plugins/setpc.c | 105 ++++++++++++++++
scripts/qemu-plugin-symbols.py | 9 +-
tests/tcg/arm/Makefile.target | 6 +
tests/tcg/hexagon/Makefile.target | 8 ++
tests/tcg/multiarch/Makefile.target | 17 ++-
.../multiarch/{ => plugin}/check-plugin-output.sh | 0
tests/tcg/plugins/meson.build | 2 +
35 files changed, 555 insertions(+), 99 deletions(-)
rename tests/tcg/multiarch/{ => plugin}/test-plugin-mem-access.c (100%)
create mode 100644 tests/tcg/multiarch/plugin/test-plugin-set-pc.c
create mode 100644 tests/tcg/plugins/registers.c
create mode 100644 tests/tcg/plugins/setpc.c
rename tests/tcg/multiarch/{ => plugin}/check-plugin-output.sh (100%)
^ permalink raw reply [flat|nested] 14+ messages in thread
* [PULL 01/12] plugins/core: clamp syscall arguments if target is 32-bit
2026-03-05 20:51 [PULL 00/12] Plugins update 2025-03-05 Pierrick Bouvier
@ 2026-03-05 20:51 ` Pierrick Bouvier
2026-03-05 20:51 ` [PULL 02/12] plugins: add flag to specify whether PC is rw Pierrick Bouvier
` (11 subsequent siblings)
12 siblings, 0 replies; 14+ messages in thread
From: Pierrick Bouvier @ 2026-03-05 20:51 UTC (permalink / raw)
To: qemu-devel, peter.maydell, richard.henderson, pbonzini, stefanha
Cc: pierrick.bouvier
Syscall arguments are abi_long in user code, and plugin syscall
interface works with uint64_t only.
According to C integer promotion rules, the value is sign extended
before becoming unsigned, thus setting high bits when only 32-bit lower
ones should have a significant value.
As a result, we need to clamp values we receive from user-code
accordingly.
Reviewed-by: Alex Bennée <alex.bennee@linaro.org>
Link: https://lore.kernel.org/qemu-devel/20260305-setpc-v5-v7-1-4c3adba52403@epfl.ch
Signed-off-by: Pierrick Bouvier <pierrick.bouvier@linaro.org>
---
plugins/core.c | 21 +++++++++++++++++++++
1 file changed, 21 insertions(+)
diff --git a/plugins/core.c b/plugins/core.c
index 42fd9865930..d6173422e98 100644
--- a/plugins/core.c
+++ b/plugins/core.c
@@ -513,6 +513,23 @@ void qemu_plugin_tb_trans_cb(CPUState *cpu, struct qemu_plugin_tb *tb)
}
}
+static void clamp_syscall_arguments(uint64_t *a1, uint64_t *a2, uint64_t *a3,
+ uint64_t *a4, uint64_t *a5, uint64_t *a6,
+ uint64_t *a7, uint64_t *a8)
+{
+ if (target_long_bits() == 32) {
+ const uint64_t mask = UINT32_MAX;
+ *a1 &= mask;
+ *a2 &= mask;
+ *a3 &= mask;
+ *a4 &= mask;
+ *a5 &= mask;
+ *a6 &= mask;
+ *a7 &= mask;
+ *a8 &= mask;
+ }
+}
+
/*
* Disable CFI checks.
* The callback function has been loaded from an external library so we do not
@@ -531,6 +548,8 @@ qemu_plugin_vcpu_syscall(CPUState *cpu, int64_t num, uint64_t a1, uint64_t a2,
return;
}
+ clamp_syscall_arguments(&a1, &a2, &a3, &a4, &a5, &a6, &a7, &a8);
+
QLIST_FOREACH_SAFE_RCU(cb, &plugin.cb_lists[ev], entry, next) {
qemu_plugin_vcpu_syscall_cb_t func = cb->f.vcpu_syscall;
@@ -584,6 +603,8 @@ qemu_plugin_vcpu_syscall_filter(CPUState *cpu, int64_t num, uint64_t a1,
return false;
}
+ clamp_syscall_arguments(&a1, &a2, &a3, &a4, &a5, &a6, &a7, &a8);
+
qemu_plugin_set_cb_flags(cpu, QEMU_PLUGIN_CB_RW_REGS);
QLIST_FOREACH_SAFE_RCU(cb, &plugin.cb_lists[ev], entry, next) {
--
2.47.3
^ permalink raw reply related [flat|nested] 14+ messages in thread
* [PULL 02/12] plugins: add flag to specify whether PC is rw
2026-03-05 20:51 [PULL 00/12] Plugins update 2025-03-05 Pierrick Bouvier
2026-03-05 20:51 ` [PULL 01/12] plugins/core: clamp syscall arguments if target is 32-bit Pierrick Bouvier
@ 2026-03-05 20:51 ` Pierrick Bouvier
2026-03-05 20:51 ` [PULL 03/12] linux-user: make syscall emulation interruptible Pierrick Bouvier
` (10 subsequent siblings)
12 siblings, 0 replies; 14+ messages in thread
From: Pierrick Bouvier @ 2026-03-05 20:51 UTC (permalink / raw)
To: qemu-devel, peter.maydell, richard.henderson, pbonzini, stefanha
Cc: pierrick.bouvier
From: Florian Hofhammer <florian.hofhammer@epfl.ch>
In addition to the flags specifying whether general-purpose registers
are read-write (rw) during a plugin callback, we add an additional flag
explicitly stating whether the PC is writable. This is in preparation of
a patch that allows to explicitly set the PC to divert control flow from
within a plugin callback, which is currently not possible.
Reviewed-by: Alex Bennée <alex.bennee@linaro.org>
Reviewed-by: Pierrick Bouvier <pierrick.bouvier@linaro.org>
Signed-off-by: Florian Hofhammer <florian.hofhammer@epfl.ch>
Link: https://lore.kernel.org/qemu-devel/20260305-setpc-v5-v7-2-4c3adba52403@epfl.ch
Signed-off-by: Pierrick Bouvier <pierrick.bouvier@linaro.org>
---
include/plugins/qemu-plugin.h | 3 +++
plugins/api.c | 4 +++-
plugins/core.c | 29 ++++++++++++++++-------------
3 files changed, 22 insertions(+), 14 deletions(-)
diff --git a/include/plugins/qemu-plugin.h b/include/plugins/qemu-plugin.h
index 17a834dca90..a6ec8e275d8 100644
--- a/include/plugins/qemu-plugin.h
+++ b/include/plugins/qemu-plugin.h
@@ -325,11 +325,14 @@ typedef struct {
* @QEMU_PLUGIN_CB_NO_REGS: callback does not access the CPU's regs
* @QEMU_PLUGIN_CB_R_REGS: callback reads the CPU's regs
* @QEMU_PLUGIN_CB_RW_REGS: callback reads and writes the CPU's regs
+ * @QEMU_PLUGIN_CB_RW_REGS_PC: callback reads and writes the CPU's
+ * regs and updates the PC
*/
enum qemu_plugin_cb_flags {
QEMU_PLUGIN_CB_NO_REGS,
QEMU_PLUGIN_CB_R_REGS,
QEMU_PLUGIN_CB_RW_REGS,
+ QEMU_PLUGIN_CB_RW_REGS_PC,
};
enum qemu_plugin_mem_rw {
diff --git a/plugins/api.c b/plugins/api.c
index 04ca7da7f18..32eb086300d 100644
--- a/plugins/api.c
+++ b/plugins/api.c
@@ -458,7 +458,9 @@ bool qemu_plugin_write_register(struct qemu_plugin_register *reg,
{
g_assert(current_cpu);
- if (buf->len == 0 || qemu_plugin_get_cb_flags() != QEMU_PLUGIN_CB_RW_REGS) {
+ if (buf->len == 0 ||
+ (qemu_plugin_get_cb_flags() != QEMU_PLUGIN_CB_RW_REGS &&
+ qemu_plugin_get_cb_flags() != QEMU_PLUGIN_CB_RW_REGS_PC)) {
return false;
}
diff --git a/plugins/core.c b/plugins/core.c
index d6173422e98..2324bbffa3d 100644
--- a/plugins/core.c
+++ b/plugins/core.c
@@ -119,7 +119,7 @@ static void plugin_vcpu_cb__discon(CPUState *cpu,
struct qemu_plugin_cb *cb, *next;
uint64_t to = cpu->cc->get_pc(cpu);
- qemu_plugin_set_cb_flags(cpu, QEMU_PLUGIN_CB_RW_REGS);
+ qemu_plugin_set_cb_flags(cpu, QEMU_PLUGIN_CB_RW_REGS_PC);
if (cpu->cpu_index < plugin.num_vcpus) {
/* iterate safely; plugins might uninstall themselves at any time */
QLIST_FOREACH_SAFE_RCU(cb, &plugin.cb_lists[ev], entry, next) {
@@ -395,15 +395,16 @@ void plugin_register_dyn_cb__udata(GArray **arr,
enum qemu_plugin_cb_flags flags,
void *udata)
{
- static TCGHelperInfo info[3] = {
+ static TCGHelperInfo info[4] = {
[QEMU_PLUGIN_CB_NO_REGS].flags = TCG_CALL_NO_RWG,
[QEMU_PLUGIN_CB_R_REGS].flags = TCG_CALL_NO_WG,
[QEMU_PLUGIN_CB_RW_REGS].flags = 0,
+ [QEMU_PLUGIN_CB_RW_REGS_PC].flags = 0,
/*
* Match qemu_plugin_vcpu_udata_cb_t:
* void (*)(uint32_t, void *)
*/
- [0 ... 2].typemask = (dh_typemask(void, 0) |
+ [0 ... 3].typemask = (dh_typemask(void, 0) |
dh_typemask(i32, 1) |
dh_typemask(ptr, 2))
};
@@ -425,15 +426,16 @@ void plugin_register_dyn_cond_cb__udata(GArray **arr,
uint64_t imm,
void *udata)
{
- static TCGHelperInfo info[3] = {
+ static TCGHelperInfo info[4] = {
[QEMU_PLUGIN_CB_NO_REGS].flags = TCG_CALL_NO_RWG,
[QEMU_PLUGIN_CB_R_REGS].flags = TCG_CALL_NO_WG,
[QEMU_PLUGIN_CB_RW_REGS].flags = 0,
+ [QEMU_PLUGIN_CB_RW_REGS_PC].flags = 0,
/*
* Match qemu_plugin_vcpu_udata_cb_t:
* void (*)(uint32_t, void *)
*/
- [0 ... 2].typemask = (dh_typemask(void, 0) |
+ [0 ... 3].typemask = (dh_typemask(void, 0) |
dh_typemask(i32, 1) |
dh_typemask(ptr, 2))
};
@@ -464,15 +466,16 @@ void plugin_register_vcpu_mem_cb(GArray **arr,
!__builtin_types_compatible_p(qemu_plugin_meminfo_t, uint32_t) &&
!__builtin_types_compatible_p(qemu_plugin_meminfo_t, int32_t));
- static TCGHelperInfo info[3] = {
+ static TCGHelperInfo info[4] = {
[QEMU_PLUGIN_CB_NO_REGS].flags = TCG_CALL_NO_RWG,
[QEMU_PLUGIN_CB_R_REGS].flags = TCG_CALL_NO_WG,
[QEMU_PLUGIN_CB_RW_REGS].flags = 0,
+ [QEMU_PLUGIN_CB_RW_REGS_PC].flags = 0,
/*
* Match qemu_plugin_vcpu_mem_cb_t:
* void (*)(uint32_t, qemu_plugin_meminfo_t, uint64_t, void *)
*/
- [0 ... 2].typemask =
+ [0 ... 3].typemask =
(dh_typemask(void, 0) |
dh_typemask(i32, 1) |
(__builtin_types_compatible_p(qemu_plugin_meminfo_t, uint32_t)
@@ -553,7 +556,7 @@ qemu_plugin_vcpu_syscall(CPUState *cpu, int64_t num, uint64_t a1, uint64_t a2,
QLIST_FOREACH_SAFE_RCU(cb, &plugin.cb_lists[ev], entry, next) {
qemu_plugin_vcpu_syscall_cb_t func = cb->f.vcpu_syscall;
- qemu_plugin_set_cb_flags(cpu, QEMU_PLUGIN_CB_RW_REGS);
+ qemu_plugin_set_cb_flags(cpu, QEMU_PLUGIN_CB_RW_REGS_PC);
func(cb->ctx->id, cpu->cpu_index, num, a1, a2, a3, a4, a5, a6, a7, a8);
qemu_plugin_set_cb_flags(cpu, QEMU_PLUGIN_CB_NO_REGS);
}
@@ -577,7 +580,7 @@ void qemu_plugin_vcpu_syscall_ret(CPUState *cpu, int64_t num, int64_t ret)
QLIST_FOREACH_SAFE_RCU(cb, &plugin.cb_lists[ev], entry, next) {
qemu_plugin_vcpu_syscall_ret_cb_t func = cb->f.vcpu_syscall_ret;
- qemu_plugin_set_cb_flags(cpu, QEMU_PLUGIN_CB_RW_REGS);
+ qemu_plugin_set_cb_flags(cpu, QEMU_PLUGIN_CB_RW_REGS_PC);
func(cb->ctx->id, cpu->cpu_index, num, ret);
qemu_plugin_set_cb_flags(cpu, QEMU_PLUGIN_CB_NO_REGS);
}
@@ -605,7 +608,7 @@ qemu_plugin_vcpu_syscall_filter(CPUState *cpu, int64_t num, uint64_t a1,
clamp_syscall_arguments(&a1, &a2, &a3, &a4, &a5, &a6, &a7, &a8);
- qemu_plugin_set_cb_flags(cpu, QEMU_PLUGIN_CB_RW_REGS);
+ qemu_plugin_set_cb_flags(cpu, QEMU_PLUGIN_CB_RW_REGS_PC);
QLIST_FOREACH_SAFE_RCU(cb, &plugin.cb_lists[ev], entry, next) {
qemu_plugin_vcpu_syscall_filter_cb_t func = cb->f.vcpu_syscall_filter;
@@ -626,7 +629,7 @@ void qemu_plugin_vcpu_idle_cb(CPUState *cpu)
{
/* idle and resume cb may be called before init, ignore in this case */
if (cpu->cpu_index < plugin.num_vcpus) {
- qemu_plugin_set_cb_flags(cpu, QEMU_PLUGIN_CB_RW_REGS);
+ qemu_plugin_set_cb_flags(cpu, QEMU_PLUGIN_CB_RW_REGS_PC);
plugin_vcpu_cb__simple(cpu, QEMU_PLUGIN_EV_VCPU_IDLE);
qemu_plugin_set_cb_flags(cpu, QEMU_PLUGIN_CB_NO_REGS);
}
@@ -635,7 +638,7 @@ void qemu_plugin_vcpu_idle_cb(CPUState *cpu)
void qemu_plugin_vcpu_resume_cb(CPUState *cpu)
{
if (cpu->cpu_index < plugin.num_vcpus) {
- qemu_plugin_set_cb_flags(cpu, QEMU_PLUGIN_CB_RW_REGS);
+ qemu_plugin_set_cb_flags(cpu, QEMU_PLUGIN_CB_RW_REGS_PC);
plugin_vcpu_cb__simple(cpu, QEMU_PLUGIN_EV_VCPU_RESUME);
qemu_plugin_set_cb_flags(cpu, QEMU_PLUGIN_CB_NO_REGS);
}
@@ -906,6 +909,6 @@ enum qemu_plugin_cb_flags tcg_call_to_qemu_plugin_cb_flags(int flags)
} else if (flags & TCG_CALL_NO_WG) {
return QEMU_PLUGIN_CB_R_REGS;
} else {
- return QEMU_PLUGIN_CB_RW_REGS;
+ return QEMU_PLUGIN_CB_RW_REGS_PC;
}
}
--
2.47.3
^ permalink raw reply related [flat|nested] 14+ messages in thread
* [PULL 03/12] linux-user: make syscall emulation interruptible
2026-03-05 20:51 [PULL 00/12] Plugins update 2025-03-05 Pierrick Bouvier
2026-03-05 20:51 ` [PULL 01/12] plugins/core: clamp syscall arguments if target is 32-bit Pierrick Bouvier
2026-03-05 20:51 ` [PULL 02/12] plugins: add flag to specify whether PC is rw Pierrick Bouvier
@ 2026-03-05 20:51 ` Pierrick Bouvier
2026-03-05 20:51 ` [PULL 04/12] plugins: add PC diversion API function Pierrick Bouvier
` (9 subsequent siblings)
12 siblings, 0 replies; 14+ messages in thread
From: Pierrick Bouvier @ 2026-03-05 20:51 UTC (permalink / raw)
To: qemu-devel, peter.maydell, richard.henderson, pbonzini, stefanha
Cc: pierrick.bouvier
From: Florian Hofhammer <florian.hofhammer@epfl.ch>
The syscall emulation code previously wasn't interruptible via
cpu_loop_exit(), as this construct relies on a longjmp target that is not
live anymore in the syscall handling code. Consequently, longjmp() would
operate on a (potentially overwritten) stale jump buffer. This patch adds an additional
setjmp and the necessary handling around it to make longjmp() (and by
proxy cpu_loop_exit() safe to call even within a syscall context.
Reviewed-by: Warner Losh <imp@bsdimp.com>
Reviewed-by: Pierrick Bouvier <pierrick.bouvier@linaro.org>
Reviewed-by: Alex Bennée <alex.bennee@linaro.org>
Signed-off-by: Florian Hofhammer <florian.hofhammer@epfl.ch>
Link: https://lore.kernel.org/qemu-devel/20260305-setpc-v5-v7-3-4c3adba52403@epfl.ch
Signed-off-by: Pierrick Bouvier <pierrick.bouvier@linaro.org>
---
linux-user/include/special-errno.h | 8 ++++++++
linux-user/aarch64/cpu_loop.c | 2 +-
linux-user/alpha/cpu_loop.c | 2 +-
linux-user/arm/cpu_loop.c | 2 +-
linux-user/hexagon/cpu_loop.c | 2 +-
linux-user/hppa/cpu_loop.c | 1 +
linux-user/i386/cpu_loop.c | 8 +++++---
linux-user/loongarch64/cpu_loop.c | 5 +++--
linux-user/m68k/cpu_loop.c | 2 +-
linux-user/microblaze/cpu_loop.c | 2 +-
linux-user/mips/cpu_loop.c | 9 ++++++---
linux-user/or1k/cpu_loop.c | 2 +-
linux-user/ppc/cpu_loop.c | 10 +++++++---
linux-user/riscv/cpu_loop.c | 2 +-
linux-user/s390x/cpu_loop.c | 2 +-
linux-user/sh4/cpu_loop.c | 2 +-
linux-user/sparc/cpu_loop.c | 4 +++-
linux-user/syscall.c | 16 ++++++++++++++++
linux-user/xtensa/cpu_loop.c | 1 +
19 files changed, 60 insertions(+), 22 deletions(-)
diff --git a/linux-user/include/special-errno.h b/linux-user/include/special-errno.h
index 4120455baaf..1db757241a3 100644
--- a/linux-user/include/special-errno.h
+++ b/linux-user/include/special-errno.h
@@ -29,4 +29,12 @@
*/
#define QEMU_ESIGRETURN 513
+/*
+ * This is returned after a plugin has used the qemu_plugin_set_pc API, to
+ * indicate that the plugin deliberately changed the PC and potentially
+ * modified the register values. The main loop should not touch the guest
+ * registers for this reason.
+ */
+#define QEMU_ESETPC 514
+
#endif /* SPECIAL_ERRNO_H */
diff --git a/linux-user/aarch64/cpu_loop.c b/linux-user/aarch64/cpu_loop.c
index 7f66a879ea9..e7f643d69d5 100644
--- a/linux-user/aarch64/cpu_loop.c
+++ b/linux-user/aarch64/cpu_loop.c
@@ -181,7 +181,7 @@ void cpu_loop(CPUARMState *env)
0, 0);
if (ret == -QEMU_ERESTARTSYS) {
env->pc -= 4;
- } else if (ret != -QEMU_ESIGRETURN) {
+ } else if (ret != -QEMU_ESIGRETURN && ret != -QEMU_ESETPC) {
env->xregs[0] = ret;
}
break;
diff --git a/linux-user/alpha/cpu_loop.c b/linux-user/alpha/cpu_loop.c
index f93597c400d..bef196b1f56 100644
--- a/linux-user/alpha/cpu_loop.c
+++ b/linux-user/alpha/cpu_loop.c
@@ -82,7 +82,7 @@ void cpu_loop(CPUAlphaState *env)
env->pc -= 4;
break;
}
- if (sysret == -QEMU_ESIGRETURN) {
+ if (sysret == -QEMU_ESIGRETURN || sysret == -QEMU_ESETPC) {
break;
}
/* Syscall writes 0 to V0 to bypass error check, similar
diff --git a/linux-user/arm/cpu_loop.c b/linux-user/arm/cpu_loop.c
index 40aefc4c1d7..19874f4c727 100644
--- a/linux-user/arm/cpu_loop.c
+++ b/linux-user/arm/cpu_loop.c
@@ -399,7 +399,7 @@ void cpu_loop(CPUARMState *env)
0, 0);
if (ret == -QEMU_ERESTARTSYS) {
env->regs[15] -= env->thumb ? 2 : 4;
- } else if (ret != -QEMU_ESIGRETURN) {
+ } else if (ret != -QEMU_ESIGRETURN && ret != -QEMU_ESETPC) {
env->regs[0] = ret;
}
}
diff --git a/linux-user/hexagon/cpu_loop.c b/linux-user/hexagon/cpu_loop.c
index 5711055aff2..9464246e9e3 100644
--- a/linux-user/hexagon/cpu_loop.c
+++ b/linux-user/hexagon/cpu_loop.c
@@ -56,7 +56,7 @@ void cpu_loop(CPUHexagonState *env)
0, 0);
if (ret == -QEMU_ERESTARTSYS) {
env->gpr[HEX_REG_PC] -= 4;
- } else if (ret != -QEMU_ESIGRETURN) {
+ } else if (ret != -QEMU_ESIGRETURN && ret != -QEMU_ESETPC) {
env->gpr[0] = ret;
}
break;
diff --git a/linux-user/hppa/cpu_loop.c b/linux-user/hppa/cpu_loop.c
index 972e85c487d..4b4b663052b 100644
--- a/linux-user/hppa/cpu_loop.c
+++ b/linux-user/hppa/cpu_loop.c
@@ -124,6 +124,7 @@ void cpu_loop(CPUHPPAState *env)
break;
case -QEMU_ERESTARTSYS:
case -QEMU_ESIGRETURN:
+ case -QEMU_ESETPC:
break;
}
break;
diff --git a/linux-user/i386/cpu_loop.c b/linux-user/i386/cpu_loop.c
index f3f58576af5..fe922fceb5a 100644
--- a/linux-user/i386/cpu_loop.c
+++ b/linux-user/i386/cpu_loop.c
@@ -181,7 +181,9 @@ static void emulate_vsyscall(CPUX86State *env)
if (ret == -TARGET_EFAULT) {
goto sigsegv;
}
- env->regs[R_EAX] = ret;
+ if (ret != -QEMU_ESETPC) {
+ env->regs[R_EAX] = ret;
+ }
/* Emulate a ret instruction to leave the vsyscall page. */
env->eip = caller;
@@ -234,7 +236,7 @@ void cpu_loop(CPUX86State *env)
0, 0);
if (ret == -QEMU_ERESTARTSYS) {
env->eip -= 2;
- } else if (ret != -QEMU_ESIGRETURN) {
+ } else if (ret != -QEMU_ESIGRETURN && ret != -QEMU_ESETPC) {
env->regs[R_EAX] = ret;
}
break;
@@ -253,7 +255,7 @@ void cpu_loop(CPUX86State *env)
0, 0);
if (ret == -QEMU_ERESTARTSYS) {
env->eip -= 2;
- } else if (ret != -QEMU_ESIGRETURN) {
+ } else if (ret != -QEMU_ESIGRETURN && ret != -QEMU_ESETPC) {
env->regs[R_EAX] = ret;
}
break;
diff --git a/linux-user/loongarch64/cpu_loop.c b/linux-user/loongarch64/cpu_loop.c
index 26a5ce3a936..603fcc39c7f 100644
--- a/linux-user/loongarch64/cpu_loop.c
+++ b/linux-user/loongarch64/cpu_loop.c
@@ -44,9 +44,10 @@ void cpu_loop(CPULoongArchState *env)
env->pc -= 4;
break;
}
- if (ret == -QEMU_ESIGRETURN) {
+ if (ret == -QEMU_ESIGRETURN || ret == -QEMU_ESETPC) {
/*
- * Returning from a successful sigreturn syscall.
+ * Returning from a successful sigreturn syscall or from
+ * control flow diversion in a plugin callback.
* Avoid clobbering register state.
*/
break;
diff --git a/linux-user/m68k/cpu_loop.c b/linux-user/m68k/cpu_loop.c
index 2c9f628241f..b98ca8ff7b9 100644
--- a/linux-user/m68k/cpu_loop.c
+++ b/linux-user/m68k/cpu_loop.c
@@ -66,7 +66,7 @@ void cpu_loop(CPUM68KState *env)
0, 0);
if (ret == -QEMU_ERESTARTSYS) {
env->pc -= 2;
- } else if (ret != -QEMU_ESIGRETURN) {
+ } else if (ret != -QEMU_ESIGRETURN && ret != -QEMU_ESETPC) {
env->dregs[0] = ret;
}
}
diff --git a/linux-user/microblaze/cpu_loop.c b/linux-user/microblaze/cpu_loop.c
index 78506ab23d9..06d92c0b90d 100644
--- a/linux-user/microblaze/cpu_loop.c
+++ b/linux-user/microblaze/cpu_loop.c
@@ -54,7 +54,7 @@ void cpu_loop(CPUMBState *env)
if (ret == -QEMU_ERESTARTSYS) {
/* Wind back to before the syscall. */
env->pc -= 4;
- } else if (ret != -QEMU_ESIGRETURN) {
+ } else if (ret != -QEMU_ESIGRETURN && ret != -QEMU_ESETPC) {
env->regs[3] = ret;
}
/* All syscall exits result in guest r14 being equal to the
diff --git a/linux-user/mips/cpu_loop.c b/linux-user/mips/cpu_loop.c
index 2365de1de1a..fa264b27ec5 100644
--- a/linux-user/mips/cpu_loop.c
+++ b/linux-user/mips/cpu_loop.c
@@ -140,9 +140,12 @@ done_syscall:
env->active_tc.PC -= 4;
break;
}
- if (ret == -QEMU_ESIGRETURN) {
- /* Returning from a successful sigreturn syscall.
- Avoid clobbering register state. */
+ if (ret == -QEMU_ESIGRETURN || ret == -QEMU_ESETPC) {
+ /*
+ * Returning from a successful sigreturn syscall or from
+ * control flow diversion in a plugin callback.
+ * Avoid clobbering register state.
+ */
break;
}
if ((abi_ulong)ret >= (abi_ulong)-1133) {
diff --git a/linux-user/or1k/cpu_loop.c b/linux-user/or1k/cpu_loop.c
index 2167d880d55..e7e9929e6f5 100644
--- a/linux-user/or1k/cpu_loop.c
+++ b/linux-user/or1k/cpu_loop.c
@@ -48,7 +48,7 @@ void cpu_loop(CPUOpenRISCState *env)
cpu_get_gpr(env, 8), 0, 0);
if (ret == -QEMU_ERESTARTSYS) {
env->pc -= 4;
- } else if (ret != -QEMU_ESIGRETURN) {
+ } else if (ret != -QEMU_ESIGRETURN && ret != -QEMU_ESETPC) {
cpu_set_gpr(env, 11, ret);
}
break;
diff --git a/linux-user/ppc/cpu_loop.c b/linux-user/ppc/cpu_loop.c
index b0b0cb14b41..1f9ee20bd0c 100644
--- a/linux-user/ppc/cpu_loop.c
+++ b/linux-user/ppc/cpu_loop.c
@@ -340,9 +340,13 @@ void cpu_loop(CPUPPCState *env)
env->nip -= 4;
break;
}
- if (ret == (target_ulong)(-QEMU_ESIGRETURN)) {
- /* Returning from a successful sigreturn syscall.
- Avoid corrupting register state. */
+ if (ret == (target_ulong)(-QEMU_ESIGRETURN) ||
+ ret == (target_ulong)(-QEMU_ESETPC)) {
+ /*
+ * Returning from a successful sigreturn syscall or from
+ * control flow diversion in a plugin callback.
+ * Avoid corrupting register state.
+ */
break;
}
if (ret > (target_ulong)(-515)) {
diff --git a/linux-user/riscv/cpu_loop.c b/linux-user/riscv/cpu_loop.c
index ce542540c28..eecc8d15178 100644
--- a/linux-user/riscv/cpu_loop.c
+++ b/linux-user/riscv/cpu_loop.c
@@ -65,7 +65,7 @@ void cpu_loop(CPURISCVState *env)
}
if (ret == -QEMU_ERESTARTSYS) {
env->pc -= 4;
- } else if (ret != -QEMU_ESIGRETURN) {
+ } else if (ret != -QEMU_ESIGRETURN && ret != -QEMU_ESETPC) {
env->gpr[xA0] = ret;
}
if (cs->singlestep_enabled) {
diff --git a/linux-user/s390x/cpu_loop.c b/linux-user/s390x/cpu_loop.c
index 4929b32e1fc..67d2a803fbc 100644
--- a/linux-user/s390x/cpu_loop.c
+++ b/linux-user/s390x/cpu_loop.c
@@ -83,7 +83,7 @@ void cpu_loop(CPUS390XState *env)
env->regs[6], env->regs[7], 0, 0);
if (ret == -QEMU_ERESTARTSYS) {
env->psw.addr -= env->int_svc_ilen;
- } else if (ret != -QEMU_ESIGRETURN) {
+ } else if (ret != -QEMU_ESIGRETURN && ret != -QEMU_ESETPC) {
env->regs[2] = ret;
}
diff --git a/linux-user/sh4/cpu_loop.c b/linux-user/sh4/cpu_loop.c
index 0c9d7e9c46b..ee2958d0d93 100644
--- a/linux-user/sh4/cpu_loop.c
+++ b/linux-user/sh4/cpu_loop.c
@@ -50,7 +50,7 @@ void cpu_loop(CPUSH4State *env)
0, 0);
if (ret == -QEMU_ERESTARTSYS) {
env->pc -= 2;
- } else if (ret != -QEMU_ESIGRETURN) {
+ } else if (ret != -QEMU_ESIGRETURN && ret != -QEMU_ESETPC) {
env->gregs[0] = ret;
}
break;
diff --git a/linux-user/sparc/cpu_loop.c b/linux-user/sparc/cpu_loop.c
index 7391e2add8d..ab633eeae3f 100644
--- a/linux-user/sparc/cpu_loop.c
+++ b/linux-user/sparc/cpu_loop.c
@@ -229,7 +229,9 @@ void cpu_loop (CPUSPARCState *env)
env->regwptr[2], env->regwptr[3],
env->regwptr[4], env->regwptr[5],
0, 0);
- if (ret == -QEMU_ERESTARTSYS || ret == -QEMU_ESIGRETURN) {
+ if (ret == -QEMU_ERESTARTSYS ||
+ ret == -QEMU_ESIGRETURN ||
+ ret == -QEMU_ESETPC) {
break;
}
if ((abi_ulong)ret >= (abi_ulong)(-515)) {
diff --git a/linux-user/syscall.c b/linux-user/syscall.c
index d466d0e32f1..99e1ed97d9f 100644
--- a/linux-user/syscall.c
+++ b/linux-user/syscall.c
@@ -43,6 +43,7 @@
#include <linux/capability.h>
#include <sched.h>
#include <sys/timex.h>
+#include <setjmp.h>
#include <sys/socket.h>
#include <linux/sockios.h>
#include <sys/un.h>
@@ -600,6 +601,9 @@ const char *target_strerror(int err)
if (err == QEMU_ESIGRETURN) {
return "Successful exit from sigreturn";
}
+ if (err == QEMU_ESETPC) {
+ return "Successfully redirected control flow";
+ }
return strerror(target_to_host_errno(err));
}
@@ -14410,6 +14414,18 @@ abi_long do_syscall(CPUArchState *cpu_env, int num, abi_long arg1,
return -QEMU_ESIGRETURN;
}
+ /*
+ * Set up a longjmp target here so that we can call cpu_loop_exit to
+ * redirect control flow back to the main loop even from within
+ * syscall-related plugin callbacks.
+ * For other types of callbacks or longjmp call sites, the longjmp target
+ * is set up in the cpu loop itself but in syscalls the target is not live
+ * anymore.
+ */
+ if (unlikely(sigsetjmp(cpu->jmp_env, 0) != 0)) {
+ return -QEMU_ESETPC;
+ }
+
record_syscall_start(cpu, num, arg1,
arg2, arg3, arg4, arg5, arg6, arg7, arg8);
diff --git a/linux-user/xtensa/cpu_loop.c b/linux-user/xtensa/cpu_loop.c
index a0ff10eff82..d2b4ccdfade 100644
--- a/linux-user/xtensa/cpu_loop.c
+++ b/linux-user/xtensa/cpu_loop.c
@@ -186,6 +186,7 @@ void cpu_loop(CPUXtensaState *env)
break;
case -QEMU_ESIGRETURN:
+ case -QEMU_ESETPC:
break;
}
break;
--
2.47.3
^ permalink raw reply related [flat|nested] 14+ messages in thread
* [PULL 04/12] plugins: add PC diversion API function
2026-03-05 20:51 [PULL 00/12] Plugins update 2025-03-05 Pierrick Bouvier
` (2 preceding siblings ...)
2026-03-05 20:51 ` [PULL 03/12] linux-user: make syscall emulation interruptible Pierrick Bouvier
@ 2026-03-05 20:51 ` Pierrick Bouvier
2026-03-05 20:51 ` [PULL 05/12] tests/tcg: add tests for qemu_plugin_set_pc API Pierrick Bouvier
` (8 subsequent siblings)
12 siblings, 0 replies; 14+ messages in thread
From: Pierrick Bouvier @ 2026-03-05 20:51 UTC (permalink / raw)
To: qemu-devel, peter.maydell, richard.henderson, pbonzini, stefanha
Cc: pierrick.bouvier
From: Florian Hofhammer <florian.hofhammer@epfl.ch>
This patch adds a plugin API function that allows diverting the program
counter during execution. A potential use case for this functionality is
to skip over parts of the code, e.g., by hooking into a specific
instruction and setting the PC to the next instruction in the callback.
Link: https://lists.nongnu.org/archive/html/qemu-devel/2025-08/msg00656.html
Reviewed-by: Pierrick Bouvier <pierrick.bouvier@linaro.org>
Signed-off-by: Florian Hofhammer <florian.hofhammer@epfl.ch>
Link: https://lore.kernel.org/qemu-devel/20260305-setpc-v5-v7-4-4c3adba52403@epfl.ch
Signed-off-by: Pierrick Bouvier <pierrick.bouvier@linaro.org>
---
include/plugins/qemu-plugin.h | 13 +++++++++++++
plugins/api.c | 11 +++++++++++
scripts/qemu-plugin-symbols.py | 9 +++++++--
3 files changed, 31 insertions(+), 2 deletions(-)
diff --git a/include/plugins/qemu-plugin.h b/include/plugins/qemu-plugin.h
index a6ec8e275d8..7b9cd6a9717 100644
--- a/include/plugins/qemu-plugin.h
+++ b/include/plugins/qemu-plugin.h
@@ -76,6 +76,7 @@ typedef uint64_t qemu_plugin_id_t;
*
* version 6:
* - changed return value of qemu_plugin_{read,write}_register from int to bool
+ * - added qemu_plugin_set_pc
*/
extern QEMU_PLUGIN_EXPORT int qemu_plugin_version;
@@ -1042,6 +1043,18 @@ QEMU_PLUGIN_API
bool qemu_plugin_write_register(struct qemu_plugin_register *handle,
GByteArray *buf);
+/**
+ * qemu_plugin_set_pc() - set the program counter for the current vCPU
+ *
+ * @vaddr: the new virtual (guest) address for the program counter
+ *
+ * This function sets the program counter for the current vCPU to @vaddr and
+ * resumes execution at that address. This function does not return.
+ */
+QEMU_PLUGIN_API
+__attribute__((__noreturn__))
+void qemu_plugin_set_pc(uint64_t vaddr);
+
/**
* qemu_plugin_read_memory_vaddr() - read from memory using a virtual address
*
diff --git a/plugins/api.c b/plugins/api.c
index 32eb086300d..23c291f6444 100644
--- a/plugins/api.c
+++ b/plugins/api.c
@@ -41,6 +41,7 @@
#include "qemu/log.h"
#include "system/memory.h"
#include "tcg/tcg.h"
+#include "exec/cpu-common.h"
#include "exec/gdbstub.h"
#include "exec/target_page.h"
#include "exec/translation-block.h"
@@ -467,6 +468,16 @@ bool qemu_plugin_write_register(struct qemu_plugin_register *reg,
return (gdb_write_register(current_cpu, buf->data, GPOINTER_TO_INT(reg) - 1) > 0);
}
+void qemu_plugin_set_pc(uint64_t vaddr)
+{
+ g_assert(current_cpu);
+
+ g_assert(qemu_plugin_get_cb_flags() == QEMU_PLUGIN_CB_RW_REGS_PC);
+
+ cpu_set_pc(current_cpu, vaddr);
+ cpu_loop_exit(current_cpu);
+}
+
bool qemu_plugin_read_memory_vaddr(uint64_t addr, GByteArray *data, size_t len)
{
g_assert(current_cpu);
diff --git a/scripts/qemu-plugin-symbols.py b/scripts/qemu-plugin-symbols.py
index 69644979c19..ce99796ce2a 100644
--- a/scripts/qemu-plugin-symbols.py
+++ b/scripts/qemu-plugin-symbols.py
@@ -20,9 +20,14 @@ def extract_symbols(plugin_header):
# Remove QEMU_PLUGIN_API macro definition.
content = content.replace('#define QEMU_PLUGIN_API', '')
expected = content.count('QEMU_PLUGIN_API')
- # Find last word between QEMU_PLUGIN_API and (, matching on several lines.
+ # Find last word between QEMU_PLUGIN_API and ( to get the function name,
+ # matching on several lines. Discard attributes, if any.
# We use *? non-greedy quantifier.
- syms = re.findall(r'QEMU_PLUGIN_API.*?(\w+)\s*\(', content, re.DOTALL)
+ syms = re.findall(
+ r'QEMU_PLUGIN_API\s+(?:__attribute__\(\(\S+\)\))?.*?(\w+)\s*\(',
+ content,
+ re.DOTALL,
+ )
syms.sort()
# Ensure we found as many symbols as API markers.
assert len(syms) == expected
--
2.47.3
^ permalink raw reply related [flat|nested] 14+ messages in thread
* [PULL 05/12] tests/tcg: add tests for qemu_plugin_set_pc API
2026-03-05 20:51 [PULL 00/12] Plugins update 2025-03-05 Pierrick Bouvier
` (3 preceding siblings ...)
2026-03-05 20:51 ` [PULL 04/12] plugins: add PC diversion API function Pierrick Bouvier
@ 2026-03-05 20:51 ` Pierrick Bouvier
2026-03-05 20:51 ` [PULL 06/12] plugins: add read-only property for registers Pierrick Bouvier
` (7 subsequent siblings)
12 siblings, 0 replies; 14+ messages in thread
From: Pierrick Bouvier @ 2026-03-05 20:51 UTC (permalink / raw)
To: qemu-devel, peter.maydell, richard.henderson, pbonzini, stefanha
Cc: pierrick.bouvier
From: Florian Hofhammer <florian.hofhammer@epfl.ch>
The test plugin intercepts execution in different contexts. Without the
plugin, any of the implemented test functions would trigger an assert
and fail. With the plugin, control flow is redirected to skip the assert
and return cleanly via the qemu_plugin_set_pc() API.
Signed-off-by: Florian Hofhammer <florian.hofhammer@epfl.ch>
Reviewed-by: Pierrick Bouvier <pierrick.bouvier@linaro.org>
Link: https://lore.kernel.org/qemu-devel/20260305-setpc-v5-v7-5-4c3adba52403@epfl.ch
Signed-off-by: Pierrick Bouvier <pierrick.bouvier@linaro.org>
---
MAINTAINERS | 1 +
.../{ => plugin}/test-plugin-mem-access.c | 0
.../tcg/multiarch/plugin/test-plugin-set-pc.c | 134 ++++++++++++++++++
tests/tcg/plugins/setpc.c | 105 ++++++++++++++
tests/tcg/arm/Makefile.target | 6 +
tests/tcg/hexagon/Makefile.target | 8 ++
tests/tcg/multiarch/Makefile.target | 17 ++-
.../{ => plugin}/check-plugin-output.sh | 0
tests/tcg/plugins/meson.build | 1 +
9 files changed, 269 insertions(+), 3 deletions(-)
rename tests/tcg/multiarch/{ => plugin}/test-plugin-mem-access.c (100%)
create mode 100644 tests/tcg/multiarch/plugin/test-plugin-set-pc.c
create mode 100644 tests/tcg/plugins/setpc.c
rename tests/tcg/multiarch/{ => plugin}/check-plugin-output.sh (100%)
diff --git a/MAINTAINERS b/MAINTAINERS
index 6698e5ff69c..63c0af4d86d 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -4104,6 +4104,7 @@ S: Maintained
F: docs/devel/tcg-plugins.rst
F: plugins/
F: tests/tcg/plugins/
+F: tests/tcg/multiarch/plugin/
F: tests/functional/aarch64/test_tcg_plugins.py
F: contrib/plugins/
F: scripts/qemu-plugin-symbols.py
diff --git a/tests/tcg/multiarch/test-plugin-mem-access.c b/tests/tcg/multiarch/plugin/test-plugin-mem-access.c
similarity index 100%
rename from tests/tcg/multiarch/test-plugin-mem-access.c
rename to tests/tcg/multiarch/plugin/test-plugin-mem-access.c
diff --git a/tests/tcg/multiarch/plugin/test-plugin-set-pc.c b/tests/tcg/multiarch/plugin/test-plugin-set-pc.c
new file mode 100644
index 00000000000..f8343dfba84
--- /dev/null
+++ b/tests/tcg/multiarch/plugin/test-plugin-set-pc.c
@@ -0,0 +1,134 @@
+/*
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * Copyright (C) 2026, Florian Hofhammer <florian.hofhammer@epfl.ch>
+ *
+ * This test set exercises the qemu_plugin_set_pc() function in four different
+ * contexts:
+ * 1. in an instruction callback during normal execution,
+ * 2. in an instruction callback during signal handling,
+ * 3. in a memory access callback.
+ * 4. in a syscall callback,
+ */
+#include <assert.h>
+#include <signal.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+
+/* If we issue this magic syscall, ... */
+#define MAGIC_SYSCALL 4096
+/* ... the plugin either jumps directly to the target address ... */
+#define SETPC 0
+/* ... or just updates the target address for future use in callbacks. */
+#define SETTARGET 1
+
+static int signal_handled;
+
+void panic(const char *msg)
+{
+ fprintf(stderr, "Panic: %s\n", msg);
+ abort();
+}
+
+/*
+ * This test executes a magic syscall which communicates two addresses to the
+ * plugin via the syscall arguments. Whenever we reach the "bad" instruction
+ * during normal execution, the plugin should redirect control flow to the
+ * "good" instruction instead.
+ */
+void test_insn(void)
+{
+ long ret = syscall(MAGIC_SYSCALL, SETTARGET, &&bad_insn, &&good_insn,
+ NULL);
+ assert(ret == 0 && "Syscall filter did not return expected value");
+bad_insn:
+ panic("PC redirection in instruction callback failed");
+good_insn:
+ puts("PC redirection in instruction callback succeeded");
+}
+
+/*
+ * This signal handler communicates a "bad" and a "good" address to the plugin
+ * similar to the previous test, and skips to the "good" address when the "bad"
+ * one is reached. This serves to test whether PC redirection via
+ * qemu_plugin_set_pc() also works properly in a signal handler context.
+ */
+void usr1_handler(int signum)
+{
+ long ret = syscall(MAGIC_SYSCALL, SETTARGET, &&bad_signal, &&good_signal,
+ NULL);
+ assert(ret == 0 && "Syscall filter did not return expected value");
+bad_signal:
+ panic("PC redirection in instruction callback failed");
+good_signal:
+ signal_handled = 1;
+ puts("PC redirection in instruction callback succeeded");
+}
+
+/*
+ * This test sends a signal to the process, which should trigger the above
+ * signal handler. The signal handler should then exercise the PC redirection
+ * functionality in the context of a signal handler, which behaves a bit
+ * differently from normal execution.
+ */
+void test_sighandler(void)
+{
+ struct sigaction sa = {0};
+ sa.sa_handler = usr1_handler;
+ sigaction(SIGUSR1, &sa, NULL);
+ pid_t pid = getpid();
+ kill(pid, SIGUSR1);
+ assert(signal_handled == 1 && "Signal handler was not executed properly");
+}
+
+/*
+ * This test communicates a "good" address and the address of a local variable
+ * to the plugin. Upon accessing the local variable, the plugin should then
+ * redirect control flow to the "good" address via qemu_plugin_set_pc().
+ */
+void test_mem(void)
+{
+ static uint32_t test = 1;
+ long ret = syscall(MAGIC_SYSCALL, SETTARGET, NULL, &&good_mem, &test);
+ assert(ret == 0 && "Syscall filter did not return expected value");
+ /* Ensure read access to the variable to trigger the plugin callback */
+ assert(test == 1);
+ panic("PC redirection in memory access callback failed");
+good_mem:
+ puts("PC redirection in memory access callback succeeded");
+}
+
+/*
+ * This test executes a magic syscall which is intercepted and its actual
+ * execution skipped via the qemu_plugin_set_pc() API. In a proper plugin,
+ * syscall skipping would rather be implemented via the syscall filtering
+ * callback, but we want to make sure qemu_plugin_set_pc() works in different
+ * contexts.
+ */
+__attribute__((noreturn))
+void test_syscall(void)
+{
+ syscall(MAGIC_SYSCALL, SETPC, &&good_syscall);
+ panic("PC redirection in syscall callback failed");
+good_syscall:
+ /*
+ * Note: we execute this test last and exit straight from here because when
+ * the plugin redirects control flow upon syscall, the stack frame for the
+ * syscall function (and potential other functions in the call chain in
+ * libc) is still live and the stack is not unwound properly. Thus,
+ * returning from here is risky and breaks on some architectures, so we
+ * just exit directly from this test.
+ */
+ _exit(EXIT_SUCCESS);
+}
+
+
+int main(int argc, char *argv[])
+{
+ test_insn();
+ test_sighandler();
+ test_mem();
+ test_syscall();
+}
diff --git a/tests/tcg/plugins/setpc.c b/tests/tcg/plugins/setpc.c
new file mode 100644
index 00000000000..8f2d025e245
--- /dev/null
+++ b/tests/tcg/plugins/setpc.c
@@ -0,0 +1,105 @@
+/*
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * Copyright (C) 2026, Florian Hofhammer <florian.hofhammer@epfl.ch>
+ */
+#include <assert.h>
+#include <glib.h>
+#include <inttypes.h>
+#include <unistd.h>
+
+#include <qemu-plugin.h>
+
+/* If we detect this magic syscall, ... */
+#define MAGIC_SYSCALL 4096
+/* ... the plugin either jumps directly to the target address ... */
+#define SETPC 0
+/* ... or just updates the target address for future use in callbacks. */
+#define SETTARGET 1
+
+QEMU_PLUGIN_EXPORT int qemu_plugin_version = QEMU_PLUGIN_VERSION;
+
+static uint64_t source_pc;
+static uint64_t target_pc;
+static uint64_t target_vaddr;
+
+static bool vcpu_syscall_filter(qemu_plugin_id_t id, unsigned int vcpu_index,
+ int64_t num, uint64_t a1, uint64_t a2,
+ uint64_t a3, uint64_t a4, uint64_t a5,
+ uint64_t a6, uint64_t a7, uint64_t a8,
+ uint64_t *sysret)
+{
+ if (num == MAGIC_SYSCALL) {
+ if (a1 == SETPC) {
+ qemu_plugin_outs("Magic syscall detected, jump to clean exit\n");
+ qemu_plugin_set_pc(a2);
+ } else if (a1 == SETTARGET) {
+ qemu_plugin_outs("Magic syscall detected, set target_pc / "
+ "target_vaddr\n");
+ source_pc = a2;
+ target_pc = a3;
+ target_vaddr = a4;
+ *sysret = 0;
+ return true;
+ } else {
+ qemu_plugin_outs("Unknown magic syscall argument, ignoring\n");
+ }
+ }
+ return false;
+}
+
+static void vcpu_insn_exec(unsigned int vcpu_index, void *userdata)
+{
+ uint64_t vaddr = (uint64_t)userdata;
+ if (vaddr == source_pc) {
+ g_assert(target_pc != 0);
+ g_assert(target_vaddr == 0);
+
+ qemu_plugin_outs("Marker insn detected, jump to clean return\n");
+ qemu_plugin_set_pc(target_pc);
+ }
+}
+
+static void vcpu_mem_access(unsigned int vcpu_index,
+ qemu_plugin_meminfo_t info,
+ uint64_t vaddr, void *userdata)
+{
+ if (vaddr != 0 && vaddr == target_vaddr) {
+ g_assert(source_pc == 0);
+ g_assert(target_pc != 0);
+
+ qemu_plugin_outs("Marker mem access detected, jump to clean return\n");
+ qemu_plugin_set_pc(target_pc);
+ }
+}
+
+static void vcpu_tb_trans(qemu_plugin_id_t id, struct qemu_plugin_tb *tb)
+{
+ size_t insns = qemu_plugin_tb_n_insns(tb);
+ for (size_t i = 0; i < insns; i++) {
+ struct qemu_plugin_insn *insn = qemu_plugin_tb_get_insn(tb, i);
+ uint64_t insn_vaddr = qemu_plugin_insn_vaddr(insn);
+ /*
+ * Note: we cannot only register the callbacks if the instruction is
+ * in one of the functions of interest, because symbol lookup for
+ * filtering does not work for all architectures (e.g., ppc64).
+ */
+ qemu_plugin_register_vcpu_insn_exec_cb(insn, vcpu_insn_exec,
+ QEMU_PLUGIN_CB_RW_REGS_PC,
+ (void *)insn_vaddr);
+ qemu_plugin_register_vcpu_mem_cb(insn, vcpu_mem_access,
+ QEMU_PLUGIN_CB_RW_REGS_PC,
+ QEMU_PLUGIN_MEM_R, NULL);
+ }
+}
+
+
+QEMU_PLUGIN_EXPORT int qemu_plugin_install(qemu_plugin_id_t id,
+ const qemu_info_t *info,
+ int argc, char **argv)
+{
+
+ qemu_plugin_register_vcpu_syscall_filter_cb(id, vcpu_syscall_filter);
+ qemu_plugin_register_vcpu_tb_trans_cb(id, vcpu_tb_trans);
+ return 0;
+}
diff --git a/tests/tcg/arm/Makefile.target b/tests/tcg/arm/Makefile.target
index 6189d7a0e24..613bbf0939a 100644
--- a/tests/tcg/arm/Makefile.target
+++ b/tests/tcg/arm/Makefile.target
@@ -78,4 +78,10 @@ sha512-vector: sha512.c
ARM_TESTS += sha512-vector
+ifeq ($(CONFIG_PLUGIN),y)
+# Require emitting arm32 instructions, otherwise the vCPU might accidentally
+# try to execute Thumb instructions in arm32 mode after qemu_plugin_set_pc()
+test-plugin-set-pc: CFLAGS+=-marm
+endif
+
TESTS += $(ARM_TESTS)
diff --git a/tests/tcg/hexagon/Makefile.target b/tests/tcg/hexagon/Makefile.target
index f86f02bb31c..a70ef2f6607 100644
--- a/tests/tcg/hexagon/Makefile.target
+++ b/tests/tcg/hexagon/Makefile.target
@@ -126,3 +126,11 @@ v73_scalar: CFLAGS += -Wno-unused-function
hvx_histogram: hvx_histogram.c hvx_histogram_row.S
$(CC) $(CFLAGS) $(CROSS_CC_GUEST_CFLAGS) $^ -o $@ $(LDFLAGS)
+
+ifeq ($(CONFIG_PLUGIN),y)
+# LLVM is way too aggressive with inlining and dead code elimination even at
+# -O0, which interferes with the test. What looks like dead code in this test
+# to the compiler isn't actually dead code, so we need to disable all potential
+# LLVM optimization passes.
+test-plugin-set-pc: CFLAGS += -Xclang -disable-llvm-passes
+endif
diff --git a/tests/tcg/multiarch/Makefile.target b/tests/tcg/multiarch/Makefile.target
index 07d0b27bdd3..a347efbadf0 100644
--- a/tests/tcg/multiarch/Makefile.target
+++ b/tests/tcg/multiarch/Makefile.target
@@ -14,6 +14,10 @@ ifeq ($(filter %-linux-user, $(TARGET)),$(TARGET))
VPATH += $(MULTIARCH_SRC)/linux
MULTIARCH_SRCS += $(notdir $(wildcard $(MULTIARCH_SRC)/linux/*.c))
endif
+ifeq ($(CONFIG_PLUGIN),y)
+VPATH += $(MULTIARCH_SRC)/plugin
+MULTIARCH_SRCS += $(notdir $(wildcard $(MULTIARCH_SRC)/plugin/*.c))
+endif
MULTIARCH_TESTS = $(MULTIARCH_SRCS:.c=)
#
@@ -200,13 +204,20 @@ run-plugin-test-plugin-mem-access-with-libmem.so: \
PLUGIN_ARGS=$(COMMA)print-accesses=true
run-plugin-test-plugin-mem-access-with-libmem.so: \
CHECK_PLUGIN_OUTPUT_COMMAND= \
- $(SRC_PATH)/tests/tcg/multiarch/check-plugin-output.sh \
+ $(SRC_PATH)/tests/tcg/multiarch/plugin/check-plugin-output.sh \
$(QEMU) $<
run-plugin-test-plugin-syscall-filter-with-libsyscall.so:
+run-plugin-test-plugin-set-pc-with-libsetpc.so:
EXTRA_RUNS_WITH_PLUGIN += run-plugin-test-plugin-mem-access-with-libmem.so \
- run-plugin-test-plugin-syscall-filter-with-libsyscall.so
-else
+ run-plugin-test-plugin-syscall-filter-with-libsyscall.so \
+ run-plugin-test-plugin-set-pc-with-libsetpc.so
+
+else # CONFIG_PLUGIN=n
+# Do not build the syscall skipping test if it's not tested with the setpc
+# plugin because it will simply fail the test.
+MULTIARCH_TESTS := $(filter-out test-plugin-set-pc, $(MULTIARCH_TESTS))
+
# test-plugin-syscall-filter needs syscall plugin to succeed
test-plugin-syscall-filter: CFLAGS+=-DSKIP
endif
diff --git a/tests/tcg/multiarch/check-plugin-output.sh b/tests/tcg/multiarch/plugin/check-plugin-output.sh
similarity index 100%
rename from tests/tcg/multiarch/check-plugin-output.sh
rename to tests/tcg/multiarch/plugin/check-plugin-output.sh
diff --git a/tests/tcg/plugins/meson.build b/tests/tcg/plugins/meson.build
index c5e49753fd9..b3e3a9a6d02 100644
--- a/tests/tcg/plugins/meson.build
+++ b/tests/tcg/plugins/meson.build
@@ -7,6 +7,7 @@ test_plugins = [
'mem.c',
'patch.c',
'reset.c',
+'setpc.c',
'syscall.c',
]
--
2.47.3
^ permalink raw reply related [flat|nested] 14+ messages in thread
* [PULL 06/12] plugins: add read-only property for registers
2026-03-05 20:51 [PULL 00/12] Plugins update 2025-03-05 Pierrick Bouvier
` (4 preceding siblings ...)
2026-03-05 20:51 ` [PULL 05/12] tests/tcg: add tests for qemu_plugin_set_pc API Pierrick Bouvier
@ 2026-03-05 20:51 ` Pierrick Bouvier
2026-03-05 20:51 ` [PULL 07/12] plugins: prohibit writing to read-only registers Pierrick Bouvier
` (6 subsequent siblings)
12 siblings, 0 replies; 14+ messages in thread
From: Pierrick Bouvier @ 2026-03-05 20:51 UTC (permalink / raw)
To: qemu-devel, peter.maydell, richard.henderson, pbonzini, stefanha
Cc: pierrick.bouvier
From: Florian Hofhammer <florian.hofhammer@epfl.ch>
Some registers should be marked as read-only from a plugin API
perspective, as writing to them via qemu_plugin_write_register has no
effect. This includes the program counter, and we expose this fact to
the plugins with this patch.
Reviewed-by: Alex Bennée <alex.bennee@linaro.org>
Reviewed-by: Pierrick Bouvier <pierrick.bouvier@linaro.org>
Signed-off-by: Florian Hofhammer <florian.hofhammer@epfl.ch>
Link: https://lore.kernel.org/qemu-devel/20260305-setpc-v5-v7-6-4c3adba52403@epfl.ch
Signed-off-by: Pierrick Bouvier <pierrick.bouvier@linaro.org>
---
include/plugins/qemu-plugin.h | 3 +++
plugins/api.c | 16 ++++++++++++++++
2 files changed, 19 insertions(+)
diff --git a/include/plugins/qemu-plugin.h b/include/plugins/qemu-plugin.h
index 7b9cd6a9717..fceb8194eb8 100644
--- a/include/plugins/qemu-plugin.h
+++ b/include/plugins/qemu-plugin.h
@@ -979,11 +979,14 @@ struct qemu_plugin_register;
* writing value with qemu_plugin_write_register
* @name: register name
* @feature: optional feature descriptor, can be NULL
+ * @is_readonly: true if the register cannot be written via
+ * qemu_plugin_write_register
*/
typedef struct {
struct qemu_plugin_register *handle;
const char *name;
const char *feature;
+ bool is_readonly;
} qemu_plugin_reg_descriptor;
/**
diff --git a/plugins/api.c b/plugins/api.c
index 23c291f6444..85b34949cbb 100644
--- a/plugins/api.c
+++ b/plugins/api.c
@@ -410,6 +410,12 @@ bool qemu_plugin_bool_parse(const char *name, const char *value, bool *ret)
* ancillary data the plugin might find useful.
*/
+static const char pc_str[] = "pc"; /* generic name for program counter */
+static const char eip_str[] = "eip"; /* x86-specific name for PC */
+static const char rip_str[] = "rip"; /* x86_64-specific name for PC */
+static const char pswa_str[] = "pswa"; /* s390x-specific name for PC */
+static const char iaoq_str[] = "iaoq"; /* HP/PA-specific name for PC */
+static const char rpc_str[] = "rpc"; /* microblaze-specific name for PC */
static GArray *create_register_handles(GArray *gdbstub_regs)
{
GArray *find_data = g_array_new(true, true,
@@ -427,6 +433,16 @@ static GArray *create_register_handles(GArray *gdbstub_regs)
/* Create a record for the plugin */
desc.handle = GINT_TO_POINTER(grd->gdb_reg + 1);
desc.name = g_intern_string(grd->name);
+ desc.is_readonly = false;
+ if (g_strcmp0(desc.name, pc_str) == 0
+ || g_strcmp0(desc.name, eip_str) == 0
+ || g_strcmp0(desc.name, rip_str) == 0
+ || g_strcmp0(desc.name, pswa_str) == 0
+ || g_strcmp0(desc.name, iaoq_str) == 0
+ || g_strcmp0(desc.name, rpc_str) == 0
+ ) {
+ desc.is_readonly = true;
+ }
desc.feature = g_intern_string(grd->feature_name);
g_array_append_val(find_data, desc);
}
--
2.47.3
^ permalink raw reply related [flat|nested] 14+ messages in thread
* [PULL 07/12] plugins: prohibit writing to read-only registers
2026-03-05 20:51 [PULL 00/12] Plugins update 2025-03-05 Pierrick Bouvier
` (5 preceding siblings ...)
2026-03-05 20:51 ` [PULL 06/12] plugins: add read-only property for registers Pierrick Bouvier
@ 2026-03-05 20:51 ` Pierrick Bouvier
2026-03-05 20:51 ` [PULL 08/12] tests/tcg/plugins: test register accesses Pierrick Bouvier
` (5 subsequent siblings)
12 siblings, 0 replies; 14+ messages in thread
From: Pierrick Bouvier @ 2026-03-05 20:51 UTC (permalink / raw)
To: qemu-devel, peter.maydell, richard.henderson, pbonzini, stefanha
Cc: pierrick.bouvier
From: Florian Hofhammer <florian.hofhammer@epfl.ch>
The opaque register handle encodes whether a register is read-only in
the lowest bit and prevents writing to the register via the plugin API
in this case.
Reviewed-by: Alex Bennée <alex.bennee@linaro.org>
Reviewed-by: Pierrick Bouvier <pierrick.bouvier@linaro.org>
Signed-off-by: Florian Hofhammer <florian.hofhammer@epfl.ch>
Link: https://lore.kernel.org/qemu-devel/20260305-setpc-v5-v7-7-4c3adba52403@epfl.ch
Signed-off-by: Pierrick Bouvier <pierrick.bouvier@linaro.org>
---
plugins/api.c | 11 ++++++++---
1 file changed, 8 insertions(+), 3 deletions(-)
diff --git a/plugins/api.c b/plugins/api.c
index 85b34949cbb..0c348a789b2 100644
--- a/plugins/api.c
+++ b/plugins/api.c
@@ -424,6 +424,7 @@ static GArray *create_register_handles(GArray *gdbstub_regs)
for (int i = 0; i < gdbstub_regs->len; i++) {
GDBRegDesc *grd = &g_array_index(gdbstub_regs, GDBRegDesc, i);
qemu_plugin_reg_descriptor desc;
+ gint plugin_ro_bit = 0;
/* skip "un-named" regs */
if (!grd->name) {
@@ -431,7 +432,6 @@ static GArray *create_register_handles(GArray *gdbstub_regs)
}
/* Create a record for the plugin */
- desc.handle = GINT_TO_POINTER(grd->gdb_reg + 1);
desc.name = g_intern_string(grd->name);
desc.is_readonly = false;
if (g_strcmp0(desc.name, pc_str) == 0
@@ -442,7 +442,9 @@ static GArray *create_register_handles(GArray *gdbstub_regs)
|| g_strcmp0(desc.name, rpc_str) == 0
) {
desc.is_readonly = true;
+ plugin_ro_bit = 1;
}
+ desc.handle = GINT_TO_POINTER((grd->gdb_reg << 1) | plugin_ro_bit);
desc.feature = g_intern_string(grd->feature_name);
g_array_append_val(find_data, desc);
}
@@ -467,7 +469,7 @@ bool qemu_plugin_read_register(struct qemu_plugin_register *reg,
return false;
}
- return (gdb_read_register(current_cpu, buf, GPOINTER_TO_INT(reg) - 1) > 0);
+ return (gdb_read_register(current_cpu, buf, GPOINTER_TO_INT(reg) >> 1) > 0);
}
bool qemu_plugin_write_register(struct qemu_plugin_register *reg,
@@ -475,13 +477,16 @@ bool qemu_plugin_write_register(struct qemu_plugin_register *reg,
{
g_assert(current_cpu);
+ /* Read-only property is encoded in least significant bit */
+ g_assert((GPOINTER_TO_INT(reg) & 1) == 0);
+
if (buf->len == 0 ||
(qemu_plugin_get_cb_flags() != QEMU_PLUGIN_CB_RW_REGS &&
qemu_plugin_get_cb_flags() != QEMU_PLUGIN_CB_RW_REGS_PC)) {
return false;
}
- return (gdb_write_register(current_cpu, buf->data, GPOINTER_TO_INT(reg) - 1) > 0);
+ return (gdb_write_register(current_cpu, buf->data, GPOINTER_TO_INT(reg) >> 1) > 0);
}
void qemu_plugin_set_pc(uint64_t vaddr)
--
2.47.3
^ permalink raw reply related [flat|nested] 14+ messages in thread
* [PULL 08/12] tests/tcg/plugins: test register accesses
2026-03-05 20:51 [PULL 00/12] Plugins update 2025-03-05 Pierrick Bouvier
` (6 preceding siblings ...)
2026-03-05 20:51 ` [PULL 07/12] plugins: prohibit writing to read-only registers Pierrick Bouvier
@ 2026-03-05 20:51 ` Pierrick Bouvier
2026-03-05 20:51 ` [PULL 09/12] plugins: add missing callbacks to version history Pierrick Bouvier
` (4 subsequent siblings)
12 siblings, 0 replies; 14+ messages in thread
From: Pierrick Bouvier @ 2026-03-05 20:51 UTC (permalink / raw)
To: qemu-devel, peter.maydell, richard.henderson, pbonzini, stefanha
Cc: pierrick.bouvier
From: Florian Hofhammer <florian.hofhammer@epfl.ch>
The additional plugin tests register accesses, specifically both for
read-only and read-write registers. Writing to a read-only register is
currently not tested, as this would trigger an assertion and fail the
test.
Reviewed-by: Pierrick Bouvier <pierrick.bouvier@linaro.org>
Signed-off-by: Florian Hofhammer <florian.hofhammer@epfl.ch>
Link: https://lore.kernel.org/qemu-devel/20260305-setpc-v5-v7-8-4c3adba52403@epfl.ch
Signed-off-by: Pierrick Bouvier <pierrick.bouvier@linaro.org>
---
tests/tcg/plugins/registers.c | 79 +++++++++++++++++++++++++++++++++++
tests/tcg/plugins/meson.build | 1 +
2 files changed, 80 insertions(+)
create mode 100644 tests/tcg/plugins/registers.c
diff --git a/tests/tcg/plugins/registers.c b/tests/tcg/plugins/registers.c
new file mode 100644
index 00000000000..6d627c70371
--- /dev/null
+++ b/tests/tcg/plugins/registers.c
@@ -0,0 +1,79 @@
+/*
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * Copyright (C) 2026, Florian Hofhammer <florian.hofhammer@epfl.ch>
+ */
+#include "glib.h"
+#include <inttypes.h>
+#include <assert.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <stdio.h>
+
+#include <qemu-plugin.h>
+
+QEMU_PLUGIN_EXPORT int qemu_plugin_version = QEMU_PLUGIN_VERSION;
+
+/*
+ * This plugin tests whether we can read and write registers via the plugin
+ * API. We try to just read/write a single register, as some architectures have
+ * registers that cannot be written to, which would fail the test.
+ * See: https://lists.gnu.org/archive/html/qemu-devel/2026-02/msg07025.html
+ */
+static void vcpu_init_cb(qemu_plugin_id_t id, unsigned int vcpu_index)
+{
+ g_autoptr(GArray) regs = qemu_plugin_get_registers();
+ g_assert(regs != NULL);
+ g_autoptr(GByteArray) buf = g_byte_array_sized_new(0);
+ qemu_plugin_reg_descriptor *reg_desc = NULL;
+ bool success = false;
+
+ /* Make sure we can read and write a register not marked as readonly */
+ for (size_t i = 0; i < regs->len; i++) {
+ reg_desc = &g_array_index(regs, qemu_plugin_reg_descriptor, i);
+ if (!reg_desc->is_readonly) {
+ g_byte_array_set_size(buf, 0);
+ success = qemu_plugin_read_register(reg_desc->handle, buf);
+ g_assert(success);
+ g_assert(buf->len > 0);
+ success = qemu_plugin_write_register(reg_desc->handle, buf);
+ g_assert(success);
+ break;
+ } else {
+ reg_desc = NULL;
+ }
+ }
+ g_assert(regs->len == 0 || reg_desc != NULL);
+
+ /*
+ * Check whether we can still read a read-only register. On each
+ * architecture, at least the PC should be read-only because it's only
+ * supposed to be modified via the qemu_plugin_set_pc() function.
+ */
+ for (size_t i = 0; i < regs->len; i++) {
+ reg_desc = &g_array_index(regs, qemu_plugin_reg_descriptor, i);
+ if (reg_desc->is_readonly) {
+ g_byte_array_set_size(buf, 0);
+ success = qemu_plugin_read_register(reg_desc->handle, buf);
+ g_assert(success);
+ g_assert(buf->len > 0);
+ break;
+ } else {
+ reg_desc = NULL;
+ }
+ }
+ g_assert(regs->len == 0 || reg_desc != NULL);
+ /*
+ * Note: we currently do not test whether the read-only register can be
+ * written to, because doing so would throw an assert in the plugin API.
+ */
+}
+
+QEMU_PLUGIN_EXPORT int qemu_plugin_install(qemu_plugin_id_t id,
+ const qemu_info_t *info,
+ int argc, char **argv)
+{
+ qemu_plugin_register_vcpu_init_cb(id, vcpu_init_cb);
+ return 0;
+}
diff --git a/tests/tcg/plugins/meson.build b/tests/tcg/plugins/meson.build
index b3e3a9a6d02..d7f8f0ae0ad 100644
--- a/tests/tcg/plugins/meson.build
+++ b/tests/tcg/plugins/meson.build
@@ -6,6 +6,7 @@ test_plugins = [
'insn.c',
'mem.c',
'patch.c',
+'registers.c',
'reset.c',
'setpc.c',
'syscall.c',
--
2.47.3
^ permalink raw reply related [flat|nested] 14+ messages in thread
* [PULL 09/12] plugins: add missing callbacks to version history
2026-03-05 20:51 [PULL 00/12] Plugins update 2025-03-05 Pierrick Bouvier
` (7 preceding siblings ...)
2026-03-05 20:51 ` [PULL 08/12] tests/tcg/plugins: test register accesses Pierrick Bouvier
@ 2026-03-05 20:51 ` Pierrick Bouvier
2026-03-05 20:51 ` [PULL 10/12] tests/tcg/plugins/mem: Don't access unaligned memory Pierrick Bouvier
` (3 subsequent siblings)
12 siblings, 0 replies; 14+ messages in thread
From: Pierrick Bouvier @ 2026-03-05 20:51 UTC (permalink / raw)
To: qemu-devel, peter.maydell, richard.henderson, pbonzini, stefanha
Cc: pierrick.bouvier
From: Florian Hofhammer <florian.hofhammer@epfl.ch>
The discontinuity and system call filter callbacks were not reflected in
the versioning comments before. The callbacks have been introduced in
aac73d85d2d6f556dbcee6041a2898cb0ef9b0e6 and
5ed628d1d398b164053f5d5685541ea705275998, respectively.
Signed-off-by: Florian Hofhammer <florian.hofhammer@epfl.ch>
Reviewed-by: Pierrick Bouvier <pierrick.bouvier@linaro.org>
Link: https://lore.kernel.org/qemu-devel/c4ecefb4-8769-403f-8420-8bce42e43e13@epfl.ch
Signed-off-by: Pierrick Bouvier <pierrick.bouvier@linaro.org>
---
include/plugins/qemu-plugin.h | 3 +++
1 file changed, 3 insertions(+)
diff --git a/include/plugins/qemu-plugin.h b/include/plugins/qemu-plugin.h
index fceb8194eb8..827e8e17877 100644
--- a/include/plugins/qemu-plugin.h
+++ b/include/plugins/qemu-plugin.h
@@ -77,6 +77,9 @@ typedef uint64_t qemu_plugin_id_t;
* version 6:
* - changed return value of qemu_plugin_{read,write}_register from int to bool
* - added qemu_plugin_set_pc
+ * - added disconinuity callback API (for interrupts, exceptions, host calls)
+ * - added syscall filter callback API, which allows skipping syscalls and
+ * setting custom syscall return values
*/
extern QEMU_PLUGIN_EXPORT int qemu_plugin_version;
--
2.47.3
^ permalink raw reply related [flat|nested] 14+ messages in thread
* [PULL 10/12] tests/tcg/plugins/mem: Don't access unaligned memory
2026-03-05 20:51 [PULL 00/12] Plugins update 2025-03-05 Pierrick Bouvier
` (8 preceding siblings ...)
2026-03-05 20:51 ` [PULL 09/12] plugins: add missing callbacks to version history Pierrick Bouvier
@ 2026-03-05 20:51 ` Pierrick Bouvier
2026-03-05 20:51 ` [PULL 11/12] tests/tcg/plugins/mem: Correct hash iteration code in plugin_exit() Pierrick Bouvier
` (2 subsequent siblings)
12 siblings, 0 replies; 14+ messages in thread
From: Pierrick Bouvier @ 2026-03-05 20:51 UTC (permalink / raw)
To: qemu-devel, peter.maydell, richard.henderson, pbonzini, stefanha
Cc: pierrick.bouvier
From: Peter Maydell <peter.maydell@linaro.org>
In commit eb3f69cac62670 we removed the dependency of this mem plugin
on the QEMU headers, but in doing that we introduced undefined
behaviour when the plugin accesses unaligned memory. This shows up
if you build with the gcc or clang undefined behaviour sanitizer
(--enable-ubsan) and run 'make check-tcg', in numerous warnings like:
../../tests/tcg/plugins/mem.c:167:27: runtime error: load of misaligned address 0x7f1f300354b1 for type 'uint16_t' (aka 'unsigned short'), which requires 2 byte alignment
0x7f1f300354b1: note: pointer points here
00 00 00 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10 11 12 13 14 15 16 17 18 19 1a 1b 1c
^
SUMMARY: UndefinedBehaviorSanitizer: undefined-behavior ../../tests/tcg/plugins/mem.c:167:27
Fix this by rearranging the data reads and writes to use
memcpy() instead.
Fixes: eb3f69cac62670 ("tests/tcg/plugins/mem.c: remove dependency on qemu headers")
Tested-by: Alex Bennée <alex.bennee@linaro.org>
Reviewed-by: Alex Bennée <alex.bennee@linaro.org>
Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
Reviewed-by: Pierrick Bouvier <pierrick.bouvier@linaro.org>
Link: https://lore.kernel.org/qemu-devel/20260305161531.1774895-2-peter.maydell@linaro.org
Signed-off-by: Pierrick Bouvier <pierrick.bouvier@linaro.org>
---
tests/tcg/plugins/mem.c | 71 +++++++++++++++++------------------------
1 file changed, 30 insertions(+), 41 deletions(-)
diff --git a/tests/tcg/plugins/mem.c b/tests/tcg/plugins/mem.c
index 7d64e7018f2..f3992abc8fb 100644
--- a/tests/tcg/plugins/mem.c
+++ b/tests/tcg/plugins/mem.c
@@ -123,6 +123,9 @@ static void update_region_info(uint64_t region, uint64_t offset,
bool is_store = qemu_plugin_mem_is_store(meminfo);
RegionInfo *ri;
bool unseen_data = false;
+ void *val_ptr;
+ unsigned int val_size;
+ qemu_plugin_mem_value swapped_value;
g_assert(offset + size <= region_size);
@@ -144,61 +147,46 @@ static void update_region_info(uint64_t region, uint64_t offset,
}
void *ri_data = &ri->data[offset];
+
+ swapped_value.type = value.type;
switch (value.type) {
case QEMU_PLUGIN_MEM_VALUE_U8:
- {
- uint8_t val = value.data.u8;
- uint8_t *p = ri_data;
- if (is_store) {
- *p = val;
- } else {
- unseen_data = *p != val;
- }
+ swapped_value.data.u8 = value.data.u8;
+ val_ptr = &swapped_value.data.u8;
+ val_size = 1;
break;
- }
case QEMU_PLUGIN_MEM_VALUE_U16:
- {
- uint16_t val = be ? GUINT16_FROM_BE(value.data.u16) :
- GUINT16_FROM_LE(value.data.u16);
- uint16_t *p = ri_data;
- if (is_store) {
- *p = val;
- } else {
- unseen_data = *p != val;
- }
+ swapped_value.data.u16 = be ? GUINT16_FROM_BE(value.data.u16) :
+ GUINT16_FROM_LE(value.data.u16);
+ val_ptr = &swapped_value.data.u16;
+ val_size = 2;
break;
- }
case QEMU_PLUGIN_MEM_VALUE_U32:
- {
- uint32_t val = be ? GUINT32_FROM_BE(value.data.u32) :
- GUINT32_FROM_LE(value.data.u32);
- uint32_t *p = ri_data;
- if (is_store) {
- *p = val;
- } else {
- unseen_data = *p != val;
- }
+ swapped_value.data.u32 = be ? GUINT32_FROM_BE(value.data.u32) :
+ GUINT32_FROM_LE(value.data.u32);
+ val_ptr = &swapped_value.data.u32;
+ val_size = 4;
break;
- }
case QEMU_PLUGIN_MEM_VALUE_U64:
- {
- uint64_t val = be ? GUINT64_FROM_BE(value.data.u64) :
- GUINT64_FROM_LE(value.data.u64);
- uint64_t *p = ri_data;
- if (is_store) {
- *p = val;
- } else {
- unseen_data = *p != val;
- }
+ swapped_value.data.u64 = be ? GUINT64_FROM_BE(value.data.u64) :
+ GUINT64_FROM_LE(value.data.u64);
+ val_ptr = &swapped_value.data.u64;
+ val_size = 8;
break;
- }
case QEMU_PLUGIN_MEM_VALUE_U128:
- /* non in test so skip */
- break;
+ /* none in test so skip */
+ goto done;
default:
g_assert_not_reached();
}
+ /* ri_data may not be aligned, so we use memcpy/memcmp */
+ if (is_store) {
+ memcpy(ri_data, val_ptr, val_size);
+ } else {
+ unseen_data = memcmp(ri_data, val_ptr, val_size) != 0;
+ }
+
/*
* This is expected for regions initialised by QEMU (.text etc) but we
* expect to see all data read and written to the test_data region
@@ -213,6 +201,7 @@ static void update_region_info(uint64_t region, uint64_t offset,
ri->seen_all = false;
}
+done:
g_mutex_unlock(&lock);
}
--
2.47.3
^ permalink raw reply related [flat|nested] 14+ messages in thread
* [PULL 11/12] tests/tcg/plugins/mem: Correct hash iteration code in plugin_exit()
2026-03-05 20:51 [PULL 00/12] Plugins update 2025-03-05 Pierrick Bouvier
` (9 preceding siblings ...)
2026-03-05 20:51 ` [PULL 10/12] tests/tcg/plugins/mem: Don't access unaligned memory Pierrick Bouvier
@ 2026-03-05 20:51 ` Pierrick Bouvier
2026-03-05 20:51 ` [PULL 12/12] tests/tcg/plugins/patch: Free read_data in patch_hwaddr() Pierrick Bouvier
2026-03-06 15:00 ` [PULL 00/12] Plugins update 2025-03-05 Peter Maydell
12 siblings, 0 replies; 14+ messages in thread
From: Pierrick Bouvier @ 2026-03-05 20:51 UTC (permalink / raw)
To: qemu-devel, peter.maydell, richard.henderson, pbonzini, stefanha
Cc: pierrick.bouvier
From: Peter Maydell <peter.maydell@linaro.org>
In plugin_exit() we call g_hash_table_get_values() to get a GList
which we look at to print some information. This code has
multiple issues:
* it names the local variable for the GList "count", which
shadows the "qemu_plugin_scoreboard *count". This isn't
incorrect, but it is unnecessarily confusing
* it doesn't free the list, and the leak sanitizer complains:
Indirect leak of 2328 byte(s) in 97 object(s) allocated from:
#0 0x5589b0b72293 in malloc (/home/pm215/qemu/build/x86-tgt-san/qemu-system-i386+0x1a2f293) (BuildId: 26964cad9e3f81d35fc144d7cc88b53adf6f60c7)
#1 0x78fd8cfa1ac9 in g_malloc (/lib/x86_64-linux-gnu/libglib-2.0.so.0+0x62ac9) (BuildId: 116e142b9b52c8a4dfd403e759e71ab8f95d8bb3)
#2 0x78fd8cf96e4a in g_list_prepend (/lib/x86_64-linux-gnu/libglib-2.0.so.0+0x57e4a) (BuildId: 116e142b9b52c8a4dfd403e759e71ab8f95d8bb3)
#3 0x78fd8cf8b318 in g_hash_table_get_values (/lib/x86_64-linux-gnu/libglib-2.0.so.0+0x4c318) (BuildId: 116e142b9b52c8a4dfd403e759e71ab8f95d8bb3)
#4 0x78fd84d1a90c in plugin_exit /home/pm215/qemu/build/x86-tgt-san/../../tests/tcg/plugins/mem.c:87:25
* in iterating through the list it updates "count", so by the
time we get to the end of the loop we no longer have a pointer
to the head of the list that we could use to free it
* it checks for the list being NULL twice (once in an if()
and once in the for() loop's "while" condition), which is
redundant
* it skips the loop if g_list_next(counts) is NULL, which means
it will wrongly skip the loop if the list has only one entry
Rewrite the iteration code to fix these problems.
Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
Reviewed-by: Pierrick Bouvier <pierrick.bouvier@linaro.org>
Link: https://lore.kernel.org/qemu-devel/20260305161531.1774895-3-peter.maydell@linaro.org
Signed-off-by: Pierrick Bouvier <pierrick.bouvier@linaro.org>
---
tests/tcg/plugins/mem.c | 24 +++++++++++-------------
1 file changed, 11 insertions(+), 13 deletions(-)
diff --git a/tests/tcg/plugins/mem.c b/tests/tcg/plugins/mem.c
index f3992abc8fb..1ee257f855b 100644
--- a/tests/tcg/plugins/mem.c
+++ b/tests/tcg/plugins/mem.c
@@ -84,24 +84,22 @@ static void plugin_exit(qemu_plugin_id_t id, void *p)
if (do_region_summary) {
- GList *counts = g_hash_table_get_values(regions);
+ g_autoptr(GList) regionlist = g_hash_table_get_values(regions);
- counts = g_list_sort_with_data(counts, addr_order, NULL);
+ regionlist = g_list_sort_with_data(regionlist, addr_order, NULL);
g_string_printf(out, "Region Base, Reads, Writes, Seen all\n");
- if (counts && g_list_next(counts)) {
- for (/* counts */; counts; counts = counts->next) {
- RegionInfo *ri = (RegionInfo *) counts->data;
+ for (GList *l = regionlist; l; l = g_list_next(l)) {
+ RegionInfo *ri = (RegionInfo *) l->data;
- g_string_append_printf(out,
- "0x%016"PRIx64", "
- "%"PRId64", %"PRId64", %s\n",
- ri->region_address,
- ri->reads,
- ri->writes,
- ri->seen_all ? "true" : "false");
- }
+ g_string_append_printf(out,
+ "0x%016"PRIx64", "
+ "%"PRId64", %"PRId64", %s\n",
+ ri->region_address,
+ ri->reads,
+ ri->writes,
+ ri->seen_all ? "true" : "false");
}
qemu_plugin_outs(out->str);
}
--
2.47.3
^ permalink raw reply related [flat|nested] 14+ messages in thread
* [PULL 12/12] tests/tcg/plugins/patch: Free read_data in patch_hwaddr()
2026-03-05 20:51 [PULL 00/12] Plugins update 2025-03-05 Pierrick Bouvier
` (10 preceding siblings ...)
2026-03-05 20:51 ` [PULL 11/12] tests/tcg/plugins/mem: Correct hash iteration code in plugin_exit() Pierrick Bouvier
@ 2026-03-05 20:51 ` Pierrick Bouvier
2026-03-06 15:00 ` [PULL 00/12] Plugins update 2025-03-05 Peter Maydell
12 siblings, 0 replies; 14+ messages in thread
From: Pierrick Bouvier @ 2026-03-05 20:51 UTC (permalink / raw)
To: qemu-devel, peter.maydell, richard.henderson, pbonzini, stefanha
Cc: pierrick.bouvier
From: Peter Maydell <peter.maydell@linaro.org>
In patch_hwaddr() we allocate a GByteArray for the data we read back
from the guest; however we forget to free it, and the leak sanitizer
complains:
Direct leak of 40 byte(s) in 1 object(s) allocated from:
#0 0x56c00ad48293 in malloc (/home/pm215/qemu/build/x86-tgt-san/qemu-system-x86_64+0x1a9f293) (BuildId: 62e2a7dbe5ff146b2fa14d26e24e443f1967edd9)
#1 0x7b3e4cc91ac9 in g_malloc (/lib/x86_64-linux-gnu/libglib-2.0.so.0+0x62ac9) (BuildId: 116e142b9b52c8a4dfd403e759e71ab8f95d8bb3)
#2 0x7b3e4cc54c12 in g_array_sized_new (/lib/x86_64-linux-gnu/libglib-2.0.so.0+0x25c12) (BuildId: 116e142b9b52c8a4dfd403e759e71ab8f95d8bb3)
#3 0x7b3e44b06b49 in patch_hwaddr /home/pm215/qemu/build/x86-tgt-san/../../tests/tcg/plugins/patch.c:68:29
Indirect leak of 16 byte(s) in 1 object(s) allocated from:
#0 0x56c00ad486b0 in realloc (/home/pm215/qemu/build/x86-tgt-san/qemu-system-x86_64+0x1a9f6b0) (BuildId: 62e2a7dbe5ff146b2fa14d26e24e443f1967edd9)
#1 0x7b3e4cc92819 in g_realloc (/lib/x86_64-linux-gnu/libglib-2.0.so.0+0x63819) (BuildId: 116e142b9b52c8a4dfd403e759e71ab8f95d8bb3)
#2 0x7b3e4cc54b36 (/lib/x86_64-linux-gnu/libglib-2.0.so.0+0x25b36) (BuildId: 116e142b9b52c8a4dfd403e759e71ab8f95d8bb3)
#3 0x7b3e4cc55276 in g_array_set_size (/lib/x86_64-linux-gnu/libglib-2.0.so.0+0x26276) (BuildId: 116e142b9b52c8a4dfd403e759e71ab8f95d8bb3)
#4 0x7b3e4cc55574 in g_byte_array_set_size (/lib/x86_64-linux-gnu/libglib-2.0.so.0+0x26574) (BuildId: 116e142b9b52c8a4dfd403e759e71ab8f95d8bb3)
#5 0x56c00be2ccc1 in qemu_plugin_read_memory_hwaddr /home/pm215/qemu/build/x86-tgt-san/../../plugins/api.c:524:5
Mark the variable as g_autoptr(), as we already do in the equivalent
code in patch_vaddr().
Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
Reviewed-by: Pierrick Bouvier <pierrick.bouvier@linaro.org>
Link: https://lore.kernel.org/qemu-devel/20260305161531.1774895-4-peter.maydell@linaro.org
Signed-off-by: Pierrick Bouvier <pierrick.bouvier@linaro.org>
---
tests/tcg/plugins/patch.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/tests/tcg/plugins/patch.c b/tests/tcg/plugins/patch.c
index 111c5c1f169..eba2f8b8d6c 100644
--- a/tests/tcg/plugins/patch.c
+++ b/tests/tcg/plugins/patch.c
@@ -65,7 +65,7 @@ static void patch_hwaddr(unsigned int vcpu_index, void *userdata)
return;
}
- GByteArray *read_data = g_byte_array_new();
+ g_autoptr(GByteArray) read_data = g_byte_array_new();
result = qemu_plugin_read_memory_hwaddr(addr, read_data,
patch_data->len);
--
2.47.3
^ permalink raw reply related [flat|nested] 14+ messages in thread
* Re: [PULL 00/12] Plugins update 2025-03-05
2026-03-05 20:51 [PULL 00/12] Plugins update 2025-03-05 Pierrick Bouvier
` (11 preceding siblings ...)
2026-03-05 20:51 ` [PULL 12/12] tests/tcg/plugins/patch: Free read_data in patch_hwaddr() Pierrick Bouvier
@ 2026-03-06 15:00 ` Peter Maydell
12 siblings, 0 replies; 14+ messages in thread
From: Peter Maydell @ 2026-03-06 15:00 UTC (permalink / raw)
To: Pierrick Bouvier; +Cc: qemu-devel, richard.henderson, pbonzini, stefanha
On Thu, 5 Mar 2026 at 20:52, Pierrick Bouvier
<pierrick.bouvier@linaro.org> wrote:
>
> The following changes since commit 314ff2e07ddc6163554077d68aed5d76a50b8e3d:
>
> Merge tag 'pull-request-2026-03-05' of https://gitlab.com/thuth/qemu into staging (2026-03-05 16:58:20 +0000)
>
> are available in the Git repository at:
>
> https://gitlab.com/pbo-linaro/qemu tags/pr-plugins-20260305
>
> for you to fetch changes up to aca77dfd90c8a17a2b3526c5cc871cc410357a5f:
>
> tests/tcg/plugins/patch: Free read_data in patch_hwaddr() (2026-03-05 10:54:09 -0800)
>
> ----------------------------------------------------------------
> Changes:
> - [PATCH v7 0/8] Enable PC diversion via the plugin API (Florian Hofhammer <florian.hofhammer@epfl.ch>)
> Link: https://lore.kernel.org/qemu-devel/20260305-setpc-v5-v7-0-4c3adba52403@epfl.ch
> - [PATCH trivial] plugins: add missing callbacks to version history (Florian Hofhammer <florian.hofhammer@epfl.ch>)
> Link: https://lore.kernel.org/qemu-devel/c4ecefb4-8769-403f-8420-8bce42e43e13@epfl.ch
> - [PATCH 0/3] tests/tcg/plugins: Fix sanitizer issues (Peter Maydell <peter.maydell@linaro.org>)
> Link: https://lore.kernel.org/qemu-devel/20260305161531.1774895-1-peter.maydell@linaro.org
>
Applied, thanks.
Please update the changelog at https://wiki.qemu.org/ChangeLog/11.0
for any user-visible changes.
-- PMM
^ permalink raw reply [flat|nested] 14+ messages in thread
end of thread, other threads:[~2026-03-06 15:06 UTC | newest]
Thread overview: 14+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-03-05 20:51 [PULL 00/12] Plugins update 2025-03-05 Pierrick Bouvier
2026-03-05 20:51 ` [PULL 01/12] plugins/core: clamp syscall arguments if target is 32-bit Pierrick Bouvier
2026-03-05 20:51 ` [PULL 02/12] plugins: add flag to specify whether PC is rw Pierrick Bouvier
2026-03-05 20:51 ` [PULL 03/12] linux-user: make syscall emulation interruptible Pierrick Bouvier
2026-03-05 20:51 ` [PULL 04/12] plugins: add PC diversion API function Pierrick Bouvier
2026-03-05 20:51 ` [PULL 05/12] tests/tcg: add tests for qemu_plugin_set_pc API Pierrick Bouvier
2026-03-05 20:51 ` [PULL 06/12] plugins: add read-only property for registers Pierrick Bouvier
2026-03-05 20:51 ` [PULL 07/12] plugins: prohibit writing to read-only registers Pierrick Bouvier
2026-03-05 20:51 ` [PULL 08/12] tests/tcg/plugins: test register accesses Pierrick Bouvier
2026-03-05 20:51 ` [PULL 09/12] plugins: add missing callbacks to version history Pierrick Bouvier
2026-03-05 20:51 ` [PULL 10/12] tests/tcg/plugins/mem: Don't access unaligned memory Pierrick Bouvier
2026-03-05 20:51 ` [PULL 11/12] tests/tcg/plugins/mem: Correct hash iteration code in plugin_exit() Pierrick Bouvier
2026-03-05 20:51 ` [PULL 12/12] tests/tcg/plugins/patch: Free read_data in patch_hwaddr() Pierrick Bouvier
2026-03-06 15:00 ` [PULL 00/12] Plugins update 2025-03-05 Peter Maydell
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox