xref: /openbmc/qemu/tests/tcg/plugins/insn.c (revision 40a770ea8b9478aefa3a60049bc67cc04ace569c)
1  /*
2   * Copyright (C) 2018, Emilio G. Cota <cota@braap.org>
3   *
4   * License: GNU GPL, version 2 or later.
5   *   See the COPYING file in the top-level directory.
6   */
7  #include <inttypes.h>
8  #include <assert.h>
9  #include <stdlib.h>
10  #include <string.h>
11  #include <unistd.h>
12  #include <stdio.h>
13  #include <glib.h>
14  
15  #include <qemu-plugin.h>
16  
17  QEMU_PLUGIN_EXPORT int qemu_plugin_version = QEMU_PLUGIN_VERSION;
18  
19  static qemu_plugin_u64 insn_count;
20  
21  static bool do_inline;
22  static bool do_size;
23  static bool do_trace;
24  static GArray *sizes;
25  
26  typedef struct {
27      uint64_t hits;
28      uint64_t last_hit;
29      uint64_t total_delta;
30  } MatchCount;
31  
32  typedef struct {
33      char *match_string;
34      struct qemu_plugin_scoreboard *counts; /* MatchCount */
35  } Match;
36  
37  static GArray *matches;
38  
39  typedef struct {
40      Match *match;
41      uint64_t vaddr;
42      uint64_t hits;
43      char *disas;
44  } Instruction;
45  
46  /* A hash table to hold matched instructions */
47  static GHashTable *match_insn_records;
48  static GMutex match_hash_lock;
49  
50  
get_insn_record(const char * disas,uint64_t vaddr,Match * m)51  static Instruction * get_insn_record(const char *disas, uint64_t vaddr, Match *m)
52  {
53      g_autofree char *str_hash = g_strdup_printf("%"PRIx64" %s", vaddr, disas);
54      Instruction *record;
55  
56      g_mutex_lock(&match_hash_lock);
57  
58      if (!match_insn_records) {
59          match_insn_records = g_hash_table_new(g_str_hash, g_str_equal);
60      }
61  
62      record = g_hash_table_lookup(match_insn_records, str_hash);
63  
64      if (!record) {
65          g_autoptr(GString) ts = g_string_new(str_hash);
66  
67          record = g_new0(Instruction, 1);
68          record->disas = g_strdup(disas);
69          record->vaddr = vaddr;
70          record->match = m;
71  
72          g_hash_table_insert(match_insn_records, str_hash, record);
73  
74          g_string_prepend(ts, "Created record for: ");
75          g_string_append(ts, "\n");
76          qemu_plugin_outs(ts->str);
77      }
78  
79      g_mutex_unlock(&match_hash_lock);
80  
81      return record;
82  }
83  
84  /*
85   * Initialise a new vcpu with reading the register list
86   */
vcpu_init(qemu_plugin_id_t id,unsigned int vcpu_index)87  static void vcpu_init(qemu_plugin_id_t id, unsigned int vcpu_index)
88  {
89      g_autoptr(GArray) reg_list = qemu_plugin_get_registers();
90      g_autoptr(GByteArray) reg_value = g_byte_array_new();
91  
92      if (reg_list) {
93          for (int i = 0; i < reg_list->len; i++) {
94              qemu_plugin_reg_descriptor *rd = &g_array_index(
95                  reg_list, qemu_plugin_reg_descriptor, i);
96              int count = qemu_plugin_read_register(rd->handle, reg_value);
97              g_assert(count > 0);
98          }
99      }
100  }
101  
102  
vcpu_insn_exec_before(unsigned int cpu_index,void * udata)103  static void vcpu_insn_exec_before(unsigned int cpu_index, void *udata)
104  {
105      qemu_plugin_u64_add(insn_count, cpu_index, 1);
106  }
107  
vcpu_insn_matched_exec_before(unsigned int cpu_index,void * udata)108  static void vcpu_insn_matched_exec_before(unsigned int cpu_index, void *udata)
109  {
110      Instruction *insn = (Instruction *) udata;
111      Match *insn_match = insn->match;
112      MatchCount *match = qemu_plugin_scoreboard_find(insn_match->counts,
113                                                      cpu_index);
114  
115      insn->hits++;
116  
117      uint64_t icount = qemu_plugin_u64_get(insn_count, cpu_index);
118      uint64_t delta = icount - match->last_hit;
119  
120      match->hits++;
121      match->total_delta += delta;
122      match->last_hit = icount;
123  
124      if (do_trace) {
125          g_autoptr(GString) ts = g_string_new("");
126          g_string_append_printf(ts, "0x%" PRIx64 ", '%s', %"PRId64 " hits",
127                                 insn->vaddr, insn->disas, insn->hits);
128          g_string_append_printf(ts,
129                                 " , cpu %u,"
130                                 " %"PRId64" match hits,"
131                                 " Δ+%"PRId64 " since last match,"
132                                 " %"PRId64 " avg insns/match\n",
133                                 cpu_index,
134                                 match->hits, delta,
135                                 match->total_delta / match->hits);
136  
137          qemu_plugin_outs(ts->str);
138      }
139  }
140  
vcpu_tb_trans(qemu_plugin_id_t id,struct qemu_plugin_tb * tb)141  static void vcpu_tb_trans(qemu_plugin_id_t id, struct qemu_plugin_tb *tb)
142  {
143      size_t n = qemu_plugin_tb_n_insns(tb);
144      size_t i;
145  
146      for (i = 0; i < n; i++) {
147          struct qemu_plugin_insn *insn = qemu_plugin_tb_get_insn(tb, i);
148  
149          if (do_inline) {
150              qemu_plugin_register_vcpu_insn_exec_inline_per_vcpu(
151                  insn, QEMU_PLUGIN_INLINE_ADD_U64, insn_count, 1);
152          } else {
153              uint64_t vaddr = qemu_plugin_insn_vaddr(insn);
154              qemu_plugin_register_vcpu_insn_exec_cb(
155                  insn, vcpu_insn_exec_before, QEMU_PLUGIN_CB_NO_REGS,
156                  GUINT_TO_POINTER(vaddr));
157          }
158  
159          if (do_size) {
160              size_t sz = qemu_plugin_insn_size(insn);
161              if (sz > sizes->len) {
162                  g_array_set_size(sizes, sz);
163              }
164              unsigned long *cnt = &g_array_index(sizes, unsigned long, sz);
165              (*cnt)++;
166          }
167  
168          /*
169           * If we are tracking certain instructions we will need more
170           * information about the instruction which we also need to
171           * save if there is a hit.
172           *
173           * We only want one record for each occurrence of the matched
174           * instruction.
175           */
176          if (matches->len) {
177              char *insn_disas = qemu_plugin_insn_disas(insn);
178              for (int j = 0; j < matches->len; j++) {
179                  Match *m = &g_array_index(matches, Match, j);
180                  if (g_str_has_prefix(insn_disas, m->match_string)) {
181                      Instruction *rec = get_insn_record(insn_disas,
182                                                         qemu_plugin_insn_vaddr(insn),
183                                                         m);
184  
185                      qemu_plugin_register_vcpu_insn_exec_cb(
186                          insn, vcpu_insn_matched_exec_before,
187                          QEMU_PLUGIN_CB_NO_REGS, rec);
188                  }
189              }
190              g_free(insn_disas);
191          }
192      }
193  }
194  
plugin_exit(qemu_plugin_id_t id,void * p)195  static void plugin_exit(qemu_plugin_id_t id, void *p)
196  {
197      g_autoptr(GString) out = g_string_new(NULL);
198      int i;
199  
200      if (do_size) {
201          for (i = 0; i <= sizes->len; i++) {
202              unsigned long *cnt = &g_array_index(sizes, unsigned long, i);
203              if (*cnt) {
204                  g_string_append_printf(out,
205                                         "len %d bytes: %ld insns\n", i, *cnt);
206              }
207          }
208      } else {
209          for (i = 0; i < qemu_plugin_num_vcpus(); i++) {
210              g_string_append_printf(out, "cpu %d insns: %" PRIu64 "\n",
211                                     i, qemu_plugin_u64_get(insn_count, i));
212          }
213          g_string_append_printf(out, "total insns: %" PRIu64 "\n",
214                                 qemu_plugin_u64_sum(insn_count));
215      }
216      qemu_plugin_outs(out->str);
217      qemu_plugin_scoreboard_free(insn_count.score);
218  
219      g_mutex_lock(&match_hash_lock);
220  
221      for (i = 0; i < matches->len; ++i) {
222          Match *m = &g_array_index(matches, Match, i);
223          GHashTableIter iter;
224          Instruction *record;
225          qemu_plugin_u64 hit_e = qemu_plugin_scoreboard_u64_in_struct(m->counts, MatchCount, hits);
226          uint64_t hits = qemu_plugin_u64_sum(hit_e);
227  
228          g_string_printf(out, "Match: %s, hits %"PRId64"\n", m->match_string, hits);
229          qemu_plugin_outs(out->str);
230  
231          g_hash_table_iter_init(&iter, match_insn_records);
232          while (g_hash_table_iter_next(&iter, NULL, (void **)&record)) {
233              if (record->match == m) {
234                  g_string_printf(out,
235                                  "  %"PRIx64": %s (hits %"PRId64")\n",
236                                  record->vaddr,
237                                  record->disas,
238                                  record->hits);
239                  qemu_plugin_outs(out->str);
240              }
241          }
242  
243          g_free(m->match_string);
244          qemu_plugin_scoreboard_free(m->counts);
245      }
246  
247      g_mutex_unlock(&match_hash_lock);
248  
249      g_array_free(matches, TRUE);
250      g_array_free(sizes, TRUE);
251  }
252  
253  
254  /* Add a match to the array of matches */
parse_match(char * match)255  static void parse_match(char *match)
256  {
257      Match new_match = {
258          .match_string = g_strdup(match),
259          .counts = qemu_plugin_scoreboard_new(sizeof(MatchCount)) };
260      g_array_append_val(matches, new_match);
261  }
262  
qemu_plugin_install(qemu_plugin_id_t id,const qemu_info_t * info,int argc,char ** argv)263  QEMU_PLUGIN_EXPORT int qemu_plugin_install(qemu_plugin_id_t id,
264                                             const qemu_info_t *info,
265                                             int argc, char **argv)
266  {
267      matches = g_array_new(false, true, sizeof(Match));
268      /* null terminated so 0 is not a special case */
269      sizes = g_array_new(true, true, sizeof(unsigned long));
270  
271      for (int i = 0; i < argc; i++) {
272          char *opt = argv[i];
273          g_auto(GStrv) tokens = g_strsplit(opt, "=", 2);
274          if (g_strcmp0(tokens[0], "inline") == 0) {
275              if (!qemu_plugin_bool_parse(tokens[0], tokens[1], &do_inline)) {
276                  fprintf(stderr, "boolean argument parsing failed: %s\n", opt);
277                  return -1;
278              }
279          } else if (g_strcmp0(tokens[0], "sizes") == 0) {
280              if (!qemu_plugin_bool_parse(tokens[0], tokens[1], &do_size)) {
281                  fprintf(stderr, "boolean argument parsing failed: %s\n", opt);
282                  return -1;
283              }
284          } else if (g_strcmp0(tokens[0], "match") == 0) {
285              parse_match(tokens[1]);
286          } else if (g_strcmp0(tokens[0], "trace") == 0) {
287              if (!qemu_plugin_bool_parse(tokens[0], tokens[1], &do_trace)) {
288                  fprintf(stderr, "boolean argument parsing failed: %s\n", opt);
289                  return -1;
290              }
291          } else {
292              fprintf(stderr, "option parsing failed: %s\n", opt);
293              return -1;
294          }
295      }
296  
297      insn_count = qemu_plugin_scoreboard_u64(
298          qemu_plugin_scoreboard_new(sizeof(uint64_t)));
299  
300      /* Register init, translation block and exit callbacks */
301      qemu_plugin_register_vcpu_init_cb(id, vcpu_init);
302      qemu_plugin_register_vcpu_tb_trans_cb(id, vcpu_tb_trans);
303      qemu_plugin_register_atexit_cb(id, plugin_exit, NULL);
304      return 0;
305  }
306