1 /*
2 * Copyright (C) 2018, Emilio G. Cota <cota@braap.org>
3 *
4 * License: GNU GPL, version 2 or later.
5 * See the COPYING file in the top-level directory.
6 */
7 #include <inttypes.h>
8 #include <assert.h>
9 #include <stdlib.h>
10 #include <string.h>
11 #include <unistd.h>
12 #include <stdio.h>
13 #include <glib.h>
14
15 /*
16 * plugins should not include anything from QEMU aside from the
17 * API header. However as this is a test plugin to exercise the
18 * internals of QEMU and we want to avoid needless code duplication we
19 * do so here. bswap.h is pretty self-contained although it needs a
20 * few things provided by compiler.h.
21 */
22 #include <compiler.h>
23 #include <stdbool.h>
24 #include <bswap.h>
25 #include <qemu-plugin.h>
26
27 QEMU_PLUGIN_EXPORT int qemu_plugin_version = QEMU_PLUGIN_VERSION;
28
29 typedef struct {
30 uint64_t mem_count;
31 uint64_t io_count;
32 } CPUCount;
33
34 typedef struct {
35 uint64_t vaddr;
36 const char *sym;
37 } InsnInfo;
38
39 /*
40 * For the "memory" system test we need to track accesses to
41 * individual regions. We mirror the data written to the region and
42 * then check when it is read that it matches up.
43 *
44 * We do this as regions rather than pages to save on complications
45 * with page crossing and the fact the test only cares about the
46 * test_data region.
47 */
48 static uint64_t region_size = 4096 * 4;
49 static uint64_t region_mask;
50
51 typedef struct {
52 uint64_t region_address;
53 uint64_t reads;
54 uint64_t writes;
55 uint8_t *data;
56 /* Did we see every write and read with correct values? */
57 bool seen_all;
58 } RegionInfo;
59
60 static struct qemu_plugin_scoreboard *counts;
61 static qemu_plugin_u64 mem_count;
62 static qemu_plugin_u64 io_count;
63 static bool do_inline, do_callback, do_print_accesses, do_region_summary;
64 static bool do_haddr;
65 static enum qemu_plugin_mem_rw rw = QEMU_PLUGIN_MEM_RW;
66
67
68 static GMutex lock;
69 static GHashTable *regions;
70
addr_order(gconstpointer a,gconstpointer b,gpointer d)71 static gint addr_order(gconstpointer a, gconstpointer b, gpointer d)
72 {
73 RegionInfo *na = (RegionInfo *) a;
74 RegionInfo *nb = (RegionInfo *) b;
75
76 return na->region_address > nb->region_address ? 1 : -1;
77 }
78
79
plugin_exit(qemu_plugin_id_t id,void * p)80 static void plugin_exit(qemu_plugin_id_t id, void *p)
81 {
82 g_autoptr(GString) out = g_string_new("");
83
84 if (do_inline || do_callback) {
85 g_string_printf(out, "mem accesses: %" PRIu64 "\n",
86 qemu_plugin_u64_sum(mem_count));
87 }
88 if (do_haddr) {
89 g_string_append_printf(out, "io accesses: %" PRIu64 "\n",
90 qemu_plugin_u64_sum(io_count));
91 }
92 qemu_plugin_outs(out->str);
93
94
95 if (do_region_summary) {
96 GList *counts = g_hash_table_get_values(regions);
97
98 counts = g_list_sort_with_data(counts, addr_order, NULL);
99
100 g_string_printf(out, "Region Base, Reads, Writes, Seen all\n");
101
102 if (counts && g_list_next(counts)) {
103 for (/* counts */; counts; counts = counts->next) {
104 RegionInfo *ri = (RegionInfo *) counts->data;
105
106 g_string_append_printf(out,
107 "0x%016"PRIx64", "
108 "%"PRId64", %"PRId64", %s\n",
109 ri->region_address,
110 ri->reads,
111 ri->writes,
112 ri->seen_all ? "true" : "false");
113 }
114 }
115 qemu_plugin_outs(out->str);
116 }
117
118 qemu_plugin_scoreboard_free(counts);
119 }
120
121 /*
122 * Update the region tracking info for the access. We split up accesses
123 * that span regions even though the plugin infrastructure will deliver
124 * it as a single access.
125 */
update_region_info(uint64_t region,uint64_t offset,qemu_plugin_meminfo_t meminfo,qemu_plugin_mem_value value,unsigned size)126 static void update_region_info(uint64_t region, uint64_t offset,
127 qemu_plugin_meminfo_t meminfo,
128 qemu_plugin_mem_value value,
129 unsigned size)
130 {
131 bool be = qemu_plugin_mem_is_big_endian(meminfo);
132 bool is_store = qemu_plugin_mem_is_store(meminfo);
133 RegionInfo *ri;
134 bool unseen_data = false;
135
136 g_assert(offset + size <= region_size);
137
138 g_mutex_lock(&lock);
139 ri = (RegionInfo *) g_hash_table_lookup(regions, ®ion);
140
141 if (!ri) {
142 ri = g_new0(RegionInfo, 1);
143 ri->region_address = region;
144 ri->data = g_malloc0(region_size);
145 ri->seen_all = true;
146 g_hash_table_insert(regions, &ri->region_address, ri);
147 }
148
149 if (is_store) {
150 ri->writes++;
151 } else {
152 ri->reads++;
153 }
154
155 switch (value.type) {
156 case QEMU_PLUGIN_MEM_VALUE_U8:
157 if (is_store) {
158 ri->data[offset] = value.data.u8;
159 } else if (ri->data[offset] != value.data.u8) {
160 unseen_data = true;
161 }
162 break;
163 case QEMU_PLUGIN_MEM_VALUE_U16:
164 {
165 uint16_t *p = (uint16_t *) &ri->data[offset];
166 if (is_store) {
167 if (be) {
168 stw_be_p(p, value.data.u16);
169 } else {
170 stw_le_p(p, value.data.u16);
171 }
172 } else {
173 uint16_t val = be ? lduw_be_p(p) : lduw_le_p(p);
174 unseen_data = val != value.data.u16;
175 }
176 break;
177 }
178 case QEMU_PLUGIN_MEM_VALUE_U32:
179 {
180 uint32_t *p = (uint32_t *) &ri->data[offset];
181 if (is_store) {
182 if (be) {
183 stl_be_p(p, value.data.u32);
184 } else {
185 stl_le_p(p, value.data.u32);
186 }
187 } else {
188 uint32_t val = be ? ldl_be_p(p) : ldl_le_p(p);
189 unseen_data = val != value.data.u32;
190 }
191 break;
192 }
193 case QEMU_PLUGIN_MEM_VALUE_U64:
194 {
195 uint64_t *p = (uint64_t *) &ri->data[offset];
196 if (is_store) {
197 if (be) {
198 stq_be_p(p, value.data.u64);
199 } else {
200 stq_le_p(p, value.data.u64);
201 }
202 } else {
203 uint64_t val = be ? ldq_be_p(p) : ldq_le_p(p);
204 unseen_data = val != value.data.u64;
205 }
206 break;
207 }
208 case QEMU_PLUGIN_MEM_VALUE_U128:
209 /* non in test so skip */
210 break;
211 default:
212 g_assert_not_reached();
213 }
214
215 /*
216 * This is expected for regions initialised by QEMU (.text etc) but we
217 * expect to see all data read and written to the test_data region
218 * of the memory test.
219 */
220 if (unseen_data && ri->seen_all) {
221 g_autoptr(GString) error = g_string_new("Warning: ");
222 g_string_append_printf(error, "0x%016"PRIx64":%"PRId64
223 " read an un-instrumented value\n",
224 region, offset);
225 qemu_plugin_outs(error->str);
226 ri->seen_all = false;
227 }
228
229 g_mutex_unlock(&lock);
230 }
231
vcpu_mem(unsigned int cpu_index,qemu_plugin_meminfo_t meminfo,uint64_t vaddr,void * udata)232 static void vcpu_mem(unsigned int cpu_index, qemu_plugin_meminfo_t meminfo,
233 uint64_t vaddr, void *udata)
234 {
235 if (do_haddr) {
236 struct qemu_plugin_hwaddr *hwaddr;
237 hwaddr = qemu_plugin_get_hwaddr(meminfo, vaddr);
238 if (qemu_plugin_hwaddr_is_io(hwaddr)) {
239 qemu_plugin_u64_add(io_count, cpu_index, 1);
240 } else {
241 qemu_plugin_u64_add(mem_count, cpu_index, 1);
242 }
243 } else {
244 qemu_plugin_u64_add(mem_count, cpu_index, 1);
245 }
246
247 if (do_region_summary) {
248 uint64_t region = vaddr & ~region_mask;
249 uint64_t offset = vaddr & region_mask;
250 qemu_plugin_mem_value value = qemu_plugin_mem_get_value(meminfo);
251 unsigned size = 1 << qemu_plugin_mem_size_shift(meminfo);
252
253 update_region_info(region, offset, meminfo, value, size);
254 }
255 }
256
print_access(unsigned int cpu_index,qemu_plugin_meminfo_t meminfo,uint64_t vaddr,void * udata)257 static void print_access(unsigned int cpu_index, qemu_plugin_meminfo_t meminfo,
258 uint64_t vaddr, void *udata)
259 {
260 InsnInfo *insn_info = udata;
261 unsigned size = 8 << qemu_plugin_mem_size_shift(meminfo);
262 const char *type = qemu_plugin_mem_is_store(meminfo) ? "store" : "load";
263 qemu_plugin_mem_value value = qemu_plugin_mem_get_value(meminfo);
264 uint64_t hwaddr =
265 qemu_plugin_hwaddr_phys_addr(qemu_plugin_get_hwaddr(meminfo, vaddr));
266 g_autoptr(GString) out = g_string_new("");
267 g_string_printf(out,
268 "0x%"PRIx64",%s,0x%"PRIx64",0x%"PRIx64",%d,%s,",
269 insn_info->vaddr, insn_info->sym,
270 vaddr, hwaddr, size, type);
271 switch (value.type) {
272 case QEMU_PLUGIN_MEM_VALUE_U8:
273 g_string_append_printf(out, "0x%02"PRIx8, value.data.u8);
274 break;
275 case QEMU_PLUGIN_MEM_VALUE_U16:
276 g_string_append_printf(out, "0x%04"PRIx16, value.data.u16);
277 break;
278 case QEMU_PLUGIN_MEM_VALUE_U32:
279 g_string_append_printf(out, "0x%08"PRIx32, value.data.u32);
280 break;
281 case QEMU_PLUGIN_MEM_VALUE_U64:
282 g_string_append_printf(out, "0x%016"PRIx64, value.data.u64);
283 break;
284 case QEMU_PLUGIN_MEM_VALUE_U128:
285 g_string_append_printf(out, "0x%016"PRIx64"%016"PRIx64,
286 value.data.u128.high, value.data.u128.low);
287 break;
288 default:
289 g_assert_not_reached();
290 }
291 g_string_append_printf(out, "\n");
292 qemu_plugin_outs(out->str);
293 }
294
vcpu_tb_trans(qemu_plugin_id_t id,struct qemu_plugin_tb * tb)295 static void vcpu_tb_trans(qemu_plugin_id_t id, struct qemu_plugin_tb *tb)
296 {
297 size_t n = qemu_plugin_tb_n_insns(tb);
298 size_t i;
299
300 for (i = 0; i < n; i++) {
301 struct qemu_plugin_insn *insn = qemu_plugin_tb_get_insn(tb, i);
302
303 if (do_inline) {
304 qemu_plugin_register_vcpu_mem_inline_per_vcpu(
305 insn, rw,
306 QEMU_PLUGIN_INLINE_ADD_U64,
307 mem_count, 1);
308 }
309 if (do_callback || do_region_summary) {
310 qemu_plugin_register_vcpu_mem_cb(insn, vcpu_mem,
311 QEMU_PLUGIN_CB_NO_REGS,
312 rw, NULL);
313 }
314 if (do_print_accesses) {
315 /* we leak this pointer, to avoid locking to keep track of it */
316 InsnInfo *insn_info = g_malloc(sizeof(InsnInfo));
317 const char *sym = qemu_plugin_insn_symbol(insn);
318 insn_info->sym = sym ? sym : "";
319 insn_info->vaddr = qemu_plugin_insn_vaddr(insn);
320 qemu_plugin_register_vcpu_mem_cb(insn, print_access,
321 QEMU_PLUGIN_CB_NO_REGS,
322 rw, (void *) insn_info);
323 }
324 }
325 }
326
qemu_plugin_install(qemu_plugin_id_t id,const qemu_info_t * info,int argc,char ** argv)327 QEMU_PLUGIN_EXPORT int qemu_plugin_install(qemu_plugin_id_t id,
328 const qemu_info_t *info,
329 int argc, char **argv)
330 {
331
332 for (int i = 0; i < argc; i++) {
333 char *opt = argv[i];
334 g_auto(GStrv) tokens = g_strsplit(opt, "=", 2);
335
336 if (g_strcmp0(tokens[0], "haddr") == 0) {
337 if (!qemu_plugin_bool_parse(tokens[0], tokens[1], &do_haddr)) {
338 fprintf(stderr, "boolean argument parsing failed: %s\n", opt);
339 return -1;
340 }
341 } else if (g_strcmp0(tokens[0], "track") == 0) {
342 if (g_strcmp0(tokens[1], "r") == 0) {
343 rw = QEMU_PLUGIN_MEM_R;
344 } else if (g_strcmp0(tokens[1], "w") == 0) {
345 rw = QEMU_PLUGIN_MEM_W;
346 } else if (g_strcmp0(tokens[1], "rw") == 0) {
347 rw = QEMU_PLUGIN_MEM_RW;
348 } else {
349 fprintf(stderr, "invalid value for argument track: %s\n", opt);
350 return -1;
351 }
352 } else if (g_strcmp0(tokens[0], "inline") == 0) {
353 if (!qemu_plugin_bool_parse(tokens[0], tokens[1], &do_inline)) {
354 fprintf(stderr, "boolean argument parsing failed: %s\n", opt);
355 return -1;
356 }
357 } else if (g_strcmp0(tokens[0], "callback") == 0) {
358 if (!qemu_plugin_bool_parse(tokens[0], tokens[1], &do_callback)) {
359 fprintf(stderr, "boolean argument parsing failed: %s\n", opt);
360 return -1;
361 }
362 } else if (g_strcmp0(tokens[0], "print-accesses") == 0) {
363 if (!qemu_plugin_bool_parse(tokens[0], tokens[1],
364 &do_print_accesses)) {
365 fprintf(stderr, "boolean argument parsing failed: %s\n", opt);
366 return -1;
367 }
368 } else if (g_strcmp0(tokens[0], "region-summary") == 0) {
369 if (!qemu_plugin_bool_parse(tokens[0], tokens[1],
370 &do_region_summary)) {
371 fprintf(stderr, "boolean argument parsing failed: %s\n", opt);
372 return -1;
373 }
374 } else {
375 fprintf(stderr, "option parsing failed: %s\n", opt);
376 return -1;
377 }
378 }
379
380 if (do_inline && do_callback) {
381 fprintf(stderr,
382 "can't enable inline and callback counting at the same time\n");
383 return -1;
384 }
385
386 if (do_print_accesses) {
387 g_autoptr(GString) out = g_string_new("");
388 g_string_printf(out,
389 "insn_vaddr,insn_symbol,mem_vaddr,mem_hwaddr,"
390 "access_size,access_type,mem_value\n");
391 qemu_plugin_outs(out->str);
392 }
393
394 if (do_region_summary) {
395 region_mask = (region_size - 1);
396 regions = g_hash_table_new(g_int64_hash, g_int64_equal);
397 }
398
399 counts = qemu_plugin_scoreboard_new(sizeof(CPUCount));
400 mem_count = qemu_plugin_scoreboard_u64_in_struct(
401 counts, CPUCount, mem_count);
402 io_count = qemu_plugin_scoreboard_u64_in_struct(counts, CPUCount, io_count);
403 qemu_plugin_register_vcpu_tb_trans_cb(id, vcpu_tb_trans);
404 qemu_plugin_register_atexit_cb(id, plugin_exit, NULL);
405 return 0;
406 }
407