* [PULL 1/2] plugins/execlog: fix segfault/race-cond on per-vCPU structures
2026-07-16 19:57 [PULL 0/2] Plugins fixes for 2026-07-16 Pierrick Bouvier
@ 2026-07-16 19:57 ` Pierrick Bouvier
2026-07-16 19:57 ` [PULL 2/2] plugins/execlog: fix execlog vcpu_exit execution print loss Pierrick Bouvier
2026-07-20 9:59 ` [PULL 0/2] Plugins fixes for 2026-07-16 Stefan Hajnoczi
2 siblings, 0 replies; 4+ messages in thread
From: Pierrick Bouvier @ 2026-07-16 19:57 UTC (permalink / raw)
To: qemu-devel, peter.maydell, richard.henderson, pbonzini, stefanha
Cc: pierrick.bouvier
From: Harry van Haaren <harry.vanhaaren@openchip.com>
The existing code in execlog was never upgraded to the Scoreboard
API, resulting in a bespoke implementation of per-vCPU datastructure
handling. This had some race-conditions, and causes segfaults with
a simple multi-threaded program and two instances of execlog running.
The patch here refactors the custom GArray and GRWLock code away, and
uses the scoreboard APIs like the other plugins. This solves the
"printing while expanding" race-condition of two plugins with multiple
threads in the guest, and hence fixes a segfault.
Output remains atomic per instruction by building the full line
(including the trailing newline) in the per-vCPU GString before making
a single qemu_plugin_outs() call, relying on QEMU's own log locking
rather than an additional mutex.
Reviewed-by: Pierrick Bouvier <pierrick.bouvier@oss.qualcomm.com>
Tested-by: Pierrick Bouvier <pierrick.bouvier@oss.qualcomm.com>
Signed-off-by: Harry van Haaren <harry.vanhaaren@openchip.com>
Link: https://lore.kernel.org/qemu-devel/20260716094126.787556-2-harry.vanhaaren@openchip.com
Signed-off-by: Pierrick Bouvier <pierrick.bouvier@oss.qualcomm.com>
---
contrib/plugins/execlog.c | 68 +++++++++++++--------------------------
1 file changed, 23 insertions(+), 45 deletions(-)
diff --git a/contrib/plugins/execlog.c b/contrib/plugins/execlog.c
index 74325495cce..dfe00bf836c 100644
--- a/contrib/plugins/execlog.c
+++ b/contrib/plugins/execlog.c
@@ -31,8 +31,12 @@ typedef struct CPU {
QEMU_PLUGIN_EXPORT int qemu_plugin_version = QEMU_PLUGIN_VERSION;
-static GArray *cpus;
-static GRWLock expand_array_lock;
+/*
+ * Per-vCPU state stored in a qemu_plugin_scoreboard. The scoreboard manages
+ * per-vCPU storage automatically, eliminating the need for manual array
+ * growth, locks, or pointer-stability workarounds.
+ */
+static struct qemu_plugin_scoreboard *cpus;
static GPtrArray *imatches;
static GArray *amatches;
@@ -41,23 +45,13 @@ static bool disas_assist;
static GMutex add_reg_name_lock;
static GPtrArray *all_reg_names;
-static CPU *get_cpu(int vcpu_index)
-{
- CPU *c;
- g_rw_lock_reader_lock(&expand_array_lock);
- c = &g_array_index(cpus, CPU, vcpu_index);
- g_rw_lock_reader_unlock(&expand_array_lock);
-
- return c;
-}
-
/**
* Add memory read or write information to current instruction log
*/
static void vcpu_mem(unsigned int cpu_index, qemu_plugin_meminfo_t info,
uint64_t vaddr, void *udata)
{
- CPU *c = get_cpu(cpu_index);
+ CPU *c = qemu_plugin_scoreboard_find(cpus, cpu_index);
GString *s = c->last_exec;
/* Find vCPU in array */
@@ -117,7 +111,7 @@ static void insn_check_regs(CPU *cpu)
/* Log last instruction while checking registers */
static void vcpu_insn_exec_with_regs(unsigned int cpu_index, void *udata)
{
- CPU *cpu = get_cpu(cpu_index);
+ CPU *cpu = qemu_plugin_scoreboard_find(cpus, cpu_index);
/* Print previous instruction in cache */
if (cpu->last_exec->len) {
@@ -125,8 +119,8 @@ static void vcpu_insn_exec_with_regs(unsigned int cpu_index, void *udata)
insn_check_regs(cpu);
}
+ g_string_append_c(cpu->last_exec, '\n');
qemu_plugin_outs(cpu->last_exec->str);
- qemu_plugin_outs("\n");
}
/* Store new instruction in cache */
@@ -138,7 +132,7 @@ static void vcpu_insn_exec_with_regs(unsigned int cpu_index, void *udata)
/* Log last instruction while checking registers, ignore next */
static void vcpu_insn_exec_only_regs(unsigned int cpu_index, void *udata)
{
- CPU *cpu = get_cpu(cpu_index);
+ CPU *cpu = qemu_plugin_scoreboard_find(cpus, cpu_index);
/* Print previous instruction in cache */
if (cpu->last_exec->len) {
@@ -146,8 +140,8 @@ static void vcpu_insn_exec_only_regs(unsigned int cpu_index, void *udata)
insn_check_regs(cpu);
}
+ g_string_append_c(cpu->last_exec, '\n');
qemu_plugin_outs(cpu->last_exec->str);
- qemu_plugin_outs("\n");
}
/* reset */
@@ -157,12 +151,12 @@ static void vcpu_insn_exec_only_regs(unsigned int cpu_index, void *udata)
/* Log last instruction without checking regs, setup next */
static void vcpu_insn_exec(unsigned int cpu_index, void *udata)
{
- CPU *cpu = get_cpu(cpu_index);
+ CPU *cpu = qemu_plugin_scoreboard_find(cpus, cpu_index);
/* Print previous instruction in cache */
if (cpu->last_exec->len) {
+ g_string_append_c(cpu->last_exec, '\n');
qemu_plugin_outs(cpu->last_exec->str);
- qemu_plugin_outs("\n");
}
/* Store new instruction in cache */
@@ -378,40 +372,28 @@ static GPtrArray *registers_init(int vcpu_index)
* - last_exec tracking data
* - list of tracked registers
* - initial value of registers
- *
- * As we could have multiple threads trying to do this we need to
- * serialise the expansion under a lock.
*/
static void vcpu_init(unsigned int vcpu_index, void *userdata)
{
- CPU *c;
-
- g_rw_lock_writer_lock(&expand_array_lock);
- if (vcpu_index >= cpus->len) {
- g_array_set_size(cpus, vcpu_index + 1);
- }
- g_rw_lock_writer_unlock(&expand_array_lock);
-
- c = get_cpu(vcpu_index);
+ CPU *c = qemu_plugin_scoreboard_find(cpus, vcpu_index);
c->last_exec = g_string_new(NULL);
c->registers = registers_init(vcpu_index);
}
/**
- * On plugin exit, print last instruction in cache
+ * On plugin exit, flush any remaining cached instructions and free state.
*/
static void plugin_exit(void *p)
{
- guint i;
- g_rw_lock_reader_lock(&expand_array_lock);
- for (i = 0; i < cpus->len; i++) {
- CPU *c = get_cpu(i);
- if (c->last_exec && c->last_exec->str) {
+ int n = qemu_plugin_num_vcpus();
+ for (int i = 0; i < n; i++) {
+ CPU *c = qemu_plugin_scoreboard_find(cpus, i);
+ if (c->last_exec && c->last_exec->len) {
+ g_string_append_c(c->last_exec, '\n');
qemu_plugin_outs(c->last_exec->str);
- qemu_plugin_outs("\n");
}
}
- g_rw_lock_reader_unlock(&expand_array_lock);
+ qemu_plugin_scoreboard_free(cpus);
}
/* Add a match to the array of matches */
@@ -452,12 +434,8 @@ QEMU_PLUGIN_EXPORT int qemu_plugin_install(qemu_plugin_id_t id,
const qemu_info_t *info, int argc,
char **argv)
{
- /*
- * Initialize dynamic array to cache vCPU instruction. In user mode
- * we don't know the size before emulation.
- */
- cpus = g_array_sized_new(true, true, sizeof(CPU),
- info->system_emulation ? info->system.max_vcpus : 1);
+ /* Initialize scoreboard to cache per-vCPU instruction state. */
+ cpus = qemu_plugin_scoreboard_new(sizeof(CPU));
for (int i = 0; i < argc; i++) {
char *opt = argv[i];
--
2.47.3
^ permalink raw reply related [flat|nested] 4+ messages in thread* [PULL 2/2] plugins/execlog: fix execlog vcpu_exit execution print loss
2026-07-16 19:57 [PULL 0/2] Plugins fixes for 2026-07-16 Pierrick Bouvier
2026-07-16 19:57 ` [PULL 1/2] plugins/execlog: fix segfault/race-cond on per-vCPU structures Pierrick Bouvier
@ 2026-07-16 19:57 ` Pierrick Bouvier
2026-07-20 9:59 ` [PULL 0/2] Plugins fixes for 2026-07-16 Stefan Hajnoczi
2 siblings, 0 replies; 4+ messages in thread
From: Pierrick Bouvier @ 2026-07-16 19:57 UTC (permalink / raw)
To: qemu-devel, peter.maydell, richard.henderson, pbonzini, stefanha
Cc: pierrick.bouvier
From: Harry van Haaren <harry.vanhaaren@openchip.com>
Executed instructions are cached in string format inside the
execlog plugin. These strings are flushed on exit of a TB, improving
performance. This causes executed instructions to be lost when an
'ecall' (riscv system call) occurs that causes the thread to terminate.
The fix in this patch registers a 'vcpu_exit' callback, and flushes
any content in the c->last_exec buffer, to ensure all instructions are
present in the final instruction log.
Reviewed-by: Pierrick Bouvier <pierrick.bouvier@oss.qualcomm.com>
Tested-by: Pierrick Bouvier <pierrick.bouvier@oss.qualcomm.com>
Signed-off-by: Harry van Haaren <harry.vanhaaren@openchip.com>
Link: https://lore.kernel.org/qemu-devel/20260716094126.787556-3-harry.vanhaaren@openchip.com
Signed-off-by: Pierrick Bouvier <pierrick.bouvier@oss.qualcomm.com>
---
contrib/plugins/execlog.c | 20 ++++++++++++++++++++
1 file changed, 20 insertions(+)
diff --git a/contrib/plugins/execlog.c b/contrib/plugins/execlog.c
index dfe00bf836c..cb0bd399d88 100644
--- a/contrib/plugins/execlog.c
+++ b/contrib/plugins/execlog.c
@@ -380,6 +380,25 @@ static void vcpu_init(unsigned int vcpu_index, void *userdata)
c->registers = registers_init(vcpu_index);
}
+/**
+ * On vCPU exit, flush the last cached instruction for this vCPU.
+ *
+ * The one-instruction-delay pattern stores each instruction in last_exec and
+ * only prints it when the *next* callback fires. When a thread exits via
+ * syscall (e.g. ecall/exit), no subsequent callback fires for that vCPU and
+ * the final instruction is silently dropped. Flushing here guarantees it is
+ * written before the vCPU is torn down.
+ */
+static void vcpu_exit(unsigned int vcpu_index, void *udata)
+{
+ CPU *c = qemu_plugin_scoreboard_find(cpus, vcpu_index);
+ if (c->last_exec && c->last_exec->len) {
+ g_string_append_c(c->last_exec, '\n');
+ qemu_plugin_outs(c->last_exec->str);
+ g_string_truncate(c->last_exec, 0);
+ }
+}
+
/**
* On plugin exit, flush any remaining cached instructions and free state.
*/
@@ -461,6 +480,7 @@ QEMU_PLUGIN_EXPORT int qemu_plugin_install(qemu_plugin_id_t id,
/* Register init, translation block and exit callbacks */
qemu_plugin_register_vcpu_init_cb(id, vcpu_init, NULL);
qemu_plugin_register_vcpu_tb_trans_cb(id, vcpu_tb_trans, NULL);
+ qemu_plugin_register_vcpu_exit_cb(id, vcpu_exit, NULL);
qemu_plugin_register_atexit_cb(id, plugin_exit, NULL);
return 0;
--
2.47.3
^ permalink raw reply related [flat|nested] 4+ messages in thread