xref: /openbmc/qemu/contrib/plugins/execlog.c (revision ab1b2ba9)
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 }
247