* [RFC PATCH v2 0/2] Enable PC diversion via the plugin API
@ 2025-10-06 13:21 Florian Hofhammer
2025-10-06 13:22 ` [RFC PATCH v2 1/2] plugins: Add PC diversion API function Florian Hofhammer
` (2 more replies)
0 siblings, 3 replies; 16+ messages in thread
From: Florian Hofhammer @ 2025-10-06 13:21 UTC (permalink / raw)
To: qemu-devel
Cc: alex.bennee, pierrick.bouvier, richard.henderson, laurent, imp,
berrange
Hi,
As originally discussed in the thread at
https://lists.nongnu.org/archive/html/qemu-devel/2025-08/msg00656.html
and later proposed in a patch at
https://lists.nongnu.org/archive/html/qemu-devel/2025-09/msg02218.html,
I am sending an updated version of my patch based on the previous
feedback.
Notable changes to v1:
- Added a setjmp() in the syscall handling path to allow redirecting
the PC via cpu_loop_exit() also in syscall callbacks. The previous
version would only work in instruction execution / memory access
callback contexts, as the setjmp() corresponding to the longjmp() in
cpu_loop_exit() was only live in those contexts.
- Added a flag to make sure the new API function is only called in
contexts where it makes sense, i.e., during execution of guest code.
- Added a test that checks the new functionality by skipping a
non-existent sentinel syscall.
I made it an RFC patch this time as I am not entirely sure if my
setting/handling of the new flag makes sense the way it is. I briefly
looked into making the QEMU_PLUGIN_CB_* flags actual flags via a
bitfield instead of enum values, but that would have required touching
a lot of code all over the place, so I'm not sure this is the way to go.
Happy to get feedback and your thoughts on the patches!
Thanks,
Florian
Florian Hofhammer (2):
plugins: Add PC diversion API function
tests/tcg: add test for qemu_plugin_set_pc API
include/qemu/qemu-plugin.h | 15 +++++++
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 | 4 ++
linux-user/i386/cpu_loop.c | 8 ++--
linux-user/include/special-errno.h | 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 | 5 ++-
linux-user/openrisc/cpu_loop.c | 2 +-
linux-user/ppc/cpu_loop.c | 6 ++-
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 | 8 ++++
linux-user/xtensa/cpu_loop.c | 3 ++
plugins/api.c | 17 +++++++-
plugins/core.c | 25 ++++++-----
tests/tcg/multiarch/Makefile.target | 42 +++++++++++++++++++
.../tcg/multiarch/test-plugin-skip-syscalls.c | 26 ++++++++++++
tests/tcg/plugins/syscall.c | 6 +++
25 files changed, 170 insertions(+), 32 deletions(-)
create mode 100644 tests/tcg/multiarch/test-plugin-skip-syscalls.c
--
2.51.0
^ permalink raw reply [flat|nested] 16+ messages in thread
* [RFC PATCH v2 1/2] plugins: Add PC diversion API function
2025-10-06 13:21 [RFC PATCH v2 0/2] Enable PC diversion via the plugin API Florian Hofhammer
@ 2025-10-06 13:22 ` Florian Hofhammer
2025-12-12 12:35 ` Alex Bennée
2025-10-06 13:23 ` [RFC PATCH v2 2/2] tests/tcg: add test for qemu_plugin_set_pc API Florian Hofhammer
2025-10-29 15:57 ` [RFC PATCH v2 0/2] Enable PC diversion via the plugin API Florian Hofhammer
2 siblings, 1 reply; 16+ messages in thread
From: Florian Hofhammer @ 2025-10-06 13:22 UTC (permalink / raw)
To: qemu-devel
Cc: alex.bennee, pierrick.bouvier, richard.henderson, laurent, imp,
berrange
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.
Redirecting control flow via cpu_loop_exit() works fine in callbacks
that are triggered during code execution due to cpu_exec_setjmp() still
being part of the call stack. If we want to use the new API in syscall
callbacks, cpu_exec_setjmp() already returned and the longjmp()
triggered by cpu_loop_exit() is undefined behavior. For this reason, we
introduce a new return constant QEMU_ESETPC and do another setjmp()
before executing syscall plugin callbacks and potentially the syscall
itself.
Link: https://lists.nongnu.org/archive/html/qemu-devel/2025-08/msg00656.html
Signed-off-by: Florian Hofhammer <florian.hofhammer@epfl.ch>
---
include/qemu/qemu-plugin.h | 15 +++++++++++++++
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 | 4 ++++
linux-user/i386/cpu_loop.c | 8 +++++---
linux-user/include/special-errno.h | 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 | 5 +++--
linux-user/openrisc/cpu_loop.c | 2 +-
linux-user/ppc/cpu_loop.c | 6 ++++--
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 | 8 ++++++++
linux-user/xtensa/cpu_loop.c | 3 +++
plugins/api.c | 17 ++++++++++++++++-
plugins/core.c | 25 ++++++++++++++-----------
22 files changed, 96 insertions(+), 32 deletions(-)
diff --git a/include/qemu/qemu-plugin.h b/include/qemu/qemu-plugin.h
index c450106af1..be72ef9d70 100644
--- a/include/qemu/qemu-plugin.h
+++ b/include/qemu/qemu-plugin.h
@@ -261,11 +261,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 {
@@ -943,6 +946,18 @@ QEMU_PLUGIN_API
int 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 only returns in case of
+ * errors.
+ */
+QEMU_PLUGIN_API
+void qemu_plugin_set_pc(uint64_t vaddr);
+
/**
* qemu_plugin_read_memory_vaddr() - read from memory using a virtual address
*
diff --git a/linux-user/aarch64/cpu_loop.c b/linux-user/aarch64/cpu_loop.c
index 50a4c99535..d220d18696 100644
--- a/linux-user/aarch64/cpu_loop.c
+++ b/linux-user/aarch64/cpu_loop.c
@@ -176,7 +176,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 f93597c400..bef196b1f5 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 cd89b7d6f5..ef77b56785 100644
--- a/linux-user/arm/cpu_loop.c
+++ b/linux-user/arm/cpu_loop.c
@@ -416,7 +416,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 1941f4c9c1..9adb3ec4c6 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 356cb48acc..5c8d2577ef 100644
--- a/linux-user/hppa/cpu_loop.c
+++ b/linux-user/hppa/cpu_loop.c
@@ -17,6 +17,7 @@
* along with this program; if not, see <http://www.gnu.org/licenses/>.
*/
+#include "qemu/compiler.h"
#include "qemu/osdep.h"
#include "qemu.h"
#include "user-internals.h"
@@ -135,7 +136,10 @@ void cpu_loop(CPUHPPAState *env)
env->iaoq_b = env->iaoq_f + 4;
break;
case -QEMU_ERESTARTSYS:
+ QEMU_FALLTHROUGH;
case -QEMU_ESIGRETURN:
+ QEMU_FALLTHROUGH;
+ case -QEMU_ESETPC:
break;
}
break;
diff --git a/linux-user/i386/cpu_loop.c b/linux-user/i386/cpu_loop.c
index f3f58576af..fe922fceb5 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/include/special-errno.h b/linux-user/include/special-errno.h
index 4120455baa..1db757241a 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/loongarch64/cpu_loop.c b/linux-user/loongarch64/cpu_loop.c
index 26a5ce3a93..603fcc39c7 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 2c9f628241..b98ca8ff7b 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 78506ab23d..06d92c0b90 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 2365de1de1..af98138eb2 100644
--- a/linux-user/mips/cpu_loop.c
+++ b/linux-user/mips/cpu_loop.c
@@ -140,8 +140,9 @@ done_syscall:
env->active_tc.PC -= 4;
break;
}
- if (ret == -QEMU_ESIGRETURN) {
- /* Returning from a successful sigreturn syscall.
+ 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;
}
diff --git a/linux-user/openrisc/cpu_loop.c b/linux-user/openrisc/cpu_loop.c
index 2167d880d5..e7e9929e6f 100644
--- a/linux-user/openrisc/cpu_loop.c
+++ b/linux-user/openrisc/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 b0b0cb14b4..1f8aae14bb 100644
--- a/linux-user/ppc/cpu_loop.c
+++ b/linux-user/ppc/cpu_loop.c
@@ -340,8 +340,10 @@ void cpu_loop(CPUPPCState *env)
env->nip -= 4;
break;
}
- if (ret == (target_ulong)(-QEMU_ESIGRETURN)) {
- /* Returning from a successful sigreturn syscall.
+ 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;
}
diff --git a/linux-user/riscv/cpu_loop.c b/linux-user/riscv/cpu_loop.c
index ce542540c2..eecc8d1517 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 4929b32e1f..67d2a803fb 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 0c9d7e9c46..ee2958d0d9 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 7391e2add8..f054316dce 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 d78b2029fa..f74b8ac596 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>
@@ -584,6 +585,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 via qemu_plugin_set_pc";
+ }
return strerror(target_to_host_errno(err));
}
@@ -14077,6 +14081,10 @@ abi_long do_syscall(CPUArchState *cpu_env, int num, abi_long arg1,
return -QEMU_ESIGRETURN;
}
+ 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 a0ff10eff8..7680e243bb 100644
--- a/linux-user/xtensa/cpu_loop.c
+++ b/linux-user/xtensa/cpu_loop.c
@@ -17,6 +17,7 @@
* along with this program; if not, see <http://www.gnu.org/licenses/>.
*/
+#include "qemu/compiler.h"
#include "qemu/osdep.h"
#include "qemu.h"
#include "user-internals.h"
@@ -185,6 +186,8 @@ void cpu_loop(CPUXtensaState *env)
env->pc -= 3;
break;
+ case -QEMU_ESETPC:
+ QEMU_FALLTHROUGH;
case -QEMU_ESIGRETURN:
break;
}
diff --git a/plugins/api.c b/plugins/api.c
index eac04cc1f6..fc19bdb40b 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"
@@ -450,13 +451,27 @@ int 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 -1;
}
return gdb_write_register(current_cpu, buf->data, GPOINTER_TO_INT(reg) - 1);
}
+void qemu_plugin_set_pc(uint64_t vaddr)
+{
+ g_assert(current_cpu);
+
+ if (qemu_plugin_get_cb_flags() != QEMU_PLUGIN_CB_RW_REGS_PC) {
+ return;
+ }
+
+ 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/plugins/core.c b/plugins/core.c
index ead09fd2f1..b514293117 100644
--- a/plugins/core.c
+++ b/plugins/core.c
@@ -369,15 +369,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))
};
@@ -399,15 +400,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))
};
@@ -438,15 +440,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)
@@ -508,7 +511,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);
}
@@ -532,7 +535,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);
}
@@ -542,7 +545,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);
}
@@ -551,7 +554,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);
}
@@ -788,6 +791,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.51.0
^ permalink raw reply related [flat|nested] 16+ messages in thread
* [RFC PATCH v2 2/2] tests/tcg: add test for qemu_plugin_set_pc API
2025-10-06 13:21 [RFC PATCH v2 0/2] Enable PC diversion via the plugin API Florian Hofhammer
2025-10-06 13:22 ` [RFC PATCH v2 1/2] plugins: Add PC diversion API function Florian Hofhammer
@ 2025-10-06 13:23 ` Florian Hofhammer
2025-12-12 12:40 ` Alex Bennée
2025-10-29 15:57 ` [RFC PATCH v2 0/2] Enable PC diversion via the plugin API Florian Hofhammer
2 siblings, 1 reply; 16+ messages in thread
From: Florian Hofhammer @ 2025-10-06 13:23 UTC (permalink / raw)
To: qemu-devel
Cc: alex.bennee, pierrick.bouvier, richard.henderson, laurent, imp,
berrange
The test executes a non-existent syscall, which the syscall plugin
intercepts and redirects to a clean exit.
Due to architecture-specific quirks, the Makefile requires setting
specific compiler and linker flags for some architectures.
Signed-off-by: Florian Hofhammer <florian.hofhammer@epfl.ch>
---
tests/tcg/multiarch/Makefile.target | 42 +++++++++++++++++++
.../tcg/multiarch/test-plugin-skip-syscalls.c | 26 ++++++++++++
tests/tcg/plugins/syscall.c | 6 +++
3 files changed, 74 insertions(+)
create mode 100644 tests/tcg/multiarch/test-plugin-skip-syscalls.c
diff --git a/tests/tcg/multiarch/Makefile.target b/tests/tcg/multiarch/Makefile.target
index f5b4d2b813..7df3da2aba 100644
--- a/tests/tcg/multiarch/Makefile.target
+++ b/tests/tcg/multiarch/Makefile.target
@@ -204,6 +204,48 @@ run-plugin-test-plugin-mem-access-with-libmem.so: \
$(QEMU) $<
EXTRA_RUNS_WITH_PLUGIN += run-plugin-test-plugin-mem-access-with-libmem.so
+
+# Test plugin control flow redirection by skipping system calls
+ifeq ($(TARGET),arm-linux-user)
+# Require emitting arm32 instructions, otherwise the vCPU might accidentally
+# try to execute Thumb instructions in arm32 mode after qemu_plugin_set_pc()
+test-plugin-skip-syscalls: CFLAGS+=-marm
+endif
+ifneq ($(filter mips64%-linux-user,$(TARGET)),)
+# Require no ABI calls to avoid $t9-relative .got address calculation
+test-plugin-skip-syscalls: CFLAGS+=-mno-abicalls -fno-pie
+test-plugin-skip-syscalls: LDFLAGS+=-no-pie
+endif
+ifneq ($(filter mips%-linux-user,\
+ $(filter-out mips64%-linux-user,\
+ $(TARGET))),)
+# qemu-mips(el) returns ENOSYS without triggering syscall plugin callbacks
+run-plugin-test-plugin-skip-syscalls-with-libsyscall.so:
+ $(call skip-test, $<, "qemu-mips(el) does not execute invalid syscalls")
+endif
+ifeq ($(TARGET),sparc64-linux-user)
+# The defined addresses for the binary are not aligned correctly for sparc64
+# but adjusting them breaks other architectures, so just skip it on sparc64.
+run-plugin-test-plugin-skip-syscalls-with-libsyscall.so:
+ $(call skip-test, $<, "qemu-sparc64 does not allow mapping at our given fixed address")
+endif
+ifeq ($(TARGET),hexagon-linux-user)
+# hexagon uses clang/lld which does not support -Ttext-segment but GNU ld does
+# not generally support --image-base.
+test-plugin-skip-syscalls: LDFLAGS+=-Wl,--image-base=0x40000
+else
+test-plugin-skip-syscalls: LDFLAGS+=-Wl,-Ttext-segment=0x40000
+endif
+test-plugin-skip-syscalls: LDFLAGS+=-Wl,--section-start,.redirect=0x20000
+run-plugin-test-plugin-skip-syscalls-with-libsyscall.so:
+
+EXTRA_RUNS_WITH_PLUGIN += run-plugin-test-plugin-skip-syscalls-with-libsyscall.so
+
+else # CONFIG_PLUGIN=n
+# Do not build the syscall skipping test if it's not tested with a plugin
+# because it will simply return an error and fail the test.
+MULTIARCH_TESTS := $(filter-out "test-plugin-skip-syscalls", $(MULTIARCH_TESTS))
+
endif
# Update TESTS
diff --git a/tests/tcg/multiarch/test-plugin-skip-syscalls.c b/tests/tcg/multiarch/test-plugin-skip-syscalls.c
new file mode 100644
index 0000000000..1f5cbc3851
--- /dev/null
+++ b/tests/tcg/multiarch/test-plugin-skip-syscalls.c
@@ -0,0 +1,26 @@
+/*
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * This test attempts to execute an invalid syscall. The syscall test plugin
+ * should intercept this.
+ */
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+void exit_success(void) __attribute__((section(".redirect"), noinline,
+ noreturn, used));
+
+void exit_success(void) {
+ _exit(EXIT_SUCCESS);
+}
+
+int main(int argc, char *argv[]) {
+ long ret = syscall(0xc0deUL);
+ if (ret != 0L) {
+ perror("");
+ }
+ /* We should never get here */
+ return EXIT_FAILURE;
+}
diff --git a/tests/tcg/plugins/syscall.c b/tests/tcg/plugins/syscall.c
index 42801f5c86..c5bac2d928 100644
--- a/tests/tcg/plugins/syscall.c
+++ b/tests/tcg/plugins/syscall.c
@@ -148,6 +148,12 @@ static void vcpu_syscall(qemu_plugin_id_t id, unsigned int vcpu_index,
fprintf(stderr, "Error reading memory from vaddr %"PRIu64"\n", a2);
}
}
+
+ if (num == 0xc0deUL) {
+ /* Special syscall to test the control flow redirection functionality. */
+ qemu_plugin_outs("Marker syscall detected, jump to clean exit\n");
+ qemu_plugin_set_pc(0x20000);
+ }
}
static void vcpu_syscall_ret(qemu_plugin_id_t id, unsigned int vcpu_idx,
--
2.51.0
^ permalink raw reply related [flat|nested] 16+ messages in thread
* Re: [RFC PATCH v2 0/2] Enable PC diversion via the plugin API
2025-10-06 13:21 [RFC PATCH v2 0/2] Enable PC diversion via the plugin API Florian Hofhammer
2025-10-06 13:22 ` [RFC PATCH v2 1/2] plugins: Add PC diversion API function Florian Hofhammer
2025-10-06 13:23 ` [RFC PATCH v2 2/2] tests/tcg: add test for qemu_plugin_set_pc API Florian Hofhammer
@ 2025-10-29 15:57 ` Florian Hofhammer
2025-12-12 12:02 ` Alex Bennée
2 siblings, 1 reply; 16+ messages in thread
From: Florian Hofhammer @ 2025-10-29 15:57 UTC (permalink / raw)
To: qemu-devel
Cc: alex.bennee, pierrick.bouvier, richard.henderson, laurent, imp,
berrange
Hi,
Sorry for necrobumping this thread. I just wanted to follow up on this
and ask if there is still interest in this plugin API extension or if it
is going to be dropped in favor of the Lorelei patches (which would also
fulfill my original use case).
Thanks for your time,
Florian
On 06/10/2025 15:21, Florian Hofhammer wrote:
> Hi,
>
> As originally discussed in the thread at
> https://lists.nongnu.org/archive/html/qemu-devel/2025-08/msg00656.html
> and later proposed in a patch at
> https://lists.nongnu.org/archive/html/qemu-devel/2025-09/msg02218.html,
> I am sending an updated version of my patch based on the previous
> feedback.
>
> Notable changes to v1:
> - Added a setjmp() in the syscall handling path to allow redirecting
> the PC via cpu_loop_exit() also in syscall callbacks. The previous
> version would only work in instruction execution / memory access
> callback contexts, as the setjmp() corresponding to the longjmp() in
> cpu_loop_exit() was only live in those contexts.
> - Added a flag to make sure the new API function is only called in
> contexts where it makes sense, i.e., during execution of guest code.
> - Added a test that checks the new functionality by skipping a
> non-existent sentinel syscall.
>
> I made it an RFC patch this time as I am not entirely sure if my
> setting/handling of the new flag makes sense the way it is. I briefly
> looked into making the QEMU_PLUGIN_CB_* flags actual flags via a
> bitfield instead of enum values, but that would have required touching
> a lot of code all over the place, so I'm not sure this is the way to go.
>
> Happy to get feedback and your thoughts on the patches!
>
> Thanks,
> Florian
>
>
> Florian Hofhammer (2):
> plugins: Add PC diversion API function
> tests/tcg: add test for qemu_plugin_set_pc API
>
> include/qemu/qemu-plugin.h | 15 +++++++
> 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 | 4 ++
> linux-user/i386/cpu_loop.c | 8 ++--
> linux-user/include/special-errno.h | 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 | 5 ++-
> linux-user/openrisc/cpu_loop.c | 2 +-
> linux-user/ppc/cpu_loop.c | 6 ++-
> 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 | 8 ++++
> linux-user/xtensa/cpu_loop.c | 3 ++
> plugins/api.c | 17 +++++++-
> plugins/core.c | 25 ++++++-----
> tests/tcg/multiarch/Makefile.target | 42 +++++++++++++++++++
> .../tcg/multiarch/test-plugin-skip-syscalls.c | 26 ++++++++++++
> tests/tcg/plugins/syscall.c | 6 +++
> 25 files changed, 170 insertions(+), 32 deletions(-)
> create mode 100644 tests/tcg/multiarch/test-plugin-skip-syscalls.c
>
^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [RFC PATCH v2 0/2] Enable PC diversion via the plugin API
2025-10-29 15:57 ` [RFC PATCH v2 0/2] Enable PC diversion via the plugin API Florian Hofhammer
@ 2025-12-12 12:02 ` Alex Bennée
2025-12-12 17:36 ` Pierrick Bouvier
2025-12-16 9:19 ` Florian Hofhammer
0 siblings, 2 replies; 16+ messages in thread
From: Alex Bennée @ 2025-12-12 12:02 UTC (permalink / raw)
To: Florian Hofhammer
Cc: qemu-devel, pierrick.bouvier, richard.henderson, laurent, imp,
berrange
Florian Hofhammer <florian.hofhammer@epfl.ch> writes:
> Hi,
>
> Sorry for necrobumping this thread. I just wanted to follow up on this
> and ask if there is still interest in this plugin API extension or if it
> is going to be dropped in favor of the Lorelei patches (which would also
> fulfill my original use case).
These are the system call filter patches?
I guess that depends on if being able to change PC is only for skipping
syscalls?
>
> Thanks for your time,
> Florian
>
> On 06/10/2025 15:21, Florian Hofhammer wrote:
>> Hi,
>>
>> As originally discussed in the thread at
>> https://lists.nongnu.org/archive/html/qemu-devel/2025-08/msg00656.html
>> and later proposed in a patch at
>> https://lists.nongnu.org/archive/html/qemu-devel/2025-09/msg02218.html,
>> I am sending an updated version of my patch based on the previous
>> feedback.
>>
>> Notable changes to v1:
>> - Added a setjmp() in the syscall handling path to allow redirecting
>> the PC via cpu_loop_exit() also in syscall callbacks. The previous
>> version would only work in instruction execution / memory access
>> callback contexts, as the setjmp() corresponding to the longjmp() in
>> cpu_loop_exit() was only live in those contexts.
>> - Added a flag to make sure the new API function is only called in
>> contexts where it makes sense, i.e., during execution of guest code.
>> - Added a test that checks the new functionality by skipping a
>> non-existent sentinel syscall.
>>
>> I made it an RFC patch this time as I am not entirely sure if my
>> setting/handling of the new flag makes sense the way it is. I briefly
>> looked into making the QEMU_PLUGIN_CB_* flags actual flags via a
>> bitfield instead of enum values, but that would have required touching
>> a lot of code all over the place, so I'm not sure this is the way to go.
>>
>> Happy to get feedback and your thoughts on the patches!
>>
>> Thanks,
>> Florian
>>
>>
>> Florian Hofhammer (2):
>> plugins: Add PC diversion API function
>> tests/tcg: add test for qemu_plugin_set_pc API
>>
>> include/qemu/qemu-plugin.h | 15 +++++++
>> 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 | 4 ++
>> linux-user/i386/cpu_loop.c | 8 ++--
>> linux-user/include/special-errno.h | 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 | 5 ++-
>> linux-user/openrisc/cpu_loop.c | 2 +-
>> linux-user/ppc/cpu_loop.c | 6 ++-
>> 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 | 8 ++++
>> linux-user/xtensa/cpu_loop.c | 3 ++
>> plugins/api.c | 17 +++++++-
>> plugins/core.c | 25 ++++++-----
>> tests/tcg/multiarch/Makefile.target | 42 +++++++++++++++++++
>> .../tcg/multiarch/test-plugin-skip-syscalls.c | 26 ++++++++++++
>> tests/tcg/plugins/syscall.c | 6 +++
>> 25 files changed, 170 insertions(+), 32 deletions(-)
>> create mode 100644 tests/tcg/multiarch/test-plugin-skip-syscalls.c
>>
--
Alex Bennée
Virtualisation Tech Lead @ Linaro
^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [RFC PATCH v2 1/2] plugins: Add PC diversion API function
2025-10-06 13:22 ` [RFC PATCH v2 1/2] plugins: Add PC diversion API function Florian Hofhammer
@ 2025-12-12 12:35 ` Alex Bennée
2025-12-16 9:27 ` Florian Hofhammer
0 siblings, 1 reply; 16+ messages in thread
From: Alex Bennée @ 2025-12-12 12:35 UTC (permalink / raw)
To: Florian Hofhammer
Cc: qemu-devel, pierrick.bouvier, richard.henderson, laurent, imp,
berrange
Florian Hofhammer <florian.hofhammer@epfl.ch> writes:
> 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.
>
> Redirecting control flow via cpu_loop_exit() works fine in callbacks
> that are triggered during code execution due to cpu_exec_setjmp() still
> being part of the call stack. If we want to use the new API in syscall
> callbacks, cpu_exec_setjmp() already returned and the longjmp()
> triggered by cpu_loop_exit() is undefined behavior. For this reason, we
> introduce a new return constant QEMU_ESETPC and do another setjmp()
> before executing syscall plugin callbacks and potentially the syscall
> itself.
>
> Link: https://lists.nongnu.org/archive/html/qemu-devel/2025-08/msg00656.html
>
> Signed-off-by: Florian Hofhammer <florian.hofhammer@epfl.ch>
> ---
> include/qemu/qemu-plugin.h | 15 +++++++++++++++
> 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 | 4 ++++
> linux-user/i386/cpu_loop.c | 8 +++++---
> linux-user/include/special-errno.h | 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 | 5 +++--
> linux-user/openrisc/cpu_loop.c | 2 +-
> linux-user/ppc/cpu_loop.c | 6 ++++--
> 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 | 8 ++++++++
> linux-user/xtensa/cpu_loop.c | 3 +++
> plugins/api.c | 17 ++++++++++++++++-
> plugins/core.c | 25 ++++++++++++++-----------
> 22 files changed, 96 insertions(+), 32 deletions(-)
>
> diff --git a/include/qemu/qemu-plugin.h b/include/qemu/qemu-plugin.h
> index c450106af1..be72ef9d70 100644
> --- a/include/qemu/qemu-plugin.h
> +++ b/include/qemu/qemu-plugin.h
> @@ -261,11 +261,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 {
> @@ -943,6 +946,18 @@ QEMU_PLUGIN_API
> int 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 only returns in case of
> + * errors.
> + */
> +QEMU_PLUGIN_API
> +void qemu_plugin_set_pc(uint64_t vaddr);
> +
> /**
> * qemu_plugin_read_memory_vaddr() - read from memory using a virtual address
> *
> diff --git a/linux-user/aarch64/cpu_loop.c b/linux-user/aarch64/cpu_loop.c
> index 50a4c99535..d220d18696 100644
> --- a/linux-user/aarch64/cpu_loop.c
> +++ b/linux-user/aarch64/cpu_loop.c
> @@ -176,7 +176,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 f93597c400..bef196b1f5 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 cd89b7d6f5..ef77b56785 100644
> --- a/linux-user/arm/cpu_loop.c
> +++ b/linux-user/arm/cpu_loop.c
> @@ -416,7 +416,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 1941f4c9c1..9adb3ec4c6 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 356cb48acc..5c8d2577ef 100644
> --- a/linux-user/hppa/cpu_loop.c
> +++ b/linux-user/hppa/cpu_loop.c
> @@ -17,6 +17,7 @@
> * along with this program; if not, see <http://www.gnu.org/licenses/>.
> */
>
> +#include "qemu/compiler.h"
> #include "qemu/osdep.h"
> #include "qemu.h"
> #include "user-internals.h"
> @@ -135,7 +136,10 @@ void cpu_loop(CPUHPPAState *env)
> env->iaoq_b = env->iaoq_f + 4;
> break;
> case -QEMU_ERESTARTSYS:
> + QEMU_FALLTHROUGH;
> case -QEMU_ESIGRETURN:
> + QEMU_FALLTHROUGH;
> + case -QEMU_ESETPC:
> break;
> }
> break;
> diff --git a/linux-user/i386/cpu_loop.c b/linux-user/i386/cpu_loop.c
> index f3f58576af..fe922fceb5 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/include/special-errno.h b/linux-user/include/special-errno.h
> index 4120455baa..1db757241a 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/loongarch64/cpu_loop.c b/linux-user/loongarch64/cpu_loop.c
> index 26a5ce3a93..603fcc39c7 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 2c9f628241..b98ca8ff7b 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 78506ab23d..06d92c0b90 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 2365de1de1..af98138eb2 100644
> --- a/linux-user/mips/cpu_loop.c
> +++ b/linux-user/mips/cpu_loop.c
> @@ -140,8 +140,9 @@ done_syscall:
> env->active_tc.PC -= 4;
> break;
> }
> - if (ret == -QEMU_ESIGRETURN) {
> - /* Returning from a successful sigreturn syscall.
> + 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;
> }
> diff --git a/linux-user/openrisc/cpu_loop.c b/linux-user/openrisc/cpu_loop.c
> index 2167d880d5..e7e9929e6f 100644
> --- a/linux-user/openrisc/cpu_loop.c
> +++ b/linux-user/openrisc/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 b0b0cb14b4..1f8aae14bb 100644
> --- a/linux-user/ppc/cpu_loop.c
> +++ b/linux-user/ppc/cpu_loop.c
> @@ -340,8 +340,10 @@ void cpu_loop(CPUPPCState *env)
> env->nip -= 4;
> break;
> }
> - if (ret == (target_ulong)(-QEMU_ESIGRETURN)) {
> - /* Returning from a successful sigreturn syscall.
> + 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;
> }
> diff --git a/linux-user/riscv/cpu_loop.c b/linux-user/riscv/cpu_loop.c
> index ce542540c2..eecc8d1517 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 4929b32e1f..67d2a803fb 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 0c9d7e9c46..ee2958d0d9 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 7391e2add8..f054316dce 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 d78b2029fa..f74b8ac596 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>
> @@ -584,6 +585,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 via qemu_plugin_set_pc";
> + }
>
> return strerror(target_to_host_errno(err));
> }
> @@ -14077,6 +14081,10 @@ abi_long do_syscall(CPUArchState *cpu_env, int num, abi_long arg1,
> return -QEMU_ESIGRETURN;
> }
>
> + 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 a0ff10eff8..7680e243bb 100644
> --- a/linux-user/xtensa/cpu_loop.c
> +++ b/linux-user/xtensa/cpu_loop.c
> @@ -17,6 +17,7 @@
> * along with this program; if not, see <http://www.gnu.org/licenses/>.
> */
>
> +#include "qemu/compiler.h"
> #include "qemu/osdep.h"
> #include "qemu.h"
> #include "user-internals.h"
> @@ -185,6 +186,8 @@ void cpu_loop(CPUXtensaState *env)
> env->pc -= 3;
> break;
>
> + case -QEMU_ESETPC:
> + QEMU_FALLTHROUGH;
> case -QEMU_ESIGRETURN:
> break;
> }
> diff --git a/plugins/api.c b/plugins/api.c
> index eac04cc1f6..fc19bdb40b 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"
> @@ -450,13 +451,27 @@ int 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 -1;
> }
If we are exposing a specific qemu_plugin_set_pc we should probably
forbid setting it via write_register. Can we filter out the PC from the
register list?
>
> return gdb_write_register(current_cpu, buf->data, GPOINTER_TO_INT(reg) - 1);
> }
>
> +void qemu_plugin_set_pc(uint64_t vaddr)
> +{
> + g_assert(current_cpu);
> +
> + if (qemu_plugin_get_cb_flags() != QEMU_PLUGIN_CB_RW_REGS_PC) {
> + return;
> + }
Given we aggressively assert that some functions are called in the
current_cpu context maybe we should do the same here? If the plugin
tries to set the PC and it doesn't work what is going to happen?
> +
> + 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/plugins/core.c b/plugins/core.c
> index ead09fd2f1..b514293117 100644
> --- a/plugins/core.c
> +++ b/plugins/core.c
> @@ -369,15 +369,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))
> };
> @@ -399,15 +400,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))
> };
> @@ -438,15 +440,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)
> @@ -508,7 +511,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);
> }
> @@ -532,7 +535,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);
> }
> @@ -542,7 +545,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);
> }
> @@ -551,7 +554,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);
> }
> @@ -788,6 +791,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;
> }
> }
--
Alex Bennée
Virtualisation Tech Lead @ Linaro
^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [RFC PATCH v2 2/2] tests/tcg: add test for qemu_plugin_set_pc API
2025-10-06 13:23 ` [RFC PATCH v2 2/2] tests/tcg: add test for qemu_plugin_set_pc API Florian Hofhammer
@ 2025-12-12 12:40 ` Alex Bennée
2025-12-16 9:29 ` Florian Hofhammer
0 siblings, 1 reply; 16+ messages in thread
From: Alex Bennée @ 2025-12-12 12:40 UTC (permalink / raw)
To: Florian Hofhammer
Cc: qemu-devel, pierrick.bouvier, richard.henderson, laurent, imp,
berrange
Florian Hofhammer <florian.hofhammer@epfl.ch> writes:
> The test executes a non-existent syscall, which the syscall plugin
> intercepts and redirects to a clean exit.
> Due to architecture-specific quirks, the Makefile requires setting
> specific compiler and linker flags for some architectures.
>
> Signed-off-by: Florian Hofhammer <florian.hofhammer@epfl.ch>
> ---
> tests/tcg/multiarch/Makefile.target | 42 +++++++++++++++++++
> .../tcg/multiarch/test-plugin-skip-syscalls.c | 26 ++++++++++++
> tests/tcg/plugins/syscall.c | 6 +++
> 3 files changed, 74 insertions(+)
> create mode 100644 tests/tcg/multiarch/test-plugin-skip-syscalls.c
>
> diff --git a/tests/tcg/multiarch/Makefile.target b/tests/tcg/multiarch/Makefile.target
> index f5b4d2b813..7df3da2aba 100644
> --- a/tests/tcg/multiarch/Makefile.target
> +++ b/tests/tcg/multiarch/Makefile.target
> @@ -204,6 +204,48 @@ run-plugin-test-plugin-mem-access-with-libmem.so: \
> $(QEMU) $<
>
> EXTRA_RUNS_WITH_PLUGIN += run-plugin-test-plugin-mem-access-with-libmem.so
> +
> +# Test plugin control flow redirection by skipping system calls
> +ifeq ($(TARGET),arm-linux-user)
> +# Require emitting arm32 instructions, otherwise the vCPU might accidentally
> +# try to execute Thumb instructions in arm32 mode after qemu_plugin_set_pc()
> +test-plugin-skip-syscalls: CFLAGS+=-marm
> +endif
> +ifneq ($(filter mips64%-linux-user,$(TARGET)),)
> +# Require no ABI calls to avoid $t9-relative .got address calculation
> +test-plugin-skip-syscalls: CFLAGS+=-mno-abicalls -fno-pie
> +test-plugin-skip-syscalls: LDFLAGS+=-no-pie
> +endif
> +ifneq ($(filter mips%-linux-user,\
> + $(filter-out mips64%-linux-user,\
> + $(TARGET))),)
> +# qemu-mips(el) returns ENOSYS without triggering syscall plugin callbacks
> +run-plugin-test-plugin-skip-syscalls-with-libsyscall.so:
> + $(call skip-test, $<, "qemu-mips(el) does not execute invalid syscalls")
> +endif
> +ifeq ($(TARGET),sparc64-linux-user)
> +# The defined addresses for the binary are not aligned correctly for sparc64
> +# but adjusting them breaks other architectures, so just skip it on sparc64.
> +run-plugin-test-plugin-skip-syscalls-with-libsyscall.so:
> + $(call skip-test, $<, "qemu-sparc64 does not allow mapping at our given fixed address")
> +endif
> +ifeq ($(TARGET),hexagon-linux-user)
> +# hexagon uses clang/lld which does not support -Ttext-segment but GNU ld does
> +# not generally support --image-base.
> +test-plugin-skip-syscalls: LDFLAGS+=-Wl,--image-base=0x40000
> +else
> +test-plugin-skip-syscalls: LDFLAGS+=-Wl,-Ttext-segment=0x40000
> +endif
> +test-plugin-skip-syscalls: LDFLAGS+=-Wl,--section-start,.redirect=0x20000
> +run-plugin-test-plugin-skip-syscalls-with-libsyscall.so:
All these arch specific hacks should be moved to the arch specific
makefiles. We do this for example for the sha1 alt builds.
> +
> +EXTRA_RUNS_WITH_PLUGIN += run-plugin-test-plugin-skip-syscalls-with-libsyscall.so
> +
> +else # CONFIG_PLUGIN=n
> +# Do not build the syscall skipping test if it's not tested with a plugin
> +# because it will simply return an error and fail the test.
> +MULTIARCH_TESTS := $(filter-out "test-plugin-skip-syscalls", $(MULTIARCH_TESTS))
> +
rather than filtering it out lets move it into a subdir (plugin-tests?)
and conditionally include it like we do with linux above?
> endif
>
> # Update TESTS
> diff --git a/tests/tcg/multiarch/test-plugin-skip-syscalls.c b/tests/tcg/multiarch/test-plugin-skip-syscalls.c
> new file mode 100644
> index 0000000000..1f5cbc3851
> --- /dev/null
> +++ b/tests/tcg/multiarch/test-plugin-skip-syscalls.c
> @@ -0,0 +1,26 @@
> +/*
> + * SPDX-License-Identifier: GPL-2.0-or-later
> + *
> + * This test attempts to execute an invalid syscall. The syscall test plugin
> + * should intercept this.
> + */
> +#include <stdint.h>
> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <unistd.h>
> +
> +void exit_success(void) __attribute__((section(".redirect"), noinline,
> + noreturn, used));
> +
> +void exit_success(void) {
> + _exit(EXIT_SUCCESS);
> +}
> +
> +int main(int argc, char *argv[]) {
> + long ret = syscall(0xc0deUL);
> + if (ret != 0L) {
> + perror("");
> + }
> + /* We should never get here */
> + return EXIT_FAILURE;
> +}
> diff --git a/tests/tcg/plugins/syscall.c b/tests/tcg/plugins/syscall.c
> index 42801f5c86..c5bac2d928 100644
> --- a/tests/tcg/plugins/syscall.c
> +++ b/tests/tcg/plugins/syscall.c
> @@ -148,6 +148,12 @@ static void vcpu_syscall(qemu_plugin_id_t id, unsigned int vcpu_index,
> fprintf(stderr, "Error reading memory from vaddr %"PRIu64"\n", a2);
> }
> }
> +
> + if (num == 0xc0deUL) {
> + /* Special syscall to test the control flow redirection functionality. */
> + qemu_plugin_outs("Marker syscall detected, jump to clean exit\n");
> + qemu_plugin_set_pc(0x20000);
> + }
> }
>
> static void vcpu_syscall_ret(qemu_plugin_id_t id, unsigned int vcpu_idx,
--
Alex Bennée
Virtualisation Tech Lead @ Linaro
^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [RFC PATCH v2 0/2] Enable PC diversion via the plugin API
2025-12-12 12:02 ` Alex Bennée
@ 2025-12-12 17:36 ` Pierrick Bouvier
2025-12-16 9:35 ` Florian Hofhammer
2025-12-16 9:19 ` Florian Hofhammer
1 sibling, 1 reply; 16+ messages in thread
From: Pierrick Bouvier @ 2025-12-12 17:36 UTC (permalink / raw)
To: Alex Bennée, Florian Hofhammer
Cc: qemu-devel, richard.henderson, laurent, imp, berrange
On 12/12/25 4:02 AM, Alex Bennée wrote:
> Florian Hofhammer <florian.hofhammer@epfl.ch> writes:
>
>> Hi,
>>
>> Sorry for necrobumping this thread. I just wanted to follow up on this
>> and ask if there is still interest in this plugin API extension or if it
>> is going to be dropped in favor of the Lorelei patches (which would also
>> fulfill my original use case).
>
> These are the system call filter patches?
>
> I guess that depends on if being able to change PC is only for skipping
> syscalls?
>
Both can probably coexist, as they serve different purposes.
If only considering syscalls, the syscall filter (that you propose
first) is the best way to deal with it, as it's architecture agnostic.
That said, it would be sad to drop the effort you made into this series,
and overriding pc can be convenient if anyone wants to filter anything
else than syscalls, or for fuzzing purpose.
>>
>> Thanks for your time,
>> Florian
>>
>> On 06/10/2025 15:21, Florian Hofhammer wrote:
>>> Hi,
>>>
>>> As originally discussed in the thread at
>>> https://lists.nongnu.org/archive/html/qemu-devel/2025-08/msg00656.html
>>> and later proposed in a patch at
>>> https://lists.nongnu.org/archive/html/qemu-devel/2025-09/msg02218.html,
>>> I am sending an updated version of my patch based on the previous
>>> feedback.
>>>
>>> Notable changes to v1:
>>> - Added a setjmp() in the syscall handling path to allow redirecting
>>> the PC via cpu_loop_exit() also in syscall callbacks. The previous
>>> version would only work in instruction execution / memory access
>>> callback contexts, as the setjmp() corresponding to the longjmp() in
>>> cpu_loop_exit() was only live in those contexts.
>>> - Added a flag to make sure the new API function is only called in
>>> contexts where it makes sense, i.e., during execution of guest code.
>>> - Added a test that checks the new functionality by skipping a
>>> non-existent sentinel syscall.
>>>
>>> I made it an RFC patch this time as I am not entirely sure if my
>>> setting/handling of the new flag makes sense the way it is. I briefly
>>> looked into making the QEMU_PLUGIN_CB_* flags actual flags via a
>>> bitfield instead of enum values, but that would have required touching
>>> a lot of code all over the place, so I'm not sure this is the way to go.
>>>
>>> Happy to get feedback and your thoughts on the patches!
>>>
>>> Thanks,
>>> Florian
>>>
>>>
>>> Florian Hofhammer (2):
>>> plugins: Add PC diversion API function
>>> tests/tcg: add test for qemu_plugin_set_pc API
>>>
>>> include/qemu/qemu-plugin.h | 15 +++++++
>>> 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 | 4 ++
>>> linux-user/i386/cpu_loop.c | 8 ++--
>>> linux-user/include/special-errno.h | 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 | 5 ++-
>>> linux-user/openrisc/cpu_loop.c | 2 +-
>>> linux-user/ppc/cpu_loop.c | 6 ++-
>>> 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 | 8 ++++
>>> linux-user/xtensa/cpu_loop.c | 3 ++
>>> plugins/api.c | 17 +++++++-
>>> plugins/core.c | 25 ++++++-----
>>> tests/tcg/multiarch/Makefile.target | 42 +++++++++++++++++++
>>> .../tcg/multiarch/test-plugin-skip-syscalls.c | 26 ++++++++++++
>>> tests/tcg/plugins/syscall.c | 6 +++
>>> 25 files changed, 170 insertions(+), 32 deletions(-)
>>> create mode 100644 tests/tcg/multiarch/test-plugin-skip-syscalls.c
>>>
>
^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [RFC PATCH v2 0/2] Enable PC diversion via the plugin API
2025-12-12 12:02 ` Alex Bennée
2025-12-12 17:36 ` Pierrick Bouvier
@ 2025-12-16 9:19 ` Florian Hofhammer
1 sibling, 0 replies; 16+ messages in thread
From: Florian Hofhammer @ 2025-12-16 9:19 UTC (permalink / raw)
To: Alex Bennée
Cc: qemu-devel, pierrick.bouvier, richard.henderson, laurent, imp,
berrange
On 12/12/2025 13:02, Alex Bennée wrote:
> Florian Hofhammer <florian.hofhammer@epfl.ch> writes:
>
>> Hi,
>>
>> Sorry for necrobumping this thread. I just wanted to follow up on this
>> and ask if there is still interest in this plugin API extension or if it
>> is going to be dropped in favor of the Lorelei patches (which would also
>> fulfill my original use case).
>
> These are the system call filter patches?
>
> I guess that depends on if being able to change PC is only for skipping
> syscalls?
Yes, the Lorelei patches are the syscall filter patches.
This was my original use case as well, but I think diverting control
flow outside of that use case can be useful as well (e.g., to divert
control flow to a different library function implementation when things
like LD_PRELOAD are not an option).
Best,
Florian
^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [RFC PATCH v2 1/2] plugins: Add PC diversion API function
2025-12-12 12:35 ` Alex Bennée
@ 2025-12-16 9:27 ` Florian Hofhammer
2025-12-16 12:28 ` Florian Hofhammer
0 siblings, 1 reply; 16+ messages in thread
From: Florian Hofhammer @ 2025-12-16 9:27 UTC (permalink / raw)
To: Alex Bennée
Cc: qemu-devel, pierrick.bouvier, richard.henderson, laurent, imp,
berrange
>> diff --git a/plugins/api.c b/plugins/api.c
>> index eac04cc1f6..fc19bdb40b 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"
>> @@ -450,13 +451,27 @@ int 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 -1;
>> }
>
> If we are exposing a specific qemu_plugin_set_pc we should probably
> forbid setting it via write_register. Can we filter out the PC from the
> register list?
The qemu_plugin_write_register API silently swallows writes to the PC
even though such a write doesn't actually have an effect, so excluding
the PC here might make sense and I'm happy to update the patch
accordingly.
Are there other registers that should be excluded as well?
General-purpose register writes are visible to the guest immediately,
but what about special registers? Do writes to those actually always
have the intended effect or should they also be excluded here?
>> return gdb_write_register(current_cpu, buf->data, GPOINTER_TO_INT(reg) - 1);
>> }
>>
>> +void qemu_plugin_set_pc(uint64_t vaddr)
>> +{
>> + g_assert(current_cpu);
>> +
>> + if (qemu_plugin_get_cb_flags() != QEMU_PLUGIN_CB_RW_REGS_PC) {
>> + return;
>> + }
>
> Given we aggressively assert that some functions are called in the
> current_cpu context maybe we should do the same here? If the plugin
> tries to set the PC and it doesn't work what is going to happen?
Could you elaborate on that? I've added the g_assert(current_cpu) as in
other API functions, but I'm not sure what you mean beyond that.
As highlighted in the doc string (see below), the function already only
returns in case of errors, so the caller could just handle the error
case in code after the call to the API function.
I originally thought about adding a boolean return value to indicate
success or failure but given that the function only returns in case of
error, I considered that to be enough of a signal to the caller here.
>> +/**
>> + * 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 only returns in case of
>> + * errors.
>> + */
>> +QEMU_PLUGIN_API
>> +void qemu_plugin_set_pc(uint64_t vaddr);
Thanks,
Florian
^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [RFC PATCH v2 2/2] tests/tcg: add test for qemu_plugin_set_pc API
2025-12-12 12:40 ` Alex Bennée
@ 2025-12-16 9:29 ` Florian Hofhammer
0 siblings, 0 replies; 16+ messages in thread
From: Florian Hofhammer @ 2025-12-16 9:29 UTC (permalink / raw)
To: Alex Bennée
Cc: qemu-devel, pierrick.bouvier, richard.henderson, laurent, imp,
berrange
> All these arch specific hacks should be moved to the arch specific
> makefiles. We do this for example for the sha1 alt builds.
Ack.
>> +EXTRA_RUNS_WITH_PLUGIN += run-plugin-test-plugin-skip-syscalls-with-libsyscall.so
>> +
>> +else # CONFIG_PLUGIN=n
>> +# Do not build the syscall skipping test if it's not tested with a plugin
>> +# because it will simply return an error and fail the test.
>> +MULTIARCH_TESTS := $(filter-out "test-plugin-skip-syscalls", $(MULTIARCH_TESTS))
>> +
>
> rather than filtering it out lets move it into a subdir (plugin-tests?)
> and conditionally include it like we do with linux above?
Good point, will do.
Best,
Florian
^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [RFC PATCH v2 0/2] Enable PC diversion via the plugin API
2025-12-12 17:36 ` Pierrick Bouvier
@ 2025-12-16 9:35 ` Florian Hofhammer
0 siblings, 0 replies; 16+ messages in thread
From: Florian Hofhammer @ 2025-12-16 9:35 UTC (permalink / raw)
To: Pierrick Bouvier, Alex Bennée
Cc: qemu-devel, richard.henderson, laurent, imp, berrange
On 12/12/2025 18:36, Pierrick Bouvier wrote:
> On 12/12/25 4:02 AM, Alex Bennée wrote:
>> Florian Hofhammer <florian.hofhammer@epfl.ch> writes:
>>
>>> Hi,
>>>
>>> Sorry for necrobumping this thread. I just wanted to follow up on this
>>> and ask if there is still interest in this plugin API extension or if it
>>> is going to be dropped in favor of the Lorelei patches (which would also
>>> fulfill my original use case).
>>
>> These are the system call filter patches?
>>
>> I guess that depends on if being able to change PC is only for skipping
>> syscalls?
>>
>
> Both can probably coexist, as they serve different purposes.
> If only considering syscalls, the syscall filter (that you propose first) is the best way to deal with it, as it's architecture agnostic.
>
> That said, it would be sad to drop the effort you made into this series, and overriding pc can be convenient if anyone wants to filter anything else than syscalls, or for fuzzing purpose.
Yes, while the syscall filtering was my initial use case, setting the PC
and diverting control flow can also be used for patching out
functionality, diverting function calls, in-process resets for fuzzing,
or other use cases.
No hard feelings if there isn't enough upstream interest, though! :)
Thanks,
Florian
^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [RFC PATCH v2 1/2] plugins: Add PC diversion API function
2025-12-16 9:27 ` Florian Hofhammer
@ 2025-12-16 12:28 ` Florian Hofhammer
2025-12-16 14:44 ` Alex Bennée
2025-12-16 17:10 ` Alex Bennée
0 siblings, 2 replies; 16+ messages in thread
From: Florian Hofhammer @ 2025-12-16 12:28 UTC (permalink / raw)
To: Alex Bennée
Cc: qemu-devel, pierrick.bouvier, richard.henderson, laurent, imp,
berrange
On 16/12/2025 10:27, Florian Hofhammer wrote:
>>> diff --git a/plugins/api.c b/plugins/api.c
>>> index eac04cc1f6..fc19bdb40b 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"
>>> @@ -450,13 +451,27 @@ int 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 -1;
>>> }
>>
>> If we are exposing a specific qemu_plugin_set_pc we should probably
>> forbid setting it via write_register. Can we filter out the PC from the
>> register list?
>
> The qemu_plugin_write_register API silently swallows writes to the PC
> even though such a write doesn't actually have an effect, so excluding
> the PC here might make sense and I'm happy to update the patch
> accordingly.
> Are there other registers that should be excluded as well?
> General-purpose register writes are visible to the guest immediately,
> but what about special registers? Do writes to those actually always
> have the intended effect or should they also be excluded here?
Actually, after looking into this for a bit, I don't think it's easily
feasible without a big revamp (but please correct me if I'm wrong or
simply missing something).
The problem is that the opaque handle passed to the plugin API only
encodes a register offset, not its name or type. So to exclude the PC,
we'd need to expose the register name to the API (which would
likely require either duplication of the name in both `struct
qemu_plugin_reg_descriptor` and `struct qemu_plugin_register` or
changing the API) and then filter in the API based on the register name
("pc", "rip", "eip", ...). This seems rather hacky and fragile to me.
Alternatively, we could encode whether a register is to be filtered out
or not in the opaque handle itself (e.g., by setting the topmost bit).
This still requires getting this information from somewhere, though.
Currently, registers are created by parsing the target XML in `gdb-xml/`
at compile time. The PC is generally marked as `type="code_ptr"` in the
XML, so we could expose this type information to the code and adjust the
register handle if it's a code pointer. The problem here is that it's
not just the PC that is marked as `code_ptr` in the XML; there may be
other registers as well (e.g., `lr` or `ra` for Arm and RISC-V).
I'm not sure whether the above explanation is clear enough, so please
let me know if you'd like me to clarify anything or point you to the
exact spots in the code.
Thanks,
Florian
^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [RFC PATCH v2 1/2] plugins: Add PC diversion API function
2025-12-16 12:28 ` Florian Hofhammer
@ 2025-12-16 14:44 ` Alex Bennée
2025-12-16 17:10 ` Alex Bennée
1 sibling, 0 replies; 16+ messages in thread
From: Alex Bennée @ 2025-12-16 14:44 UTC (permalink / raw)
To: Florian Hofhammer
Cc: qemu-devel, pierrick.bouvier, richard.henderson, laurent, imp,
berrange
Florian Hofhammer <florian.hofhammer@epfl.ch> writes:
> On 16/12/2025 10:27, Florian Hofhammer wrote:
>>>> diff --git a/plugins/api.c b/plugins/api.c
>>>> index eac04cc1f6..fc19bdb40b 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"
>>>> @@ -450,13 +451,27 @@ int 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 -1;
>>>> }
>>>
>>> If we are exposing a specific qemu_plugin_set_pc we should probably
>>> forbid setting it via write_register. Can we filter out the PC from the
>>> register list?
>>
>> The qemu_plugin_write_register API silently swallows writes to the PC
>> even though such a write doesn't actually have an effect, so excluding
>> the PC here might make sense and I'm happy to update the patch
>> accordingly.
>> Are there other registers that should be excluded as well?
>> General-purpose register writes are visible to the guest immediately,
>> but what about special registers? Do writes to those actually always
>> have the intended effect or should they also be excluded here?
>
> Actually, after looking into this for a bit, I don't think it's easily
> feasible without a big revamp (but please correct me if I'm wrong or
> simply missing something).
>
> The problem is that the opaque handle passed to the plugin API only
> encodes a register offset, not its name or type. So to exclude the PC,
> we'd need to expose the register name to the API (which would
> likely require either duplication of the name in both `struct
> qemu_plugin_reg_descriptor` and `struct qemu_plugin_register` or
> changing the API) and then filter in the API based on the register name
> ("pc", "rip", "eip", ...). This seems rather hacky and fragile to me.
>
> Alternatively, we could encode whether a register is to be filtered out
> or not in the opaque handle itself (e.g., by setting the topmost bit).
> This still requires getting this information from somewhere, though.
> Currently, registers are created by parsing the target XML in `gdb-xml/`
> at compile time. The PC is generally marked as `type="code_ptr"` in the
> XML, so we could expose this type information to the code and adjust the
> register handle if it's a code pointer. The problem here is that it's
> not just the PC that is marked as `code_ptr` in the XML; there may be
> other registers as well (e.g., `lr` or `ra` for Arm and RISC-V).
Hmm thats interesting. I wonder what the semantics of that for gdb are?
However I think thinks like the link registers are normally rectified.
For example from the systrace plugin I'm toying with:
But I'm not always able to catch it on being automatically set:
CPU: 0 taking host call from 0xffffffc08003a35c to 0xffffffc08003a35c
LAST SYSREG: 0xffffffc0809d3d38 msr daif, x23
REG: id_aa64pfr0_el1 is 1100000011110012 (previously 1100000010110012, 0 to 1 hits)
REG: sctlr_el1 is 0200000034f4d91d (previously 0000000000000000, 0 to 3 hits)
REG: spsr_el1 is 00000000000003c5 (previously 0000000000000000, 0 to 1 hits)
REG: elr_el1 is 0000000041406100 (previously 0000000000000000, 0 to 1 hits)
REG: vbar_el1 is ffffffc080010800 (previously 0000000000000000, 0 to 1 hits)
REG: mdscr_el1 is 0000000000001000 (previously 0000000000000000, 0 to 1 hits)
REG: ttbr0_el1 is 0000000041408000 (previously 0000000000000000, 0 to 2 hits)
REG: ttbr1_el1 is 0000000041409000 (previously 0000000000000000, 0 to 5 hits)
REG: tcr_el1 is 001000f5b5593519 (previously 0000000000000000, 0 to 3 hits)
REG: mair_el1 is 000000040044ffff (previously 0000000000000000, 0 to 1 hits)
REG: midr_el1 is 00000000414fd0c1 (previously 0000000000000000, 0 to 2 hits)
CPU: 0 taking host call from 0xffffffc08003a35c to 0xffffffc08003a35c
LAST SYSREG: 0xffffffc0800cd6dc msr daif, x21
CPU: 0 taking host call from 0xffffffc08003a35c to 0xffffffc08003a35c
LAST SYSREG: 0xffffffc08010fe9c msr daif, x27
CPU: 0 taking host call from 0xffffffc08003a35c to 0xffffffc08003a35c
LAST SYSREG: 0xffffffc08010fe9c msr daif, x27
CPU: 0 taking host call from 0xffffffc08003a35c to 0xffffffc08003a35c
LAST SYSREG: 0xffffffc08010fe9c msr daif, x27
CPU: 0 taking host call from 0xffffffc08003a35c to 0xffffffc08003a35c
LAST SYSREG: 0xffffffc08010fe9c msr daif, x27
CPU: 0 taking host call from 0xffffffc08003a35c to 0xffffffc08003a35c
LAST SYSREG: 0xffffffc08010fe9c msr daif, x27
CPU: 0 taking irq from 0xffffffc08121102c to 0xffffffc080010a80
LAST SYSREG: 0xffffffc081211028 msr daifclr, #3
REG: cntkctl_el1 is 0000000000000002 (previously 0000000000000000, 0 to 2 hits)
REG: tpidr_el1 is ffffffbfbe084000 (previously 0000000000000000, 513 to 4453 hits)
CPU: 0 taking irq from 0xffffffc08121102c to 0xffffffc080010a80
LAST SYSREG: 0xffffffc08001207c msr spsr_el1, x22
REG: spsr_el1 is 0000000000000005 (previously 00000000000003c5, 1 to 3 hits)
REG: elr_el1 is ffffffc08121102c (previously 0000000041406100, 1 to 3 hits)
CPU: 0 taking irq from 0xffffffc08065c988 to 0xffffffc080010a80
LAST SYSREG: 0xffffffc080657970 msr daif, x21
REG: spsr_el1 is 0000000020000005 (previously 0000000000000005, 3 to 5 hits)
REG: elr_el1 is ffffffc08065c988 (previously ffffffc08121102c, 3 to 5 hits)
CPU: 0 taking irq from 0xffffffc0806591c4 to 0xffffffc080010a80
LAST SYSREG: 0xffffffc080657ad8 msr daif, x22
REG: spsr_el1 is 0000000060000005 (previously 0000000020000005, 5 to 7 hits)
REG: elr_el1 is ffffffc0806591c4 (previously ffffffc08065c988, 5 to 7 hits)
I think the elr_el1 changes I'm picking up are where its explicitly set
by the code and not the implicit updates.
>
> I'm not sure whether the above explanation is clear enough, so please
> let me know if you'd like me to clarify anything or point you to the
> exact spots in the code.
>
> Thanks,
> Florian
--
Alex Bennée
Virtualisation Tech Lead @ Linaro
^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [RFC PATCH v2 1/2] plugins: Add PC diversion API function
2025-12-16 12:28 ` Florian Hofhammer
2025-12-16 14:44 ` Alex Bennée
@ 2025-12-16 17:10 ` Alex Bennée
2026-01-07 11:20 ` Florian Hofhammer
1 sibling, 1 reply; 16+ messages in thread
From: Alex Bennée @ 2025-12-16 17:10 UTC (permalink / raw)
To: Florian Hofhammer
Cc: qemu-devel, pierrick.bouvier, richard.henderson, laurent, imp,
berrange
Florian Hofhammer <florian.hofhammer@epfl.ch> writes:
> On 16/12/2025 10:27, Florian Hofhammer wrote:
>>>> diff --git a/plugins/api.c b/plugins/api.c
>>>> index eac04cc1f6..fc19bdb40b 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"
>>>> @@ -450,13 +451,27 @@ int 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 -1;
>>>> }
>>>
>>> If we are exposing a specific qemu_plugin_set_pc we should probably
>>> forbid setting it via write_register. Can we filter out the PC from the
>>> register list?
>>
>> The qemu_plugin_write_register API silently swallows writes to the PC
>> even though such a write doesn't actually have an effect, so excluding
>> the PC here might make sense and I'm happy to update the patch
>> accordingly.
>> Are there other registers that should be excluded as well?
>> General-purpose register writes are visible to the guest immediately,
>> but what about special registers? Do writes to those actually always
>> have the intended effect or should they also be excluded here?
>
> Actually, after looking into this for a bit, I don't think it's easily
> feasible without a big revamp (but please correct me if I'm wrong or
> simply missing something).
>
> The problem is that the opaque handle passed to the plugin API only
> encodes a register offset, not its name or type. So to exclude the PC,
> we'd need to expose the register name to the API (which would
> likely require either duplication of the name in both `struct
> qemu_plugin_reg_descriptor` and `struct qemu_plugin_register` or
> changing the API) and then filter in the API based on the register name
> ("pc", "rip", "eip", ...). This seems rather hacky and fragile to me.
I agree - although I don't think there are too many more variations.
Most of the other arches use pc.
>
> Alternatively, we could encode whether a register is to be filtered out
> or not in the opaque handle itself (e.g., by setting the topmost bit).
> This still requires getting this information from somewhere, though.
> Currently, registers are created by parsing the target XML in `gdb-xml/`
> at compile time. The PC is generally marked as `type="code_ptr"` in the
> XML, so we could expose this type information to the code and adjust the
> register handle if it's a code pointer. The problem here is that it's
> not just the PC that is marked as `code_ptr` in the XML; there may be
> other registers as well (e.g., `lr` or `ra` for Arm and RISC-V).
Hmm thats interesting. Looking at the gdb docs:
code_ptr
data_ptr
Pointers to unspecified code and data. The program counter and any
dedicated return address register may be marked as code pointers;
printing a code pointer converts it into a symbolic address. The
stack pointer and any dedicated address registers may be marked as
data pointers.
I'm not sure we can necessarily infer PC like behaviour based on that.
I have been messing with a systrace plugin and I can track the values of
el1_elr and they seem sane:
CPU: 0 taking host call from 0xffffffc08003a35c to 0xffffffc08003a35c
FROM: 0xffffffc08003a358 hvc #0 (lepc)
LAST SYSREG: 0xffffffc0809d3d38 msr daif, x23
REG: id_aa64pfr0_el1 is 1100000011110012 (previously 1100000010110012, 0 to 1 hits)
REG: sctlr_el1 is 0200000034f4d91d (previously 0000000000000000, 0 to 3 hits)
REG: spsr_el1 is 00000000000003c5 (previously 0000000000000000, 0 to 1 hits)
REG: elr_el1 is 0000000041406100 (previously 0000000000000000, 0 to 1 hits)
REG: vbar_el1 is ffffffc080010800 (previously 0000000000000000, 0 to 1 hits)
REG: mdscr_el1 is 0000000000001000 (previously 0000000000000000, 0 to 1 hits)
REG: ttbr0_el1 is 0000000041408000 (previously 0000000000000000, 0 to 2 hits)
REG: ttbr1_el1 is 0000000041409000 (previously 0000000000000000, 0 to 5 hits)
REG: tcr_el1 is 001000f5b5593519 (previously 0000000000000000, 0 to 3 hits)
REG: mair_el1 is 000000040044ffff (previously 0000000000000000, 0 to 1 hits)
REG: midr_el1 is 00000000414fd0c1 (previously 0000000000000000, 0 to 2 hits)
CPU: 0 taking host call from 0xffffffc08003a35c to 0xffffffc08003a35c
FROM: 0xffffffc08003a35c ldr x4, [sp] (fpc)
LAST SYSREG: 0xffffffc0800cd6dc msr daif, x21
CPU: 0 taking host call from 0xffffffc08003a35c to 0xffffffc08003a35c
FROM: 0xffffffc08003a35c ldr x4, [sp] (fpc)
LAST SYSREG: 0xffffffc08010fe9c msr daif, x27
CPU: 0 taking host call from 0xffffffc08003a35c to 0xffffffc08003a35c
FROM: 0xffffffc08003a35c ldr x4, [sp] (fpc)
LAST SYSREG: 0xffffffc08010fe9c msr daif, x27
CPU: 0 taking host call from 0xffffffc08003a35c to 0xffffffc08003a35c
FROM: 0xffffffc08003a35c ldr x4, [sp] (fpc)
LAST SYSREG: 0xffffffc08010fe9c msr daif, x27
CPU: 0 taking host call from 0xffffffc08003a35c to 0xffffffc08003a35c
FROM: 0xffffffc08003a35c ldr x4, [sp] (fpc)
LAST SYSREG: 0xffffffc08010fe9c msr daif, x27
CPU: 0 taking host call from 0xffffffc08003a35c to 0xffffffc08003a35c
FROM: 0xffffffc08003a35c ldr x4, [sp] (fpc)
LAST SYSREG: 0xffffffc08010fe9c msr daif, x27
CPU: 0 taking irq from 0xffffffc08121102c to 0xffffffc080010a80
FROM: 0xffffffc081211028 msr daifclr, #3 (lepc)
LAST SYSREG: 0xffffffc081211028 msr daifclr, #3
REG: cntkctl_el1 is 0000000000000002 (previously 0000000000000000, 0 to 2 hits)
REG: tpidr_el1 is ffffffbfbe084000 (previously 0000000000000000, 513 to 4454 hits)
CPU: 0 taking irq from 0xffffffc08121102c to 0xffffffc080010a80
FROM: 0xffffffc0800120c8 eret (lepc)
LAST SYSREG: 0xffffffc08001207c msr spsr_el1, x22
REG: spsr_el1 is 0000000000000005 (previously 00000000000003c5, 1 to 3 hits)
REG: elr_el1 is ffffffc08121102c (previously 0000000041406100, 1 to 3 hits)
But due to the way I track the changes I don't see changes that are
implicit due to branch instructions, only when the kernel directly
manipulates them.
We could extend the XML itself but that makes things tricky when we sync
with gdb.
> I'm not sure whether the above explanation is clear enough, so please
> let me know if you'd like me to clarify anything or point you to the
> exact spots in the code.
>
> Thanks,
> Florian
--
Alex Bennée
Virtualisation Tech Lead @ Linaro
^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [RFC PATCH v2 1/2] plugins: Add PC diversion API function
2025-12-16 17:10 ` Alex Bennée
@ 2026-01-07 11:20 ` Florian Hofhammer
0 siblings, 0 replies; 16+ messages in thread
From: Florian Hofhammer @ 2026-01-07 11:20 UTC (permalink / raw)
To: Alex Bennée
Cc: qemu-devel, pierrick.bouvier, richard.henderson, laurent, imp,
berrange
On 16/12/2025 18:10, Alex Bennée wrote:
>>> The qemu_plugin_write_register API silently swallows writes to the PC
>>> even though such a write doesn't actually have an effect, so excluding
>>> the PC here might make sense and I'm happy to update the patch
>>> accordingly.
>>> Are there other registers that should be excluded as well?
>>> General-purpose register writes are visible to the guest immediately,
>>> but what about special registers? Do writes to those actually always
>>> have the intended effect or should they also be excluded here?
>>
>> Actually, after looking into this for a bit, I don't think it's easily
>> feasible without a big revamp (but please correct me if I'm wrong or
>> simply missing something).
>>
>> The problem is that the opaque handle passed to the plugin API only
>> encodes a register offset, not its name or type. So to exclude the PC,
>> we'd need to expose the register name to the API (which would
>> likely require either duplication of the name in both `struct
>> qemu_plugin_reg_descriptor` and `struct qemu_plugin_register` or
>> changing the API) and then filter in the API based on the register name
>> ("pc", "rip", "eip", ...). This seems rather hacky and fragile to me.
>
> I agree - although I don't think there are too many more variations.
> Most of the other arches use pc.
From what I can tell, the only exceptions are x86 (eip/rip), microblaze
(rpc), and s390 (pswa). We could special-case those in the API but this
would need to be considered when adding or removing support for new
architectures.
As I said, it seems hacky but I think it's not too bad, as the behavior
when forgetting to change the corresponding code when adding or removing
architectures would not be disastrous: when adding an architecture that
doesn't use "pc" as the program counter register name, writes to the PC
would be silently swallowed, just like it is the case now. When removing
an architecture that uses a different name for the PC, forgetting to
remove the special case would just mean that there's an unecessary check
(but that's just a few instructions and shouldn't be in the critical
path).
>> Alternatively, we could encode whether a register is to be filtered out
>> or not in the opaque handle itself (e.g., by setting the topmost bit).
>> This still requires getting this information from somewhere, though.
>> Currently, registers are created by parsing the target XML in `gdb-xml/`
>> at compile time. The PC is generally marked as `type="code_ptr"` in the
>> XML, so we could expose this type information to the code and adjust the
>> register handle if it's a code pointer. The problem here is that it's
>> not just the PC that is marked as `code_ptr` in the XML; there may be
>> other registers as well (e.g., `lr` or `ra` for Arm and RISC-V).
>
> Hmm thats interesting. Looking at the gdb docs:
>
> code_ptr
> data_ptr
>
> Pointers to unspecified code and data. The program counter and any
> dedicated return address register may be marked as code pointers;
> printing a code pointer converts it into a symbolic address. The
> stack pointer and any dedicated address registers may be marked as
> data pointers.
>
> I'm not sure we can necessarily infer PC like behaviour based on that.
> *snip*
> We could extend the XML itself but that makes things tricky when we sync
> with gdb.
I agree, maybe the above "hacky" approach is easier in that regard.
Maybe adding a custom attribute to the XML would be an option, and that
should in theory be easy to rebase when syncing with gdb. The custom
attribute would never be exposed to gdb or just be ignored by gdb when
parsing the XML, so it shouldn't cause any issues (again, in theory :)).
I'll implement the custom attribute and see whether this holds up in
practice!
Best regards,
Florian
^ permalink raw reply [flat|nested] 16+ messages in thread
end of thread, other threads:[~2026-01-07 11:26 UTC | newest]
Thread overview: 16+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-10-06 13:21 [RFC PATCH v2 0/2] Enable PC diversion via the plugin API Florian Hofhammer
2025-10-06 13:22 ` [RFC PATCH v2 1/2] plugins: Add PC diversion API function Florian Hofhammer
2025-12-12 12:35 ` Alex Bennée
2025-12-16 9:27 ` Florian Hofhammer
2025-12-16 12:28 ` Florian Hofhammer
2025-12-16 14:44 ` Alex Bennée
2025-12-16 17:10 ` Alex Bennée
2026-01-07 11:20 ` Florian Hofhammer
2025-10-06 13:23 ` [RFC PATCH v2 2/2] tests/tcg: add test for qemu_plugin_set_pc API Florian Hofhammer
2025-12-12 12:40 ` Alex Bennée
2025-12-16 9:29 ` Florian Hofhammer
2025-10-29 15:57 ` [RFC PATCH v2 0/2] Enable PC diversion via the plugin API Florian Hofhammer
2025-12-12 12:02 ` Alex Bennée
2025-12-12 17:36 ` Pierrick Bouvier
2025-12-16 9:35 ` Florian Hofhammer
2025-12-16 9:19 ` Florian Hofhammer
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.