master
c 221 lines 7.37 KB
Raw
1 /*
2 * SPDX-License-Identifier: GPL-2.0-or-later
3 * Copyright (C) 2025, Julian Ganz <neither@nut.email>
4 *
5 * This plugin exercises the discontinuity plugin API and asserts some
6 * of its behaviour regarding reported program counters.
7 */
8 #include <stdio.h>
9
10 #include <qemu-plugin.h>
11
12 QEMU_PLUGIN_EXPORT int qemu_plugin_version = QEMU_PLUGIN_VERSION;
13
14 struct cpu_state {
15 uint64_t last_pc;
16 uint64_t from_pc;
17 uint64_t next_pc;
18 uint64_t has_from;
19 bool has_next;
20 enum qemu_plugin_discon_type next_type;
21 };
22
23 struct insn_data {
24 uint64_t addr;
25 uint64_t next_pc;
26 bool next_valid;
27 };
28
29 static struct qemu_plugin_scoreboard *states;
30
31 static qemu_plugin_u64 last_pc;
32 static qemu_plugin_u64 from_pc;
33 static qemu_plugin_u64 has_from;
34
35 static bool abort_on_mismatch;
36 static bool trace_all_insns;
37
38 static bool addr_eq(uint64_t a, uint64_t b)
39 {
40 if (a == b) {
41 return true;
42 }
43
44 uint64_t a_hw;
45 uint64_t b_hw;
46 if (!qemu_plugin_translate_vaddr(a, &a_hw) ||
47 !qemu_plugin_translate_vaddr(b, &b_hw))
48 {
49 return false;
50 }
51
52 return a_hw == b_hw;
53 }
54
55 static void report_mismatch(const char *pc_name, unsigned int vcpu_index,
56 enum qemu_plugin_discon_type type, uint64_t last,
57 uint64_t expected, uint64_t encountered)
58 {
59 gchar *report;
60 const char *discon_type_name = "unknown";
61
62 if (addr_eq(expected, encountered)) {
63 return;
64 }
65
66 switch (type) {
67 case QEMU_PLUGIN_DISCON_INTERRUPT:
68 discon_type_name = "interrupt";
69 break;
70 case QEMU_PLUGIN_DISCON_EXCEPTION:
71 discon_type_name = "exception";
72 break;
73 case QEMU_PLUGIN_DISCON_HOSTCALL:
74 discon_type_name = "hostcall";
75 break;
76 default:
77 break;
78 }
79
80 report = g_strdup_printf("Discon %s PC mismatch on VCPU %d\n"
81 "Expected: %"PRIx64"\nEncountered: %"
82 PRIx64"\nExecuted Last: %"PRIx64
83 "\nEvent type: %s\n",
84 pc_name, vcpu_index, expected, encountered, last,
85 discon_type_name);
86 if (abort_on_mismatch) {
87 /*
88 * The qemu log infrastructure may lose messages when aborting. Using
89 * fputs directly ensures the final report is visible to developers.
90 */
91 fputs(report, stderr);
92 g_abort();
93 } else {
94 qemu_plugin_outs(report);
95 }
96 g_free(report);
97 }
98
99 static void vcpu_discon(unsigned int vcpu_index,
100 enum qemu_plugin_discon_type type, uint64_t from_pc,
101 uint64_t to_pc, void *userdata)
102 {
103 struct cpu_state *state = qemu_plugin_scoreboard_find(states, vcpu_index);
104
105 if (type == QEMU_PLUGIN_DISCON_EXCEPTION &&
106 addr_eq(state->last_pc, from_pc))
107 {
108 /*
109 * For some types of exceptions, insn_exec will be called for the
110 * instruction that caused the exception. This is valid behaviour and
111 * does not need to be reported.
112 */
113 } else if (state->has_next) {
114 /*
115 * We may encounter discontinuity chains without any instructions
116 * being executed in between.
117 */
118 report_mismatch("source", vcpu_index, type, state->last_pc,
119 state->next_pc, from_pc);
120 } else if (state->has_from) {
121 report_mismatch("source", vcpu_index, type, state->last_pc,
122 state->from_pc, from_pc);
123 }
124
125 state->has_from = false;
126
127 state->next_pc = to_pc;
128 state->next_type = type;
129 state->has_next = true;
130 }
131
132 static void insn_exec(unsigned int vcpu_index, void *userdata)
133 {
134 struct cpu_state *state = qemu_plugin_scoreboard_find(states, vcpu_index);
135
136 if (state->has_next) {
137 report_mismatch("target", vcpu_index, state->next_type, state->last_pc,
138 state->next_pc, state->last_pc);
139 state->has_next = false;
140 }
141
142 if (trace_all_insns) {
143 g_autoptr(GString) report = g_string_new(NULL);
144 g_string_append_printf(report, "Exec insn at %"PRIx64" on VCPU %d\n",
145 state->last_pc, vcpu_index);
146 qemu_plugin_outs(report->str);
147 }
148 }
149
150 static void vcpu_tb_trans(struct qemu_plugin_tb *tb, void *userdata)
151 {
152 size_t n_insns = qemu_plugin_tb_n_insns(tb);
153 for (size_t i = 0; i < n_insns; i++) {
154 struct qemu_plugin_insn *insn = qemu_plugin_tb_get_insn(tb, i);
155 uint64_t pc = qemu_plugin_insn_vaddr(insn);
156 uint64_t next_pc = pc + qemu_plugin_insn_size(insn);
157 uint64_t has_next = (i + 1) < n_insns;
158
159 qemu_plugin_register_vcpu_insn_exec_inline_per_vcpu(insn,
160 QEMU_PLUGIN_INLINE_STORE_U64,
161 last_pc, pc);
162 qemu_plugin_register_vcpu_insn_exec_inline_per_vcpu(insn,
163 QEMU_PLUGIN_INLINE_STORE_U64,
164 from_pc, next_pc);
165 qemu_plugin_register_vcpu_insn_exec_inline_per_vcpu(insn,
166 QEMU_PLUGIN_INLINE_STORE_U64,
167 has_from, has_next);
168 qemu_plugin_register_vcpu_insn_exec_cb(insn, insn_exec,
169 QEMU_PLUGIN_CB_NO_REGS, NULL);
170 }
171 }
172
173 QEMU_PLUGIN_EXPORT int qemu_plugin_install(qemu_plugin_id_t id,
174 const qemu_info_t *info,
175 int argc, char **argv)
176 {
177 if (!info->system_emulation) {
178 qemu_plugin_outs("Testing of the disontinuity plugin API is only"
179 " possible in system emulation mode.");
180 return 0;
181 }
182
183 /* Set defaults */
184 abort_on_mismatch = true;
185 trace_all_insns = false;
186
187 for (int i = 0; i < argc; i++) {
188 char *opt = argv[i];
189 g_auto(GStrv) tokens = g_strsplit(opt, "=", 2);
190 if (g_strcmp0(tokens[0], "abort") == 0) {
191 if (!qemu_plugin_bool_parse(tokens[0], tokens[1],
192 &abort_on_mismatch)) {
193 fprintf(stderr, "boolean argument parsing failed: %s\n", opt);
194 return -1;
195 }
196 } else if (g_strcmp0(tokens[0], "trace-all") == 0) {
197 if (!qemu_plugin_bool_parse(tokens[0], tokens[1],
198 &trace_all_insns)) {
199 fprintf(stderr, "boolean argument parsing failed: %s\n", opt);
200 return -1;
201 }
202 } else {
203 fprintf(stderr, "option parsing failed: %s\n", opt);
204 return -1;
205 }
206 }
207
208 states = qemu_plugin_scoreboard_new(sizeof(struct cpu_state));
209 last_pc = qemu_plugin_scoreboard_u64_in_struct(states, struct cpu_state,
210 last_pc);
211 from_pc = qemu_plugin_scoreboard_u64_in_struct(states, struct cpu_state,
212 from_pc);
213 has_from = qemu_plugin_scoreboard_u64_in_struct(states, struct cpu_state,
214 has_from);
215
216 qemu_plugin_register_vcpu_discon_cb(id, QEMU_PLUGIN_DISCON_ALL,
217 vcpu_discon, NULL);
218 qemu_plugin_register_vcpu_tb_trans_cb(id, vcpu_tb_trans, NULL);
219
220 return 0;
221 }