public inbox for qemu-devel@nongnu.org
 help / color / mirror / Atom feed
* [PATCH v7 0/8] Enable PC diversion via the plugin API
@ 2026-03-05 10:05 Florian Hofhammer
  2026-03-05 10:05 ` [PATCH v7 1/8] plugins/core: clamp syscall arguments if target is 32-bit Florian Hofhammer
                   ` (8 more replies)
  0 siblings, 9 replies; 18+ messages in thread
From: Florian Hofhammer @ 2026-03-05 10:05 UTC (permalink / raw)
  To: qemu-devel
  Cc: Florian Hofhammer, Pierrick Bouvier, Alex Bennée,
	Warner Losh

Hi,

This patch series builds on top of the discussion from the thread at
https://lore.kernel.org/qemu-devel/e9bcd7c7-2d67-469e-b2f3-d1a68e456b2b@epfl.ch/
and adds a plugin API function to set the program counter of the guest,
as just writing to it via qemu_plugin_write_register() has no direct
effect.

This version v7 of the patch series addresses the requested changes from
the previous v6 submission (details below).

Note: checkpatch.pl still reports a warning about line length violations
in patch nr. 7/8 but I did not fix this, as the line was already > 80
characters long previously, the change added only a single character,
and I think the readability of the code is better as it is now. Please
let me know if you disagree and would like me to fix this!

@Pierrick: you already reviewed patch 3/7 (now 4/8) previously, but I
had to change the regex in scripts/qemu-plugin-symbols.py to account for
attributes, so please re-review!

Best regards,
Florian 

Changes:
v7:
- Add Pierrick's patch to clamp 32-bit registers to prevent accidental
  sign extension
- Fix documentation build by using __attribute__((__noreturn__)) instead
  of G_NORETURN (required adjusting scripts/qemu-plugin-symbols.py).
- Rework test-plugin-set-pc test to get rid of volatile guard and make
  control flow more linear
- Link to v6: https://lore.kernel.org/qemu-devel/20260303-setpc-v5-v6-0-15c77cfe184e@epfl.ch
v6:
- update commit message for patch 4/7
v5:
- make QEMU abort via asserts instead of just returning an error from
  the plugin API if preconditions are violated
- extend tests for qemu_plugin_set_pc() to different contexts
- fix issues highlighted by checkpatch.pl
v4:
- switch strcmp out in favor of g_strcmp0 
- split the patch introducing the qemu_plugin_set_pc() API into three
  patches, two for preparing the plugin infrastructure and the syscall
  handling code and a third introducing the actual plugin API
v3:
- make PC registers read-only across architectures
- add tests for read-only registers
- adjust test structure for qemu_plugin_set_pc() by moving
   architecture-specific tests into corresponding directories
v2:
- add setjmp() in syscall handling path to allow PC redirection from
   syscall callbacks (via longjmp(), the cpu_loop()'s setjmp() for
   exiting a TB would not be live anymore in syscall handlers)
- add flags to ensure the qemu_plugin_set_pc() API is only called from
   contexts where the CPU is live
- add test for qemu_plugin_set_pc() API
v1:
- initial version

---
Florian Hofhammer (7):
      plugins: add flag to specify whether PC is rw
      linux-user: make syscall emulation interruptible
      plugins: add PC diversion API function
      tests/tcg: add tests for qemu_plugin_set_pc API
      plugins: add read-only property for registers
      plugins: prohibit writing to read-only registers
      tests/tcg/plugins: test register accesses

Pierrick Bouvier (1):
      plugins/core: clamp syscall arguments if target is 32-bit

 MAINTAINERS                                        |   1 +
 include/plugins/qemu-plugin.h                      |  19 +++
 linux-user/aarch64/cpu_loop.c                      |   2 +-
 linux-user/alpha/cpu_loop.c                        |   2 +-
 linux-user/arm/cpu_loop.c                          |   2 +-
 linux-user/hexagon/cpu_loop.c                      |   2 +-
 linux-user/hppa/cpu_loop.c                         |   1 +
 linux-user/i386/cpu_loop.c                         |   8 +-
 linux-user/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                         |   9 +-
 linux-user/or1k/cpu_loop.c                         |   2 +-
 linux-user/ppc/cpu_loop.c                          |  10 +-
 linux-user/riscv/cpu_loop.c                        |   2 +-
 linux-user/s390x/cpu_loop.c                        |   2 +-
 linux-user/sh4/cpu_loop.c                          |   2 +-
 linux-user/sparc/cpu_loop.c                        |   4 +-
 linux-user/syscall.c                               |  16 +++
 linux-user/xtensa/cpu_loop.c                       |   1 +
 plugins/api.c                                      |  42 ++++++-
 plugins/core.c                                     |  50 ++++++--
 scripts/qemu-plugin-symbols.py                     |   9 +-
 tests/tcg/arm/Makefile.target                      |   6 +
 tests/tcg/hexagon/Makefile.target                  |   8 ++
 tests/tcg/multiarch/Makefile.target                |  17 ++-
 .../multiarch/{ => plugin}/check-plugin-output.sh  |   0
 .../{ => plugin}/test-plugin-mem-access.c          |   0
 tests/tcg/multiarch/plugin/test-plugin-set-pc.c    | 134 +++++++++++++++++++++
 tests/tcg/plugins/meson.build                      |   2 +
 tests/tcg/plugins/registers.c                      |  79 ++++++++++++
 tests/tcg/plugins/setpc.c                          | 109 +++++++++++++++++
 33 files changed, 514 insertions(+), 44 deletions(-)
---
base-commit: 3fb456e9a0e9eef6a71d9b49bfff596a0f0046e9
change-id: 20260303-setpc-v5-c1df30bad07f


^ permalink raw reply	[flat|nested] 18+ messages in thread

* [PATCH v7 1/8] plugins/core: clamp syscall arguments if target is 32-bit
  2026-03-05 10:05 [PATCH v7 0/8] Enable PC diversion via the plugin API Florian Hofhammer
@ 2026-03-05 10:05 ` Florian Hofhammer
  2026-03-05 16:09   ` Pierrick Bouvier
                     ` (2 more replies)
  2026-03-05 10:06 ` [PATCH v7 2/8] plugins: add flag to specify whether PC is rw Florian Hofhammer
                   ` (7 subsequent siblings)
  8 siblings, 3 replies; 18+ messages in thread
From: Florian Hofhammer @ 2026-03-05 10:05 UTC (permalink / raw)
  To: qemu-devel; +Cc: Florian Hofhammer, Pierrick Bouvier

From: Pierrick Bouvier <pierrick.bouvier@linaro.org>

Syscall arguments are abi_long in user code, and plugin syscall
interface works with uint64_t only.

According to C integer promotion rules, the value is sign extended
before becoming unsigned, thus setting high bits when only 32-bit lower
ones should have a significant value.

As a result, we need to clamp values we receive from user-code
accordingly.

Signed-off-by: Pierrick Bouvier <pierrick.bouvier@linaro.org>
---
 plugins/core.c | 21 +++++++++++++++++++++
 1 file changed, 21 insertions(+)

diff --git a/plugins/core.c b/plugins/core.c
index 42fd986593..d6173422e9 100644
--- a/plugins/core.c
+++ b/plugins/core.c
@@ -513,6 +513,23 @@ void qemu_plugin_tb_trans_cb(CPUState *cpu, struct qemu_plugin_tb *tb)
     }
 }
 
+static void clamp_syscall_arguments(uint64_t *a1, uint64_t *a2, uint64_t *a3,
+                                    uint64_t *a4, uint64_t *a5, uint64_t *a6,
+                                    uint64_t *a7, uint64_t *a8)
+{
+    if (target_long_bits() == 32) {
+        const uint64_t mask = UINT32_MAX;
+        *a1 &= mask;
+        *a2 &= mask;
+        *a3 &= mask;
+        *a4 &= mask;
+        *a5 &= mask;
+        *a6 &= mask;
+        *a7 &= mask;
+        *a8 &= mask;
+    }
+}
+
 /*
  * Disable CFI checks.
  * The callback function has been loaded from an external library so we do not
@@ -531,6 +548,8 @@ qemu_plugin_vcpu_syscall(CPUState *cpu, int64_t num, uint64_t a1, uint64_t a2,
         return;
     }
 
+    clamp_syscall_arguments(&a1, &a2, &a3, &a4, &a5, &a6, &a7, &a8);
+
     QLIST_FOREACH_SAFE_RCU(cb, &plugin.cb_lists[ev], entry, next) {
         qemu_plugin_vcpu_syscall_cb_t func = cb->f.vcpu_syscall;
 
@@ -584,6 +603,8 @@ qemu_plugin_vcpu_syscall_filter(CPUState *cpu, int64_t num, uint64_t a1,
         return false;
     }
 
+    clamp_syscall_arguments(&a1, &a2, &a3, &a4, &a5, &a6, &a7, &a8);
+
     qemu_plugin_set_cb_flags(cpu, QEMU_PLUGIN_CB_RW_REGS);
 
     QLIST_FOREACH_SAFE_RCU(cb, &plugin.cb_lists[ev], entry, next) {

-- 
2.53.0



^ permalink raw reply related	[flat|nested] 18+ messages in thread

* [PATCH v7 2/8] plugins: add flag to specify whether PC is rw
  2026-03-05 10:05 [PATCH v7 0/8] Enable PC diversion via the plugin API Florian Hofhammer
  2026-03-05 10:05 ` [PATCH v7 1/8] plugins/core: clamp syscall arguments if target is 32-bit Florian Hofhammer
