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