@samitouri / QOSamiQemu / commits / c0c8990a2b

plugins/execlog: fix segfault/race-cond on per-vCPU structures

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>

Harry van Haaren committed Jul 16, 2026 at 09:41 UTC c0c8990a2bbd89fc3986d01697f156576c5c0984
1 file changed +23 -45
contrib/plugins/execlog.c
+23 -45
@@ -31,8 +31,12 @@ typedef struct CPU {
31
32 QEMU_PLUGIN_EXPORT int qemu_plugin_version = QEMU_PLUGIN_VERSION;
33
34 -static GArray *cpus;
35 -static GRWLock expand_array_lock;
34 +/*
35 + * Per-vCPU state stored in a qemu_plugin_scoreboard. The scoreboard manages
36 + * per-vCPU storage automatically, eliminating the need for manual array
37 + * growth, locks, or pointer-stability workarounds.
38 + */
39 +static struct qemu_plugin_scoreboard *cpus;
40
41 static GPtrArray *imatches;
42 static GArray *amatches;
@@ -41,23 +45,13 @@ static bool disas_assist;
45 static GMutex add_reg_name_lock;
46 static GPtrArray *all_reg_names;
47
44 -static CPU *get_cpu(int vcpu_index)
45 -{
46 - CPU *c;
47 - g_rw_lock_reader_lock(&expand_array_lock);
48 - c = &g_array_index(cpus, CPU, vcpu_index);
49 - g_rw_lock_reader_unlock(&expand_array_lock);
50 -
51 - return c;
52 -}
53 -
48 /**
49 * Add memory read or write information to current instruction log
50 */
51 static void vcpu_mem(unsigned int cpu_index, qemu_plugin_meminfo_t info,
52 uint64_t vaddr, void *udata)
53 {
60 - CPU *c = get_cpu(cpu_index);
54 + CPU *c = qemu_plugin_scoreboard_find(cpus, cpu_index);
55 GString *s = c->last_exec;
56
57 /* Find vCPU in array */
@@ -117,7 +111,7 @@ static void insn_check_regs(CPU *cpu)
111 /* Log last instruction while checking registers */
112 static void vcpu_insn_exec_with_regs(unsigned int cpu_index, void *udata)
113 {
120 - CPU *cpu = get_cpu(cpu_index);
114 + CPU *cpu = qemu_plugin_scoreboard_find(cpus, cpu_index);
115
116 /* Print previous instruction in cache */
117 if (cpu->last_exec->len) {
@@ -125,8 +119,8 @@ static void vcpu_insn_exec_with_regs(unsigned int cpu_index, void *udata)
119 insn_check_regs(cpu);
120 }
121
122 + g_string_append_c(cpu->last_exec, '\n');
123 qemu_plugin_outs(cpu->last_exec->str);
129 - qemu_plugin_outs("\n");
124 }
125
126 /* Store new instruction in cache */
@@ -138,7 +132,7 @@ static void vcpu_insn_exec_with_regs(unsigned int cpu_index, void *udata)
132 /* Log last instruction while checking registers, ignore next */
133 static void vcpu_insn_exec_only_regs(unsigned int cpu_index, void *udata)
134 {
141 - CPU *cpu = get_cpu(cpu_index);
135 + CPU *cpu = qemu_plugin_scoreboard_find(cpus, cpu_index);
136
137 /* Print previous instruction in cache */
138 if (cpu->last_exec->len) {
@@ -146,8 +140,8 @@ static void vcpu_insn_exec_only_regs(unsigned int cpu_index, void *udata)
140 insn_check_regs(cpu);
141 }
142
143 + g_string_append_c(cpu->last_exec, '\n');
144 qemu_plugin_outs(cpu->last_exec->str);
150 - qemu_plugin_outs("\n");
145 }
146
147 /* reset */
@@ -157,12 +151,12 @@ static void vcpu_insn_exec_only_regs(unsigned int cpu_index, void *udata)
151 /* Log last instruction without checking regs, setup next */
152 static void vcpu_insn_exec(unsigned int cpu_index, void *udata)
153 {
160 - CPU *cpu = get_cpu(cpu_index);
154 + CPU *cpu = qemu_plugin_scoreboard_find(cpus, cpu_index);
155
156 /* Print previous instruction in cache */
157 if (cpu->last_exec->len) {
158 + g_string_append_c(cpu->last_exec, '\n');
159 qemu_plugin_outs(cpu->last_exec->str);
165 - qemu_plugin_outs("\n");
160 }
161
162 /* Store new instruction in cache */
@@ -378,40 +372,28 @@ static GPtrArray *registers_init(int vcpu_index)
372 * - last_exec tracking data
373 * - list of tracked registers
374 * - initial value of registers
381 - *
382 - * As we could have multiple threads trying to do this we need to
383 - * serialise the expansion under a lock.
375 */
376 static void vcpu_init(unsigned int vcpu_index, void *userdata)
377 {
387 - CPU *c;
388 -
389 - g_rw_lock_writer_lock(&expand_array_lock);
390 - if (vcpu_index >= cpus->len) {
391 - g_array_set_size(cpus, vcpu_index + 1);
392 - }
393 - g_rw_lock_writer_unlock(&expand_array_lock);
394 -
395 - c = get_cpu(vcpu_index);
378 + CPU *c = qemu_plugin_scoreboard_find(cpus, vcpu_index);
379 c->last_exec = g_string_new(NULL);
380 c->registers = registers_init(vcpu_index);
381 }
382
383 /**
401 - * On plugin exit, print last instruction in cache
384 + * On plugin exit, flush any remaining cached instructions and free state.
385 */
386 static void plugin_exit(void *p)
387 {
405 - guint i;
406 - g_rw_lock_reader_lock(&expand_array_lock);
407 - for (i = 0; i < cpus->len; i++) {
408 - CPU *c = get_cpu(i);
409 - if (c->last_exec && c->last_exec->str) {
388 + int n = qemu_plugin_num_vcpus();
389 + for (int i = 0; i < n; i++) {
390 + CPU *c = qemu_plugin_scoreboard_find(cpus, i);
391 + if (c->last_exec && c->last_exec->len) {
392 + g_string_append_c(c->last_exec, '\n');
393 qemu_plugin_outs(c->last_exec->str);
411 - qemu_plugin_outs("\n");
394 }
395 }
414 - g_rw_lock_reader_unlock(&expand_array_lock);
396 + qemu_plugin_scoreboard_free(cpus);
397 }
398
399 /* Add a match to the array of matches */
@@ -452,12 +434,8 @@ QEMU_PLUGIN_EXPORT int qemu_plugin_install(qemu_plugin_id_t id,
434 const qemu_info_t *info, int argc,
435 char **argv)
436 {
455 - /*
456 - * Initialize dynamic array to cache vCPU instruction. In user mode
457 - * we don't know the size before emulation.
458 - */
459 - cpus = g_array_sized_new(true, true, sizeof(CPU),
460 - info->system_emulation ? info->system.max_vcpus : 1);
437 + /* Initialize scoreboard to cache per-vCPU instruction state. */
438 + cpus = qemu_plugin_scoreboard_new(sizeof(CPU));
439
440 for (int i = 0; i < argc; i++) {
441 char *opt = argv[i];