@ 2026-03-05 10:06 ` Florian Hofhammer
  2026-03-05 10:06 ` [PATCH v7 3/8] linux-user: make syscall emulation interruptible Florian Hofhammer
                   ` (6 subsequent siblings)
  8 siblings, 0 replies; 18+ messages in thread
From: Florian Hofhammer @ 2026-03-05 10:06 UTC (permalink / raw)
  To: qemu-devel; +Cc: Florian Hofhammer, Alex Bennée, Pierrick Bouvier

In addition to the flags specifying whether general-purpose registers
are read-write (rw) during a plugin callback, we add an additional flag
explicitly stating whether the PC is writable. This is in preparation of
a patch that allows to explicitly set the PC to divert control flow from
within a plugin callback, which is currently not possible.

Reviewed-by: Alex Bennée <alex.bennee@linaro.org>
Reviewed-by: Pierrick Bouvier <pierrick.bouvier@linaro.org>
Signed-off-by: Florian Hofhammer <florian.hofhammer@epfl.ch>
---
 include/plugins/qemu-plugin.h |  3 +++
 plugins/api.c                 |  4 +++-
 plugins/core.c                | 29 ++++++++++++++++-------------
 3 files changed, 22 insertions(+), 14 deletions(-)

diff --git a/include/plugins/qemu-plugin.h b/include/plugins/qemu-plugin.h
index 17a834dca9..a6ec8e275d 100644
--- a/include/plugins/qemu-plugin.h
+++ b/include/plugins/qemu-plugin.h
@@ -325,11 +325,14 @@ typedef struct {
  * @QEMU_PLUGIN_CB_NO_REGS: callback does not access the CPU's regs
  * @QEMU_PLUGIN_CB_R_REGS: callback reads the CPU's regs
  * @QEMU_PLUGIN_CB_RW_REGS: callback reads and writes the CPU's regs
+ * @QEMU_PLUGIN_CB_RW_REGS_PC: callback reads and writes the CPU's
+ *                             regs and updates the PC
  */
 enum qemu_plugin_cb_flags {
     QEMU_PLUGIN_CB_NO_REGS,
     QEMU_PLUGIN_CB_R_REGS,
     QEMU_PLUGIN_CB_RW_REGS,
+    QEMU_PLUGIN_CB_RW_REGS_PC,
 };
 
 enum qemu_plugin_mem_rw {
diff --git a/plugins/api.c b/plugins/api.c
index 04ca7da7f1..32eb086300 100644
--- a/plugins/api.c
+++ b/plugins/api.c
@@ -458,7 +458,9 @@ bool qemu_plugin_write_register(struct qemu_plugin_register *reg,
 {
     g_assert(current_cpu);
 
-    if (buf->len == 0 || qemu_plugin_get_cb_flags() != QEMU_PLUGIN_CB_RW_REGS) {
+    if (buf->len == 0 ||
+        (qemu_plugin_get_cb_flags() != QEMU_PLUGIN_CB_RW_REGS &&
+         qemu_plugin_get_cb_flags() != QEMU_PLUGIN_CB_RW_REGS_PC)) {
         return false;
     }
 
diff --git a/plugins/core.c b/plugins/core.c
index d6173422e9..2324bbffa3 100644
--- a/plugins/core.c
+++ b/plugins/core.c
@@ -119,7 +119,7 @@ static void plugin_vcpu_cb__discon(CPUState *cpu,
     struct qemu_plugin_cb *cb, *next;
     uint64_t to = cpu->cc->get_pc(cpu);
 
-    qemu_plugin_set_cb_flags(cpu, QEMU_PLUGIN_CB_RW_REGS);
+    qemu_plugin_set_cb_flags(cpu, QEMU_PLUGIN_CB_RW_REGS_PC);
     if (cpu->cpu_index < plugin.num_vcpus) {
         /* iterate safely; plugins might uninstall themselves at any time */
         QLIST_FOREACH_SAFE_RCU(cb, &plugin.cb_lists[ev], entry, next) {
@@ -395,15 +395,16 @@ void plugin_register_dyn_cb__udata(GArray **arr,
                                    enum qemu_plugin_cb_flags flags,
                                    void *udata)
 {
-    static TCGHelperInfo info[3] = {
+    static TCGHelperInfo info[4] = {
         [QEMU_PLUGIN_CB_NO_REGS].flags = TCG_CALL_NO_RWG,
         [QEMU_PLUGIN_CB_R_REGS].flags = TCG_CALL_NO_WG,
         [QEMU_PLUGIN_CB_RW_REGS].flags = 0,
+        [QEMU_PLUGIN_CB_RW_REGS_PC].flags = 0,
         /*
          * Match qemu_plugin_vcpu_udata_cb_t:
          *   void (*)(uint32_t, void *)
          */
-        [0 ... 2].typemask = (dh_typemask(void, 0) |
+        [0 ... 3].typemask = (dh_typemask(void, 0) |
                               dh_typemask(i32, 1) |
                               dh_typemask(ptr, 2))
     };
@@ -425,15 +426,16 @@ void plugin_register_dyn_cond_cb__udata(GArray **arr,
                                         uint64_t imm,
                                         void *udata)
 {
-    static TCGHelperInfo info[3] = {
+    static TCGHelperInfo info[4] = {
         [QEMU_PLUGIN_CB_NO_REGS].flags = TCG_CALL_NO_RWG,
         [QEMU_PLUGIN_CB_R_REGS].flags = TCG_CALL_NO_WG,
         [QEMU_PLUGIN_CB_RW_REGS].flags = 0,
+        [QEMU_PLUGIN_CB_RW_REGS_PC].flags = 0,
         /*
          * Match qemu_plugin_vcpu_udata_cb_t:
          *   void (*)(uint32_t, void *)
          */
-        [0 ... 2].typemask = (dh_typemask(void, 0) |
+        [0 ... 3].typemask = (dh_typemask(void, 0) |
                               dh_typemask(i32, 1) |
                               dh_typemask(ptr, 2))
     };
@@ -464,15 +466,16 @@ void plugin_register_vcpu_mem_cb(GArray **arr,
         !__builtin_types_compatible_p(qemu_plugin_meminfo_t, uint32_t) &&
         !__builtin_types_compatible_p(qemu_plugin_meminfo_t, int32_t));
 
-    static TCGHelperInfo info[3] = {
+    static TCGHelperInfo info[4] = {
         [QEMU_PLUGIN_CB_NO_REGS].flags = TCG_CALL_NO_RWG,
         [QEMU_PLUGIN_CB_R_REGS].flags = TCG_CALL_NO_WG,
         [QEMU_PLUGIN_CB_RW_REGS].flags = 0,
+        [QEMU_PLUGIN_CB_RW_REGS_PC].flags = 0,
         /*
          * Match qemu_plugin_vcpu_mem_cb_t:
          *   void (*)(uint32_t, qemu_plugin_meminfo_t, uint64_t, void *)
          */
-        [0 ... 2].typemask =
+        [0 ... 3].typemask =
             (dh_typemask(void, 0) |
              dh_typemask(i32, 1) |
              (__builtin_types_compatible_p(qemu_plugin_meminfo_t, uint32_t)
@@ -553,7 +556,7 @@ qemu_plugin_vcpu_syscall(CPUState *cpu, int64_t num, uint64_t a1, uint64_t a2,
     QLIST_FOREACH_SAFE_RCU(cb, &plugin.cb_lists[ev], entry, next) {
         qemu_plugin_vcpu_syscall_cb_t func = cb->f.vcpu_syscall;
 
-        qemu_plugin_set_cb_flags(cpu, QEMU_PLUGIN_CB_RW_REGS);
+        qemu_plugin_set_cb_flags(cpu, QEMU_PLUGIN_CB_RW_REGS_PC);
         func(cb->ctx->id, cpu->cpu_index, num, a1, a2, a3, a4, a5, a6, a7, a8);
         qemu_plugin_set_cb_flags(cpu, QEMU_PLUGIN_CB_NO_REGS);
     }
@@ -577,7 +580,7 @@ void qemu_plugin_vcpu_syscall_ret(CPUState *cpu, int64_t num, int64_t ret)
     QLIST_FOREACH_SAFE_RCU(cb, &plugin.cb_lists[ev], entry, next) {
         qemu_plugin_vcpu_syscall_ret_cb_t func = cb->f.vcpu_syscall_ret;
 
-        qemu_plugin_set_cb_flags(cpu, QEMU_PLUGIN_CB_RW_REGS);
+        qemu_plugin_set_cb_flags(cpu, QEMU_PLUGIN_CB_RW_REGS_PC);
         func(cb->ctx->id, cpu->cpu_index, num, ret);
         qemu_plugin_set_cb_flags(cpu, QEMU_PLUGIN_CB_NO_REGS);
     }
@@ -605,7 +608,7 @@ qemu_plugin_vcpu_syscall_filter(CPUState *cpu, int64_t num, uint64_t a1,
 
     clamp_syscall_arguments(&a1, &a2, &a3, &a4, &a5, &a6, &a7, &a8);
 
-    qemu_plugin_set_cb_flags(cpu, QEMU_PLUGIN_CB_RW_REGS);
+    qemu_plugin_set_cb_flags(cpu, QEMU_PLUGIN_CB_RW_REGS_PC);
 
     QLIST_FOREACH_SAFE_RCU(cb, &plugin.cb_lists[ev], entry, next) {
         qemu_plugin_vcpu_syscall_filter_cb_t func = cb->f.vcpu_syscall_filter;
@@ -626,7 +629,7 @@ void qemu_plugin_vcpu_idle_cb(CPUState *cpu)
 {
     /* idle and resume cb may be called before init, ignore in this case */
     if (cpu->cpu_index < plugin.num_vcpus) {
-        qemu_plugin_set_cb_flags(cpu, QEMU_PLUGIN_CB_RW_REGS);
+        qemu_plugin_set_cb_flags(cpu, QEMU_PLUGIN_CB_RW_REGS_PC);
         plugin_vcpu_cb__simple(cpu, QEMU_PLUGIN_EV_VCPU_IDLE);
         qemu_plugin_set_cb_flags(cpu, QEMU_PLUGIN_CB_NO_REGS);
     }
@@ -635,7 +638,7 @@ void qemu_plugin_vcpu_idle_cb(CPUState *cpu)
 void qemu_plugin_vcpu_resume_cb(CPUState *cpu)
 {
     if (cpu->cpu_index < plugin.num_vcpus) {
-        qemu_plugin_set_cb_flags(cpu, QEMU_PLUGIN_CB_RW_REGS);
+        qemu_plugin_set_cb_flags(cpu, QEMU_PLUGIN_CB_RW_REGS_PC);
         plugin_vcpu_cb__simple(cpu, QEMU_PLUGIN_EV_VCPU_RESUME);
         qemu_plugin_set_cb_flags(cpu, QEMU_PLUGIN_CB_NO_REGS);
     }
@@ -906,6 +909,6 @@ enum qemu_plugin_cb_flags tcg_call_to_qemu_plugin_cb_flags(int flags)
     } else if (flags & TCG_CALL_NO_WG) {
         return QEMU_PLUGIN_CB_R_REGS;
     } else {
-        return QEMU_PLUGIN_CB_RW_REGS;
+        return QEMU_PLUGIN_CB_RW_REGS_PC;
     }
 }

-- 
2.53.0



^ permalink raw reply related	[flat|nested] 18+ messages in thread

* [PATCH v7 3/8] linux-user: make syscall emulation interruptible
  2026-03-05 10:05 [PATCH v7 0/8] Enable PC diversion via the plugin API Florian Hofhammer
  2026-03-05 10:05 ` [PATCH v7 1/8] plugins/core: clamp syscall arguments if target is 32-bit Florian Hofhammer
  2026-03-05 10:06 ` [PATCH v7 2/8] plugins: add flag to specify whether PC is rw Florian Hofhammer
@ 2026-03-05 10:06 ` Florian Hofhammer
  2026-03-05 10:06 ` [PATCH v7 4/8] plugins: add PC diversion API function Florian Hofhammer
                   ` (5 subsequent siblings)
  8 siblings, 0 replies; 18+ messages in thread
From: Florian Hofhammer @ 2026-03-05 10:06 UTC (permalink / raw)
  To: qemu-devel
  Cc: Florian Hofhammer, Warner Losh, Pierrick Bouvier,
	Alex Bennée

The syscall emulation code previously wasn't interruptible via
cpu_loop_exit(), as this construct relies on a longjmp target that is not
live anymore in the syscall handling code. Consequently, longjmp() would
operate on a (potentially overwritten) stale jump buffer. This patch adds an additional
setjmp and the necessary handling around it to make longjmp() (and by
proxy cpu_loop_exit() safe to call even within a syscall context.

Reviewed-by: Warner Losh <imp@bsdimp.com>
Reviewed-by: Pierrick Bouvier <pierrick.bouvier@linaro.org>
Reviewed-by: Alex Bennée <alex.bennee@linaro.org>
Signed-off-by: Florian Hofhammer <florian.hofhammer@epfl.ch>
---
 linux-user/aarch64/cpu_loop.c      |  2 +-
 linux-user/alpha/cpu_loop.c        |  2 +-
 linux-user/arm/cpu_loop.c          |  2 +-
 linux-user/hexagon/cpu_loop.c      |  2 +-
 linux-user/hppa/cpu_loop.c         |  1 +
 linux-user/i386/cpu_loop.c         |  8 +++++---
 linux-user/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         |  9 ++++++---
 linux-user/or1k/cpu_loop.c         |  2 +-
 linux-user/ppc/cpu_loop.c          | 10 +++++++---
 linux-user/riscv/cpu_loop.c        |  2 +-
 linux-user/s390x/cpu_loop.c        |  2 +-
 linux-user/sh4/cpu_loop.c          |  2 +-
 linux-user/sparc/cpu_loop.c        |  4 +++-
 linux-user/syscall.c               | 16 ++++++++++++++++
 linux-user/xtensa/cpu_loop.c       |  1 +
 19 files changed, 60 insertions(+), 22 deletions(-)

diff --git a/linux-user/aarch64/cpu_loop.c b/linux-user/aarch64/cpu_loop.c
index 7f66a879ea..e7f643d69d 100644
--- a/linux-user/aarch64/cpu_loop.c
+++ b/linux-user/aarch64/cpu_loop.c
@@ -181,7 +181,7 @@ void cpu_loop(CPUARMState *env)
                              0, 0);
             if (ret == -QEMU_ERESTARTSYS) {
                 env->pc -= 4;
-            } else if (ret != -QEMU_ESIGRETURN) {
+            } else if (ret != -QEMU_ESIGRETURN && ret != -QEMU_ESETPC) {
                 env->xregs[0] = ret;
             }
             break;
