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