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 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 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 */ 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 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 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 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 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