diff --git a/linux-user/alpha/cpu_loop.c b/linux-user/alpha/cpu_loop.c
index 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 40aefc4c1d..19874f4c72 100644
--- a/linux-user/arm/cpu_loop.c
+++ b/linux-user/arm/cpu_loop.c
@@ -399,7 +399,7 @@ void cpu_loop(CPUARMState *env)
                                      0, 0);
                     if (ret == -QEMU_ERESTARTSYS) {
                         env->regs[15] -= env->thumb ? 2 : 4;
-                    } else if (ret != -QEMU_ESIGRETURN) {
+                    } else if (ret != -QEMU_ESIGRETURN && ret != -QEMU_ESETPC) {
                         env->regs[0] = ret;
                     }
                 }
diff --git a/linux-user/hexagon/cpu_loop.c b/linux-user/hexagon/cpu_loop.c
index 5711055aff..9464246e9e 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 972e85c487..4b4b663052 100644
--- a/linux-user/hppa/cpu_loop.c
+++ b/linux-user/hppa/cpu_loop.c
@@ -124,6 +124,7 @@ void cpu_loop(CPUHPPAState *env)
                 break;
             case -QEMU_ERESTARTSYS:
             case -QEMU_ESIGRETURN:
+            case -QEMU_ESETPC:
                 break;
             }
             break;
diff --git a/linux-user/i386/cpu_loop.c b/linux-user/i386/cpu_loop.c
index 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..fa264b27ec 100644
--- a/linux-user/mips/cpu_loop.c
+++ b/linux-user/mips/cpu_loop.c
@@ -140,9 +140,12 @@ done_syscall:
                 env->active_tc.PC -= 4;
                 break;
             }
-            if (ret == -QEMU_ESIGRETURN) {
-                /* Returning from a successful sigreturn syscall.
-                   Avoid clobbering register state.  */
+            if (ret == -QEMU_ESIGRETURN || ret == -QEMU_ESETPC) {
+                /*
+                 * Returning from a successful sigreturn syscall or from
+                 * control flow diversion in a plugin callback.
+                 * Avoid clobbering register state.
+                 */
                 break;
             }
             if ((abi_ulong)ret >= (abi_ulong)-1133) {
diff --git a/linux-user/or1k/cpu_loop.c b/linux-user/or1k/cpu_loop.c
index 2167d880d5..e7e9929e6f 100644
--- a/linux-user/or1k/cpu_loop.c
+++ b/linux-user/or1k/cpu_loop.c
@@ -48,7 +48,7 @@ void cpu_loop(CPUOpenRISCState *env)
                              cpu_get_gpr(env, 8), 0, 0);
             if (ret == -QEMU_ERESTARTSYS) {
                 env->pc -= 4;
-            } else if (ret != -QEMU_ESIGRETURN) {
+            } else if (ret != -QEMU_ESIGRETURN && ret != -QEMU_ESETPC) {
                 cpu_set_gpr(env, 11, ret);
             }
             break;
diff --git a/linux-user/ppc/cpu_loop.c b/linux-user/ppc/cpu_loop.c
index b0b0cb14b4..1f9ee20bd0 100644
--- a/linux-user/ppc/cpu_loop.c
+++ b/linux-user/ppc/cpu_loop.c
@@ -340,9 +340,13 @@ void cpu_loop(CPUPPCState *env)
                 env->nip -= 4;
                 break;
             }
-            if (ret == (target_ulong)(-QEMU_ESIGRETURN)) {
-                /* Returning from a successful sigreturn syscall.
-                   Avoid corrupting register state.  */
+            if (ret == (target_ulong)(-QEMU_ESIGRETURN) ||
+                ret == (target_ulong)(-QEMU_ESETPC)) {
+                /*
+                 * Returning from a successful sigreturn syscall or from
+                 * control flow diversion in a plugin callback.
+                 * Avoid corrupting register state.
+                 */
                 break;
             }
             if (ret > (target_ulong)(-515)) {
diff --git a/linux-user/riscv/cpu_loop.c b/linux-user/riscv/cpu_loop.c
index 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..ab633eeae3 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 d466d0e32f..99e1ed97d9 100644
--- a/linux-user/syscall.c
+++ b/linux-user/syscall.c
@@ -43,6 +43,7 @@
 #include <linux/capability.h>
 #include <sched.h>
 #include <sys/timex.h>
+#include <setjmp.h>
 #include <sys/socket.h>
 #include <linux/sockios.h>
 #include <sys/un.h>
@@ -600,6 +601,9 @@ const char *target_strerror(int err)
     if (err == QEMU_ESIGRETURN) {
         return "Successful exit from sigreturn";
     }
+    if (err == QEMU_ESETPC) {
+        return "Successfully redirected control flow";
+    }
 
     return strerror(target_to_host_errno(err));
 }
@@ -14410,6 +14414,18 @@ abi_long do_syscall(CPUArchState *cpu_env, int num, abi_long arg1,
         return -QEMU_ESIGRETURN;
     }
 
+    /*
+     * Set up a longjmp target here so that we can call cpu_loop_exit to
+     * redirect control flow back to the main loop even from within
+     * syscall-related plugin callbacks.
+     * For other types of callbacks or longjmp call sites, the longjmp target
+     * is set up in the cpu loop itself but in syscalls the target is not live
+     * anymore.
+     */
+    if (unlikely(sigsetjmp(cpu->jmp_env, 0) != 0)) {
+        return -QEMU_ESETPC;
+    }
+
     record_syscall_start(cpu, num, arg1,
                          arg2, arg3, arg4, arg5, arg6, arg7, arg8);
 
diff --git a/linux-user/xtensa/cpu_loop.c b/linux-user/xtensa/cpu_loop.c
index a0ff10eff8..d2b4ccdfad 100644
--- a/linux-user/xtensa/cpu_loop.c
+++ b/linux-user/xtensa/cpu_loop.c
@@ -186,6 +186,7 @@ void cpu_loop(CPUXtensaState *env)
                     break;
 
                 case -QEMU_ESIGRETURN:
+                case -QEMU_ESETPC:
                     break;
                 }
                 break;

-- 
2.53.0



^ permalink raw reply related	[flat|nested] 18+ messages in thread

* [PATCH v7 4/8] plugins: add PC diversion API function
  2026-03-05 10:05 [PATCH v7 0/8] Enable PC diversion via the plugin API Florian Hofhammer
                   ` (2 preceding siblings ...)
  2026-03-05 10:06 ` [PATCH v7 3/8] linux-user: make syscall emulation interruptible Florian Hofhammer
