]>
Commit | Line | Data |
---|---|---|
671f760b EC |
1 | /* |
2 | * Copyright (C) 2018, Emilio G. Cota <[email protected]> | |
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 | ||
3fb356cc AB |
17 | QEMU_PLUGIN_EXPORT int qemu_plugin_version = QEMU_PLUGIN_VERSION; |
18 | ||
39be9dd3 AB |
19 | #define MAX_CPUS 8 /* lets not go nuts */ |
20 | ||
21 | typedef struct { | |
22 | uint64_t last_pc; | |
23 | uint64_t insn_count; | |
24 | } InstructionCount; | |
25 | ||
26 | static InstructionCount counts[MAX_CPUS]; | |
27 | static uint64_t inline_insn_count; | |
28 | ||
671f760b | 29 | static bool do_inline; |
e83f79b3 AB |
30 | static bool do_size; |
31 | static GArray *sizes; | |
671f760b | 32 | |
f6d1cd4d AB |
33 | typedef struct { |
34 | char *match_string; | |
35 | uint64_t hits[MAX_CPUS]; | |
36 | uint64_t last_hit[MAX_CPUS]; | |
37 | uint64_t total_delta[MAX_CPUS]; | |
38 | GPtrArray *history[MAX_CPUS]; | |
39 | } Match; | |
40 | ||
41 | static GArray *matches; | |
42 | ||
43 | typedef struct { | |
44 | Match *match; | |
45 | uint64_t vaddr; | |
46 | uint64_t hits; | |
47 | char *disas; | |
48 | } Instruction; | |
49 | ||
671f760b EC |
50 | static void vcpu_insn_exec_before(unsigned int cpu_index, void *udata) |
51 | { | |
39be9dd3 AB |
52 | unsigned int i = cpu_index % MAX_CPUS; |
53 | InstructionCount *c = &counts[i]; | |
e025d799 | 54 | uint64_t this_pc = GPOINTER_TO_UINT(udata); |
39be9dd3 | 55 | if (this_pc == c->last_pc) { |
e025d799 AB |
56 | g_autofree gchar *out = g_strdup_printf("detected repeat execution @ 0x%" |
57 | PRIx64 "\n", this_pc); | |
58 | qemu_plugin_outs(out); | |
59 | } | |
39be9dd3 AB |
60 | c->last_pc = this_pc; |
61 | c->insn_count++; | |
671f760b EC |
62 | } |
63 | ||
f6d1cd4d AB |
64 | static void vcpu_insn_matched_exec_before(unsigned int cpu_index, void *udata) |
65 | { | |
66 | unsigned int i = cpu_index % MAX_CPUS; | |
67 | Instruction *insn = (Instruction *) udata; | |
68 | Match *match = insn->match; | |
69 | g_autoptr(GString) ts = g_string_new(""); | |
70 | ||
71 | insn->hits++; | |
72 | g_string_append_printf(ts, "0x%" PRIx64 ", '%s', %"PRId64 " hits", | |
73 | insn->vaddr, insn->disas, insn->hits); | |
74 | ||
75 | uint64_t icount = counts[i].insn_count; | |
76 | uint64_t delta = icount - match->last_hit[i]; | |
77 | ||
78 | match->hits[i]++; | |
79 | match->total_delta[i] += delta; | |
80 | ||
81 | g_string_append_printf(ts, | |
82 | ", %"PRId64" match hits, " | |
83 | "Δ+%"PRId64 " since last match," | |
84 | " %"PRId64 " avg insns/match\n", | |
85 | match->hits[i], delta, | |
86 | match->total_delta[i] / match->hits[i]); | |
87 | ||
88 | match->last_hit[i] = icount; | |
89 | ||
90 | qemu_plugin_outs(ts->str); | |
91 | ||
92 | g_ptr_array_add(match->history[i], insn); | |
93 | } | |
94 | ||
671f760b EC |
95 | static void vcpu_tb_trans(qemu_plugin_id_t id, struct qemu_plugin_tb *tb) |
96 | { | |
97 | size_t n = qemu_plugin_tb_n_insns(tb); | |
98 | size_t i; | |
99 | ||
100 | for (i = 0; i < n; i++) { | |
101 | struct qemu_plugin_insn *insn = qemu_plugin_tb_get_insn(tb, i); | |
102 | ||
103 | if (do_inline) { | |
104 | qemu_plugin_register_vcpu_insn_exec_inline( | |
39be9dd3 | 105 | insn, QEMU_PLUGIN_INLINE_ADD_U64, &inline_insn_count, 1); |
671f760b | 106 | } else { |
e025d799 | 107 | uint64_t vaddr = qemu_plugin_insn_vaddr(insn); |
671f760b | 108 | qemu_plugin_register_vcpu_insn_exec_cb( |
e025d799 AB |
109 | insn, vcpu_insn_exec_before, QEMU_PLUGIN_CB_NO_REGS, |
110 | GUINT_TO_POINTER(vaddr)); | |
671f760b | 111 | } |
e83f79b3 AB |
112 | |
113 | if (do_size) { | |
114 | size_t sz = qemu_plugin_insn_size(insn); | |
115 | if (sz > sizes->len) { | |
116 | g_array_set_size(sizes, sz); | |
117 | } | |
118 | unsigned long *cnt = &g_array_index(sizes, unsigned long, sz); | |
119 | (*cnt)++; | |
120 | } | |
f6d1cd4d AB |
121 | |
122 | /* | |
123 | * If we are tracking certain instructions we will need more | |
124 | * information about the instruction which we also need to | |
125 | * save if there is a hit. | |
126 | */ | |
127 | if (matches) { | |
128 | char *insn_disas = qemu_plugin_insn_disas(insn); | |
129 | int j; | |
130 | for (j = 0; j < matches->len; j++) { | |
131 | Match *m = &g_array_index(matches, Match, j); | |
132 | if (g_str_has_prefix(insn_disas, m->match_string)) { | |
133 | Instruction *rec = g_new0(Instruction, 1); | |
134 | rec->disas = g_strdup(insn_disas); | |
135 | rec->vaddr = qemu_plugin_insn_vaddr(insn); | |
136 | rec->match = m; | |
137 | qemu_plugin_register_vcpu_insn_exec_cb( | |
138 | insn, vcpu_insn_matched_exec_before, | |
139 | QEMU_PLUGIN_CB_NO_REGS, rec); | |
140 | } | |
141 | } | |
142 | g_free(insn_disas); | |
143 | } | |
671f760b EC |
144 | } |
145 | } | |
146 | ||
147 | static void plugin_exit(qemu_plugin_id_t id, void *p) | |
148 | { | |
e83f79b3 | 149 | g_autoptr(GString) out = g_string_new(NULL); |
39be9dd3 | 150 | int i; |
e83f79b3 AB |
151 | |
152 | if (do_size) { | |
e83f79b3 AB |
153 | for (i = 0; i <= sizes->len; i++) { |
154 | unsigned long *cnt = &g_array_index(sizes, unsigned long, i); | |
155 | if (*cnt) { | |
156 | g_string_append_printf(out, | |
157 | "len %d bytes: %ld insns\n", i, *cnt); | |
158 | } | |
159 | } | |
39be9dd3 AB |
160 | } else if (do_inline) { |
161 | g_string_append_printf(out, "insns: %" PRIu64 "\n", inline_insn_count); | |
e83f79b3 | 162 | } else { |
39be9dd3 AB |
163 | uint64_t total_insns = 0; |
164 | for (i = 0; i < MAX_CPUS; i++) { | |
165 | InstructionCount *c = &counts[i]; | |
166 | if (c->insn_count) { | |
167 | g_string_append_printf(out, "cpu %d insns: %" PRIu64 "\n", | |
168 | i, c->insn_count); | |
169 | total_insns += c->insn_count; | |
170 | } | |
171 | } | |
172 | g_string_append_printf(out, "total insns: %" PRIu64 "\n", | |
173 | total_insns); | |
e83f79b3 AB |
174 | } |
175 | qemu_plugin_outs(out->str); | |
671f760b EC |
176 | } |
177 | ||
f6d1cd4d AB |
178 | |
179 | /* Add a match to the array of matches */ | |
180 | static void parse_match(char *match) | |
181 | { | |
182 | Match new_match = { .match_string = match }; | |
183 | int i; | |
184 | for (i = 0; i < MAX_CPUS; i++) { | |
185 | new_match.history[i] = g_ptr_array_new(); | |
186 | } | |
187 | if (!matches) { | |
188 | matches = g_array_new(false, true, sizeof(Match)); | |
189 | } | |
190 | g_array_append_val(matches, new_match); | |
191 | } | |
192 | ||
671f760b EC |
193 | QEMU_PLUGIN_EXPORT int qemu_plugin_install(qemu_plugin_id_t id, |
194 | const qemu_info_t *info, | |
195 | int argc, char **argv) | |
196 | { | |
0163ce31 MM |
197 | for (int i = 0; i < argc; i++) { |
198 | char *opt = argv[i]; | |
199 | g_autofree char **tokens = g_strsplit(opt, "=", 2); | |
200 | if (g_strcmp0(tokens[0], "inline") == 0) { | |
201 | if (!qemu_plugin_bool_parse(tokens[0], tokens[1], &do_inline)) { | |
202 | fprintf(stderr, "boolean argument parsing failed: %s\n", opt); | |
203 | return -1; | |
204 | } | |
e83f79b3 AB |
205 | } else if (g_strcmp0(tokens[0], "sizes") == 0) { |
206 | if (!qemu_plugin_bool_parse(tokens[0], tokens[1], &do_size)) { | |
207 | fprintf(stderr, "boolean argument parsing failed: %s\n", opt); | |
208 | return -1; | |
209 | } | |
f6d1cd4d AB |
210 | } else if (g_strcmp0(tokens[0], "match") == 0) { |
211 | parse_match(tokens[1]); | |
0163ce31 MM |
212 | } else { |
213 | fprintf(stderr, "option parsing failed: %s\n", opt); | |
214 | return -1; | |
215 | } | |
671f760b EC |
216 | } |
217 | ||
e83f79b3 AB |
218 | if (do_size) { |
219 | sizes = g_array_new(true, true, sizeof(unsigned long)); | |
220 | } | |
221 | ||
671f760b EC |
222 | qemu_plugin_register_vcpu_tb_trans_cb(id, vcpu_tb_trans); |
223 | qemu_plugin_register_atexit_cb(id, plugin_exit, NULL); | |
224 | return 0; | |
225 | } |