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
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
>> 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
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
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
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
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
© 2016 - 2026 Red Hat, Inc.