@ 2026-03-05 10:06 ` Florian Hofhammer
  2026-03-05 10:06 ` [PATCH v7 5/8] tests/tcg: add tests for qemu_plugin_set_pc API Florian Hofhammer
                   ` (4 subsequent siblings)
  8 siblings, 0 replies; 18+ messages in thread
From: Florian Hofhammer @ 2026-03-05 10:06 UTC (permalink / raw)
  To: qemu-devel; +Cc: Florian Hofhammer, Pierrick Bouvier

This patch adds a plugin API function that allows diverting the program
counter during execution. A potential use case for this functionality is
to skip over parts of the code, e.g., by hooking into a specific
instruction and setting the PC to the next instruction in the callback.

Link: https://lists.nongnu.org/archive/html/qemu-devel/2025-08/msg00656.html
Reviewed-by: Pierrick Bouvier <pierrick.bouvier@linaro.org>
Signed-off-by: Florian Hofhammer <florian.hofhammer@epfl.ch>
---
 include/plugins/qemu-plugin.h  | 13 +++++++++++++
 plugins/api.c                  | 11 +++++++++++
 scripts/qemu-plugin-symbols.py |  9 +++++++--
 3 files changed, 31 insertions(+), 2 deletions(-)

diff --git a/include/plugins/qemu-plugin.h b/include/plugins/qemu-plugin.h
index a6ec8e275d..7b9cd6a971 100644
--- a/include/plugins/qemu-plugin.h
+++ b/include/plugins/qemu-plugin.h
@@ -76,6 +76,7 @@ typedef uint64_t qemu_plugin_id_t;
  *
  * version 6:
  * - changed return value of qemu_plugin_{read,write}_register from int to bool
+ * - added qemu_plugin_set_pc
  */
 
 extern QEMU_PLUGIN_EXPORT int qemu_plugin_version;
@@ -1042,6 +1043,18 @@ QEMU_PLUGIN_API
 bool qemu_plugin_write_register(struct qemu_plugin_register *handle,
                                 GByteArray *buf);
 
+/**
+ * qemu_plugin_set_pc() - set the program counter for the current vCPU
+ *
+ * @vaddr: the new virtual (guest) address for the program counter
+ *
+ * This function sets the program counter for the current vCPU to @vaddr and
+ * resumes execution at that address. This function does not return.
+ */
+QEMU_PLUGIN_API
+__attribute__((__noreturn__))
+void qemu_plugin_set_pc(uint64_t vaddr);
+
 /**
  * qemu_plugin_read_memory_vaddr() - read from memory using a virtual address
  *
diff --git a/plugins/api.c b/plugins/api.c
index 32eb086300..23c291f644 100644
--- a/plugins/api.c
+++ b/plugins/api.c
@@ -41,6 +41,7 @@
 #include "qemu/log.h"
 #include "system/memory.h"
 #include "tcg/tcg.h"
+#include "exec/cpu-common.h"
 #include "exec/gdbstub.h"
 #include "exec/target_page.h"
 #include "exec/translation-block.h"
@@ -467,6 +468,16 @@ bool qemu_plugin_write_register(struct qemu_plugin_register *reg,
     return (gdb_write_register(current_cpu, buf->data, GPOINTER_TO_INT(reg) - 1) > 0);
 }
 
+void qemu_plugin_set_pc(uint64_t vaddr)
+{
+    g_assert(current_cpu);
+
+    g_assert(qemu_plugin_get_cb_flags() == QEMU_PLUGIN_CB_RW_REGS_PC);
+
+    cpu_set_pc(current_cpu, vaddr);
+    cpu_loop_exit(current_cpu);
+}
+
 bool qemu_plugin_read_memory_vaddr(uint64_t addr, GByteArray *data, size_t len)
 {
     g_assert(current_cpu);
diff --git a/scripts/qemu-plugin-symbols.py b/scripts/qemu-plugin-symbols.py
index 69644979c1..ce99796ce2 100644
--- a/scripts/qemu-plugin-symbols.py
+++ b/scripts/qemu-plugin-symbols.py
@@ -20,9 +20,14 @@ def extract_symbols(plugin_header):
     # Remove QEMU_PLUGIN_API macro definition.
     content = content.replace('#define QEMU_PLUGIN_API', '')
     expected = content.count('QEMU_PLUGIN_API')
-    # Find last word between QEMU_PLUGIN_API and (, matching on several lines.
+    # Find last word between QEMU_PLUGIN_API and ( to get the function name,
+    # matching on several lines. Discard attributes, if any.
     # We use *? non-greedy quantifier.
-    syms = re.findall(r'QEMU_PLUGIN_API.*?(\w+)\s*\(', content, re.DOTALL)
+    syms = re.findall(
+        r'QEMU_PLUGIN_API\s+(?:__attribute__\(\(\S+\)\))?.*?(\w+)\s*\(',
+        content,
+        re.DOTALL,
+    )
     syms.sort()
     # Ensure we found as many symbols as API markers.
     assert len(syms) == expected

-- 
2.53.0



^ permalink raw reply related	[flat|nested] 18+ messages in thread

* [PATCH v7 5/8] tests/tcg: add tests for qemu_plugin_set_pc API
  2026-03-05 10:05 [PATCH v7 0/8] Enable PC diversion via the plugin API Florian Hofhammer
                   ` (3 preceding siblings ...)
  2026-03-05 10:06 ` [PATCH v7 4/8] plugins: add PC diversion API function Florian Hofhammer
@ 2026-03-05 10:06 ` Florian Hofhammer
  2026-03-05 18:24   ` Pierrick Bouvier
  2026-03-05 10:06 ` [PATCH v7 6/8] plugins: add read-only property for registers Florian Hofhammer
                   ` (3 subsequent siblings)
  8 siblings, 1 reply; 18+ messages in thread
From: Florian Hofhammer @ 2026-03-05 10:06 UTC (permalink / raw)
  To: qemu-devel; +Cc: Florian Hofhammer, Pierrick Bouvier

The test plugin intercepts execution in different contexts. Without the
plugin, any of the implemented test functions would trigger an assert
and fail. With the plugin, control flow is redirected to skip the assert
and return cleanly via the qemu_plugin_set_pc() API.

Signed-off-by: Pierrick Bouvier <pierrick.bouvier@linaro.org>
Signed-off-by: Florian Hofhammer <florian.hofhammer@epfl.ch>
---
 MAINTAINERS                                        |   1 +
 tests/tcg/arm/Makefile.target                      |   6 +
 tests/tcg/hexagon/Makefile.target                  |   8 ++
 tests/tcg/multiarch/Makefile.target                |  17 ++-
 .../multiarch/{ => plugin}/check-plugin-output.sh  |   0
 .../{ => plugin}/test-plugin-mem-access.c          |   0
 tests/tcg/multiarch/plugin/test-plugin-set-pc.c    | 134 +++++++++++++++++++++
 tests/tcg/plugins/meson.build                      |   1 +
 tests/tcg/plugins/setpc.c                          | 109 +++++++++++++++++
 9 files changed, 273 insertions(+), 3 deletions(-)

diff --git a/MAINTAINERS b/MAINTAINERS
index 6698e5ff69..63c0af4d86 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -4104,6 +4104,7 @@ S: Maintained
 F: docs/devel/tcg-plugins.rst
 F: plugins/
 F: tests/tcg/plugins/
+F: tests/tcg/multiarch/plugin/
 F: tests/functional/aarch64/test_tcg_plugins.py
 F: contrib/plugins/
 F: scripts/qemu-plugin-symbols.py
diff --git a/tests/tcg/arm/Makefile.target b/tests/tcg/arm/Makefile.target
index 6189d7a0e2..613bbf0939 100644
--- a/tests/tcg/arm/Makefile.target
+++ b/tests/tcg/arm/Makefile.target
@@ -78,4 +78,10 @@ sha512-vector: sha512.c
 
 ARM_TESTS += sha512-vector
 
+ifeq ($(CONFIG_PLUGIN),y)
+# Require emitting arm32 instructions, otherwise the vCPU might accidentally
+# try to execute Thumb instructions in arm32 mode after qemu_plugin_set_pc()
+test-plugin-set-pc: CFLAGS+=-marm
+endif
+
 TESTS += $(ARM_TESTS)
diff --git a/tests/tcg/hexagon/Makefile.target b/tests/tcg/hexagon/Makefile.target
index f86f02bb31..a70ef2f660 100644
--- a/tests/tcg/hexagon/Makefile.target
+++ b/tests/tcg/hexagon/Makefile.target
@@ -126,3 +126,11 @@ v73_scalar: CFLAGS += -Wno-unused-function
 
 hvx_histogram: hvx_histogram.c hvx_histogram_row.S
 	$(CC) $(CFLAGS) $(CROSS_CC_GUEST_CFLAGS) $^ -o $@ $(LDFLAGS)
+
+ifeq ($(CONFIG_PLUGIN),y)
+# LLVM is way too aggressive with inlining and dead code elimination even at
+# -O0, which interferes with the test. What looks like dead code in this test
+# to the compiler isn't actually dead code, so we need to disable all potential
+# LLVM optimization passes.
+test-plugin-set-pc: CFLAGS += -Xclang -disable-llvm-passes
+endif
diff --git a/tests/tcg/multiarch/Makefile.target b/tests/tcg/multiarch/Makefile.target
index 07d0b27bdd..a347efbadf 100644
--- a/tests/tcg/multiarch/Makefile.target
+++ b/tests/tcg/multiarch/Makefile.target
@@ -14,6 +14,10 @@ ifeq ($(filter %-linux-user, $(TARGET)),$(TARGET))
 VPATH 	       += $(MULTIARCH_SRC)/linux
 MULTIARCH_SRCS += $(notdir $(wildcard $(MULTIARCH_SRC)/linux/*.c))
 endif
+ifeq ($(CONFIG_PLUGIN),y)
+VPATH 	       += $(MULTIARCH_SRC)/plugin
+MULTIARCH_SRCS += $(notdir $(wildcard $(MULTIARCH_SRC)/plugin/*.c))
+endif
 MULTIARCH_TESTS = $(MULTIARCH_SRCS:.c=)
 
 #
@@ -200,13 +204,20 @@ run-plugin-test-plugin-mem-access-with-libmem.so: \
 	PLUGIN_ARGS=$(COMMA)print-accesses=true
 run-plugin-test-plugin-mem-access-with-libmem.so: \
 	CHECK_PLUGIN_OUTPUT_COMMAND= \
-	$(SRC_PATH)/tests/tcg/multiarch/check-plugin-output.sh \
+	$(SRC_PATH)/tests/tcg/multiarch/plugin/check-plugin-output.sh \
 	$(QEMU) $<
 run-plugin-test-plugin-syscall-filter-with-libsyscall.so:
+run-plugin-test-plugin-set-pc-with-libsetpc.so:
 
 EXTRA_RUNS_WITH_PLUGIN += run-plugin-test-plugin-mem-access-with-libmem.so \
-			  run-plugin-test-plugin-syscall-filter-with-libsyscall.so
-else
+			  run-plugin-test-plugin-syscall-filter-with-libsyscall.so \
+			  run-plugin-test-plugin-set-pc-with-libsetpc.so
+
+else # CONFIG_PLUGIN=n
+# Do not build the syscall skipping test if it's not tested with the setpc
+# plugin because it will simply fail the test.
+MULTIARCH_TESTS := $(filter-out test-plugin-set-pc, $(MULTIARCH_TESTS))
+
 # test-plugin-syscall-filter needs syscall plugin to succeed
 test-plugin-syscall-filter: CFLAGS+=-DSKIP
 endif
diff --git a/tests/tcg/multiarch/check-plugin-output.sh b/tests/tcg/multiarch/plugin/check-plugin-output.sh
similarity index 100%
rename from tests/tcg/multiarch/check-plugin-output.sh
rename to tests/tcg/multiarch/plugin/check-plugin-output.sh
diff --git a/tests/tcg/multiarch/test-plugin-mem-access.c b/tests/tcg/multiarch/plugin/test-plugin-mem-access.c
similarity index 100%
rename from tests/tcg/multiarch/test-plugin-mem-access.c
rename to tests/tcg/multiarch/plugin/test-plugin-mem-access.c
diff --git a/tests/tcg/multiarch/plugin/test-plugin-set-pc.c b/tests/tcg/multiarch/plugin/test-plugin-set-pc.c
new file mode 100644
index 0000000000..f8343dfba8
--- /dev/null
+++ b/tests/tcg/multiarch/plugin/test-plugin-set-pc.c
@@ -0,0 +1,134 @@
+/*
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * Copyright (C) 2026, Florian Hofhammer <florian.hofhammer@epfl.ch>
+ *
+ * This test set exercises the qemu_plugin_set_pc() function in four different
+ * contexts:
+ * 1. in an instruction callback during normal execution,
+ * 2. in an instruction callback during signal handling,
+ * 3. in a memory access callback.
+ * 4. in a syscall callback,
+ */
+#include <assert.h>
+#include <signal.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+
+/* If we issue this magic syscall, ... */
+#define MAGIC_SYSCALL 4096
+/* ... the plugin either jumps directly to the target address ... */
+#define SETPC 0
+/* ... or just updates the target address for future use in callbacks. */
+#define SETTARGET 1
+
+static int signal_handled;
+
+void panic(const char *msg)
+{
+    fprintf(stderr, "Panic: %s\n", msg);
+    abort();
+}
+
+/*
+ * This test executes a magic syscall which communicates two addresses to the
+ * plugin via the syscall arguments. Whenever we reach the "bad" instruction
+ * during normal execution, the plugin should redirect control flow to the
+ * "good" instruction instead.
+ */
+void test_insn(void)
+{
+    long ret = syscall(MAGIC_SYSCALL, SETTARGET, &&bad_insn, &&good_insn,
+                       NULL);
+    assert(ret == 0 && "Syscall filter did not return expected value");
+bad_insn:
+    panic("PC redirection in instruction callback failed");
+good_insn:
+    puts("PC redirection in instruction callback succeeded");
+}
+
+/*
+ * This signal handler communicates a "bad" and a "good" address to the plugin
+ * similar to the previous test, and skips to the "good" address when the "bad"
+ * one is reached. This serves to test whether PC redirection via
+ * qemu_plugin_set_pc() also works properly in a signal handler context.
+ */
+void usr1_handler(int signum)
+{
+    long ret = syscall(MAGIC_SYSCALL, SETTARGET, &&bad_signal, &&good_signal,
+                       NULL);
+    assert(ret == 0 && "Syscall filter did not return expected value");
+bad_signal:
+    panic("PC redirection in instruction callback failed");
+good_signal:
+    signal_handled = 1;
+    puts("PC redirection in instruction callback succeeded");
+}
+
+/*
+ * This test sends a signal to the process, which should trigger the above
+ * signal handler. The signal handler should then exercise the PC redirection
+ * functionality in the context of a signal handler, which behaves a bit
+ * differently from normal execution.
+ */
+void test_sighandler(void)
+{
+    struct sigaction sa = {0};
+    sa.sa_handler = usr1_handler;
+    sigaction(SIGUSR1, &sa, NULL);
+    pid_t pid = getpid();
+    kill(pid, SIGUSR1);
+    assert(signal_handled == 1 && "Signal handler was not executed properly");
+}
+
+/*
+ * This test communicates a "good" address and the address of a local variable
+ * to the plugin. Upon accessing the local variable, the plugin should then
+ * redirect control flow to the "good" address via qemu_plugin_set_pc().
+ */
+void test_mem(void)
+{
+    static uint32_t test = 1;
+    long ret = syscall(MAGIC_SYSCALL, SETTARGET, NULL, &&good_mem, &test);
+    assert(ret == 0 && "Syscall filter did not return expected value");
+    /* Ensure read access to the variable to trigger the plugin callback */
+    assert(test == 1);
+    panic("PC redirection in memory access callback failed");
+good_mem:
+    puts("PC redirection in memory access callback succeeded");
+}
+
+/*
+ * This test executes a magic syscall which is intercepted and its actual
+ * execution skipped via the qemu_plugin_set_pc() API. In a proper plugin,
+ * syscall skipping would rather be implemented via the syscall filtering
+ * callback, but we want to make sure qemu_plugin_set_pc() works in different
+ * contexts.
+ */
+__attribute__((noreturn))
+void test_syscall(void)
+{
+    syscall(MAGIC_SYSCALL, SETPC, &&good_syscall);
+    panic("PC redirection in syscall callback failed");
+good_syscall:
+    /*
+     * Note: we execute this test last and exit straight from here because when
+     * the plugin redirects control flow upon syscall, the stack frame for the
+     * syscall function (and potential other functions in the call chain in
+     * libc) is still live and the stack is not unwound properly. Thus,
+     * returning from here is risky and breaks on some architectures, so we
+     * just exit directly from this test.
+     */
+    _exit(EXIT_SUCCESS);
+}
+
+
+int main(int argc, char *argv[])
+{
+    test_insn();
+    test_sighandler();
+    test_mem();
+    test_syscall();
+}
diff --git a/tests/tcg/plugins/meson.build b/tests/tcg/plugins/meson.build
index c5e49753fd..b3e3a9a6d0 100644
--- a/tests/tcg/plugins/meson.build
+++ b/tests/tcg/plugins/meson.build
@@ -7,6 +7,7 @@ test_plugins = [
 'mem.c',
 'patch.c',
 'reset.c',
+'setpc.c',
 'syscall.c',
 ]
 
diff --git a/tests/tcg/plugins/setpc.c b/tests/tcg/plugins/setpc.c
new file mode 100644
index 0000000000..8e66026d2c
--- /dev/null
+++ b/tests/tcg/plugins/setpc.c
@@ -0,0 +1,109 @@
+/*
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * Copyright (C) 2026, Florian Hofhammer <florian.hofhammer@epfl.ch>
+ */
+#include <assert.h>
+#include <glib.h>
+#include <inttypes.h>
+#include <unistd.h>
+
+#include <qemu-plugin.h>
+
+/* If we detect this magic syscall, ... */
+#define MAGIC_SYSCALL 4096
+/* ... the plugin either jumps directly to the target address ... */
+#define SETPC 0
+/* ... or just updates the target address for future use in callbacks. */
+#define SETTARGET 1
+
+QEMU_PLUGIN_EXPORT int qemu_plugin_version = QEMU_PLUGIN_VERSION;
+
+static uint64_t source_pc;
+static uint64_t target_pc;
+static uint64_t target_vaddr;
+
+static bool vcpu_syscall_filter(qemu_plugin_id_t id, unsigned int vcpu_index,
+                                int64_t num, uint64_t a1, uint64_t a2,
+                                uint64_t a3, uint64_t a4, uint64_t a5,
+                                uint64_t a6, uint64_t a7, uint64_t a8,
+                                uint64_t *sysret)
+{
+    if (num == MAGIC_SYSCALL) {
+        if (a1 == SETPC) {
+            qemu_plugin_outs("Magic syscall detected, jump to clean exit\n");
+            qemu_plugin_set_pc(a2);
+        } else if (a1 == SETTARGET) {
+            qemu_plugin_outs("Magic syscall detected, set target_pc / "
+                             "target_vaddr\n");
+            source_pc = a2;
+            target_pc = a3;
+            target_vaddr = a4;
+            *sysret = 0;
+            return true;
+        } else {
+            qemu_plugin_outs("Unknown magic syscall argument, ignoring\n");
+        }
+    }
+    return false;
+}
+
+static void vcpu_insn_exec(unsigned int vcpu_index, void *userdata)
+{
+    uint64_t vaddr = (uint64_t)userdata;
+    if (vaddr == source_pc) {
+        g_assert(target_pc != 0);
+        g_assert(target_vaddr == 0);
+
+        qemu_plugin_outs("Marker insn detected, jump to clean return\n");
+        qemu_plugin_set_pc(target_pc);
+    }
+}
+
+static void vcpu_mem_access(unsigned int vcpu_index,
+                            qemu_plugin_meminfo_t info,
+                            uint64_t vaddr, void *userdata)
+{
+    if (vaddr != 0 && vaddr == target_vaddr) {
+        g_assert(source_pc == 0);
+        g_assert(target_pc != 0);
+        qemu_plugin_mem_value val = qemu_plugin_mem_get_value(info);
+        /* target_vaddr points to our volatile guard ==> should always be 1 */
+        g_assert(val.type == QEMU_PLUGIN_MEM_VALUE_U32);
+        g_assert(val.data.u32 == 1);
+
+        qemu_plugin_outs("Marker mem access detected, jump to clean return\n");
+        qemu_plugin_set_pc(target_pc);
+    }
+}
+
+static void vcpu_tb_trans(qemu_plugin_id_t id, struct qemu_plugin_tb *tb)
+{
+    size_t insns = qemu_plugin_tb_n_insns(tb);
+    for (size_t i = 0; i < insns; i++) {
+        struct qemu_plugin_insn *insn = qemu_plugin_tb_get_insn(tb, i);
+        uint64_t insn_vaddr = qemu_plugin_insn_vaddr(insn);
+        /*
+         * Note: we cannot only register the callbacks if the instruction is
+         * in one of the functions of interest, because symbol lookup for
+         * filtering does not work for all architectures (e.g., ppc64).
+         */
+        qemu_plugin_register_vcpu_insn_exec_cb(insn, vcpu_insn_exec,
+                                               QEMU_PLUGIN_CB_RW_REGS_PC,
+                                               (void *)insn_vaddr);
+        qemu_plugin_register_vcpu_mem_cb(insn, vcpu_mem_access,
+                                         QEMU_PLUGIN_CB_RW_REGS_PC,
+                                         QEMU_PLUGIN_MEM_R, NULL);
+    }
+}
+
+
+QEMU_PLUGIN_EXPORT int qemu_plugin_install(qemu_plugin_id_t id,
+                                           const qemu_info_t *info,
+                                           int argc, char **argv)
+{
+
+    qemu_plugin_register_vcpu_syscall_filter_cb(id, vcpu_syscall_filter);
+    qemu_plugin_register_vcpu_tb_trans_cb(id, vcpu_tb_trans);
+    return 0;
+}

-- 
2.53.0



^ permalink raw reply related	[flat|nested] 18+ messages in thread

* [PATCH v7 6/8] plugins: add read-only property for registers
  2026-03-05 10:05 [PATCH v7 0/8] Enable PC diversion via the plugin API Florian Hofhammer
                   ` (4 preceding siblings ...)
  2026-03-05 10:06 ` [PATCH v7 5/8] tests/tcg: add tests for qemu_plugin_set_pc API Florian Hofhammer
@ 2026-03-05 10:06 ` Florian Hofhammer
  2026-03-05 10:06 ` [PATCH v7 7/8] plugins: prohibit writing to read-only registers Florian Hofhammer
                   ` (2 subsequent siblings)
  8 siblings, 0 replies; 18+ messages in thread
