xref: /openbmc/qemu/contrib/plugins/execlog.c (revision 2df1eb27)
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 GRWLock 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.
32  */
33 static void expand_last_exec(int cpu_index)
34 {
35     g_rw_lock_writer_lock(&expand_array_lock);
36     while (cpu_index >= last_exec->len) {
37         GString *s = g_string_new(NULL);
38         g_ptr_array_add(last_exec, s);
39     }
40     g_rw_lock_writer_unlock(&expand_array_lock);
41 }
42 
43 /**
44  * Add memory read or write information to current instruction log
45  */
46 static void vcpu_mem(unsigned int cpu_index, qemu_plugin_meminfo_t info,
47                      uint64_t vaddr, void *udata)
48 {
49     GString *s;
50 
51     /* Find vCPU in array */
52     g_rw_lock_reader_lock(&expand_array_lock);
53     g_assert(cpu_index < last_exec->len);
54     s = g_ptr_array_index(last_exec, cpu_index);
55     g_rw_lock_reader_unlock(&expand_array_lock);
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     g_rw_lock_reader_lock(&expand_array_lock);
84     if (cpu_index >= last_exec->len) {
85         g_rw_lock_reader_unlock(&expand_array_lock);
86         expand_last_exec(cpu_index);
87         g_rw_lock_reader_lock(&expand_array_lock);
88     }
89     s = g_ptr_array_index(last_exec, cpu_index);
90     g_rw_lock_reader_unlock(&expand_array_lock);
91 
92     /* Print previous instruction in cache */
93     if (s->len) {
94         qemu_plugin_outs(s->str);
95         qemu_plugin_outs("\n");
96     }
97 
98     /* Store new instruction in cache */
99     /* vcpu_mem will add memory access information to last_exec */
100     g_string_printf(s, "%u, ", cpu_index);
101     g_string_append(s, (char *)udata);
102 }
103 
104 /**
105  * On translation block new translation
106  *
107  * QEMU convert code by translation block (TB). By hooking here we can then hook
108  * a callback on each instruction and memory access.
109  */
110 static void vcpu_tb_trans(qemu_plugin_id_t id, struct qemu_plugin_tb *tb)
111 {
112     struct qemu_plugin_insn *insn;
113     bool skip = (imatches || amatches);
114 
115     size_t n = qemu_plugin_tb_n_insns(tb);
116     for (size_t i = 0; i < n; i++) {
117         char *insn_disas;
118         uint64_t insn_vaddr;
119 
120         /*
121          * `insn` is shared between translations in QEMU, copy needed data here.
122          * `output` is never freed as it might be used multiple times during
123          * the emulation lifetime.
124          * We only consider the first 32 bits of the instruction, this may be
125          * a limitation for CISC architectures.
126          */
127         insn = qemu_plugin_tb_get_insn(tb, i);
128         insn_disas = qemu_plugin_insn_disas(insn);
129         insn_vaddr = qemu_plugin_insn_vaddr(insn);
130 
131         /*
132          * If we are filtering we better check out if we have any
133          * hits. The skip "latches" so we can track memory accesses
134          * after the instruction we care about.
135          */
136         if (skip && imatches) {
137             int j;
138             for (j = 0; j < imatches->len && skip; j++) {
139                 char *m = g_ptr_array_index(imatches, j);
140                 if (g_str_has_prefix(insn_disas, m)) {
141                     skip = false;
142                 }
143             }
144         }
145 
146         if (skip && amatches) {
147             int j;
148             for (j = 0; j < amatches->len && skip; j++) {
149                 uint64_t v = g_array_index(amatches, uint64_t, j);
150                 if (v == insn_vaddr) {
151                     skip = false;
152                 }
153             }
154         }
155 
156         if (skip) {
157             g_free(insn_disas);
158         } else {
159             uint32_t insn_opcode;
160             insn_opcode = *((uint32_t *)qemu_plugin_insn_data(insn));
161             char *output = g_strdup_printf("0x%"PRIx64", 0x%"PRIx32", \"%s\"",
162                                            insn_vaddr, insn_opcode, insn_disas);
163 
164             /* Register callback on memory read or write */
165             qemu_plugin_register_vcpu_mem_cb(insn, vcpu_mem,
166                                              QEMU_PLUGIN_CB_NO_REGS,
167                                              QEMU_PLUGIN_MEM_RW, NULL);
168 
169             /* Register callback on instruction */
170             qemu_plugin_register_vcpu_insn_exec_cb(insn, vcpu_insn_exec,
171                                                    QEMU_PLUGIN_CB_NO_REGS, output);
172 
173             /* reset skip */
174             skip = (imatches || amatches);
175         }
176 
177     }
178 }
179 
180 /**
181  * On plugin exit, print last instruction in cache
182  */
183 static void plugin_exit(qemu_plugin_id_t id, void *p)
184 {
185     guint i;
186     GString *s;
187     for (i = 0; i < last_exec->len; i++) {
188         s = g_ptr_array_index(last_exec, i);
189         if (s->str) {
190             qemu_plugin_outs(s->str);
191             qemu_plugin_outs("\n");
192         }
193     }
194 }
195 
196 /* Add a match to the array of matches */
197 static void parse_insn_match(char *match)
198 {
199     if (!imatches) {
200         imatches = g_ptr_array_new();
201     }
202     g_ptr_array_add(imatches, match);
203 }
204 
205 static void parse_vaddr_match(char *match)
206 {
207     uint64_t v = g_ascii_strtoull(match, NULL, 16);
208 
209     if (!amatches) {
210         amatches = g_array_new(false, true, sizeof(uint64_t));
211     }
212     g_array_append_val(amatches, v);
213 }
214 
215 /**
216  * Install the plugin
217  */
218 QEMU_PLUGIN_EXPORT int qemu_plugin_install(qemu_plugin_id_t id,
219                                            const qemu_info_t *info, int argc,
220                                            char **argv)
221 {
222     /*
223      * Initialize dynamic array to cache vCPU instruction. In user mode
224      * we don't know the size before emulation.
225      */
226     if (info->system_emulation) {
227         last_exec = g_ptr_array_sized_new(info->system.max_vcpus);
228     } else {
229         last_exec = g_ptr_array_new();
230     }
231 
232     for (int i = 0; i < argc; i++) {
233         char *opt = argv[i];
234         g_auto(GStrv) tokens = g_strsplit(opt, "=", 2);
235         if (g_strcmp0(tokens[0], "ifilter") == 0) {
236             parse_insn_match(tokens[1]);
237         } else if (g_strcmp0(tokens[0], "afilter") == 0) {
238             parse_vaddr_match(tokens[1]);
239         } else {
240             fprintf(stderr, "option parsing failed: %s\n", opt);
241             return -1;
242         }
243     }
244 
245     /* Register translation block and exit callbacks */
246     qemu_plugin_register_vcpu_tb_trans_cb(id, vcpu_tb_trans);
247     qemu_plugin_register_atexit_cb(id, plugin_exit, NULL);
248 
249     return 0;
250 }
251