]>
Commit | Line | Data |
---|---|---|
54cb65d8 EC |
1 | /* |
2 | * QEMU Plugin Core Loader Code | |
3 | * | |
4 | * This is the code responsible for loading and unloading the plugins. | |
5 | * Aside from the basic housekeeping tasks we also need to ensure any | |
6 | * generated code is flushed when we remove a plugin so we cannot end | |
7 | * up calling and unloaded helper function. | |
8 | * | |
9 | * Copyright (C) 2017, Emilio G. Cota <cota@braap.org> | |
10 | * Copyright (C) 2019, Linaro | |
11 | * | |
12 | * License: GNU GPL, version 2 or later. | |
13 | * See the COPYING file in the top-level directory. | |
14 | * | |
15 | * SPDX-License-Identifier: GPL-2.0-or-later | |
16 | */ | |
17 | ||
18 | #include "qemu/osdep.h" | |
19 | #include "qemu/error-report.h" | |
20 | #include "qemu/config-file.h" | |
21 | #include "qapi/error.h" | |
ac90871c | 22 | #include "qemu/lockable.h" |
54cb65d8 EC |
23 | #include "qemu/option.h" |
24 | #include "qemu/rcu_queue.h" | |
25 | #include "qemu/qht.h" | |
26 | #include "qemu/bitmap.h" | |
ad768e6f | 27 | #include "qemu/cacheinfo.h" |
54cb65d8 EC |
28 | #include "qemu/xxhash.h" |
29 | #include "qemu/plugin.h" | |
5df022cf | 30 | #include "qemu/memalign.h" |
54cb65d8 | 31 | #include "hw/core/cpu.h" |
548c9609 | 32 | #include "exec/tb-flush.h" |
5901b2e1 AB |
33 | #ifndef CONFIG_USER_ONLY |
34 | #include "hw/boards.h" | |
35 | #endif | |
36 | ||
54cb65d8 EC |
37 | #include "plugin.h" |
38 | ||
39 | /* | |
40 | * For convenience we use a bitmap for plugin.mask, but really all we need is a | |
41 | * u32, which is what we store in TranslationBlock. | |
42 | */ | |
43 | QEMU_BUILD_BUG_ON(QEMU_PLUGIN_EV_MAX > 32); | |
44 | ||
45 | struct qemu_plugin_desc { | |
46 | char *path; | |
47 | char **argv; | |
48 | QTAILQ_ENTRY(qemu_plugin_desc) entry; | |
49 | int argc; | |
50 | }; | |
51 | ||
52 | struct qemu_plugin_parse_arg { | |
53 | QemuPluginList *head; | |
54 | struct qemu_plugin_desc *curr; | |
55 | }; | |
56 | ||
57 | QemuOptsList qemu_plugin_opts = { | |
58 | .name = "plugin", | |
59 | .implied_opt_name = "file", | |
60 | .head = QTAILQ_HEAD_INITIALIZER(qemu_plugin_opts.head), | |
61 | .desc = { | |
62 | /* do our own parsing to support multiple plugins */ | |
63 | { /* end of list */ } | |
64 | }, | |
65 | }; | |
66 | ||
5901b2e1 | 67 | typedef int (*qemu_plugin_install_func_t)(qemu_plugin_id_t, const qemu_info_t *, int, char **); |
54cb65d8 EC |
68 | |
69 | extern struct qemu_plugin_state plugin; | |
70 | ||
71 | void qemu_plugin_add_dyn_cb_arr(GArray *arr) | |
72 | { | |
73 | uint32_t hash = qemu_xxhash2((uint64_t)(uintptr_t)arr); | |
74 | bool inserted; | |
75 | ||
76 | inserted = qht_insert(&plugin.dyn_cb_arr_ht, arr, hash, NULL); | |
77 | g_assert(inserted); | |
78 | } | |
79 | ||
80 | static struct qemu_plugin_desc *plugin_find_desc(QemuPluginList *head, | |
81 | const char *path) | |
82 | { | |
83 | struct qemu_plugin_desc *desc; | |
84 | ||
85 | QTAILQ_FOREACH(desc, head, entry) { | |
86 | if (strcmp(desc->path, path) == 0) { | |
87 | return desc; | |
88 | } | |
89 | } | |
90 | return NULL; | |
91 | } | |
92 | ||
93 | static int plugin_add(void *opaque, const char *name, const char *value, | |
94 | Error **errp) | |
95 | { | |
96 | struct qemu_plugin_parse_arg *arg = opaque; | |
97 | struct qemu_plugin_desc *p; | |
3a445acb MM |
98 | bool is_on; |
99 | char *fullarg; | |
54cb65d8 EC |
100 | |
101 | if (strcmp(name, "file") == 0) { | |
102 | if (strcmp(value, "") == 0) { | |
103 | error_setg(errp, "requires a non-empty argument"); | |
104 | return 1; | |
105 | } | |
106 | p = plugin_find_desc(arg->head, value); | |
107 | if (p == NULL) { | |
108 | p = g_new0(struct qemu_plugin_desc, 1); | |
109 | p->path = g_strdup(value); | |
110 | QTAILQ_INSERT_TAIL(arg->head, p, entry); | |
111 | } | |
112 | arg->curr = p; | |
3a445acb | 113 | } else { |
54cb65d8 EC |
114 | if (arg->curr == NULL) { |
115 | error_setg(errp, "missing earlier '-plugin file=' option"); | |
116 | return 1; | |
117 | } | |
3a445acb MM |
118 | |
119 | if (g_strcmp0(name, "arg") == 0 && | |
120 | !qapi_bool_parse(name, value, &is_on, NULL)) { | |
121 | if (strchr(value, '=') == NULL) { | |
122 | /* Will treat arg="argname" as "argname=on" */ | |
123 | fullarg = g_strdup_printf("%s=%s", value, "on"); | |
124 | } else { | |
125 | fullarg = g_strdup_printf("%s", value); | |
126 | } | |
127 | warn_report("using 'arg=%s' is deprecated", value); | |
128 | error_printf("Please use '%s' directly\n", fullarg); | |
129 | } else { | |
130 | fullarg = g_strdup_printf("%s=%s", name, value); | |
131 | } | |
132 | ||
54cb65d8 EC |
133 | p = arg->curr; |
134 | p->argc++; | |
135 | p->argv = g_realloc_n(p->argv, p->argc, sizeof(char *)); | |
3a445acb | 136 | p->argv[p->argc - 1] = fullarg; |
54cb65d8 | 137 | } |
3a445acb | 138 | |
54cb65d8 EC |
139 | return 0; |
140 | } | |
141 | ||
82f3346f | 142 | void qemu_plugin_opt_parse(const char *optstr, QemuPluginList *head) |
54cb65d8 EC |
143 | { |
144 | struct qemu_plugin_parse_arg arg; | |
145 | QemuOpts *opts; | |
146 | ||
82f3346f | 147 | opts = qemu_opts_parse_noisily(qemu_find_opts("plugin"), optstr, true); |
54cb65d8 EC |
148 | if (opts == NULL) { |
149 | exit(1); | |
150 | } | |
151 | arg.head = head; | |
152 | arg.curr = NULL; | |
153 | qemu_opt_foreach(opts, plugin_add, &arg, &error_fatal); | |
154 | qemu_opts_del(opts); | |
155 | } | |
156 | ||
157 | /* | |
158 | * From: https://en.wikipedia.org/wiki/Xorshift | |
159 | * This is faster than rand_r(), and gives us a wider range (RAND_MAX is only | |
160 | * guaranteed to be >= INT_MAX). | |
161 | */ | |
162 | static uint64_t xorshift64star(uint64_t x) | |
163 | { | |
164 | x ^= x >> 12; /* a */ | |
165 | x ^= x << 25; /* b */ | |
166 | x ^= x >> 27; /* c */ | |
167 | return x * UINT64_C(2685821657736338717); | |
168 | } | |
169 | ||
c905a368 DB |
170 | /* |
171 | * Disable CFI checks. | |
172 | * The install and version functions have been loaded from an external library | |
173 | * so we do not have type information | |
174 | */ | |
175 | QEMU_DISABLE_CFI | |
0572f558 | 176 | static int plugin_load(struct qemu_plugin_desc *desc, const qemu_info_t *info, Error **errp) |
54cb65d8 EC |
177 | { |
178 | qemu_plugin_install_func_t install; | |
179 | struct qemu_plugin_ctx *ctx; | |
180 | gpointer sym; | |
181 | int rc; | |
182 | ||
183 | ctx = qemu_memalign(qemu_dcache_linesize, sizeof(*ctx)); | |
184 | memset(ctx, 0, sizeof(*ctx)); | |
185 | ctx->desc = desc; | |
186 | ||
187 | ctx->handle = g_module_open(desc->path, G_MODULE_BIND_LOCAL); | |
188 | if (ctx->handle == NULL) { | |
0572f558 | 189 | error_setg(errp, "Could not load plugin %s: %s", desc->path, g_module_error()); |
54cb65d8 EC |
190 | goto err_dlopen; |
191 | } | |
192 | ||
193 | if (!g_module_symbol(ctx->handle, "qemu_plugin_install", &sym)) { | |
0572f558 | 194 | error_setg(errp, "Could not load plugin %s: %s", desc->path, g_module_error()); |
54cb65d8 EC |
195 | goto err_symbol; |
196 | } | |
197 | install = (qemu_plugin_install_func_t) sym; | |
198 | /* symbol was found; it could be NULL though */ | |
199 | if (install == NULL) { | |
0572f558 PB |
200 | error_setg(errp, "Could not load plugin %s: qemu_plugin_install is NULL", |
201 | desc->path); | |
54cb65d8 EC |
202 | goto err_symbol; |
203 | } | |
204 | ||
3fb356cc | 205 | if (!g_module_symbol(ctx->handle, "qemu_plugin_version", &sym)) { |
0572f558 PB |
206 | error_setg(errp, "Could not load plugin %s: plugin does not declare API version %s", |
207 | desc->path, g_module_error()); | |
3fb356cc AB |
208 | goto err_symbol; |
209 | } else { | |
210 | int version = *(int *)sym; | |
211 | if (version < QEMU_PLUGIN_MIN_VERSION) { | |
0572f558 PB |
212 | error_setg(errp, "Could not load plugin %s: plugin requires API version %d, but " |
213 | "this QEMU supports only a minimum version of %d", | |
214 | desc->path, version, QEMU_PLUGIN_MIN_VERSION); | |
3fb356cc AB |
215 | goto err_symbol; |
216 | } else if (version > QEMU_PLUGIN_VERSION) { | |
0572f558 PB |
217 | error_setg(errp, "Could not load plugin %s: plugin requires API version %d, but " |
218 | "this QEMU supports only up to version %d", | |
219 | desc->path, version, QEMU_PLUGIN_VERSION); | |
3fb356cc AB |
220 | goto err_symbol; |
221 | } | |
222 | } | |
223 | ||
54cb65d8 EC |
224 | qemu_rec_mutex_lock(&plugin.lock); |
225 | ||
226 | /* find an unused random id with &ctx as the seed */ | |
227 | ctx->id = (uint64_t)(uintptr_t)ctx; | |
228 | for (;;) { | |
229 | void *existing; | |
230 | ||
231 | ctx->id = xorshift64star(ctx->id); | |
232 | existing = g_hash_table_lookup(plugin.id_ht, &ctx->id); | |
233 | if (likely(existing == NULL)) { | |
234 | bool success; | |
235 | ||
236 | success = g_hash_table_insert(plugin.id_ht, &ctx->id, &ctx->id); | |
237 | g_assert(success); | |
238 | break; | |
239 | } | |
240 | } | |
241 | QTAILQ_INSERT_TAIL(&plugin.ctxs, ctx, entry); | |
242 | ctx->installing = true; | |
5901b2e1 | 243 | rc = install(ctx->id, info, desc->argc, desc->argv); |
54cb65d8 EC |
244 | ctx->installing = false; |
245 | if (rc) { | |
0572f558 PB |
246 | error_setg(errp, "Could not load plugin %s: qemu_plugin_install returned error code %d", |
247 | desc->path, rc); | |
54cb65d8 EC |
248 | /* |
249 | * we cannot rely on the plugin doing its own cleanup, so | |
250 | * call a full uninstall if the plugin did not yet call it. | |
251 | */ | |
252 | if (!ctx->uninstalling) { | |
253 | plugin_reset_uninstall(ctx->id, NULL, false); | |
254 | } | |
255 | } | |
256 | ||
257 | qemu_rec_mutex_unlock(&plugin.lock); | |
258 | return rc; | |
259 | ||
260 | err_symbol: | |
b3137100 | 261 | g_module_close(ctx->handle); |
54cb65d8 EC |
262 | err_dlopen: |
263 | qemu_vfree(ctx); | |
264 | return 1; | |
265 | } | |
266 | ||
267 | /* call after having removed @desc from the list */ | |
268 | static void plugin_desc_free(struct qemu_plugin_desc *desc) | |
269 | { | |
270 | int i; | |
271 | ||
272 | for (i = 0; i < desc->argc; i++) { | |
273 | g_free(desc->argv[i]); | |
274 | } | |
275 | g_free(desc->argv); | |
276 | g_free(desc->path); | |
277 | g_free(desc); | |
278 | } | |
279 | ||
280 | /** | |
281 | * qemu_plugin_load_list - load a list of plugins | |
282 | * @head: head of the list of descriptors of the plugins to be loaded | |
283 | * | |
284 | * Returns 0 if all plugins in the list are installed, !0 otherwise. | |
285 | * | |
286 | * Note: the descriptor of each successfully installed plugin is removed | |
287 | * from the list given by @head. | |
288 | */ | |
0572f558 | 289 | int qemu_plugin_load_list(QemuPluginList *head, Error **errp) |
54cb65d8 EC |
290 | { |
291 | struct qemu_plugin_desc *desc, *next; | |
5901b2e1 AB |
292 | g_autofree qemu_info_t *info = g_new0(qemu_info_t, 1); |
293 | ||
294 | info->target_name = TARGET_NAME; | |
3fb356cc AB |
295 | info->version.min = QEMU_PLUGIN_MIN_VERSION; |
296 | info->version.cur = QEMU_PLUGIN_VERSION; | |
5901b2e1 AB |
297 | #ifndef CONFIG_USER_ONLY |
298 | MachineState *ms = MACHINE(qdev_get_machine()); | |
299 | info->system_emulation = true; | |
300 | info->system.smp_vcpus = ms->smp.cpus; | |
301 | info->system.max_vcpus = ms->smp.max_cpus; | |
302 | #else | |
303 | info->system_emulation = false; | |
304 | #endif | |
54cb65d8 EC |
305 | |
306 | QTAILQ_FOREACH_SAFE(desc, head, entry, next) { | |
307 | int err; | |
308 | ||
0572f558 | 309 | err = plugin_load(desc, info, errp); |
54cb65d8 EC |
310 | if (err) { |
311 | return err; | |
312 | } | |
313 | QTAILQ_REMOVE(head, desc, entry); | |
314 | } | |
315 | return 0; | |
316 | } | |
317 | ||
318 | struct qemu_plugin_reset_data { | |
319 | struct qemu_plugin_ctx *ctx; | |
320 | qemu_plugin_simple_cb_t cb; | |
321 | bool reset; | |
322 | }; | |
323 | ||
324 | static void plugin_reset_destroy__locked(struct qemu_plugin_reset_data *data) | |
325 | { | |
326 | struct qemu_plugin_ctx *ctx = data->ctx; | |
327 | enum qemu_plugin_event ev; | |
328 | bool success; | |
329 | ||
330 | /* | |
331 | * After updating the subscription lists there is no need to wait for an RCU | |
332 | * grace period to elapse, because right now we either are in a "safe async" | |
333 | * work environment (i.e. all vCPUs are asleep), or no vCPUs have yet been | |
334 | * created. | |
335 | */ | |
336 | for (ev = 0; ev < QEMU_PLUGIN_EV_MAX; ev++) { | |
337 | plugin_unregister_cb__locked(ctx, ev); | |
338 | } | |
339 | ||
340 | if (data->reset) { | |
341 | g_assert(ctx->resetting); | |
342 | if (data->cb) { | |
343 | data->cb(ctx->id); | |
344 | } | |
345 | ctx->resetting = false; | |
346 | g_free(data); | |
347 | return; | |
348 | } | |
349 | ||
350 | g_assert(ctx->uninstalling); | |
351 | /* we cannot dlclose if we are going to return to plugin code */ | |
352 | if (ctx->installing) { | |
353 | error_report("Calling qemu_plugin_uninstall from the install function " | |
354 | "is a bug. Instead, return !0 from the install function."); | |
355 | abort(); | |
356 | } | |
357 | ||
358 | success = g_hash_table_remove(plugin.id_ht, &ctx->id); | |
359 | g_assert(success); | |
360 | QTAILQ_REMOVE(&plugin.ctxs, ctx, entry); | |
361 | if (data->cb) { | |
362 | data->cb(ctx->id); | |
363 | } | |
364 | if (!g_module_close(ctx->handle)) { | |
365 | warn_report("%s: %s", __func__, g_module_error()); | |
366 | } | |
367 | plugin_desc_free(ctx->desc); | |
368 | qemu_vfree(ctx); | |
369 | g_free(data); | |
370 | } | |
371 | ||
372 | static void plugin_reset_destroy(struct qemu_plugin_reset_data *data) | |
373 | { | |
374 | qemu_rec_mutex_lock(&plugin.lock); | |
375 | plugin_reset_destroy__locked(data); | |
376 | qemu_rec_mutex_lock(&plugin.lock); | |
377 | } | |
378 | ||
379 | static void plugin_flush_destroy(CPUState *cpu, run_on_cpu_data arg) | |
380 | { | |
381 | struct qemu_plugin_reset_data *data = arg.host_ptr; | |
382 | ||
383 | g_assert(cpu_in_exclusive_context(cpu)); | |
384 | tb_flush(cpu); | |
385 | plugin_reset_destroy(data); | |
386 | } | |
387 | ||
388 | void plugin_reset_uninstall(qemu_plugin_id_t id, | |
389 | qemu_plugin_simple_cb_t cb, | |
390 | bool reset) | |
391 | { | |
392 | struct qemu_plugin_reset_data *data; | |
393 | struct qemu_plugin_ctx *ctx; | |
394 | ||
ac90871c SH |
395 | WITH_QEMU_LOCK_GUARD(&plugin.lock) { |
396 | ctx = plugin_id_to_ctx_locked(id); | |
397 | if (ctx->uninstalling || (reset && ctx->resetting)) { | |
398 | return; | |
399 | } | |
400 | ctx->resetting = reset; | |
401 | ctx->uninstalling = !reset; | |
54cb65d8 | 402 | } |
54cb65d8 EC |
403 | |
404 | data = g_new(struct qemu_plugin_reset_data, 1); | |
405 | data->ctx = ctx; | |
406 | data->cb = cb; | |
407 | data->reset = reset; | |
408 | /* | |
409 | * Only flush the code cache if the vCPUs have been created. If so, | |
410 | * current_cpu must be non-NULL. | |
411 | */ | |
412 | if (current_cpu) { | |
413 | async_safe_run_on_cpu(current_cpu, plugin_flush_destroy, | |
414 | RUN_ON_CPU_HOST_PTR(data)); | |
415 | } else { | |
416 | /* | |
417 | * If current_cpu isn't set, then we don't have yet any vCPU threads | |
418 | * and we therefore can remove the callbacks synchronously. | |
419 | */ | |
420 | plugin_reset_destroy(data); | |
421 | } | |
422 | } |