From: Florian Hofhammer @ 2026-03-05 10:06 UTC (permalink / raw)
  To: qemu-devel; +Cc: Florian Hofhammer, Alex Bennée, Pierrick Bouvier

Some registers should be marked as read-only from a plugin API
perspective, as writing to them via qemu_plugin_write_register has no
effect. This includes the program counter, and we expose this fact to
the plugins with this patch.

Reviewed-by: Alex Bennée <alex.bennee@linaro.org>
Reviewed-by: Pierrick Bouvier <pierrick.bouvier@linaro.org>
Signed-off-by: Florian Hofhammer <florian.hofhammer@epfl.ch>
---
 include/plugins/qemu-plugin.h |  3 +++
 plugins/api.c                 | 16 ++++++++++++++++
 2 files changed, 19 insertions(+)

diff --git a/include/plugins/qemu-plugin.h b/include/plugins/qemu-plugin.h
index 7b9cd6a971..fceb8194eb 100644
--- a/include/plugins/qemu-plugin.h
+++ b/include/plugins/qemu-plugin.h
@@ -979,11 +979,14 @@ struct qemu_plugin_register;
  *          writing value with qemu_plugin_write_register
  * @name: register name
  * @feature: optional feature descriptor, can be NULL
+ * @is_readonly: true if the register cannot be written via
+ *               qemu_plugin_write_register
  */
 typedef struct {
     struct qemu_plugin_register *handle;
     const char *name;
     const char *feature;
+    bool is_readonly;
 } qemu_plugin_reg_descriptor;
 
 /**
diff --git a/plugins/api.c b/plugins/api.c
index 23c291f644..85b34949cb 100644
--- a/plugins/api.c
+++ b/plugins/api.c
@@ -410,6 +410,12 @@ bool qemu_plugin_bool_parse(const char *name, const char *value, bool *ret)
  * ancillary data the plugin might find useful.
  */
 
