1 /* 2 * Copyright (C) 2021, Alexandre Iooss <erdnaxe@crans.org> 3 * 4 * Log instruction execution with memory access. 5 * 6 * License: GNU GPL, version 2 or later. 7 * See the COPYING file in the top-level directory. 8 */ 9 #include <glib.h> 10 #include <inttypes.h> 11 #include <stdio.h> 12 #include <stdlib.h> 13 #include <string.h> 14 #include <unistd.h> 15 16 #include <qemu-plugin.h> 17 18 QEMU_PLUGIN_EXPORT int qemu_plugin_version = QEMU_PLUGIN_VERSION; 19 20 /* Store last executed instruction on each vCPU as a GString */ 21 static GPtrArray *last_exec; 22 static GMutex expand_array_lock; 23 24 static GPtrArray *imatches; 25 static GArray *amatches; 26 27 /* 28 * Expand last_exec array. 29 * 30 * As we could have multiple threads trying to do this we need to 31 * serialise the expansion under a lock. Threads accessing already 32 * created entries can continue without issue even if the ptr array 33 * gets reallocated during resize. 34 */ 35 static void expand_last_exec(int cpu_index) 36 { 37 g_mutex_lock(&expand_array_lock); 38 while (cpu_index >= last_exec->len) { 39 GString *s = g_string_new(NULL); 40 g_ptr_array_add(last_exec, s); 41 } 42 g_mutex_unlock(&expand_array_lock); 43 } 44 45 /** 46 * Add memory read or write information to current instruction log 47 */ 48 static void vcpu_mem(unsigned int cpu_index, qemu_plugin_meminfo_t info, 49 uint64_t vaddr, void *udata) 50 { 51 GString *s; 52 53 /* Find vCPU in array */ 54 g_assert(cpu_index < last_exec->len); 55 s = g_ptr_array_index(last_exec, cpu_index); 56 57 /* Indicate type of memory access */ 58 if (qemu_plugin_mem_is_store(info)) { 59 g_string_append(s, ", store"); 60 } else { 61 g_string_append(s, ", load"); 62 } 63 64 /* If full system emulation log physical address and device name */ 65 struct qemu_plugin_hwaddr *hwaddr = qemu_plugin_get_hwaddr(info, vaddr); 66 if (hwaddr) { 67 uint64_t addr = qemu_plugin_hwaddr_phys_addr(hwaddr); 68 const char *name = qemu_plugin_hwaddr_device_name(hwaddr); 69 g_string_append_printf(s, ", 0x%08"PRIx64", %s", addr, name); 70 } else { 71 g_string_append_printf(s, ", 0x%08"PRIx64, vaddr); 72 } 73 } 74 75 /** 76 * Log instruction execution 77 */ 78 static void vcpu_insn_exec(unsigned int cpu_index, void *udata) 79 { 80 GString *s; 81 82 /* Find or create vCPU in array */ 83 if (cpu_index >= last_exec->len) { 84 expand_last_exec(cpu_index); 85 } 86 s = g_ptr_array_index(last_exec, cpu_index); 87 88 /* Print previous instruction in cache */ 89 if (s->len) { 90 qemu_plugin_outs(s->str); 91 qemu_plugin_outs("\n"); 92 } 93 94 /* Store new instruction in cache */ 95 /* vcpu_mem will add memory access information to last_exec */ 96 g_string_printf(s, "%u, ", cpu_index); 97 g_string_append(s, (char *)udata); 98 } 99 100 /** 101 * On translation block new translation 102 * 103 * QEMU convert code by translation block (TB). By hooking here we can then hook 104 * a callback on each instruction and memory access. 105 */ 106 static void vcpu_tb_trans(qemu_plugin_id_t id, struct qemu_plugin_tb *tb) 107 { 108 struct qemu_plugin_insn *insn; 109 bool skip = (imatches || amatches); 110 111 size_t n = qemu_plugin_tb_n_insns(tb); 112 for (size_t i = 0; i < n; i++) { 113 char *insn_disas; 114 uint64_t insn_vaddr; 115 116 /* 117 * `insn` is shared between translations in QEMU, copy needed data here. 118 * `output` is never freed as it might be used multiple times during 119 * the emulation lifetime. 120 * We only consider the first 32 bits of the instruction, this may be 121 * a limitation for CISC architectures. 122 */ 123 insn = qemu_plugin_tb_get_insn(tb, i); 124 insn_disas = qemu_plugin_insn_disas(insn); 125 insn_vaddr = qemu_plugin_insn_vaddr(insn); 126 127 /* 128 * If we are filtering we better check out if we have any 129 * hits. The skip "latches" so we can track memory accesses 130 * after the instruction we care about. 131 */ 132 if (skip && imatches) { 133 int j; 134 for (j = 0; j < imatches->len && skip; j++) { 135 char *m = g_ptr_array_index(imatches, j); 136 if (g_str_has_prefix(insn_disas, m)) { 137 skip = false; 138 } 139 } 140 } 141 142 if (skip && amatches) { 143 int j; 144 for (j = 0; j < amatches->len && skip; j++) { 145 uint64_t v = g_array_index(amatches, uint64_t, j); 146 if (v == insn_vaddr) { 147 skip = false; 148 } 149 } 150 } 151 152 if (skip) { 153 g_free(insn_disas); 154 } else { 155 uint32_t insn_opcode; 156 insn_opcode = *((uint32_t *)qemu_plugin_insn_data(insn)); 157 char *output = g_strdup_printf("0x%"PRIx64", 0x%"PRIx32", \"%s\"", 158 insn_vaddr, insn_opcode, insn_disas); 159 160 /* Register callback on memory read or write */ 161 qemu_plugin_register_vcpu_mem_cb(insn, vcpu_mem, 162 QEMU_PLUGIN_CB_NO_REGS, 163 QEMU_PLUGIN_MEM_RW, NULL); 164 165 /* Register callback on instruction */ 166 qemu_plugin_register_vcpu_insn_exec_cb(insn, vcpu_insn_exec, 167 QEMU_PLUGIN_CB_NO_REGS, output); 168 169 /* reset skip */ 170 skip = (imatches || amatches); 171 } 172 173 } 174 } 175 176 /** 177 * On plugin exit, print last instruction in cache 178 */ 179 static void plugin_exit(qemu_plugin_id_t id, void *p) 180 { 181 guint i; 182 GString *s; 183 for (i = 0; i < last_exec->len; i++) { 184 s = g_ptr_array_index(last_exec, i); 185 if (s->str) { 186 qemu_plugin_outs(s->str); 187 qemu_plugin_outs("\n"); 188 } 189 } 190 } 191 192 /* Add a match to the array of matches */ 193 static void parse_insn_match(char *match) 194 { 195 if (!imatches) { 196 imatches = g_ptr_array_new(); 197 } 198 g_ptr_array_add(imatches, match); 199 } 200 201 static void parse_vaddr_match(char *match) 202 { 203 uint64_t v = g_ascii_strtoull(match, NULL, 16); 204 205 if (!amatches) { 206 amatches = g_array_new(false, true, sizeof(uint64_t)); 207 } 208 g_array_append_val(amatches, v); 209 } 210 211 /** 212 * Install the plugin 213 */ 214 QEMU_PLUGIN_EXPORT int qemu_plugin_install(qemu_plugin_id_t id, 215 const qemu_info_t *info, int argc, 216 char **argv) 217 { 218 /* 219 * Initialize dynamic array to cache vCPU instruction. In user mode 220 * we don't know the size before emulation. 221 */ 222 if (info->system_emulation) { 223 last_exec = g_ptr_array_sized_new(info->system.max_vcpus); 224 } else { 225 last_exec = g_ptr_array_new(); 226 } 227 228 for (int i = 0; i < argc; i++) { 229 char *opt = argv[i]; 230 g_auto(GStrv) tokens = g_strsplit(opt, "=", 2); 231 if (g_strcmp0(tokens[0], "ifilter") == 0) { 232 parse_insn_match(tokens[1]); 233 } else if (g_strcmp0(tokens[0], "afilter") == 0) { 234 parse_vaddr_match(tokens[1]); 235 } else { 236 fprintf(stderr, "option parsing failed: %s\n", opt); 237 return -1; 238 } 239 } 240 241 /* Register translation block and exit callbacks */ 242 qemu_plugin_register_vcpu_tb_trans_cb(id, vcpu_tb_trans); 243 qemu_plugin_register_atexit_cb(id, plugin_exit, NULL); 244 245 return 0; 246 } 247