xref: /openbmc/qemu/tests/tcg/plugins/syscall.c (revision 173c427eb5705064da7dc8db22553c8df34f7f58)
1 /*
2  * Copyright (C) 2020, Matthias Weckbecker <matthias@weckbecker.name>
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 typedef struct {
20     int64_t num;
21     int64_t calls;
22     int64_t errors;
23 } SyscallStats;
24 
25 struct SyscallInfo {
26     const char *name;
27     int64_t write_sysno;
28 };
29 
30 static const struct SyscallInfo arch_syscall_info[] = {
31     { "aarch64", 64 },
32     { "aarch64_be", 64 },
33     { "alpha", 4 },
34     { "arm", 4 },
35     { "armeb", 4 },
36     { "avr", -1 },
37     { "cris", -1 },
38     { "hexagon", 64 },
39     { "hppa", -1 },
40     { "i386", 4 },
41     { "loongarch64", -1 },
42     { "m68k", 4 },
43     { "microblaze", 4 },
44     { "microblazeel", 4 },
45     { "mips", 1 },
46     { "mips64", 1 },
47     { "mips64el", 1 },
48     { "mipsel", 1 },
49     { "mipsn32", 1 },
50     { "mipsn32el", 1 },
51     { "or1k", -1 },
52     { "ppc", 4 },
53     { "ppc64", 4 },
54     { "ppc64le", 4 },
55     { "riscv32", 64 },
56     { "riscv64", 64 },
57     { "rx", -1 },
58     { "s390x", -1 },
59     { "sh4", -1 },
60     { "sh4eb", -1 },
61     { "sparc", 4 },
62     { "sparc32plus", 4 },
63     { "sparc64", 4 },
64     { "tricore", -1 },
65     { "x86_64", 1 },
66     { "xtensa", 13 },
67     { "xtensaeb", 13 },
68     { NULL, -1 },
69 };
70 
71 static GMutex lock;
72 static GHashTable *statistics;
73 static GByteArray *memory_buffer;
74 static bool do_log_writes;
75 static int64_t write_sysno = -1;
76 
77 static SyscallStats *get_or_create_entry(int64_t num)
78 {
79     SyscallStats *entry =
80         (SyscallStats *) g_hash_table_lookup(statistics, GINT_TO_POINTER(num));
81 
82     if (!entry) {
83         entry = g_new0(SyscallStats, 1);
84         entry->num = num;
85         g_hash_table_insert(statistics, GINT_TO_POINTER(num), (gpointer) entry);
86     }
87 
88     return entry;
89 }
90 
91 /*
92  * Hex-dump a GByteArray to the QEMU plugin output in the format:
93  * 61 63 63 65 6c 09 09 20 20 20 66 70 75 09 09 09  | accel.....fpu...
94  * 20 6d 6f 64 75 6c 65 2d 63 6f 6d 6d 6f 6e 2e 63  | .module-common.c
95  */
96 static void hexdump(const GByteArray *data)
97 {
98     g_autoptr(GString) out = g_string_new("");
99 
100     for (guint index = 0; index < data->len; index += 16) {
101         for (guint col = 0; col < 16; col++) {
102             if (index + col < data->len) {
103                 g_string_append_printf(out, "%02x ", data->data[index + col]);
104             } else {
105                 g_string_append(out, "   ");
106             }
107         }
108 
109         g_string_append(out, " | ");
110 
111         for (guint col = 0; col < 16; col++) {
112             if (index + col >= data->len) {
113                 break;
114             }
115 
116             if (g_ascii_isgraph(data->data[index + col])) {
117                 g_string_append_printf(out, "%c", data->data[index + col]);
118             } else {
119                 g_string_append(out, ".");
120             }
121         }
122 
123         g_string_append(out, "\n");
124     }
125 
126     qemu_plugin_outs(out->str);
127 }
128 
129 static void vcpu_syscall(qemu_plugin_id_t id, unsigned int vcpu_index,
130                          int64_t num, uint64_t a1, uint64_t a2,
131                          uint64_t a3, uint64_t a4, uint64_t a5,
132                          uint64_t a6, uint64_t a7, uint64_t a8)
133 {
134     if (statistics) {
135         SyscallStats *entry;
136         g_mutex_lock(&lock);
137         entry = get_or_create_entry(num);
138         entry->calls++;
139         g_mutex_unlock(&lock);
140     } else {
141         g_autofree gchar *out = g_strdup_printf("syscall #%" PRIi64 "\n", num);
142         qemu_plugin_outs(out);
143     }
144 
145     if (do_log_writes && num == write_sysno) {
146         if (qemu_plugin_read_memory_vaddr(a2, memory_buffer, a3)) {
147             hexdump(memory_buffer);
148         } else {
149             fprintf(stderr, "Error reading memory from vaddr %"PRIu64"\n", a2);
150         }
151     }
152 }
153 
154 static void vcpu_syscall_ret(qemu_plugin_id_t id, unsigned int vcpu_idx,
155                              int64_t num, int64_t ret)
156 {
157     if (statistics) {
158         SyscallStats *entry;
159 
160         g_mutex_lock(&lock);
161         /* Should always return an existent entry. */
162         entry = get_or_create_entry(num);
163         if (ret < 0) {
164             entry->errors++;
165         }
166         g_mutex_unlock(&lock);
167     } else {
168         g_autofree gchar *out = g_strdup_printf(
169              "syscall #%" PRIi64 " returned -> %" PRIi64 "\n", num, ret);
170         qemu_plugin_outs(out);
171     }
172 }
173 
174 static void print_entry(gpointer val, gpointer user_data)
175 {
176     SyscallStats *entry = (SyscallStats *) val;
177     int64_t syscall_num = entry->num;
178     g_autofree gchar *out = g_strdup_printf(
179         "%-13" PRIi64 "%-6" PRIi64 " %" PRIi64 "\n",
180         syscall_num, entry->calls, entry->errors);
181     qemu_plugin_outs(out);
182 }
183 
184 static gint comp_func(gconstpointer ea, gconstpointer eb)
185 {
186     SyscallStats *ent_a = (SyscallStats *) ea;
187     SyscallStats *ent_b = (SyscallStats *) eb;
188 
189     return ent_a->calls > ent_b->calls ? -1 : 1;
190 }
191 
192 /* ************************************************************************* */
193 static void plugin_exit(qemu_plugin_id_t id, void *p)
194 {
195     if (!statistics) {
196         return;
197     }
198 
199     g_mutex_lock(&lock);
200     GList *entries = g_hash_table_get_values(statistics);
201     entries = g_list_sort(entries, comp_func);
202     qemu_plugin_outs("syscall no.  calls  errors\n");
203 
204     g_list_foreach(entries, print_entry, NULL);
205 
206     g_list_free(entries);
207     g_hash_table_destroy(statistics);
208     g_mutex_unlock(&lock);
209 }
210 
211 QEMU_PLUGIN_EXPORT int qemu_plugin_install(qemu_plugin_id_t id,
212                                            const qemu_info_t *info,
213                                            int argc, char **argv)
214 {
215     bool do_print = false;
216 
217     for (int i = 0; i < argc; i++) {
218         char *opt = argv[i];
219         g_auto(GStrv) tokens = g_strsplit(opt, "=", 2);
220 
221         if (g_strcmp0(tokens[0], "print") == 0) {
222             if (!qemu_plugin_bool_parse(tokens[0], tokens[1], &do_print)) {
223                 fprintf(stderr, "boolean argument parsing failed: %s\n", opt);
224             }
225         } else if (g_strcmp0(tokens[0], "log_writes") == 0) {
226             if (!qemu_plugin_bool_parse(tokens[0], tokens[1], &do_log_writes)) {
227                 fprintf(stderr, "boolean argument parsing failed: %s\n", opt);
228             }
229         } else {
230             fprintf(stderr, "unsupported argument: %s\n", argv[i]);
231             return -1;
232         }
233     }
234 
235     if (!do_print) {
236         statistics = g_hash_table_new_full(NULL, g_direct_equal, NULL, g_free);
237     }
238 
239     if (do_log_writes) {
240         for (const struct SyscallInfo *syscall_info = arch_syscall_info;
241             syscall_info->name != NULL; syscall_info++) {
242 
243             if (g_strcmp0(syscall_info->name, info->target_name) == 0) {
244                 write_sysno = syscall_info->write_sysno;
245                 break;
246             }
247         }
248 
249         if (write_sysno == -1) {
250             fprintf(stderr, "write syscall number not found\n");
251             return -1;
252         }
253 
254         memory_buffer = g_byte_array_new();
255     }
256 
257     qemu_plugin_register_vcpu_syscall_cb(id, vcpu_syscall);
258     qemu_plugin_register_vcpu_syscall_ret_cb(id, vcpu_syscall_ret);
259     qemu_plugin_register_atexit_cb(id, plugin_exit, NULL);
260     return 0;
261 }
262