+static const char pc_str[] = "pc"; /* generic name for program counter */
+static const char eip_str[] = "eip"; /* x86-specific name for PC */
+static const char rip_str[] = "rip"; /* x86_64-specific name for PC */
+static const char pswa_str[] = "pswa"; /* s390x-specific name for PC */
+static const char iaoq_str[] = "iaoq"; /* HP/PA-specific name for PC */
+static const char rpc_str[] = "rpc"; /* microblaze-specific name for PC */
 static GArray *create_register_handles(GArray *gdbstub_regs)
 {
     GArray *find_data = g_array_new(true, true,
@@ -427,6 +433,16 @@ static GArray *create_register_handles(GArray *gdbstub_regs)
         /* Create a record for the plugin */
         desc.handle = GINT_TO_POINTER(grd->gdb_reg + 1);
         desc.name = g_intern_string(grd->name);
+        desc.is_readonly = false;
+        if (g_strcmp0(desc.name, pc_str) == 0
+            || g_strcmp0(desc.name, eip_str) == 0
+            || g_strcmp0(desc.name, rip_str) == 0
+            || g_strcmp0(desc.name, pswa_str) == 0
+            || g_strcmp0(desc.name, iaoq_str) == 0
+            || g_strcmp0(desc.name, rpc_str) == 0
+           ) {
+            desc.is_readonly = true;
+        }
         desc.feature = g_intern_string(grd->feature_name);
         g_array_append_val(find_data, desc);
     }

-- 
2.53.0



^ permalink raw reply related	[flat|nested] 18+ messages in thread

* [PATCH v7 7/8] plugins: prohibit writing to read-only registers
  2026-03-05 10:05 [PATCH v7 0/8] Enable PC diversion via the plugin API Florian Hofhammer
                   ` (5 preceding siblings ...)
  2026-03-05 10:06 ` [PATCH v7 6/8] plugins: add read-only property for registers Florian Hofhammer
@ 2026-03-05 10:06 ` Florian Hofhammer
  2026-03-05 10:06 ` [PATCH v7 8/8] tests/tcg/plugins: test register accesses Florian Hofhammer
  2026-03-06 17:44 ` [PATCH v7 0/8] Enable PC diversion via the plugin API Pierrick Bouvier
  8 siblings, 0 replies; 18+ messages in thread
From: Florian Hofhammer @ 2026-03-05 10:06 UTC (permalink / raw)
  To: qemu-devel; +Cc: Florian Hofhammer, Alex Bennée, Pierrick Bouvier

The opaque register handle encodes whether a register is read-only in
the lowest bit and prevents writing to the register via the plugin API
in this case.

Reviewed-by: Alex Bennée <alex.bennee@linaro.org>
Reviewed-by: Pierrick Bouvier <pierrick.bouvier@linaro.org>
Signed-off-by: Florian Hofhammer <florian.hofhammer@epfl.ch>
---
 plugins/api.c | 11 ++++++++---
 1 file changed, 8 insertions(+), 3 deletions(-)

diff --git a/plugins/api.c b/plugins/api.c
index 85b34949cb..0c348a789b 100644
--- a/plugins/api.c
+++ b/plugins/api.c
@@ -424,6 +424,7 @@ static GArray *create_register_handles(GArray *gdbstub_regs)
     for (int i = 0; i < gdbstub_regs->len; i++) {
         GDBRegDesc *grd = &g_array_index(gdbstub_regs, GDBRegDesc, i);
         qemu_plugin_reg_descriptor desc;
+        gint plugin_ro_bit = 0;
 
         /* skip "un-named" regs */
         if (!grd->name) {
@@ -431,7 +432,6 @@ static GArray *create_register_handles(GArray *gdbstub_regs)
         }
 
         /* Create a record for the plugin */
-        desc.handle = GINT_TO_POINTER(grd->gdb_reg + 1);
         desc.name = g_intern_string(grd->name);
         desc.is_readonly = false;
         if (g_strcmp0(desc.name, pc_str) == 0
@@ -442,7 +442,9 @@ static GArray *create_register_handles(GArray *gdbstub_regs)
             || g_strcmp0(desc.name, rpc_str) == 0
            ) {
             desc.is_readonly = true;
+            plugin_ro_bit = 1;
         }
+        desc.handle = GINT_TO_POINTER((grd->gdb_reg << 1) | plugin_ro_bit);
         desc.feature = g_intern_string(grd->feature_name);
         g_array_append_val(find_data, desc);
     }
@@ -467,7 +469,7 @@ bool qemu_plugin_read_register(struct qemu_plugin_register *reg,
         return false;
     }
 
-    return (gdb_read_register(current_cpu, buf, GPOINTER_TO_INT(reg) - 1) > 0);
+    return (gdb_read_register(current_cpu, buf, GPOINTER_TO_INT(reg) >> 1) > 0);
 }
 
 bool qemu_plugin_write_register(struct qemu_plugin_register *reg,
@@ -475,13 +477,16 @@ bool qemu_plugin_write_register(struct qemu_plugin_register *reg,
 {
     g_assert(current_cpu);
 
+    /* Read-only property is encoded in least significant bit */
+    g_assert((GPOINTER_TO_INT(reg) & 1) == 0);
+
     if (buf->len == 0 ||
         (qemu_plugin_get_cb_flags() != QEMU_PLUGIN_CB_RW_REGS &&
          qemu_plugin_get_cb_flags() != QEMU_PLUGIN_CB_RW_REGS_PC)) {
         return false;
     }
 
-    return (gdb_write_register(current_cpu, buf->data, GPOINTER_TO_INT(reg) - 1) > 0);
+    return (gdb_write_register(current_cpu, buf->data, GPOINTER_TO_INT(reg) >> 1) > 0);
 }
 
 void qemu_plugin_set_pc(uint64_t vaddr)

-- 
2.53.0



^ permalink raw reply related	[flat|nested] 18+ messages in thread

* [PATCH v7 8/8] tests/tcg/plugins: test register accesses
  2026-03-05 10:05 [PATCH v7 0/8] Enable PC diversion via the plugin API Florian Hofhammer
                   ` (6 preceding siblings ...)
  2026-03-05 10:06 ` [PATCH v7 7/8] plugins: prohibit writing to read-only registers Florian Hofhammer
@ 2026-03-05 10:06 ` Florian Hofhammer
  2026-03-06 17:44 ` [PATCH v7 0/8] Enable PC diversion via the plugin API Pierrick Bouvier
  8 siblings, 0 replies; 18+ messages in thread
From: Florian Hofhammer @ 2026-03-05 10:06 UTC (permalink / raw)
  To: qemu-devel; +Cc: Florian Hofhammer, Pierrick Bouvier

The additional plugin tests register accesses, specifically both for
read-only and read-write registers. Writing to a read-only register is
currently not tested, as this would trigger an assertion and fail the
test.

Reviewed-by: Pierrick Bouvier <pierrick.bouvier@linaro.org>
Signed-off-by: Florian Hofhammer <florian.hofhammer@epfl.ch>
---
 tests/tcg/plugins/meson.build |  1 +
 tests/tcg/plugins/registers.c | 79 +++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 80 insertions(+)

diff --git a/tests/tcg/plugins/meson.build b/tests/tcg/plugins/meson.build
index b3e3a9a6d0..d7f8f0ae0a 100644
--- a/tests/tcg/plugins/meson.build
+++ b/tests/tcg/plugins/meson.build
@@ -6,6 +6,7 @@ test_plugins = [
 'insn.c',
 'mem.c',
 'patch.c',
+'registers.c',
 'reset.c',
 'setpc.c',
 'syscall.c',
diff --git a/tests/tcg/plugins/registers.c b/tests/tcg/plugins/registers.c
new file mode 100644
index 0000000000..6d627c7037
--- /dev/null
+++ b/tests/tcg/plugins/registers.c
@@ -0,0 +1,79 @@
+/*
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * Copyright (C) 2026, Florian Hofhammer <florian.hofhammer@epfl.ch>
+ */
+#include "glib.h"
+#include <inttypes.h>
+#include <assert.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <stdio.h>
+
+#include <qemu-plugin.h>
+
+QEMU_PLUGIN_EXPORT int qemu_plugin_version = QEMU_PLUGIN_VERSION;
+
+/*
+ * This plugin tests whether we can read and write registers via the plugin
+ * API. We try to just read/write a single register, as some architectures have
+ * registers that cannot be written to, which would fail the test.
+ * See: https://lists.gnu.org/archive/html/qemu-devel/2026-02/msg07025.html
+ */
+static void vcpu_init_cb(qemu_plugin_id_t id, unsigned int vcpu_index)
+{
+    g_autoptr(GArray) regs = qemu_plugin_get_registers();
+    g_assert(regs != NULL);
+    g_autoptr(GByteArray) buf = g_byte_array_sized_new(0);
+    qemu_plugin_reg_descriptor *reg_desc = NULL;
+    bool success = false;
+
+    /* Make sure we can read and write a register not marked as readonly */
+    for (size_t i = 0; i < regs->len; i++) {
+        reg_desc = &g_array_index(regs, qemu_plugin_reg_descriptor, i);
+        if (!reg_desc->is_readonly) {
+            g_byte_array_set_size(buf, 0);
+            success = qemu_plugin_read_register(reg_desc->handle, buf);
+            g_assert(success);
+            g_assert(buf->len > 0);
+            success = qemu_plugin_write_register(reg_desc->handle, buf);
+            g_assert(success);
+            break;
+        } else {
+            reg_desc = NULL;
+        }
+    }
+    g_assert(regs->len == 0 || reg_desc != NULL);
+
+    /*
+     * Check whether we can still read a read-only register. On each
+     * architecture, at least the PC should be read-only because it's only
+     * supposed to be modified via the qemu_plugin_set_pc() function.
+     */
+    for (size_t i = 0; i < regs->len; i++) {
+        reg_desc = &g_array_index(regs, qemu_plugin_reg_descriptor, i);
+        if (reg_desc->is_readonly) {
+            g_byte_array_set_size(buf, 0);
+            success = qemu_plugin_read_register(reg_desc->handle, buf);
+            g_assert(success);
+            g_assert(buf->len > 0);
+            break;
+        } else {
+            reg_desc = NULL;
+        }
+    }
+    g_assert(regs->len == 0 || reg_desc != NULL);
+    /*
+     * Note: we currently do not test whether the read-only register can be
+     * written to, because doing so would throw an assert in the plugin API.
+     */
+}
+
+QEMU_PLUGIN_EXPORT int qemu_plugin_install(qemu_plugin_id_t id,
+                                           const qemu_info_t *info,
+                                           int argc, char **argv)
+{
+    qemu_plugin_register_vcpu_init_cb(id, vcpu_init_cb);
+    return 0;
+}

-- 
2.53.0



^ permalink raw reply related	[flat|nested] 18+ messages in thread

* Re: [PATCH v7 1/8] plugins/core: clamp syscall arguments if target is 32-bit
  2026-03-05 10:05 ` [PATCH v7 1/8] plugins/core: clamp syscall arguments if target is 32-bit Florian Hofhammer
@ 2026-03-05 16:09   ` Pierrick Bouvier
  2026-03-05 17:26   ` Alex Bennée
  2026-03-05 17:33   ` Philippe Mathieu-Daudé
  2 siblings, 0 replies; 18+ messages in thread
From: Pierrick Bouvier @ 2026-03-05 16:09 UTC (permalink / raw)
  To: Florian Hofhammer, qemu-devel, Alex Bennée,
	Philippe Mathieu-Daudé

On 3/5/26 2:05 AM, Florian Hofhammer wrote:
> From: Pierrick Bouvier <pierrick.bouvier@linaro.org>
> 
> Syscall arguments are abi_long in user code, and plugin syscall
> interface works with uint64_t only.
> 
> According to C integer promotion rules, the value is sign extended
> before becoming unsigned, thus setting high bits when only 32-bit lower
> ones should have a significant value.
> 
> As a result, we need to clamp values we receive from user-code
> accordingly.
> 
> Signed-off-by: Pierrick Bouvier <pierrick.bouvier@linaro.org>
> ---
>   plugins/core.c | 21 +++++++++++++++++++++
>   1 file changed, 21 insertions(+)
> 

@Alex, Philippe: may I ask you a review on this one?

Regards,
Pierrick


^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: [PATCH v7 1/8] plugins/core: clamp syscall arguments if target is 32-bit
  2026-03-05 10:05 ` [PATCH v7 1/8] plugins/core: clamp syscall arguments if target is 32-bit Florian Hofhammer
  2026-03-05 16:09   ` Pierrick Bouvier
@ 2026-03-05 17:26   ` Alex Bennée
  2026-03-05 17:33   ` Philippe Mathieu-Daudé
  2 siblings, 0 replies; 18+ messages in thread
From: Alex Bennée @ 2026-03-05 17:26 UTC (permalink / raw)
  To: Florian Hofhammer; +Cc: qemu-devel, Pierrick Bouvier

Florian Hofhammer <florian.hofhammer@epfl.ch> writes:

> From: Pierrick Bouvier <pierrick.bouvier@linaro.org>
>
> Syscall arguments are abi_long in user code, and plugin syscall
> interface works with uint64_t only.
>
> According to C integer promotion rules, the value is sign extended
> before becoming unsigned, thus setting high bits when only 32-bit lower
> ones should have a significant value.
>
> As a result, we need to clamp values we receive from user-code
> accordingly.
>
> Signed-off-by: Pierrick Bouvier <pierrick.bouvier@linaro.org>

Reviewed-by: Alex Bennée <alex.bennee@linaro.org>

-- 
Alex Bennée
Virtualisation Tech Lead @ Linaro


^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: [PATCH v7 1/8] plugins/core: clamp syscall arguments if target is 32-bit
  2026-03-05 10:05 ` [PATCH v7 1/8] plugins/core: clamp syscall arguments if target is 32-bit Florian Hofhammer
  2026-03-05 16:09   ` Pierrick Bouvier
  2026-03-05 17:26   ` Alex Bennée
@ 2026-03-05 17:33   ` Philippe Mathieu-Daudé
  2026-03-05 17:38     ` Pierrick Bouvier
  2026-03-05 18:23     ` Pierrick Bouvier
  2 siblings, 2 replies; 18+ messages in thread
From: Philippe Mathieu-Daudé @ 2026-03-05 17:33 UTC (permalink / raw)
  To: Florian Hofhammer, qemu-devel
  Cc: Pierrick Bouvier, Laurent Vivier, Richard Henderson

+Laurent

On 5/3/26 11:05, Florian Hofhammer wrote:
> From: Pierrick Bouvier <pierrick.bouvier@linaro.org>
> 
> Syscall arguments are abi_long in user code, and plugin syscall
> interface works with uint64_t only.
> 
> According to C integer promotion rules, the value is sign extended
> before becoming unsigned, thus setting high bits when only 32-bit lower
> ones should have a significant value.
> 
> As a result, we need to clamp values we receive from user-code
> accordingly.
> 
> Signed-off-by: Pierrick Bouvier <pierrick.bouvier@linaro.org>
> ---
>   plugins/core.c | 21 +++++++++++++++++++++
>   1 file changed, 21 insertions(+)
> 
> diff --git a/plugins/core.c b/plugins/core.c
> index 42fd986593..d6173422e9 100644
> --- a/plugins/core.c
> +++ b/plugins/core.c
> @@ -513,6 +513,23 @@ void qemu_plugin_tb_trans_cb(CPUState *cpu, struct qemu_plugin_tb *tb)
>       }
>   }
>   
> +static void clamp_syscall_arguments(uint64_t *a1, uint64_t *a2, uint64_t *a3,
> +                                    uint64_t *a4, uint64_t *a5, uint64_t *a6,
> +                                    uint64_t *a7, uint64_t *a8)
> +{
> +    if (target_long_bits() == 32) {

IIUC this is related to the target ABI, so maybe we want to
unconditionally use:

    const uint64_t mask = MAKE_64BIT_MASK(0, TARGET_ABI_BITS);

Maybe guarded with #if TARGET_ABI_BITS < 64?

> +        const uint64_t mask = UINT32_MAX;
> +        *a1 &= mask;
> +        *a2 &= mask;
> +        *a3 &= mask;
> +        *a4 &= mask;
> +        *a5 &= mask;
> +        *a6 &= mask;
> +        *a7 &= mask;
> +        *a8 &= mask;
> +    }
> +}


^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: [PATCH v7 1/8] plugins/core: clamp syscall arguments if target is 32-bit
  2026-03-05 17:33   ` Philippe Mathieu-Daudé
@ 2026-03-05 17:38     ` Pierrick Bouvier
  2026-03-06  7:46       ` Florian Hofhammer
  2026-03-05 18:23     ` Pierrick Bouvier
  1 sibling, 1 reply; 18+ messages in thread
From: Pierrick Bouvier @ 2026-03-05 17:38 UTC (permalink / raw)
  To: Philippe Mathieu-Daudé, Florian Hofhammer, qemu-devel
  Cc: Laurent Vivier, Richard Henderson

On 3/5/26 9:33 AM, Philippe Mathieu-Daudé wrote:
> +Laurent
> 
> On 5/3/26 11:05, Florian Hofhammer wrote:
>> From: Pierrick Bouvier <pierrick.bouvier@linaro.org>
>>
>> Syscall arguments are abi_long in user code, and plugin syscall
>> interface works with uint64_t only.
>>
>> According to C integer promotion rules, the value is sign extended
>> before becoming unsigned, thus setting high bits when only 32-bit lower
>> ones should have a significant value.
>>
>> As a result, we need to clamp values we receive from user-code
>> accordingly.
>>
>> Signed-off-by: Pierrick Bouvier <pierrick.bouvier@linaro.org>
>> ---
>>    plugins/core.c | 21 +++++++++++++++++++++
>>    1 file changed, 21 insertions(+)
>>
>> diff --git a/plugins/core.c b/plugins/core.c
>> index 42fd986593..d6173422e9 100644
>> --- a/plugins/core.c
>> +++ b/plugins/core.c
>> @@ -513,6 +513,23 @@ void qemu_plugin_tb_trans_cb(CPUState *cpu, struct qemu_plugin_tb *tb)
>>        }
>>    }
>>    
>> +static void clamp_syscall_arguments(uint64_t *a1, uint64_t *a2, uint64_t *a3,
>> +                                    uint64_t *a4, uint64_t *a5, uint64_t *a6,
>> +                                    uint64_t *a7, uint64_t *a8)
>> +{
>> +    if (target_long_bits() == 32) {
> 
> IIUC this is related to the target ABI, so maybe we want to
> unconditionally use:
> 
>      const uint64_t mask = MAKE_64BIT_MASK(0, TARGET_ABI_BITS);
>
> Maybe guarded with #if TARGET_ABI_BITS < 64?
>

More broadly, I really wonder why we use signed type for syscall 
arguments in linux-user, while I think they should always be unsigned.
But since soft freeze is coming very soon, it's not the time for a 
refactoring solution, thus this pragmatic, but correct, patch.

>> +        const uint64_t mask = UINT32_MAX;
>> +        *a1 &= mask;
>> +        *a2 &= mask;
>> +        *a3 &= mask;
>> +        *a4 &= mask;
>> +        *a5 &= mask;
>> +        *a6 &= mask;
>> +        *a7 &= mask;
>> +        *a8 &= mask;
>> +    }
>> +}



^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: [PATCH v7 1/8] plugins/core: clamp syscall arguments if target is 32-bit
  2026-03-05 17:33   ` Philippe Mathieu-Daudé
  2026-03-05 17:38     ` Pierrick Bouvier
@ 2026-03-05 18:23     ` Pierrick Bouvier
  1 sibling, 0 replies; 18+ messages in thread
From: Pierrick Bouvier @ 2026-03-05 18:23 UTC (permalink / raw)
  To: Philippe Mathieu-Daudé, Florian Hofhammer, qemu-devel
  Cc: Laurent Vivier, Richard Henderson

On 3/5/26 9:33 AM, Philippe Mathieu-Daudé wrote:
> +Laurent
> 
> On 5/3/26 11:05, Florian Hofhammer wrote:
>> From: Pierrick Bouvier <pierrick.bouvier@linaro.org>
>>
>> Syscall arguments are abi_long in user code, and plugin syscall
>> interface works with uint64_t only.
>>
>> According to C integer promotion rules, the value is sign extended
>> before becoming unsigned, thus setting high bits when only 32-bit lower
>> ones should have a significant value.
>>
>> As a result, we need to clamp values we receive from user-code
>> accordingly.
>>
>> Signed-off-by: Pierrick Bouvier <pierrick.bouvier@linaro.org>
>> ---
>>    plugins/core.c | 21 +++++++++++++++++++++
>>    1 file changed, 21 insertions(+)
>>
>> diff --git a/plugins/core.c b/plugins/core.c
>> index 42fd986593..d6173422e9 100644
>> --- a/plugins/core.c
>> +++ b/plugins/core.c
>> @@ -513,6 +513,23 @@ void qemu_plugin_tb_trans_cb(CPUState *cpu, struct qemu_plugin_tb *tb)
>>        }
>>    }
>>    
>> +static void clamp_syscall_arguments(uint64_t *a1, uint64_t *a2, uint64_t *a3,
>> +                                    uint64_t *a4, uint64_t *a5, uint64_t *a6,
>> +                                    uint64_t *a7, uint64_t *a8)
>> +{
>> +    if (target_long_bits() == 32) {
> 
> IIUC this is related to the target ABI, so maybe we want to
> unconditionally use:
> 
>      const uint64_t mask = MAKE_64BIT_MASK(0, TARGET_ABI_BITS);
> 
> Maybe guarded with #if TARGET_ABI_BITS < 64?
>

TARGET_ABI_BITS is a target specific define, but we are in common code 
here, so it's not usable.
As well, since this path is only exercised by linux-user (and not 
system), target_long_bits() should not be different from its runtime value.

>> +        const uint64_t mask = UINT32_MAX;
>> +        *a1 &= mask;
>> +        *a2 &= mask;
>> +        *a3 &= mask;
>> +        *a4 &= mask;
>> +        *a5 &= mask;
>> +        *a6 &= mask;
>> +        *a7 &= mask;
>> +        *a8 &= mask;
>> +    }
>> +}



