execlog.c (4741B)
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 18QEMU_PLUGIN_EXPORT int qemu_plugin_version = QEMU_PLUGIN_VERSION; 19 20/* Store last executed instruction on each vCPU as a GString */ 21GArray *last_exec; 22 23/** 24 * Add memory read or write information to current instruction log 25 */ 26static void vcpu_mem(unsigned int cpu_index, qemu_plugin_meminfo_t info, 27 uint64_t vaddr, void *udata) 28{ 29 GString *s; 30 31 /* Find vCPU in array */ 32 g_assert(cpu_index < last_exec->len); 33 s = g_array_index(last_exec, GString *, cpu_index); 34 35 /* Indicate type of memory access */ 36 if (qemu_plugin_mem_is_store(info)) { 37 g_string_append(s, ", store"); 38 } else { 39 g_string_append(s, ", load"); 40 } 41 42 /* If full system emulation log physical address and device name */ 43 struct qemu_plugin_hwaddr *hwaddr = qemu_plugin_get_hwaddr(info, vaddr); 44 if (hwaddr) { 45 uint64_t addr = qemu_plugin_hwaddr_phys_addr(hwaddr); 46 const char *name = qemu_plugin_hwaddr_device_name(hwaddr); 47 g_string_append_printf(s, ", 0x%08"PRIx64", %s", addr, name); 48 } else { 49 g_string_append_printf(s, ", 0x%08"PRIx64, vaddr); 50 } 51} 52 53/** 54 * Log instruction execution 55 */ 56static void vcpu_insn_exec(unsigned int cpu_index, void *udata) 57{ 58 GString *s; 59 60 /* Find or create vCPU in array */ 61 while (cpu_index >= last_exec->len) { 62 s = g_string_new(NULL); 63 g_array_append_val(last_exec, s); 64 } 65 s = g_array_index(last_exec, GString *, cpu_index); 66 67 /* Print previous instruction in cache */ 68 if (s->len) { 69 qemu_plugin_outs(s->str); 70 qemu_plugin_outs("\n"); 71 } 72 73 /* Store new instruction in cache */ 74 /* vcpu_mem will add memory access information to last_exec */ 75 g_string_printf(s, "%u, ", cpu_index); 76 g_string_append(s, (char *)udata); 77} 78 79/** 80 * On translation block new translation 81 * 82 * QEMU convert code by translation block (TB). By hooking here we can then hook 83 * a callback on each instruction and memory access. 84 */ 85static void vcpu_tb_trans(qemu_plugin_id_t id, struct qemu_plugin_tb *tb) 86{ 87 struct qemu_plugin_insn *insn; 88 uint64_t insn_vaddr; 89 uint32_t insn_opcode; 90 char *insn_disas; 91 92 size_t n = qemu_plugin_tb_n_insns(tb); 93 for (size_t i = 0; i < n; i++) { 94 /* 95 * `insn` is shared between translations in QEMU, copy needed data here. 96 * `output` is never freed as it might be used multiple times during 97 * the emulation lifetime. 98 * We only consider the first 32 bits of the instruction, this may be 99 * a limitation for CISC architectures. 100 */ 101 insn = qemu_plugin_tb_get_insn(tb, i); 102 insn_vaddr = qemu_plugin_insn_vaddr(insn); 103 insn_opcode = *((uint32_t *)qemu_plugin_insn_data(insn)); 104 insn_disas = qemu_plugin_insn_disas(insn); 105 char *output = g_strdup_printf("0x%"PRIx64", 0x%"PRIx32", \"%s\"", 106 insn_vaddr, insn_opcode, insn_disas); 107 108 /* Register callback on memory read or write */ 109 qemu_plugin_register_vcpu_mem_cb(insn, vcpu_mem, 110 QEMU_PLUGIN_CB_NO_REGS, 111 QEMU_PLUGIN_MEM_RW, NULL); 112 113 /* Register callback on instruction */ 114 qemu_plugin_register_vcpu_insn_exec_cb(insn, vcpu_insn_exec, 115 QEMU_PLUGIN_CB_NO_REGS, output); 116 } 117} 118 119/** 120 * On plugin exit, print last instruction in cache 121 */ 122static void plugin_exit(qemu_plugin_id_t id, void *p) 123{ 124 guint i; 125 GString *s; 126 for (i = 0; i < last_exec->len; i++) { 127 s = g_array_index(last_exec, GString *, i); 128 if (s->str) { 129 qemu_plugin_outs(s->str); 130 qemu_plugin_outs("\n"); 131 } 132 } 133} 134 135/** 136 * Install the plugin 137 */ 138QEMU_PLUGIN_EXPORT int qemu_plugin_install(qemu_plugin_id_t id, 139 const qemu_info_t *info, int argc, 140 char **argv) 141{ 142 /* 143 * Initialize dynamic array to cache vCPU instruction. In user mode 144 * we don't know the size before emulation. 145 */ 146 last_exec = g_array_new(FALSE, FALSE, sizeof(GString *)); 147 148 /* Register translation block and exit callbacks */ 149 qemu_plugin_register_vcpu_tb_trans_cb(id, vcpu_tb_trans); 150 qemu_plugin_register_atexit_cb(id, plugin_exit, NULL); 151 152 return 0; 153}