xref: /openbmc/qemu/tests/tcg/plugins/mem.c (revision e452053097371880910c744a5d42ae2df058a4a7)
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, &region);
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