^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: [PATCH v7 5/8] tests/tcg: add tests for qemu_plugin_set_pc API
  2026-03-05 10:06 ` [PATCH v7 5/8] tests/tcg: add tests for qemu_plugin_set_pc API Florian Hofhammer
@ 2026-03-05 18:24   ` Pierrick Bouvier
  0 siblings, 0 replies; 18+ messages in thread
From: Pierrick Bouvier @ 2026-03-05 18:24 UTC (permalink / raw)
  To: Florian Hofhammer, qemu-devel

On 3/5/26 2:06 AM, Florian Hofhammer wrote:
> The test plugin intercepts execution in different contexts. Without the
> plugin, any of the implemented test functions would trigger an assert
> and fail. With the plugin, control flow is redirected to skip the assert
> and return cleanly via the qemu_plugin_set_pc() API.
> 
> Signed-off-by: Pierrick Bouvier <pierrick.bouvier@linaro.org>
> Signed-off-by: Florian Hofhammer <florian.hofhammer@epfl.ch>
> ---
>   MAINTAINERS                                        |   1 +
>   tests/tcg/arm/Makefile.target                      |   6 +
>   tests/tcg/hexagon/Makefile.target                  |   8 ++
>   tests/tcg/multiarch/Makefile.target                |  17 ++-
>   .../multiarch/{ => plugin}/check-plugin-output.sh  |   0
>   .../{ => plugin}/test-plugin-mem-access.c          |   0
>   tests/tcg/multiarch/plugin/test-plugin-set-pc.c    | 134 +++++++++++++++++++++
>   tests/tcg/plugins/meson.build                      |   1 +
>   tests/tcg/plugins/setpc.c                          | 109 +++++++++++++++++
>   9 files changed, 273 insertions(+), 3 deletions(-)
> 

Thanks, it looks good!
Reviewed-by: Pierrick Bouvier <pierrick.bouvier@linaro.org>


^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: [PATCH v7 1/8] plugins/core: clamp syscall arguments if target is 32-bit
  2026-03-05 17:38     ` Pierrick Bouvier
@ 2026-03-06  7:46       ` Florian Hofhammer
  2026-03-06 17:46         ` Pierrick Bouvier
  0 siblings, 1 reply; 18+ messages in thread
From: Florian Hofhammer @ 2026-03-06  7:46 UTC (permalink / raw)
  To: Pierrick Bouvier
  Cc: Laurent Vivier, Richard Henderson, qemu-devel,
	Philippe Mathieu-Daudé

On 05/03/2026 18:38, Pierrick Bouvier wrote:
> On 3/5/26 9:33 AM, Philippe Mathieu-Daudé wrote:
>> +Laurent
>>
>> On 5/3/26 11:05, Florian Hofhammer wrote:
>>> From: Pierrick Bouvier <pierrick.bouvier@linaro.org>
>>>
>>> Syscall arguments are abi_long in user code, and plugin syscall
>>> interface works with uint64_t only.
>>>
>>> According to C integer promotion rules, the value is sign extended
>>> before becoming unsigned, thus setting high bits when only 32-bit lower
>>> ones should have a significant value.
>>>
>>> As a result, we need to clamp values we receive from user-code
>>> accordingly.
>>>
>>> Signed-off-by: Pierrick Bouvier <pierrick.bouvier@linaro.org>
>>> ---
>>>    plugins/core.c | 21 +++++++++++++++++++++
>>>    1 file changed, 21 insertions(+)
>>>
>>> diff --git a/plugins/core.c b/plugins/core.c
>>> index 42fd986593..d6173422e9 100644
>>> --- a/plugins/core.c
>>> +++ b/plugins/core.c
>>> @@ -513,6 +513,23 @@ void qemu_plugin_tb_trans_cb(CPUState *cpu, struct qemu_plugin_tb *tb)
>>>        }
>>>    }
>>>    +static void clamp_syscall_arguments(uint64_t *a1, uint64_t *a2, uint64_t *a3,
>>> +                                    uint64_t *a4, uint64_t *a5, uint64_t *a6,
>>> +                                    uint64_t *a7, uint64_t *a8)
>>> +{
>>> +    if (target_long_bits() == 32) {
>>
>> IIUC this is related to the target ABI, so maybe we want to
>> unconditionally use:
>>
>>      const uint64_t mask = MAKE_64BIT_MASK(0, TARGET_ABI_BITS);
>>
>> Maybe guarded with #if TARGET_ABI_BITS < 64?
>>
> 
> More broadly, I really wonder why we use signed type for syscall arguments in linux-user, while I think they should always be unsigned.
> But since soft freeze is coming very soon, it's not the time for a refactoring solution, thus this pragmatic, but correct, patch.

If you think it makes sense to refactor this for the next development
cycle/window, I'd happily give it a short and prepare an RFC patch for
it.

> 
>>> +        const uint64_t mask = UINT32_MAX;
>>> +        *a1 &= mask;
>>> +        *a2 &= mask;
>>> +        *a3 &= mask;
>>> +        *a4 &= mask;
>>> +        *a5 &= mask;
>>> +        *a6 &= mask;
>>> +        *a7 &= mask;
>>> +        *a8 &= mask;
>>> +    }
>>> +}
> 

Best regards,
Florian


^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: [PATCH v7 0/8] Enable PC diversion via the plugin API
  2026-03-05 10:05 [PATCH v7 0/8] Enable PC diversion via the plugin API Florian Hofhammer
                   ` (7 preceding siblings ...)
  2026-03-05 10:06 ` [PATCH v7 8/8] tests/tcg/plugins: test register accesses Florian Hofhammer
@ 2026-03-06 17:44 ` Pierrick Bouvier
  8 siblings, 0 replies; 18+ messages in thread
