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