qemu

FORK: QEMU emulator
git clone https://git.neptards.moe/neptards/qemu.git
Log | Files | Refs | Submodules | LICENSE

execlog.c (7311B)


      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_autofree char **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 }