From: Pierrick Bouvier @ 2026-03-06 17:44 UTC (permalink / raw)
  To: Florian Hofhammer, qemu-devel; +Cc: Alex Bennée, Warner Losh

On 3/5/26 2:05 AM, Florian Hofhammer wrote:
> Hi,
> 
> This patch series builds on top of the discussion from the thread at
> https://lore.kernel.org/qemu-devel/e9bcd7c7-2d67-469e-b2f3-d1a68e456b2b@epfl.ch/
> and adds a plugin API function to set the program counter of the guest,
> as just writing to it via qemu_plugin_write_register() has no direct
> effect.
> 
> This version v7 of the patch series addresses the requested changes from
> the previous v6 submission (details below).
> 
> Note: checkpatch.pl still reports a warning about line length violations
> in patch nr. 7/8 but I did not fix this, as the line was already > 80
> characters long previously, the change added only a single character,
> and I think the readability of the code is better as it is now. Please
> let me know if you disagree and would like me to fix this!
> 
> @Pierrick: you already reviewed patch 3/7 (now 4/8) previously, but I
> had to change the regex in scripts/qemu-plugin-symbols.py to account for
> attributes, so please re-review!
> 
> Best regards,
> Florian
> 
> Changes:
> v7:
> - Add Pierrick's patch to clamp 32-bit registers to prevent accidental
>    sign extension
> - Fix documentation build by using __attribute__((__noreturn__)) instead
>    of G_NORETURN (required adjusting scripts/qemu-plugin-symbols.py).
> - Rework test-plugin-set-pc test to get rid of volatile guard and make
>    control flow more linear
> - Link to v6: https://lore.kernel.org/qemu-devel/20260303-setpc-v5-v6-0-15c77cfe184e@epfl.ch
> v6:
> - update commit message for patch 4/7
> v5:
> - make QEMU abort via asserts instead of just returning an error from
>    the plugin API if preconditions are violated
> - extend tests for qemu_plugin_set_pc() to different contexts
> - fix issues highlighted by checkpatch.pl
> v4:
> - switch strcmp out in favor of g_strcmp0
> - split the patch introducing the qemu_plugin_set_pc() API into three
>    patches, two for preparing the plugin infrastructure and the syscall
>    handling code and a third introducing the actual plugin API
> v3:
> - make PC registers read-only across architectures
> - add tests for read-only registers
> - adjust test structure for qemu_plugin_set_pc() by moving
>     architecture-specific tests into corresponding directories
> v2:
> - add setjmp() in syscall handling path to allow PC redirection from
>     syscall callbacks (via longjmp(), the cpu_loop()'s setjmp() for
>     exiting a TB would not be live anymore in syscall handlers)
> - add flags to ensure the qemu_plugin_set_pc() API is only called from
>     contexts where the CPU is live
> - add test for qemu_plugin_set_pc() API
> v1:
> - initial version
> 
> ---
> Florian Hofhammer (7):
>        plugins: add flag to specify whether PC is rw
>        linux-user: make syscall emulation interruptible
>        plugins: add PC diversion API function
>        tests/tcg: add tests for qemu_plugin_set_pc API
>        plugins: add read-only property for registers
>        plugins: prohibit writing to read-only registers
>        tests/tcg/plugins: test register accesses
> 
> Pierrick Bouvier (1):
>        plugins/core: clamp syscall arguments if target is 32-bit
> 
>   MAINTAINERS                                        |   1 +
>   include/plugins/qemu-plugin.h                      |  19 +++
>   linux-user/aarch64/cpu_loop.c                      |   2 +-
>   linux-user/alpha/cpu_loop.c                        |   2 +-
>   linux-user/arm/cpu_loop.c                          |   2 +-
>   linux-user/hexagon/cpu_loop.c                      |   2 +-
>   linux-user/hppa/cpu_loop.c                         |   1 +
>   linux-user/i386/cpu_loop.c                         |   8 +-
>   linux-user/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                         |   9 +-
>   linux-user/or1k/cpu_loop.c                         |   2 +-
>   linux-user/ppc/cpu_loop.c                          |  10 +-
>   linux-user/riscv/cpu_loop.c                        |   2 +-
>   linux-user/s390x/cpu_loop.c                        |   2 +-
>   linux-user/sh4/cpu_loop.c                          |   2 +-
>   linux-user/sparc/cpu_loop.c                        |   4 +-
>   linux-user/syscall.c                               |  16 +++
>   linux-user/xtensa/cpu_loop.c                       |   1 +
>   plugins/api.c                                      |  42 ++++++-
>   plugins/core.c                                     |  50 ++++++--
>   scripts/qemu-plugin-symbols.py                     |   9 +-
>   tests/tcg/arm/Makefile.target                      |   6 +
>   tests/tcg/hexagon/Makefile.target                  |   8 ++
>   tests/tcg/multiarch/Makefile.target                |  17 ++-
>   .../multiarch/{ => plugin}/check-plugin-output.sh  |   0
>   .../{ => plugin}/test-plugin-mem-access.c          |   0
>   tests/tcg/multiarch/plugin/test-plugin-set-pc.c    | 134 +++++++++++++++++++++
>   tests/tcg/plugins/meson.build                      |   2 +
>   tests/tcg/plugins/registers.c                      |  79 ++++++++++++
>   tests/tcg/plugins/setpc.c                          | 109 +++++++++++++++++
>   33 files changed, 514 insertions(+), 44 deletions(-)
> ---
> base-commit: 3fb456e9a0e9eef6a71d9b49bfff596a0f0046e9
> change-id: 20260303-setpc-v5-c1df30bad07f

This was merged into master (2d3f78269a14a389aefecba28decd16dd5151802).
Thank you for your contribution!

Regards,
Pierrick


^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: [PATCH v7 1/8] plugins/core: clamp syscall arguments if target is 32-bit
  2026-03-06  7:46       ` Florian Hofhammer
@ 2026-03-06 17:46         ` Pierrick Bouvier
  0 siblings, 0 replies; 18+ messages in thread
From: Pierrick Bouvier @ 2026-03-06 17:46 UTC (permalink / raw)
  To: Florian Hofhammer
  Cc: Laurent Vivier, Richard Henderson, qemu-devel,
	Philippe Mathieu-Daudé

On 3/5/26 11:46 PM, Florian Hofhammer wrote:
> On 05/03/2026 18:38, Pierrick Bouvier wrote:
>> On 3/5/26 9:33 AM, Philippe Mathieu-Daudé wrote:
>>> +Laurent
>>>
>>> On 5/3/26 11:05, Florian Hofhammer wrote:
>>>> From: Pierrick Bouvier <pierrick.bouvier@linaro.org>
>>>>
>>>> Syscall arguments are abi_long in user code, and plugin syscall
>>>> interface works with uint64_t only.
>>>>
>>>> According to C integer promotion rules, the value is sign extended
>>>> before becoming unsigned, thus setting high bits when only 32-bit lower
>>>> ones should have a significant value.
>>>>
>>>> As a result, we need to clamp values we receive from user-code
>>>> accordingly.
>>>>
>>>> Signed-off-by: Pierrick Bouvier <pierrick.bouvier@linaro.org>
>>>> ---
>>>>     plugins/core.c | 21 +++++++++++++++++++++
>>>>     1 file changed, 21 insertions(+)
>>>>
>>>> diff --git a/plugins/core.c b/plugins/core.c
>>>> index 42fd986593..d6173422e9 100644
>>>> --- a/plugins/core.c
>>>> +++ b/plugins/core.c
>>>> @@ -513,6 +513,23 @@ void qemu_plugin_tb_trans_cb(CPUState *cpu, struct qemu_plugin_tb *tb)
>>>>         }
>>>>     }
>>>>     +static void clamp_syscall_arguments(uint64_t *a1, uint64_t *a2, uint64_t *a3,
>>>> +                                    uint64_t *a4, uint64_t *a5, uint64_t *a6,
>>>> +                                    uint64_t *a7, uint64_t *a8)
>>>> +{
>>>> +    if (target_long_bits() == 32) {
>>>
>>> IIUC this is related to the target ABI, so maybe we want to
>>> unconditionally use:
>>>
>>>       const uint64_t mask = MAKE_64BIT_MASK(0, TARGET_ABI_BITS);
>>>
>>> Maybe guarded with #if TARGET_ABI_BITS < 64?
>>>
>>
>> More broadly, I really wonder why we use signed type for syscall arguments in linux-user, while I think they should always be unsigned.
>> But since soft freeze is coming very soon, it's not the time for a refactoring solution, thus this pragmatic, but correct, patch.
> 
> If you think it makes sense to refactor this for the next development
> cycle/window, I'd happily give it a short and prepare an RFC patch for
> it.
>

Sure, I would like to get feedback from Laurent and Richard first to see 
if we have a specific reason to have signed syscall arguments.

Regards,
Pierrick


^ permalink raw reply	[flat|nested] 18+ messages in thread

end of thread, other threads:[~2026-03-06 17:47 UTC | newest]

Thread overview: 18+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-03-05 10:05 [PATCH v7 0/8] Enable PC diversion via the plugin API Florian Hofhammer
2026-03-05 10:05 ` [PATCH v7 1/8] plugins/core: clamp syscall arguments if target is 32-bit Florian Hofhammer
2026-03-05 16:09   ` Pierrick Bouvier
2026-03-05 17:26   ` Alex Bennée
2026-03-05 17:33   ` Philippe Mathieu-Daudé
2026-03-05 17:38     ` Pierrick Bouvier
2026-03-06  7:46       ` Florian Hofhammer
2026-03-06 17:46         ` Pierrick Bouvier
2026-03-05 18:23     ` Pierrick Bouvier
2026-03-05 10:06 ` [PATCH v7 2/8] plugins: add flag to specify whether PC is rw Florian Hofhammer
2026-03-05 10:06 ` [PATCH v7 3/8] linux-user: make syscall emulation interruptible Florian Hofhammer
2026-03-05 10:06 ` [PATCH v7 4/8] plugins: add PC diversion API function Florian Hofhammer
2026-03-05 10:06 ` [PATCH v7 5/8] tests/tcg: add tests for qemu_plugin_set_pc API Florian Hofhammer
2026-03-05 18:24   ` Pierrick Bouvier
2026-03-05 10:06 ` [PATCH v7 6/8] plugins: add read-only property for registers Florian Hofhammer
2026-03-05 10:06 ` [PATCH v7 7/8] plugins: prohibit writing to read-only registers Florian Hofhammer
2026-03-05 10:06 ` [PATCH v7 8/8] tests/tcg/plugins: test register accesses Florian Hofhammer
2026-03-06 17:44 ` [PATCH v7 0/8] Enable PC diversion via the plugin API Pierrick Bouvier

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox