Import QEMU upstream snapshot d2e570c
Upstream: https://gitlab.com/qemu-project/qemu.git Upstream-Commit: d2e570cc0f97b936902a5b1b86b73c0f5998b475
This commit is contained in:
@@ -0,0 +1,131 @@
|
||||
/*
|
||||
* QEMU Plugin API - System specific implementations
|
||||
*
|
||||
* This provides the APIs that have a specific system implementation
|
||||
* or are only relevant to system-mode.
|
||||
*
|
||||
* Copyright (C) 2017, Emilio G. Cota <[email protected]>
|
||||
* Copyright (C) 2019-2025, Linaro
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
|
||||
#include "qemu/osdep.h"
|
||||
#include "qemu/main-loop.h"
|
||||
#include "qapi/error.h"
|
||||
#include "migration/blocker.h"
|
||||
#include "hw/core/boards.h"
|
||||
#include "qemu/plugin-memory.h"
|
||||
#include "qemu/plugin.h"
|
||||
|
||||
/*
|
||||
* In system mode we cannot trace the binary being executed so the
|
||||
* helpers all return NULL/0.
|
||||
*/
|
||||
const char *qemu_plugin_path_to_binary(void)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
uint64_t qemu_plugin_start_code(void)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint64_t qemu_plugin_end_code(void)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint64_t qemu_plugin_entry_code(void)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Virtual Memory queries
|
||||
*/
|
||||
|
||||
static __thread struct qemu_plugin_hwaddr hwaddr_info;
|
||||
|
||||
struct qemu_plugin_hwaddr *qemu_plugin_get_hwaddr(qemu_plugin_meminfo_t info,
|
||||
uint64_t vaddr)
|
||||
{
|
||||
CPUState *cpu = current_cpu;
|
||||
unsigned int mmu_idx = get_mmuidx(info);
|
||||
enum qemu_plugin_mem_rw rw = get_plugin_meminfo_rw(info);
|
||||
hwaddr_info.is_store = (rw & QEMU_PLUGIN_MEM_W) != 0;
|
||||
|
||||
assert(mmu_idx < NB_MMU_MODES);
|
||||
|
||||
if (!tlb_plugin_lookup(cpu, vaddr, mmu_idx,
|
||||
hwaddr_info.is_store, &hwaddr_info)) {
|
||||
error_report("invalid use of qemu_plugin_get_hwaddr");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return &hwaddr_info;
|
||||
}
|
||||
|
||||
bool qemu_plugin_hwaddr_is_io(const struct qemu_plugin_hwaddr *haddr)
|
||||
{
|
||||
return haddr->is_io;
|
||||
}
|
||||
|
||||
uint64_t qemu_plugin_hwaddr_phys_addr(const struct qemu_plugin_hwaddr *haddr)
|
||||
{
|
||||
if (haddr) {
|
||||
return haddr->phys_addr;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
const char *qemu_plugin_hwaddr_device_name(const struct qemu_plugin_hwaddr *h)
|
||||
{
|
||||
if (h && h->is_io) {
|
||||
MemoryRegion *mr = h->mr;
|
||||
if (!mr->name) {
|
||||
unsigned maddr = (uintptr_t)mr;
|
||||
g_autofree char *temp = g_strdup_printf("anon%08x", maddr);
|
||||
return g_intern_string(temp);
|
||||
} else {
|
||||
return g_intern_string(mr->name);
|
||||
}
|
||||
} else {
|
||||
return g_intern_static_string("RAM");
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Time control
|
||||
*/
|
||||
static bool has_control;
|
||||
static Error *migration_blocker;
|
||||
|
||||
const void *qemu_plugin_request_time_control(void)
|
||||
{
|
||||
if (!has_control) {
|
||||
has_control = true;
|
||||
error_setg(&migration_blocker,
|
||||
"TCG plugin time control does not support migration");
|
||||
migrate_add_blocker(&migration_blocker, &error_fatal);
|
||||
return &has_control;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void advance_virtual_time__async(CPUState *cpu, run_on_cpu_data data)
|
||||
{
|
||||
int64_t new_time = data.host_ulong;
|
||||
qemu_clock_advance_virtual_time(new_time);
|
||||
}
|
||||
|
||||
void qemu_plugin_update_ns(const void *handle, int64_t new_time)
|
||||
{
|
||||
if (handle == &has_control) {
|
||||
/* Need to execute out of cpu_exec, so bql can be locked. */
|
||||
async_run_on_cpu(current_cpu,
|
||||
advance_virtual_time__async,
|
||||
RUN_ON_CPU_HOST_ULONG(new_time));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* QEMU Plugin API - user-mode only implementations
|
||||
*
|
||||
* This provides the APIs that have a user-mode specific
|
||||
* implementations or are only relevant to user-mode.
|
||||
*
|
||||
* Copyright (C) 2017, Emilio G. Cota <[email protected]>
|
||||
* Copyright (C) 2019-2025, Linaro
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
|
||||
#include "qemu/osdep.h"
|
||||
#include "qemu/plugin.h"
|
||||
#include "exec/log.h"
|
||||
|
||||
/*
|
||||
* Virtual Memory queries - these are all NOPs for user-mode which
|
||||
* only ever has visibility of virtual addresses.
|
||||
*/
|
||||
|
||||
struct qemu_plugin_hwaddr *qemu_plugin_get_hwaddr(qemu_plugin_meminfo_t info,
|
||||
uint64_t vaddr)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
bool qemu_plugin_hwaddr_is_io(const struct qemu_plugin_hwaddr *haddr)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
uint64_t qemu_plugin_hwaddr_phys_addr(const struct qemu_plugin_hwaddr *haddr)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
const char *qemu_plugin_hwaddr_device_name(const struct qemu_plugin_hwaddr *h)
|
||||
{
|
||||
return g_intern_static_string("Invalid");
|
||||
}
|
||||
|
||||
/*
|
||||
* Time control - for user mode the only real time is wall clock time
|
||||
* so realistically all you can do in user mode is slow down execution
|
||||
* which doesn't require the ability to mess with the clock.
|
||||
*/
|
||||
|
||||
const void *qemu_plugin_request_time_control(void)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void qemu_plugin_update_ns(const void *handle, int64_t new_time)
|
||||
{
|
||||
qemu_log_mask(LOG_UNIMP, "user-mode can't control time");
|
||||
}
|
||||
+697
@@ -0,0 +1,697 @@
|
||||
/*
|
||||
* QEMU Plugin API
|
||||
*
|
||||
* This provides the API that is available to the plugins to interact
|
||||
* with QEMU. We have to be careful not to expose internal details of
|
||||
* how QEMU works so we abstract out things like translation and
|
||||
* instructions to anonymous data types:
|
||||
*
|
||||
* qemu_plugin_tb
|
||||
* qemu_plugin_insn
|
||||
* qemu_plugin_register
|
||||
*
|
||||
* Which can then be passed back into the API to do additional things.
|
||||
* As such all the public functions in here are exported in
|
||||
* qemu-plugin.h.
|
||||
*
|
||||
* The general life-cycle of a plugin is:
|
||||
*
|
||||
* - plugin is loaded, public qemu_plugin_install called
|
||||
* - the install func registers callbacks for events
|
||||
* - usually an atexit_cb is registered to dump info at the end
|
||||
* - when a registered event occurs the plugin is called
|
||||
* - some events pass additional info
|
||||
* - during translation the plugin can decide to instrument any
|
||||
* instruction
|
||||
* - when QEMU exits all the registered atexit callbacks are called
|
||||
*
|
||||
* Copyright (C) 2017, Emilio G. Cota <[email protected]>
|
||||
* Copyright (C) 2019, Linaro
|
||||
*
|
||||
* License: GNU GPL, version 2 or later.
|
||||
* See the COPYING file in the top-level directory.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*
|
||||
*/
|
||||
|
||||
#include "qemu/osdep.h"
|
||||
#include "qemu/main-loop.h"
|
||||
#include "qemu/plugin.h"
|
||||
#include "qemu/log.h"
|
||||
#include "system/memory.h"
|
||||
#include "accel/tcg/cpu-loop.h"
|
||||
#include "tcg/tcg.h"
|
||||
#include "exec/cpu-common.h"
|
||||
#include "exec/gdbstub.h"
|
||||
#include "exec/target_page.h"
|
||||
#include "exec/translation-block.h"
|
||||
#include "exec/translator.h"
|
||||
#include "disas/disas.h"
|
||||
#include "plugin.h"
|
||||
|
||||
/* Uninstall and Reset handlers */
|
||||
|
||||
void qemu_plugin_uninstall(qemu_plugin_id_t id, qemu_plugin_udata_cb_t cb,
|
||||
void *userdata)
|
||||
{
|
||||
plugin_reset_uninstall(id, cb, userdata, false);
|
||||
}
|
||||
|
||||
void qemu_plugin_reset(qemu_plugin_id_t id, qemu_plugin_udata_cb_t cb,
|
||||
void *userdata)
|
||||
{
|
||||
plugin_reset_uninstall(id, cb, userdata, true);
|
||||
}
|
||||
|
||||
/*
|
||||
* Plugin Register Functions
|
||||
*
|
||||
* This allows the plugin to register callbacks for various events
|
||||
* during the translation.
|
||||
*/
|
||||
|
||||
void qemu_plugin_register_vcpu_init_cb(qemu_plugin_id_t id,
|
||||
qemu_plugin_vcpu_udata_cb_t cb,
|
||||
void *userdata)
|
||||
{
|
||||
plugin_register_cb_udata(id, QEMU_PLUGIN_EV_VCPU_INIT, cb, userdata);
|
||||
}
|
||||
|
||||
void qemu_plugin_register_vcpu_exit_cb(qemu_plugin_id_t id,
|
||||
qemu_plugin_vcpu_udata_cb_t cb,
|
||||
void *userdata)
|
||||
{
|
||||
plugin_register_cb_udata(id, QEMU_PLUGIN_EV_VCPU_EXIT, cb, userdata);
|
||||
}
|
||||
|
||||
static bool tb_is_mem_only(void)
|
||||
{
|
||||
return tb_cflags(tcg_ctx->gen_tb) & CF_MEMI_ONLY;
|
||||
}
|
||||
|
||||
void qemu_plugin_register_vcpu_tb_exec_cb(struct qemu_plugin_tb *tb,
|
||||
qemu_plugin_vcpu_udata_cb_t cb,
|
||||
enum qemu_plugin_cb_flags flags,
|
||||
void *udata)
|
||||
{
|
||||
if (!tb_is_mem_only()) {
|
||||
plugin_register_dyn_cb__udata(&tb->cbs, cb, flags, udata);
|
||||
}
|
||||
}
|
||||
|
||||
void qemu_plugin_register_vcpu_tb_exec_cond_cb(struct qemu_plugin_tb *tb,
|
||||
qemu_plugin_vcpu_udata_cb_t cb,
|
||||
enum qemu_plugin_cb_flags flags,
|
||||
enum qemu_plugin_cond cond,
|
||||
qemu_plugin_u64 entry,
|
||||
uint64_t imm,
|
||||
void *udata)
|
||||
{
|
||||
if (cond == QEMU_PLUGIN_COND_NEVER || tb_is_mem_only()) {
|
||||
return;
|
||||
}
|
||||
if (cond == QEMU_PLUGIN_COND_ALWAYS) {
|
||||
qemu_plugin_register_vcpu_tb_exec_cb(tb, cb, flags, udata);
|
||||
return;
|
||||
}
|
||||
plugin_register_dyn_cond_cb__udata(&tb->cbs, cb, flags,
|
||||
cond, entry, imm, udata);
|
||||
}
|
||||
|
||||
void qemu_plugin_register_vcpu_tb_exec_inline_per_vcpu(
|
||||
struct qemu_plugin_tb *tb,
|
||||
enum qemu_plugin_op op,
|
||||
qemu_plugin_u64 entry,
|
||||
uint64_t imm)
|
||||
{
|
||||
if (!tb_is_mem_only()) {
|
||||
plugin_register_inline_op_on_entry(&tb->cbs, 0, op, entry, imm);
|
||||
}
|
||||
}
|
||||
|
||||
void qemu_plugin_register_vcpu_insn_exec_cb(struct qemu_plugin_insn *insn,
|
||||
qemu_plugin_vcpu_udata_cb_t cb,
|
||||
enum qemu_plugin_cb_flags flags,
|
||||
void *udata)
|
||||
{
|
||||
if (!tb_is_mem_only()) {
|
||||
plugin_register_dyn_cb__udata(&insn->insn_cbs, cb, flags, udata);
|
||||
}
|
||||
}
|
||||
|
||||
void qemu_plugin_register_vcpu_insn_exec_cond_cb(
|
||||
struct qemu_plugin_insn *insn,
|
||||
qemu_plugin_vcpu_udata_cb_t cb,
|
||||
enum qemu_plugin_cb_flags flags,
|
||||
enum qemu_plugin_cond cond,
|
||||
qemu_plugin_u64 entry,
|
||||
uint64_t imm,
|
||||
void *udata)
|
||||
{
|
||||
if (cond == QEMU_PLUGIN_COND_NEVER || tb_is_mem_only()) {
|
||||
return;
|
||||
}
|
||||
if (cond == QEMU_PLUGIN_COND_ALWAYS) {
|
||||
qemu_plugin_register_vcpu_insn_exec_cb(insn, cb, flags, udata);
|
||||
return;
|
||||
}
|
||||
plugin_register_dyn_cond_cb__udata(&insn->insn_cbs, cb, flags,
|
||||
cond, entry, imm, udata);
|
||||
}
|
||||
|
||||
void qemu_plugin_register_vcpu_insn_exec_inline_per_vcpu(
|
||||
struct qemu_plugin_insn *insn,
|
||||
enum qemu_plugin_op op,
|
||||
qemu_plugin_u64 entry,
|
||||
uint64_t imm)
|
||||
{
|
||||
if (!tb_is_mem_only()) {
|
||||
plugin_register_inline_op_on_entry(&insn->insn_cbs, 0, op, entry, imm);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* We always plant memory instrumentation because they don't finalise until
|
||||
* after the operation has complete.
|
||||
*/
|
||||
void qemu_plugin_register_vcpu_mem_cb(struct qemu_plugin_insn *insn,
|
||||
qemu_plugin_vcpu_mem_cb_t cb,
|
||||
enum qemu_plugin_cb_flags flags,
|
||||
enum qemu_plugin_mem_rw rw,
|
||||
void *udata)
|
||||
{
|
||||
plugin_register_vcpu_mem_cb(&insn->mem_cbs, cb, flags, rw, udata);
|
||||
}
|
||||
|
||||
void qemu_plugin_register_vcpu_mem_inline_per_vcpu(
|
||||
struct qemu_plugin_insn *insn,
|
||||
enum qemu_plugin_mem_rw rw,
|
||||
enum qemu_plugin_op op,
|
||||
qemu_plugin_u64 entry,
|
||||
uint64_t imm)
|
||||
{
|
||||
plugin_register_inline_op_on_entry(&insn->mem_cbs, rw, op, entry, imm);
|
||||
}
|
||||
|
||||
void qemu_plugin_register_vcpu_tb_trans_cb(qemu_plugin_id_t id,
|
||||
qemu_plugin_vcpu_tb_trans_cb_t cb,
|
||||
void *userdata)
|
||||
{
|
||||
plugin_register_cb_udata(id, QEMU_PLUGIN_EV_VCPU_TB_TRANS, cb, userdata);
|
||||
}
|
||||
|
||||
void qemu_plugin_register_vcpu_syscall_cb(qemu_plugin_id_t id,
|
||||
qemu_plugin_vcpu_syscall_cb_t cb,
|
||||
void *userdata)
|
||||
{
|
||||
plugin_register_cb_udata(id, QEMU_PLUGIN_EV_VCPU_SYSCALL, cb, userdata);
|
||||
}
|
||||
|
||||
void
|
||||
qemu_plugin_register_vcpu_syscall_ret_cb(qemu_plugin_id_t id,
|
||||
qemu_plugin_vcpu_syscall_ret_cb_t cb,
|
||||
void *userdata)
|
||||
{
|
||||
plugin_register_cb_udata(id, QEMU_PLUGIN_EV_VCPU_SYSCALL_RET, cb, userdata);
|
||||
}
|
||||
|
||||
void
|
||||
qemu_plugin_register_vcpu_syscall_filter_cb(qemu_plugin_id_t id,
|
||||
qemu_plugin_vcpu_syscall_filter_cb_t cb,
|
||||
void *userdata)
|
||||
{
|
||||
plugin_register_cb_udata(id, QEMU_PLUGIN_EV_VCPU_SYSCALL_FILTER, cb, userdata);
|
||||
}
|
||||
|
||||
/*
|
||||
* Plugin Queries
|
||||
*
|
||||
* These are queries that the plugin can make to gauge information
|
||||
* from our opaque data types. We do not want to leak internal details
|
||||
* here just information useful to the plugin.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Translation block information:
|
||||
*
|
||||
* A plugin can query the virtual address of the start of the block
|
||||
* and the number of instructions in it. It can also get access to
|
||||
* each translated instruction.
|
||||
*/
|
||||
|
||||
size_t qemu_plugin_tb_n_insns(const struct qemu_plugin_tb *tb)
|
||||
{
|
||||
return tb->n;
|
||||
}
|
||||
|
||||
uint64_t qemu_plugin_tb_vaddr(const struct qemu_plugin_tb *tb)
|
||||
{
|
||||
const DisasContextBase *db = tcg_ctx->plugin_db;
|
||||
return db->pc_first;
|
||||
}
|
||||
|
||||
struct qemu_plugin_insn *
|
||||
qemu_plugin_tb_get_insn(const struct qemu_plugin_tb *tb, size_t idx)
|
||||
{
|
||||
if (unlikely(idx >= tb->n)) {
|
||||
return NULL;
|
||||
}
|
||||
return g_ptr_array_index(tb->insns, idx);
|
||||
}
|
||||
|
||||
/*
|
||||
* Instruction information
|
||||
*
|
||||
* These queries allow the plugin to retrieve information about each
|
||||
* instruction being translated.
|
||||
*/
|
||||
|
||||
size_t qemu_plugin_insn_data(const struct qemu_plugin_insn *insn,
|
||||
void *dest, size_t len)
|
||||
{
|
||||
const DisasContextBase *db = tcg_ctx->plugin_db;
|
||||
|
||||
len = MIN(len, insn->len);
|
||||
return translator_st(db, dest, insn->vaddr, len) ? len : 0;
|
||||
}
|
||||
|
||||
size_t qemu_plugin_insn_size(const struct qemu_plugin_insn *insn)
|
||||
{
|
||||
return insn->len;
|
||||
}
|
||||
|
||||
uint64_t qemu_plugin_insn_vaddr(const struct qemu_plugin_insn *insn)
|
||||
{
|
||||
return insn->vaddr;
|
||||
}
|
||||
|
||||
void *qemu_plugin_insn_haddr(const struct qemu_plugin_insn *insn)
|
||||
{
|
||||
const DisasContextBase *db = tcg_ctx->plugin_db;
|
||||
vaddr page0_last = db->pc_first | ~qemu_target_page_mask();
|
||||
|
||||
if (db->fake_insn) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* ??? The return value is not intended for use of host memory,
|
||||
* but as a proxy for address space and physical address.
|
||||
* Thus we are only interested in the first byte and do not
|
||||
* care about spanning pages.
|
||||
*/
|
||||
if (insn->vaddr <= page0_last) {
|
||||
if (db->host_addr[0] == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
return db->host_addr[0] + insn->vaddr - db->pc_first;
|
||||
} else {
|
||||
if (db->host_addr[1] == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
return db->host_addr[1] + insn->vaddr - (page0_last + 1);
|
||||
}
|
||||
}
|
||||
|
||||
char *qemu_plugin_insn_disas(const struct qemu_plugin_insn *insn)
|
||||
{
|
||||
return plugin_disas(tcg_ctx->cpu, tcg_ctx->plugin_db,
|
||||
insn->vaddr, insn->len);
|
||||
}
|
||||
|
||||
const char *qemu_plugin_insn_symbol(const struct qemu_plugin_insn *insn)
|
||||
{
|
||||
const char *sym = lookup_symbol(insn->vaddr);
|
||||
return sym[0] != 0 ? sym : NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* The memory queries allow the plugin to query information about a
|
||||
* memory access.
|
||||
*/
|
||||
|
||||
unsigned qemu_plugin_mem_size_shift(qemu_plugin_meminfo_t info)
|
||||
{
|
||||
MemOp op = get_memop(info);
|
||||
return op & MO_SIZE;
|
||||
}
|
||||
|
||||
bool qemu_plugin_mem_is_sign_extended(qemu_plugin_meminfo_t info)
|
||||
{
|
||||
MemOp op = get_memop(info);
|
||||
return op & MO_SIGN;
|
||||
}
|
||||
|
||||
bool qemu_plugin_mem_is_big_endian(qemu_plugin_meminfo_t info)
|
||||
{
|
||||
MemOp op = get_memop(info);
|
||||
return (op & MO_BSWAP) == MO_BE;
|
||||
}
|
||||
|
||||
bool qemu_plugin_mem_is_store(qemu_plugin_meminfo_t info)
|
||||
{
|
||||
return get_plugin_meminfo_rw(info) & QEMU_PLUGIN_MEM_W;
|
||||
}
|
||||
|
||||
qemu_plugin_mem_value qemu_plugin_mem_get_value(qemu_plugin_meminfo_t info)
|
||||
{
|
||||
uint64_t low = current_cpu->neg.plugin_mem_value_low;
|
||||
qemu_plugin_mem_value value;
|
||||
|
||||
switch (qemu_plugin_mem_size_shift(info)) {
|
||||
case 0:
|
||||
value.type = QEMU_PLUGIN_MEM_VALUE_U8;
|
||||
value.data.u8 = (uint8_t)low;
|
||||
break;
|
||||
case 1:
|
||||
value.type = QEMU_PLUGIN_MEM_VALUE_U16;
|
||||
value.data.u16 = (uint16_t)low;
|
||||
break;
|
||||
case 2:
|
||||
value.type = QEMU_PLUGIN_MEM_VALUE_U32;
|
||||
value.data.u32 = (uint32_t)low;
|
||||
break;
|
||||
case 3:
|
||||
value.type = QEMU_PLUGIN_MEM_VALUE_U64;
|
||||
value.data.u64 = low;
|
||||
break;
|
||||
case 4:
|
||||
value.type = QEMU_PLUGIN_MEM_VALUE_U128;
|
||||
value.data.u128.low = low;
|
||||
value.data.u128.high = current_cpu->neg.plugin_mem_value_high;
|
||||
break;
|
||||
default:
|
||||
g_assert_not_reached();
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
int qemu_plugin_num_vcpus(void)
|
||||
{
|
||||
return plugin_num_vcpus();
|
||||
}
|
||||
|
||||
/*
|
||||
* Plugin output
|
||||
*/
|
||||
void qemu_plugin_outs(const char *string)
|
||||
{
|
||||
qemu_log_mask(CPU_LOG_PLUGIN, "%s", string);
|
||||
}
|
||||
|
||||
bool qemu_plugin_bool_parse(const char *name, const char *value, bool *ret)
|
||||
{
|
||||
return name && value && qapi_bool_parse(name, value, ret, NULL);
|
||||
}
|
||||
|
||||
/*
|
||||
* Create register handles.
|
||||
*
|
||||
* We need to create a handle for each register so the plugin
|
||||
* infrastructure can call gdbstub to read a register. They are
|
||||
* currently just a pointer encapsulation of the gdb_reg but in
|
||||
* future may hold internal plugin state so its important plugin
|
||||
* authors are not tempted to treat them as numbers.
|
||||
*
|
||||
* We also construct a result array with those handles and some
|
||||
* ancillary data the plugin might find useful.
|
||||
*/
|
||||
|
||||
static const char pc_str[] = "pc"; /* generic name for program counter */
|
||||
static const char eip_str[] = "eip"; /* x86-specific name for PC */
|
||||
static const char rip_str[] = "rip"; /* x86_64-specific name for PC */
|
||||
static const char pswa_str[] = "pswa"; /* s390x-specific name for PC */
|
||||
static const char iaoq_str[] = "iaoq"; /* HP/PA-specific name for PC */
|
||||
static const char rpc_str[] = "rpc"; /* microblaze-specific name for PC */
|
||||
static GArray *create_register_handles(GArray *gdbstub_regs)
|
||||
{
|
||||
GArray *find_data = g_array_new(true, true,
|
||||
sizeof(qemu_plugin_reg_descriptor));
|
||||
|
||||
for (int i = 0; i < gdbstub_regs->len; i++) {
|
||||
GDBRegDesc *grd = &g_array_index(gdbstub_regs, GDBRegDesc, i);
|
||||
qemu_plugin_reg_descriptor desc;
|
||||
gint plugin_ro_bit = 0;
|
||||
|
||||
/* skip "un-named" regs */
|
||||
if (!grd->name) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Create a record for the plugin */
|
||||
desc.name = g_intern_string(grd->name);
|
||||
desc.is_readonly = false;
|
||||
if (g_strcmp0(desc.name, pc_str) == 0
|
||||
|| g_strcmp0(desc.name, eip_str) == 0
|
||||
|| g_strcmp0(desc.name, rip_str) == 0
|
||||
|| g_strcmp0(desc.name, pswa_str) == 0
|
||||
|| g_strcmp0(desc.name, iaoq_str) == 0
|
||||
|| g_strcmp0(desc.name, rpc_str) == 0
|
||||
) {
|
||||
desc.is_readonly = true;
|
||||
plugin_ro_bit = 1;
|
||||
}
|
||||
desc.handle = GINT_TO_POINTER((grd->gdb_reg << 1) | plugin_ro_bit);
|
||||
desc.feature = g_intern_string(grd->feature_name);
|
||||
g_array_append_val(find_data, desc);
|
||||
}
|
||||
|
||||
return find_data;
|
||||
}
|
||||
|
||||
GArray *qemu_plugin_get_registers(void)
|
||||
{
|
||||
g_assert(current_cpu);
|
||||
|
||||
g_autoptr(GArray) regs = gdb_get_register_list(current_cpu);
|
||||
return create_register_handles(regs);
|
||||
}
|
||||
|
||||
bool qemu_plugin_read_register(struct qemu_plugin_register *reg,
|
||||
GByteArray *buf)
|
||||
{
|
||||
g_assert(current_cpu);
|
||||
|
||||
if (qemu_plugin_get_cb_flags() == QEMU_PLUGIN_CB_NO_REGS) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return (gdb_read_register(current_cpu, buf, GPOINTER_TO_INT(reg) >> 1) > 0);
|
||||
}
|
||||
|
||||
bool qemu_plugin_write_register(struct qemu_plugin_register *reg,
|
||||
GByteArray *buf)
|
||||
{
|
||||
g_assert(current_cpu);
|
||||
|
||||
/* Read-only property is encoded in least significant bit */
|
||||
g_assert((GPOINTER_TO_INT(reg) & 1) == 0);
|
||||
|
||||
if (buf->len == 0 ||
|
||||
(qemu_plugin_get_cb_flags() != QEMU_PLUGIN_CB_RW_REGS &&
|
||||
qemu_plugin_get_cb_flags() != QEMU_PLUGIN_CB_RW_REGS_PC)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return (gdb_write_register(current_cpu, buf->data, GPOINTER_TO_INT(reg) >> 1) > 0);
|
||||
}
|
||||
|
||||
void qemu_plugin_set_pc(uint64_t vaddr)
|
||||
{
|
||||
g_assert(current_cpu);
|
||||
|
||||
g_assert(qemu_plugin_get_cb_flags() == QEMU_PLUGIN_CB_RW_REGS_PC);
|
||||
|
||||
cpu_set_pc(current_cpu, vaddr);
|
||||
cpu_loop_exit(current_cpu);
|
||||
}
|
||||
|
||||
bool qemu_plugin_read_memory_vaddr(uint64_t addr, GByteArray *data, size_t len)
|
||||
{
|
||||
g_assert(current_cpu);
|
||||
|
||||
if (len == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
g_byte_array_set_size(data, len);
|
||||
|
||||
int result = cpu_memory_rw_debug(current_cpu, addr, data->data,
|
||||
data->len, false);
|
||||
|
||||
if (result < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool qemu_plugin_write_memory_vaddr(uint64_t addr, GByteArray *data)
|
||||
{
|
||||
g_assert(current_cpu);
|
||||
|
||||
if (data->len == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int result = cpu_memory_rw_debug(current_cpu, addr, data->data,
|
||||
data->len, true);
|
||||
|
||||
if (result < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
enum qemu_plugin_hwaddr_operation_result
|
||||
qemu_plugin_read_memory_hwaddr(hwaddr addr, GByteArray *data, size_t len)
|
||||
{
|
||||
#ifdef CONFIG_SOFTMMU
|
||||
if (len == 0) {
|
||||
return QEMU_PLUGIN_HWADDR_OPERATION_ERROR;
|
||||
}
|
||||
|
||||
g_assert(current_cpu);
|
||||
|
||||
|
||||
int as_idx = cpu_asidx_from_attrs(current_cpu, MEMTXATTRS_UNSPECIFIED);
|
||||
AddressSpace *as = cpu_get_address_space(current_cpu, as_idx);
|
||||
|
||||
if (as == NULL) {
|
||||
return QEMU_PLUGIN_HWADDR_OPERATION_INVALID_ADDRESS_SPACE;
|
||||
}
|
||||
|
||||
g_byte_array_set_size(data, len);
|
||||
MemTxResult res = address_space_rw(as, addr,
|
||||
MEMTXATTRS_UNSPECIFIED, data->data,
|
||||
data->len, false);
|
||||
|
||||
switch (res) {
|
||||
case MEMTX_OK:
|
||||
return QEMU_PLUGIN_HWADDR_OPERATION_OK;
|
||||
case MEMTX_ERROR:
|
||||
return QEMU_PLUGIN_HWADDR_OPERATION_DEVICE_ERROR;
|
||||
case MEMTX_DECODE_ERROR:
|
||||
return QEMU_PLUGIN_HWADDR_OPERATION_INVALID_ADDRESS;
|
||||
case MEMTX_ACCESS_ERROR:
|
||||
return QEMU_PLUGIN_HWADDR_OPERATION_ACCESS_DENIED;
|
||||
default:
|
||||
return QEMU_PLUGIN_HWADDR_OPERATION_ERROR;
|
||||
}
|
||||
#else
|
||||
return QEMU_PLUGIN_HWADDR_OPERATION_ERROR;
|
||||
#endif
|
||||
}
|
||||
|
||||
enum qemu_plugin_hwaddr_operation_result
|
||||
qemu_plugin_write_memory_hwaddr(hwaddr addr, GByteArray *data)
|
||||
{
|
||||
#ifdef CONFIG_SOFTMMU
|
||||
if (data->len == 0) {
|
||||
return QEMU_PLUGIN_HWADDR_OPERATION_ERROR;
|
||||
}
|
||||
|
||||
g_assert(current_cpu);
|
||||
|
||||
int as_idx = cpu_asidx_from_attrs(current_cpu, MEMTXATTRS_UNSPECIFIED);
|
||||
AddressSpace *as = cpu_get_address_space(current_cpu, as_idx);
|
||||
|
||||
if (as == NULL) {
|
||||
return QEMU_PLUGIN_HWADDR_OPERATION_INVALID_ADDRESS_SPACE;
|
||||
}
|
||||
|
||||
MemTxResult res = address_space_rw(as, addr,
|
||||
MEMTXATTRS_UNSPECIFIED, data->data,
|
||||
data->len, true);
|
||||
switch (res) {
|
||||
case MEMTX_OK:
|
||||
return QEMU_PLUGIN_HWADDR_OPERATION_OK;
|
||||
case MEMTX_ERROR:
|
||||
return QEMU_PLUGIN_HWADDR_OPERATION_DEVICE_ERROR;
|
||||
case MEMTX_DECODE_ERROR:
|
||||
return QEMU_PLUGIN_HWADDR_OPERATION_INVALID_ADDRESS;
|
||||
case MEMTX_ACCESS_ERROR:
|
||||
return QEMU_PLUGIN_HWADDR_OPERATION_ACCESS_DENIED;
|
||||
default:
|
||||
return QEMU_PLUGIN_HWADDR_OPERATION_ERROR;
|
||||
}
|
||||
#else
|
||||
return QEMU_PLUGIN_HWADDR_OPERATION_ERROR;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool qemu_plugin_translate_vaddr(uint64_t vaddr, uint64_t *hwaddr)
|
||||
{
|
||||
#ifdef CONFIG_SOFTMMU
|
||||
TranslateForDebugResult tres;
|
||||
|
||||
g_assert(current_cpu);
|
||||
|
||||
if (!cpu_translate_for_debug(current_cpu, vaddr, &tres)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
*hwaddr = tres.physaddr;
|
||||
|
||||
return true;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
struct qemu_plugin_scoreboard *qemu_plugin_scoreboard_new(size_t element_size)
|
||||
{
|
||||
return plugin_scoreboard_new(element_size);
|
||||
}
|
||||
|
||||
void qemu_plugin_scoreboard_free(struct qemu_plugin_scoreboard *score)
|
||||
{
|
||||
plugin_scoreboard_free(score);
|
||||
}
|
||||
|
||||
void *qemu_plugin_scoreboard_find(struct qemu_plugin_scoreboard *score,
|
||||
unsigned int vcpu_index)
|
||||
{
|
||||
g_assert(vcpu_index < qemu_plugin_num_vcpus());
|
||||
/* we can't use g_array_index since entry size is not statically known */
|
||||
char *base_ptr = score->data->data;
|
||||
return base_ptr + vcpu_index * g_array_get_element_size(score->data);
|
||||
}
|
||||
|
||||
static uint64_t *plugin_u64_address(qemu_plugin_u64 entry,
|
||||
unsigned int vcpu_index)
|
||||
{
|
||||
char *ptr = qemu_plugin_scoreboard_find(entry.score, vcpu_index);
|
||||
return (uint64_t *)(ptr + entry.offset);
|
||||
}
|
||||
|
||||
void qemu_plugin_u64_add(qemu_plugin_u64 entry, unsigned int vcpu_index,
|
||||
uint64_t added)
|
||||
{
|
||||
*plugin_u64_address(entry, vcpu_index) += added;
|
||||
}
|
||||
|
||||
uint64_t qemu_plugin_u64_get(qemu_plugin_u64 entry,
|
||||
unsigned int vcpu_index)
|
||||
{
|
||||
return *plugin_u64_address(entry, vcpu_index);
|
||||
}
|
||||
|
||||
void qemu_plugin_u64_set(qemu_plugin_u64 entry, unsigned int vcpu_index,
|
||||
uint64_t val)
|
||||
{
|
||||
*plugin_u64_address(entry, vcpu_index) = val;
|
||||
}
|
||||
|
||||
uint64_t qemu_plugin_u64_sum(qemu_plugin_u64 entry)
|
||||
{
|
||||
uint64_t total = 0;
|
||||
for (int i = 0, n = qemu_plugin_num_vcpus(); i < n; ++i) {
|
||||
total += qemu_plugin_u64_get(entry, i);
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
+895
@@ -0,0 +1,895 @@
|
||||
/*
|
||||
* QEMU Plugin Core code
|
||||
*
|
||||
* This is the core code that deals with injecting instrumentation into the code
|
||||
*
|
||||
* Copyright (C) 2017, Emilio G. Cota <[email protected]>
|
||||
* Copyright (C) 2019, Linaro
|
||||
*
|
||||
* License: GNU GPL, version 2 or later.
|
||||
* See the COPYING file in the top-level directory.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
#include "qemu/osdep.h"
|
||||
#include "qemu/lockable.h"
|
||||
#include "qemu/option.h"
|
||||
#include "qemu/plugin.h"
|
||||
#include "plugins/qemu-plugin.h"
|
||||
#include "qemu/queue.h"
|
||||
#include "qemu/rcu_queue.h"
|
||||
#include "qemu/rcu.h"
|
||||
#include "exec/tb-flush.h"
|
||||
#include "tcg/tcg-op-common.h"
|
||||
#include "plugin.h"
|
||||
|
||||
struct qemu_plugin_cb {
|
||||
struct qemu_plugin_ctx *ctx;
|
||||
union qemu_plugin_cb_sig f;
|
||||
void *udata;
|
||||
QLIST_ENTRY(qemu_plugin_cb) entry;
|
||||
};
|
||||
|
||||
struct qemu_plugin_state plugin;
|
||||
|
||||
struct qemu_plugin_ctx *plugin_id_to_ctx_locked(qemu_plugin_id_t id)
|
||||
{
|
||||
struct qemu_plugin_ctx *ctx;
|
||||
qemu_plugin_id_t *id_p;
|
||||
|
||||
id_p = g_hash_table_lookup(plugin.id_ht, &id);
|
||||
ctx = container_of(id_p, struct qemu_plugin_ctx, id);
|
||||
if (ctx == NULL) {
|
||||
error_report("plugin: invalid plugin id %" PRIu64, id);
|
||||
abort();
|
||||
}
|
||||
return ctx;
|
||||
}
|
||||
|
||||
static void plugin_cpu_update__async(CPUState *cpu, run_on_cpu_data data)
|
||||
{
|
||||
bitmap_copy(cpu->plugin_state->event_mask,
|
||||
&data.host_ulong, QEMU_PLUGIN_EV_MAX);
|
||||
tcg_flush_jmp_cache(cpu);
|
||||
}
|
||||
|
||||
static void plugin_cpu_update__locked(gpointer k, gpointer v, gpointer udata)
|
||||
{
|
||||
CPUState *cpu = container_of(k, CPUState, cpu_index);
|
||||
run_on_cpu_data mask = RUN_ON_CPU_HOST_ULONG(*plugin.mask);
|
||||
|
||||
async_run_on_cpu(cpu, plugin_cpu_update__async, mask);
|
||||
}
|
||||
|
||||
void plugin_unregister_cb__locked(struct qemu_plugin_ctx *ctx,
|
||||
enum qemu_plugin_event ev)
|
||||
{
|
||||
struct qemu_plugin_cb *cb = ctx->callbacks[ev];
|
||||
|
||||
if (cb == NULL) {
|
||||
return;
|
||||
}
|
||||
QLIST_REMOVE_RCU(cb, entry);
|
||||
g_free(cb);
|
||||
ctx->callbacks[ev] = NULL;
|
||||
if (QLIST_EMPTY_RCU(&plugin.cb_lists[ev])) {
|
||||
clear_bit(ev, plugin.mask);
|
||||
g_hash_table_foreach(plugin.cpu_ht, plugin_cpu_update__locked, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Disable CFI checks.
|
||||
* The callback function has been loaded from an external library so we do not
|
||||
* have type information
|
||||
*/
|
||||
QEMU_DISABLE_CFI
|
||||
static void plugin_vcpu_cb__udata(CPUState *cpu, enum qemu_plugin_event ev)
|
||||
{
|
||||
struct qemu_plugin_cb *cb, *next;
|
||||
|
||||
switch (ev) {
|
||||
case QEMU_PLUGIN_EV_VCPU_INIT:
|
||||
case QEMU_PLUGIN_EV_VCPU_IDLE:
|
||||
case QEMU_PLUGIN_EV_VCPU_RESUME:
|
||||
case QEMU_PLUGIN_EV_VCPU_EXIT:
|
||||
QLIST_FOREACH_SAFE_RCU(cb, &plugin.cb_lists[ev], entry, next) {
|
||||
qemu_plugin_vcpu_udata_cb_t func = cb->f.vcpu_udata;
|
||||
func(cpu->cpu_index, cb->udata);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
g_assert_not_reached();
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Disable CFI checks.
|
||||
* The callback function has been loaded from an external library so we do not
|
||||
* have type information
|
||||
*/
|
||||
QEMU_DISABLE_CFI
|
||||
static void plugin_vcpu_cb__discon(CPUState *cpu,
|
||||
enum qemu_plugin_event ev,
|
||||
enum qemu_plugin_discon_type type,
|
||||
uint64_t from)
|
||||
{
|
||||
struct qemu_plugin_cb *cb, *next;
|
||||
uint64_t to = cpu->cc->get_pc(cpu);
|
||||
|
||||
qemu_plugin_set_cb_flags(cpu, QEMU_PLUGIN_CB_RW_REGS_PC);
|
||||
if (cpu->cpu_index < plugin.num_vcpus) {
|
||||
/* iterate safely; plugins might uninstall themselves at any time */
|
||||
QLIST_FOREACH_SAFE_RCU(cb, &plugin.cb_lists[ev], entry, next) {
|
||||
qemu_plugin_vcpu_discon_cb_t func = cb->f.vcpu_discon;
|
||||
func(cpu->cpu_index, type, from, to, cb->udata);
|
||||
}
|
||||
}
|
||||
qemu_plugin_set_cb_flags(cpu, QEMU_PLUGIN_CB_NO_REGS);
|
||||
}
|
||||
|
||||
/*
|
||||
* Disable CFI checks.
|
||||
* The callback function has been loaded from an external library so we do not
|
||||
* have type information
|
||||
*/
|
||||
QEMU_DISABLE_CFI
|
||||
static void plugin_cb__udata(enum qemu_plugin_event ev)
|
||||
{
|
||||
struct qemu_plugin_cb *cb, *next;
|
||||
|
||||
switch (ev) {
|
||||
case QEMU_PLUGIN_EV_ATEXIT:
|
||||
case QEMU_PLUGIN_EV_FLUSH:
|
||||
QLIST_FOREACH_SAFE_RCU(cb, &plugin.cb_lists[ev], entry, next) {
|
||||
qemu_plugin_udata_cb_t func = cb->f.udata;
|
||||
|
||||
func(cb->udata);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
g_assert_not_reached();
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
do_plugin_register_cb(qemu_plugin_id_t id, enum qemu_plugin_event ev,
|
||||
void *func, void *udata)
|
||||
{
|
||||
struct qemu_plugin_ctx *ctx;
|
||||
|
||||
QEMU_LOCK_GUARD(&plugin.lock);
|
||||
ctx = plugin_id_to_ctx_locked(id);
|
||||
/* if the plugin is on its way out, ignore this request */
|
||||
if (unlikely(ctx->uninstalling)) {
|
||||
return;
|
||||
}
|
||||
if (func) {
|
||||
struct qemu_plugin_cb *cb = ctx->callbacks[ev];
|
||||
|
||||
if (cb) {
|
||||
cb->f.generic = func;
|
||||
cb->udata = udata;
|
||||
} else {
|
||||
cb = g_new(struct qemu_plugin_cb, 1);
|
||||
cb->ctx = ctx;
|
||||
cb->f.generic = func;
|
||||
cb->udata = udata;
|
||||
ctx->callbacks[ev] = cb;
|
||||
QLIST_INSERT_HEAD_RCU(&plugin.cb_lists[ev], cb, entry);
|
||||
if (!test_bit(ev, plugin.mask)) {
|
||||
set_bit(ev, plugin.mask);
|
||||
g_hash_table_foreach(plugin.cpu_ht, plugin_cpu_update__locked,
|
||||
NULL);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
plugin_unregister_cb__locked(ctx, ev);
|
||||
}
|
||||
}
|
||||
|
||||
void plugin_register_cb(qemu_plugin_id_t id, enum qemu_plugin_event ev,
|
||||
void *func)
|
||||
{
|
||||
do_plugin_register_cb(id, ev, func, NULL);
|
||||
}
|
||||
|
||||
void
|
||||
plugin_register_cb_udata(qemu_plugin_id_t id, enum qemu_plugin_event ev,
|
||||
void *func, void *udata)
|
||||
{
|
||||
do_plugin_register_cb(id, ev, func, udata);
|
||||
}
|
||||
|
||||
CPUPluginState *qemu_plugin_create_vcpu_state(void)
|
||||
{
|
||||
return g_new0(CPUPluginState, 1);
|
||||
}
|
||||
|
||||
static void plugin_grow_scoreboards__locked(CPUState *cpu)
|
||||
{
|
||||
size_t scoreboard_size = plugin.scoreboard_alloc_size;
|
||||
bool need_realloc = false;
|
||||
|
||||
if (cpu->cpu_index < scoreboard_size) {
|
||||
return;
|
||||
}
|
||||
|
||||
while (cpu->cpu_index >= scoreboard_size) {
|
||||
scoreboard_size *= 2;
|
||||
need_realloc = true;
|
||||
}
|
||||
|
||||
if (!need_realloc) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (QLIST_EMPTY(&plugin.scoreboards)) {
|
||||
/* just update size for future scoreboards */
|
||||
plugin.scoreboard_alloc_size = scoreboard_size;
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* A scoreboard creation/deletion might be in progress. If a new vcpu is
|
||||
* initialized at the same time, we are safe, as the new
|
||||
* plugin.scoreboard_alloc_size was not yet written.
|
||||
*/
|
||||
qemu_rec_mutex_unlock(&plugin.lock);
|
||||
|
||||
/* cpus must be stopped, as tb might still use an existing scoreboard. */
|
||||
start_exclusive();
|
||||
/* re-acquire lock */
|
||||
qemu_rec_mutex_lock(&plugin.lock);
|
||||
/* in case another vcpu is created between unlock and exclusive section. */
|
||||
if (scoreboard_size > plugin.scoreboard_alloc_size) {
|
||||
struct qemu_plugin_scoreboard *score;
|
||||
QLIST_FOREACH(score, &plugin.scoreboards, entry) {
|
||||
g_array_set_size(score->data, scoreboard_size);
|
||||
}
|
||||
plugin.scoreboard_alloc_size = scoreboard_size;
|
||||
/* force all tb to be flushed, as scoreboard pointers were changed. */
|
||||
tb_flush__exclusive_or_serial();
|
||||
}
|
||||
end_exclusive();
|
||||
}
|
||||
|
||||
static void qemu_plugin_vcpu_init__async(CPUState *cpu, run_on_cpu_data unused)
|
||||
{
|
||||
bool success;
|
||||
|
||||
assert(cpu->cpu_index != UNASSIGNED_CPU_INDEX);
|
||||
qemu_rec_mutex_lock(&plugin.lock);
|
||||
plugin.num_vcpus = MAX(plugin.num_vcpus, cpu->cpu_index + 1);
|
||||
plugin_cpu_update__locked(&cpu->cpu_index, NULL, NULL);
|
||||
success = g_hash_table_insert(plugin.cpu_ht, &cpu->cpu_index,
|
||||
&cpu->cpu_index);
|
||||
g_assert(success);
|
||||
plugin_grow_scoreboards__locked(cpu);
|
||||
qemu_rec_mutex_unlock(&plugin.lock);
|
||||
|
||||
qemu_plugin_set_cb_flags(cpu, QEMU_PLUGIN_CB_RW_REGS);
|
||||
plugin_vcpu_cb__udata(cpu, QEMU_PLUGIN_EV_VCPU_INIT);
|
||||
qemu_plugin_set_cb_flags(cpu, QEMU_PLUGIN_CB_NO_REGS);
|
||||
}
|
||||
|
||||
void qemu_plugin_vcpu_init_hook(CPUState *cpu)
|
||||
{
|
||||
/* Plugin initialization must wait until the cpu start executing code */
|
||||
async_run_on_cpu(cpu, qemu_plugin_vcpu_init__async, RUN_ON_CPU_NULL);
|
||||
}
|
||||
|
||||
void qemu_plugin_vcpu_exit_hook(CPUState *cpu)
|
||||
{
|
||||
bool success;
|
||||
|
||||
qemu_plugin_set_cb_flags(cpu, QEMU_PLUGIN_CB_RW_REGS);
|
||||
plugin_vcpu_cb__udata(cpu, QEMU_PLUGIN_EV_VCPU_EXIT);
|
||||
qemu_plugin_set_cb_flags(cpu, QEMU_PLUGIN_CB_NO_REGS);
|
||||
|
||||
assert(cpu->cpu_index != UNASSIGNED_CPU_INDEX);
|
||||
qemu_rec_mutex_lock(&plugin.lock);
|
||||
success = g_hash_table_remove(plugin.cpu_ht, &cpu->cpu_index);
|
||||
g_assert(success);
|
||||
qemu_rec_mutex_unlock(&plugin.lock);
|
||||
}
|
||||
|
||||
struct plugin_for_each_args {
|
||||
struct qemu_plugin_ctx *ctx;
|
||||
qemu_plugin_vcpu_udata_cb_t cb;
|
||||
void *userdata;
|
||||
};
|
||||
|
||||
static void plugin_vcpu_for_each(gpointer k, gpointer v, gpointer udata)
|
||||
{
|
||||
struct plugin_for_each_args *args = udata;
|
||||
int cpu_index = *(int *)k;
|
||||
args->cb(cpu_index, args->userdata);
|
||||
}
|
||||
|
||||
void qemu_plugin_vcpu_for_each(qemu_plugin_id_t id,
|
||||
qemu_plugin_vcpu_udata_cb_t cb,
|
||||
void *userdata)
|
||||
{
|
||||
struct plugin_for_each_args args;
|
||||
|
||||
if (cb == NULL) {
|
||||
return;
|
||||
}
|
||||
qemu_rec_mutex_lock(&plugin.lock);
|
||||
args.ctx = plugin_id_to_ctx_locked(id);
|
||||
args.cb = cb;
|
||||
args.userdata = userdata;
|
||||
g_hash_table_foreach(plugin.cpu_ht, plugin_vcpu_for_each, &args);
|
||||
qemu_rec_mutex_unlock(&plugin.lock);
|
||||
}
|
||||
|
||||
/* Allocate and return a callback record */
|
||||
static struct qemu_plugin_dyn_cb *plugin_get_dyn_cb(GArray **arr)
|
||||
{
|
||||
GArray *cbs = *arr;
|
||||
|
||||
if (!cbs) {
|
||||
cbs = g_array_sized_new(false, true,
|
||||
sizeof(struct qemu_plugin_dyn_cb), 1);
|
||||
*arr = cbs;
|
||||
}
|
||||
|
||||
g_array_set_size(cbs, cbs->len + 1);
|
||||
return &g_array_index(cbs, struct qemu_plugin_dyn_cb, cbs->len - 1);
|
||||
}
|
||||
|
||||
static enum plugin_dyn_cb_type op_to_cb_type(enum qemu_plugin_op op)
|
||||
{
|
||||
switch (op) {
|
||||
case QEMU_PLUGIN_INLINE_ADD_U64:
|
||||
return PLUGIN_CB_INLINE_ADD_U64;
|
||||
case QEMU_PLUGIN_INLINE_STORE_U64:
|
||||
return PLUGIN_CB_INLINE_STORE_U64;
|
||||
default:
|
||||
g_assert_not_reached();
|
||||
}
|
||||
}
|
||||
|
||||
void plugin_register_inline_op_on_entry(GArray **arr,
|
||||
enum qemu_plugin_mem_rw rw,
|
||||
enum qemu_plugin_op op,
|
||||
qemu_plugin_u64 entry,
|
||||
uint64_t imm)
|
||||
{
|
||||
struct qemu_plugin_dyn_cb *dyn_cb;
|
||||
|
||||
struct qemu_plugin_inline_cb inline_cb = { .rw = rw,
|
||||
.entry = entry,
|
||||
.imm = imm };
|
||||
dyn_cb = plugin_get_dyn_cb(arr);
|
||||
dyn_cb->type = op_to_cb_type(op);
|
||||
dyn_cb->inline_insn = inline_cb;
|
||||
}
|
||||
|
||||
void plugin_register_dyn_cb__udata(GArray **arr,
|
||||
qemu_plugin_vcpu_udata_cb_t cb,
|
||||
enum qemu_plugin_cb_flags flags,
|
||||
void *udata)
|
||||
{
|
||||
static TCGHelperInfo info[4] = {
|
||||
[QEMU_PLUGIN_CB_NO_REGS].flags = TCG_CALL_NO_RWG,
|
||||
[QEMU_PLUGIN_CB_R_REGS].flags = TCG_CALL_NO_WG,
|
||||
[QEMU_PLUGIN_CB_RW_REGS].flags = 0,
|
||||
[QEMU_PLUGIN_CB_RW_REGS_PC].flags = 0,
|
||||
/*
|
||||
* Match qemu_plugin_vcpu_udata_cb_t:
|
||||
* void (*)(uint32_t, void *)
|
||||
*/
|
||||
[0 ... 3].typemask = (dh_typemask(void, 0) |
|
||||
dh_typemask(i32, 1) |
|
||||
dh_typemask(ptr, 2))
|
||||
};
|
||||
assert((unsigned)flags < ARRAY_SIZE(info));
|
||||
|
||||
struct qemu_plugin_dyn_cb *dyn_cb = plugin_get_dyn_cb(arr);
|
||||
struct qemu_plugin_regular_cb regular_cb = { .f.vcpu_udata = cb,
|
||||
.userp = udata,
|
||||
.info = &info[flags] };
|
||||
dyn_cb->type = PLUGIN_CB_REGULAR;
|
||||
dyn_cb->regular = regular_cb;
|
||||
}
|
||||
|
||||
void plugin_register_dyn_cond_cb__udata(GArray **arr,
|
||||
qemu_plugin_vcpu_udata_cb_t cb,
|
||||
enum qemu_plugin_cb_flags flags,
|
||||
enum qemu_plugin_cond cond,
|
||||
qemu_plugin_u64 entry,
|
||||
uint64_t imm,
|
||||
void *udata)
|
||||
{
|
||||
static TCGHelperInfo info[4] = {
|
||||
[QEMU_PLUGIN_CB_NO_REGS].flags = TCG_CALL_NO_RWG,
|
||||
[QEMU_PLUGIN_CB_R_REGS].flags = TCG_CALL_NO_WG,
|
||||
[QEMU_PLUGIN_CB_RW_REGS].flags = 0,
|
||||
[QEMU_PLUGIN_CB_RW_REGS_PC].flags = 0,
|
||||
/*
|
||||
* Match qemu_plugin_vcpu_udata_cb_t:
|
||||
* void (*)(uint32_t, void *)
|
||||
*/
|
||||
[0 ... 3].typemask = (dh_typemask(void, 0) |
|
||||
dh_typemask(i32, 1) |
|
||||
dh_typemask(ptr, 2))
|
||||
};
|
||||
assert((unsigned)flags < ARRAY_SIZE(info));
|
||||
|
||||
struct qemu_plugin_dyn_cb *dyn_cb = plugin_get_dyn_cb(arr);
|
||||
struct qemu_plugin_conditional_cb cond_cb = { .userp = udata,
|
||||
.f.vcpu_udata = cb,
|
||||
.cond = cond,
|
||||
.entry = entry,
|
||||
.imm = imm,
|
||||
.info = &info[flags] };
|
||||
dyn_cb->type = PLUGIN_CB_COND;
|
||||
dyn_cb->cond = cond_cb;
|
||||
}
|
||||
|
||||
void plugin_register_vcpu_mem_cb(GArray **arr,
|
||||
void *cb,
|
||||
enum qemu_plugin_cb_flags flags,
|
||||
enum qemu_plugin_mem_rw rw,
|
||||
void *udata)
|
||||
{
|
||||
/*
|
||||
* Expect that the underlying type for enum qemu_plugin_meminfo_t
|
||||
* is either int32_t or uint32_t, aka int or unsigned int.
|
||||
*/
|
||||
QEMU_BUILD_BUG_ON(
|
||||
!__builtin_types_compatible_p(qemu_plugin_meminfo_t, uint32_t) &&
|
||||
!__builtin_types_compatible_p(qemu_plugin_meminfo_t, int32_t));
|
||||
|
||||
static TCGHelperInfo info[4] = {
|
||||
[QEMU_PLUGIN_CB_NO_REGS].flags = TCG_CALL_NO_RWG,
|
||||
[QEMU_PLUGIN_CB_R_REGS].flags = TCG_CALL_NO_WG,
|
||||
[QEMU_PLUGIN_CB_RW_REGS].flags = 0,
|
||||
[QEMU_PLUGIN_CB_RW_REGS_PC].flags = 0,
|
||||
/*
|
||||
* Match qemu_plugin_vcpu_mem_cb_t:
|
||||
* void (*)(uint32_t, qemu_plugin_meminfo_t, uint64_t, void *)
|
||||
*/
|
||||
[0 ... 3].typemask =
|
||||
(dh_typemask(void, 0) |
|
||||
dh_typemask(i32, 1) |
|
||||
(__builtin_types_compatible_p(qemu_plugin_meminfo_t, uint32_t)
|
||||
? dh_typemask(i32, 2) : dh_typemask(s32, 2)) |
|
||||
dh_typemask(i64, 3) |
|
||||
dh_typemask(ptr, 4))
|
||||
};
|
||||
assert((unsigned)flags < ARRAY_SIZE(info));
|
||||
|
||||
struct qemu_plugin_dyn_cb *dyn_cb = plugin_get_dyn_cb(arr);
|
||||
struct qemu_plugin_regular_cb regular_cb = { .userp = udata,
|
||||
.rw = rw,
|
||||
.f.vcpu_mem = cb,
|
||||
.info = &info[flags] };
|
||||
dyn_cb->type = PLUGIN_CB_MEM_REGULAR;
|
||||
dyn_cb->regular = regular_cb;
|
||||
}
|
||||
|
||||
/*
|
||||
* Disable CFI checks.
|
||||
* The callback function has been loaded from an external library so we do not
|
||||
* have type information
|
||||
*/
|
||||
QEMU_DISABLE_CFI
|
||||
void qemu_plugin_tb_trans_cb(CPUState *cpu, struct qemu_plugin_tb *tb)
|
||||
{
|
||||
struct qemu_plugin_cb *cb, *next;
|
||||
enum qemu_plugin_event ev = QEMU_PLUGIN_EV_VCPU_TB_TRANS;
|
||||
|
||||
/* no plugin_state->event_mask check here; caller should have checked */
|
||||
|
||||
QLIST_FOREACH_SAFE_RCU(cb, &plugin.cb_lists[ev], entry, next) {
|
||||
qemu_plugin_vcpu_tb_trans_cb_t func = cb->f.vcpu_tb_trans;
|
||||
|
||||
qemu_plugin_set_cb_flags(cpu, QEMU_PLUGIN_CB_RW_REGS);
|
||||
func(tb, cb->udata);
|
||||
qemu_plugin_set_cb_flags(cpu, QEMU_PLUGIN_CB_NO_REGS);
|
||||
}
|
||||
}
|
||||
|
||||
static void clamp_syscall_arguments(uint64_t *a1, uint64_t *a2, uint64_t *a3,
|
||||
uint64_t *a4, uint64_t *a5, uint64_t *a6,
|
||||
uint64_t *a7, uint64_t *a8)
|
||||
{
|
||||
if (target_long_bits() == 32) {
|
||||
const uint64_t mask = UINT32_MAX;
|
||||
*a1 &= mask;
|
||||
*a2 &= mask;
|
||||
*a3 &= mask;
|
||||
*a4 &= mask;
|
||||
*a5 &= mask;
|
||||
*a6 &= mask;
|
||||
*a7 &= mask;
|
||||
*a8 &= mask;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Disable CFI checks.
|
||||
* The callback function has been loaded from an external library so we do not
|
||||
* have type information
|
||||
*/
|
||||
QEMU_DISABLE_CFI
|
||||
void
|
||||
qemu_plugin_vcpu_syscall(CPUState *cpu, int64_t num, uint64_t a1, uint64_t a2,
|
||||
uint64_t a3, uint64_t a4, uint64_t a5,
|
||||
uint64_t a6, uint64_t a7, uint64_t a8)
|
||||
{
|
||||
struct qemu_plugin_cb *cb, *next;
|
||||
enum qemu_plugin_event ev = QEMU_PLUGIN_EV_VCPU_SYSCALL;
|
||||
|
||||
if (!test_bit(ev, cpu->plugin_state->event_mask)) {
|
||||
return;
|
||||
}
|
||||
|
||||
clamp_syscall_arguments(&a1, &a2, &a3, &a4, &a5, &a6, &a7, &a8);
|
||||
|
||||
QLIST_FOREACH_SAFE_RCU(cb, &plugin.cb_lists[ev], entry, next) {
|
||||
qemu_plugin_vcpu_syscall_cb_t func = cb->f.vcpu_syscall;
|
||||
|
||||
qemu_plugin_set_cb_flags(cpu, QEMU_PLUGIN_CB_RW_REGS_PC);
|
||||
func(cpu->cpu_index, num, a1, a2, a3, a4, a5, a6, a7, a8, cb->udata);
|
||||
qemu_plugin_set_cb_flags(cpu, QEMU_PLUGIN_CB_NO_REGS);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Disable CFI checks.
|
||||
* The callback function has been loaded from an external library so we do not
|
||||
* have type information
|
||||
*/
|
||||
QEMU_DISABLE_CFI
|
||||
void qemu_plugin_vcpu_syscall_ret(CPUState *cpu, int64_t num, int64_t ret)
|
||||
{
|
||||
struct qemu_plugin_cb *cb, *next;
|
||||
enum qemu_plugin_event ev = QEMU_PLUGIN_EV_VCPU_SYSCALL_RET;
|
||||
|
||||
if (!test_bit(ev, cpu->plugin_state->event_mask)) {
|
||||
return;
|
||||
}
|
||||
|
||||
QLIST_FOREACH_SAFE_RCU(cb, &plugin.cb_lists[ev], entry, next) {
|
||||
qemu_plugin_vcpu_syscall_ret_cb_t func = cb->f.vcpu_syscall_ret;
|
||||
|
||||
qemu_plugin_set_cb_flags(cpu, QEMU_PLUGIN_CB_RW_REGS_PC);
|
||||
func(cpu->cpu_index, num, ret, cb->udata);
|
||||
qemu_plugin_set_cb_flags(cpu, QEMU_PLUGIN_CB_NO_REGS);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Disable CFI checks.
|
||||
* The callback function has been loaded from an external library so we do not
|
||||
* have type information
|
||||
*/
|
||||
QEMU_DISABLE_CFI
|
||||
bool
|
||||
qemu_plugin_vcpu_syscall_filter(CPUState *cpu, int64_t num, uint64_t a1,
|
||||
uint64_t a2, uint64_t a3, uint64_t a4,
|
||||
uint64_t a5, uint64_t a6, uint64_t a7,
|
||||
uint64_t a8, int64_t *sysret)
|
||||
{
|
||||
struct qemu_plugin_cb *cb, *next;
|
||||
enum qemu_plugin_event ev = QEMU_PLUGIN_EV_VCPU_SYSCALL_FILTER;
|
||||
bool filtered = false;
|
||||
|
||||
if (!test_bit(ev, cpu->plugin_state->event_mask)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
clamp_syscall_arguments(&a1, &a2, &a3, &a4, &a5, &a6, &a7, &a8);
|
||||
|
||||
qemu_plugin_set_cb_flags(cpu, QEMU_PLUGIN_CB_RW_REGS_PC);
|
||||
|
||||
QLIST_FOREACH_SAFE_RCU(cb, &plugin.cb_lists[ev], entry, next) {
|
||||
qemu_plugin_vcpu_syscall_filter_cb_t func = cb->f.vcpu_syscall_filter;
|
||||
|
||||
if (func(cpu->cpu_index, num, a1, a2, a3, a4,
|
||||
a5, a6, a7, a8, sysret, cb->udata)) {
|
||||
filtered = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
qemu_plugin_set_cb_flags(cpu, QEMU_PLUGIN_CB_NO_REGS);
|
||||
|
||||
return filtered;
|
||||
}
|
||||
|
||||
void qemu_plugin_vcpu_idle_cb(CPUState *cpu)
|
||||
{
|
||||
/* idle and resume cb may be called before init, ignore in this case */
|
||||
if (cpu->cpu_index < plugin.num_vcpus) {
|
||||
qemu_plugin_set_cb_flags(cpu, QEMU_PLUGIN_CB_RW_REGS_PC);
|
||||
plugin_vcpu_cb__udata(cpu, QEMU_PLUGIN_EV_VCPU_IDLE);
|
||||
qemu_plugin_set_cb_flags(cpu, QEMU_PLUGIN_CB_NO_REGS);
|
||||
}
|
||||
}
|
||||
|
||||
void qemu_plugin_vcpu_resume_cb(CPUState *cpu)
|
||||
{
|
||||
if (cpu->cpu_index < plugin.num_vcpus) {
|
||||
qemu_plugin_set_cb_flags(cpu, QEMU_PLUGIN_CB_RW_REGS_PC);
|
||||
plugin_vcpu_cb__udata(cpu, QEMU_PLUGIN_EV_VCPU_RESUME);
|
||||
qemu_plugin_set_cb_flags(cpu, QEMU_PLUGIN_CB_NO_REGS);
|
||||
}
|
||||
}
|
||||
|
||||
void qemu_plugin_vcpu_interrupt_cb(CPUState *cpu, uint64_t from)
|
||||
{
|
||||
plugin_vcpu_cb__discon(cpu, QEMU_PLUGIN_EV_VCPU_INTERRUPT,
|
||||
QEMU_PLUGIN_DISCON_INTERRUPT, from);
|
||||
}
|
||||
|
||||
void qemu_plugin_vcpu_exception_cb(CPUState *cpu, uint64_t from)
|
||||
{
|
||||
plugin_vcpu_cb__discon(cpu, QEMU_PLUGIN_EV_VCPU_EXCEPTION,
|
||||
QEMU_PLUGIN_DISCON_EXCEPTION, from);
|
||||
}
|
||||
|
||||
void qemu_plugin_vcpu_hostcall_cb(CPUState *cpu, uint64_t from)
|
||||
{
|
||||
plugin_vcpu_cb__discon(cpu, QEMU_PLUGIN_EV_VCPU_HOSTCALL,
|
||||
QEMU_PLUGIN_DISCON_HOSTCALL, from);
|
||||
}
|
||||
|
||||
void qemu_plugin_register_vcpu_idle_cb(qemu_plugin_id_t id,
|
||||
qemu_plugin_vcpu_udata_cb_t cb,
|
||||
void *userdata)
|
||||
{
|
||||
plugin_register_cb_udata(id, QEMU_PLUGIN_EV_VCPU_IDLE, cb, userdata);
|
||||
}
|
||||
|
||||
void qemu_plugin_register_vcpu_resume_cb(qemu_plugin_id_t id,
|
||||
qemu_plugin_vcpu_udata_cb_t cb,
|
||||
void *userdata)
|
||||
{
|
||||
plugin_register_cb_udata(id, QEMU_PLUGIN_EV_VCPU_RESUME, cb, userdata);
|
||||
}
|
||||
|
||||
void qemu_plugin_register_vcpu_discon_cb(qemu_plugin_id_t id,
|
||||
enum qemu_plugin_discon_type type,
|
||||
qemu_plugin_vcpu_discon_cb_t cb,
|
||||
void *userdata)
|
||||
{
|
||||
if (type & QEMU_PLUGIN_DISCON_INTERRUPT) {
|
||||
plugin_register_cb_udata(id, QEMU_PLUGIN_EV_VCPU_INTERRUPT, cb, userdata);
|
||||
}
|
||||
if (type & QEMU_PLUGIN_DISCON_EXCEPTION) {
|
||||
plugin_register_cb_udata(id, QEMU_PLUGIN_EV_VCPU_EXCEPTION, cb, userdata);
|
||||
}
|
||||
if (type & QEMU_PLUGIN_DISCON_HOSTCALL) {
|
||||
plugin_register_cb_udata(id, QEMU_PLUGIN_EV_VCPU_HOSTCALL, cb, userdata);
|
||||
}
|
||||
}
|
||||
|
||||
void qemu_plugin_register_flush_cb(qemu_plugin_id_t id,
|
||||
qemu_plugin_udata_cb_t cb,
|
||||
void *userdata)
|
||||
{
|
||||
plugin_register_cb_udata(id, QEMU_PLUGIN_EV_FLUSH, cb, userdata);
|
||||
}
|
||||
|
||||
static bool free_dyn_cb_arr(void *p, uint32_t h, void *userp)
|
||||
{
|
||||
g_array_free((GArray *) p, true);
|
||||
return true;
|
||||
}
|
||||
|
||||
void qemu_plugin_flush_cb(void)
|
||||
{
|
||||
qht_iter_remove(&plugin.dyn_cb_arr_ht, free_dyn_cb_arr, NULL);
|
||||
qht_reset(&plugin.dyn_cb_arr_ht);
|
||||
|
||||
plugin_cb__udata(QEMU_PLUGIN_EV_FLUSH);
|
||||
}
|
||||
|
||||
void exec_inline_op(enum plugin_dyn_cb_type type,
|
||||
struct qemu_plugin_inline_cb *cb,
|
||||
int cpu_index)
|
||||
{
|
||||
char *ptr = cb->entry.score->data->data;
|
||||
size_t elem_size = g_array_get_element_size(
|
||||
cb->entry.score->data);
|
||||
size_t offset = cb->entry.offset;
|
||||
uint64_t *val = (uint64_t *)(ptr + offset + cpu_index * elem_size);
|
||||
|
||||
switch (type) {
|
||||
case PLUGIN_CB_INLINE_ADD_U64:
|
||||
*val += cb->imm;
|
||||
break;
|
||||
case PLUGIN_CB_INLINE_STORE_U64:
|
||||
*val = cb->imm;
|
||||
break;
|
||||
default:
|
||||
g_assert_not_reached();
|
||||
}
|
||||
}
|
||||
|
||||
QEMU_DISABLE_CFI
|
||||
void qemu_plugin_vcpu_mem_cb(CPUState *cpu, uint64_t vaddr,
|
||||
uint64_t value_low,
|
||||
uint64_t value_high,
|
||||
MemOpIdx oi, enum qemu_plugin_mem_rw rw)
|
||||
{
|
||||
GArray *arr = cpu->neg.plugin_mem_cbs;
|
||||
size_t i;
|
||||
|
||||
if (arr == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
cpu->neg.plugin_mem_value_low = value_low;
|
||||
cpu->neg.plugin_mem_value_high = value_high;
|
||||
|
||||
for (i = 0; i < arr->len; i++) {
|
||||
struct qemu_plugin_dyn_cb *cb =
|
||||
&g_array_index(arr, struct qemu_plugin_dyn_cb, i);
|
||||
|
||||
switch (cb->type) {
|
||||
case PLUGIN_CB_MEM_REGULAR:
|
||||
if (rw & cb->regular.rw) {
|
||||
qemu_plugin_set_cb_flags(cpu,
|
||||
tcg_call_to_qemu_plugin_cb_flags(cb->regular.info->flags));
|
||||
|
||||
cb->regular.f.vcpu_mem(cpu->cpu_index,
|
||||
make_plugin_meminfo(oi, rw),
|
||||
vaddr, cb->regular.userp);
|
||||
qemu_plugin_set_cb_flags(cpu, QEMU_PLUGIN_CB_NO_REGS);
|
||||
}
|
||||
break;
|
||||
case PLUGIN_CB_INLINE_ADD_U64:
|
||||
case PLUGIN_CB_INLINE_STORE_U64:
|
||||
if (rw & cb->inline_insn.rw) {
|
||||
exec_inline_op(cb->type, &cb->inline_insn, cpu->cpu_index);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
g_assert_not_reached();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void qemu_plugin_atexit_cb(void)
|
||||
{
|
||||
plugin_cb__udata(QEMU_PLUGIN_EV_ATEXIT);
|
||||
}
|
||||
|
||||
void qemu_plugin_register_atexit_cb(qemu_plugin_id_t id,
|
||||
qemu_plugin_udata_cb_t cb,
|
||||
void *udata)
|
||||
{
|
||||
plugin_register_cb_udata(id, QEMU_PLUGIN_EV_ATEXIT, cb, udata);
|
||||
}
|
||||
|
||||
/*
|
||||
* Handle exit from linux-user. Unlike the normal atexit() mechanism
|
||||
* we need to handle the clean-up manually as it's possible threads
|
||||
* are still running. We need to remove all callbacks from code
|
||||
* generation, flush the current translations and then we can safely
|
||||
* trigger the exit callbacks.
|
||||
*/
|
||||
|
||||
void qemu_plugin_user_exit(void)
|
||||
{
|
||||
enum qemu_plugin_event ev;
|
||||
CPUState *cpu;
|
||||
|
||||
/*
|
||||
* Locking order: we must acquire locks in an order that is consistent
|
||||
* with the one in fork_start(). That is:
|
||||
* - start_exclusive(), which acquires qemu_cpu_list_lock,
|
||||
* must be called before acquiring plugin.lock.
|
||||
*/
|
||||
start_exclusive();
|
||||
|
||||
qemu_rec_mutex_lock(&plugin.lock);
|
||||
/* un-register all callbacks except the final AT_EXIT one */
|
||||
for (ev = 0; ev < QEMU_PLUGIN_EV_MAX; ev++) {
|
||||
if (ev != QEMU_PLUGIN_EV_ATEXIT) {
|
||||
struct qemu_plugin_cb *cb, *next;
|
||||
|
||||
QLIST_FOREACH_SAFE_RCU(cb, &plugin.cb_lists[ev], entry, next) {
|
||||
plugin_unregister_cb__locked(cb->ctx, ev);
|
||||
}
|
||||
}
|
||||
}
|
||||
CPU_FOREACH(cpu) {
|
||||
qemu_plugin_disable_mem_helpers(cpu);
|
||||
}
|
||||
qemu_rec_mutex_unlock(&plugin.lock);
|
||||
|
||||
tb_flush__exclusive_or_serial();
|
||||
end_exclusive();
|
||||
|
||||
/* now it's safe to handle the exit case */
|
||||
qemu_plugin_atexit_cb();
|
||||
}
|
||||
|
||||
/*
|
||||
* Helpers for *-user to ensure locks are sane across fork() events.
|
||||
*/
|
||||
|
||||
void qemu_plugin_user_prefork_lock(void)
|
||||
{
|
||||
qemu_rec_mutex_lock(&plugin.lock);
|
||||
}
|
||||
|
||||
void qemu_plugin_user_postfork(bool is_child)
|
||||
{
|
||||
if (is_child) {
|
||||
/* should we just reset via plugin_init? */
|
||||
qemu_rec_mutex_init(&plugin.lock);
|
||||
} else {
|
||||
qemu_rec_mutex_unlock(&plugin.lock);
|
||||
}
|
||||
}
|
||||
|
||||
static bool plugin_dyn_cb_arr_cmp(const void *ap, const void *bp)
|
||||
{
|
||||
return ap == bp;
|
||||
}
|
||||
|
||||
static void __attribute__((__constructor__)) plugin_init(void)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < QEMU_PLUGIN_EV_MAX; i++) {
|
||||
QLIST_INIT(&plugin.cb_lists[i]);
|
||||
}
|
||||
qemu_rec_mutex_init(&plugin.lock);
|
||||
plugin.id_ht = g_hash_table_new(g_int64_hash, g_int64_equal);
|
||||
plugin.cpu_ht = g_hash_table_new(g_int_hash, g_int_equal);
|
||||
QLIST_INIT(&plugin.scoreboards);
|
||||
plugin.scoreboard_alloc_size = 16; /* avoid frequent reallocation */
|
||||
QTAILQ_INIT(&plugin.ctxs);
|
||||
qht_init(&plugin.dyn_cb_arr_ht, plugin_dyn_cb_arr_cmp, 16,
|
||||
QHT_MODE_AUTO_RESIZE);
|
||||
atexit(qemu_plugin_atexit_cb);
|
||||
}
|
||||
|
||||
int plugin_num_vcpus(void)
|
||||
{
|
||||
return plugin.num_vcpus;
|
||||
}
|
||||
|
||||
struct qemu_plugin_scoreboard *plugin_scoreboard_new(size_t element_size)
|
||||
{
|
||||
struct qemu_plugin_scoreboard *score =
|
||||
g_malloc0(sizeof(struct qemu_plugin_scoreboard));
|
||||
score->data = g_array_new(FALSE, TRUE, element_size);
|
||||
g_array_set_size(score->data, plugin.scoreboard_alloc_size);
|
||||
|
||||
qemu_rec_mutex_lock(&plugin.lock);
|
||||
QLIST_INSERT_HEAD(&plugin.scoreboards, score, entry);
|
||||
qemu_rec_mutex_unlock(&plugin.lock);
|
||||
|
||||
return score;
|
||||
}
|
||||
|
||||
void plugin_scoreboard_free(struct qemu_plugin_scoreboard *score)
|
||||
{
|
||||
qemu_rec_mutex_lock(&plugin.lock);
|
||||
QLIST_REMOVE(score, entry);
|
||||
qemu_rec_mutex_unlock(&plugin.lock);
|
||||
|
||||
g_array_free(score->data, TRUE);
|
||||
g_free(score);
|
||||
}
|
||||
|
||||
enum qemu_plugin_cb_flags tcg_call_to_qemu_plugin_cb_flags(int flags)
|
||||
{
|
||||
if (flags & TCG_CALL_NO_RWG) {
|
||||
return QEMU_PLUGIN_CB_NO_REGS;
|
||||
} else if (flags & TCG_CALL_NO_WG) {
|
||||
return QEMU_PLUGIN_CB_R_REGS;
|
||||
} else {
|
||||
return QEMU_PLUGIN_CB_RW_REGS_PC;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,422 @@
|
||||
/*
|
||||
* QEMU Plugin Core Loader Code
|
||||
*
|
||||
* This is the code responsible for loading and unloading the plugins.
|
||||
* Aside from the basic housekeeping tasks we also need to ensure any
|
||||
* generated code is flushed when we remove a plugin so we cannot end
|
||||
* up calling and unloaded helper function.
|
||||
*
|
||||
* Copyright (C) 2017, Emilio G. Cota <[email protected]>
|
||||
* Copyright (C) 2019, Linaro
|
||||
*
|
||||
* License: GNU GPL, version 2 or later.
|
||||
* See the COPYING file in the top-level directory.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
|
||||
#include "qemu/osdep.h"
|
||||
#include "qemu/error-report.h"
|
||||
#include "qemu/config-file.h"
|
||||
#include "qemu/help_option.h"
|
||||
#include "qapi/error.h"
|
||||
#include "qemu/lockable.h"
|
||||
#include "qemu/option.h"
|
||||
#include "qemu/rcu_queue.h"
|
||||
#include "qemu/qht.h"
|
||||
#include "qemu/bitmap.h"
|
||||
#include "qemu/cacheinfo.h"
|
||||
#include "qemu/xxhash.h"
|
||||
#include "qemu/plugin.h"
|
||||
#include "qemu/memalign.h"
|
||||
#include "qemu/target-info.h"
|
||||
#include "exec/tb-flush.h"
|
||||
|
||||
#include "plugin.h"
|
||||
|
||||
/*
|
||||
* For convenience we use a bitmap for plugin.mask, but really all we need is a
|
||||
* u32, which is what we store in TranslationBlock.
|
||||
*/
|
||||
QEMU_BUILD_BUG_ON(QEMU_PLUGIN_EV_MAX > 32);
|
||||
|
||||
struct qemu_plugin_desc {
|
||||
char *path;
|
||||
char **argv;
|
||||
QTAILQ_ENTRY(qemu_plugin_desc) entry;
|
||||
int argc;
|
||||
};
|
||||
|
||||
struct qemu_plugin_parse_arg {
|
||||
QemuPluginList *head;
|
||||
struct qemu_plugin_desc *curr;
|
||||
};
|
||||
|
||||
QemuOptsList qemu_plugin_opts = {
|
||||
.name = "plugin",
|
||||
.implied_opt_name = "file",
|
||||
.head = QTAILQ_HEAD_INITIALIZER(qemu_plugin_opts.head),
|
||||
.desc = {
|
||||
/* do our own parsing to support multiple plugins */
|
||||
{ /* end of list */ }
|
||||
},
|
||||
};
|
||||
|
||||
typedef int (*qemu_plugin_install_func_t)(qemu_plugin_id_t, const qemu_info_t *, int, char **);
|
||||
|
||||
extern struct qemu_plugin_state plugin;
|
||||
|
||||
void qemu_plugin_add_dyn_cb_arr(GArray *arr)
|
||||
{
|
||||
uint32_t hash = qemu_xxhash2((uint64_t)(uintptr_t)arr);
|
||||
bool inserted;
|
||||
|
||||
inserted = qht_insert(&plugin.dyn_cb_arr_ht, arr, hash, NULL);
|
||||
g_assert(inserted);
|
||||
}
|
||||
|
||||
static struct qemu_plugin_desc *plugin_find_desc(QemuPluginList *head,
|
||||
const char *path)
|
||||
{
|
||||
struct qemu_plugin_desc *desc;
|
||||
|
||||
QTAILQ_FOREACH(desc, head, entry) {
|
||||
if (strcmp(desc->path, path) == 0) {
|
||||
return desc;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int plugin_add(void *opaque, const char *name, const char *value,
|
||||
Error **errp)
|
||||
{
|
||||
struct qemu_plugin_parse_arg *arg = opaque;
|
||||
struct qemu_plugin_desc *p;
|
||||
bool is_on;
|
||||
char *fullarg;
|
||||
|
||||
if (is_help_option(value)) {
|
||||
printf("Plugin options\n");
|
||||
printf(" file=<path/to/plugin.so>\n");
|
||||
printf(" plugin specific arguments\n");
|
||||
exit(0);
|
||||
} else if (strcmp(name, "file") == 0) {
|
||||
if (strcmp(value, "") == 0) {
|
||||
error_setg(errp, "requires a non-empty argument");
|
||||
return 1;
|
||||
}
|
||||
p = plugin_find_desc(arg->head, value);
|
||||
if (p == NULL) {
|
||||
p = g_new0(struct qemu_plugin_desc, 1);
|
||||
p->path = g_strdup(value);
|
||||
QTAILQ_INSERT_TAIL(arg->head, p, entry);
|
||||
}
|
||||
arg->curr = p;
|
||||
} else {
|
||||
if (arg->curr == NULL) {
|
||||
error_setg(errp, "missing earlier '-plugin file=' option");
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (g_strcmp0(name, "arg") == 0 &&
|
||||
!qapi_bool_parse(name, value, &is_on, NULL)) {
|
||||
if (strchr(value, '=') == NULL) {
|
||||
/* Will treat arg="argname" as "argname=on" */
|
||||
fullarg = g_strdup_printf("%s=%s", value, "on");
|
||||
} else {
|
||||
fullarg = g_strdup(value);
|
||||
}
|
||||
warn_report("using 'arg=%s' is deprecated", value);
|
||||
error_printf("Please use '%s' directly\n", fullarg);
|
||||
} else {
|
||||
fullarg = g_strdup_printf("%s=%s", name, value);
|
||||
}
|
||||
|
||||
p = arg->curr;
|
||||
p->argc++;
|
||||
p->argv = g_realloc_n(p->argv, p->argc, sizeof(char *));
|
||||
p->argv[p->argc - 1] = fullarg;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void qemu_plugin_opt_parse(const char *optstr, QemuPluginList *head)
|
||||
{
|
||||
struct qemu_plugin_parse_arg arg;
|
||||
QemuOpts *opts;
|
||||
|
||||
opts = qemu_opts_parse_noisily(qemu_find_opts("plugin"), optstr, true);
|
||||
if (opts == NULL) {
|
||||
exit(1);
|
||||
}
|
||||
arg.head = head;
|
||||
arg.curr = NULL;
|
||||
qemu_opt_foreach(opts, plugin_add, &arg, &error_fatal);
|
||||
qemu_opts_del(opts);
|
||||
}
|
||||
|
||||
/*
|
||||
* From: https://en.wikipedia.org/wiki/Xorshift
|
||||
* This is faster than rand_r(), and gives us a wider range (RAND_MAX is only
|
||||
* guaranteed to be >= INT_MAX).
|
||||
*/
|
||||
static uint64_t xorshift64star(uint64_t x)
|
||||
{
|
||||
x ^= x >> 12; /* a */
|
||||
x ^= x << 25; /* b */
|
||||
x ^= x >> 27; /* c */
|
||||
return x * UINT64_C(2685821657736338717);
|
||||
}
|
||||
|
||||
/*
|
||||
* Disable CFI checks.
|
||||
* The install and version functions have been loaded from an external library
|
||||
* so we do not have type information
|
||||
*/
|
||||
QEMU_DISABLE_CFI
|
||||
static int plugin_load(struct qemu_plugin_desc *desc, const qemu_info_t *info, Error **errp)
|
||||
{
|
||||
qemu_plugin_install_func_t install;
|
||||
struct qemu_plugin_ctx *ctx;
|
||||
gpointer sym;
|
||||
int rc;
|
||||
|
||||
ctx = qemu_memalign(qemu_dcache_linesize, sizeof(*ctx));
|
||||
memset(ctx, 0, sizeof(*ctx));
|
||||
ctx->desc = desc;
|
||||
|
||||
ctx->handle = g_module_open(desc->path, G_MODULE_BIND_LOCAL);
|
||||
if (ctx->handle == NULL) {
|
||||
error_setg(errp, "Could not load plugin %s: %s", desc->path, g_module_error());
|
||||
goto err_dlopen;
|
||||
}
|
||||
|
||||
if (!g_module_symbol(ctx->handle, "qemu_plugin_install", &sym)) {
|
||||
error_setg(errp, "Could not load plugin %s: %s", desc->path, g_module_error());
|
||||
goto err_symbol;
|
||||
}
|
||||
install = (qemu_plugin_install_func_t) sym;
|
||||
/* symbol was found; it could be NULL though */
|
||||
if (install == NULL) {
|
||||
error_setg(errp, "Could not load plugin %s: qemu_plugin_install is NULL",
|
||||
desc->path);
|
||||
goto err_symbol;
|
||||
}
|
||||
|
||||
if (!g_module_symbol(ctx->handle, "qemu_plugin_version", &sym)) {
|
||||
error_setg(errp, "Could not load plugin %s: plugin does not declare API version %s",
|
||||
desc->path, g_module_error());
|
||||
goto err_symbol;
|
||||
} else {
|
||||
int version = *(int *)sym;
|
||||
if (version < QEMU_PLUGIN_MIN_VERSION) {
|
||||
error_setg(errp, "Could not load plugin %s: plugin requires API version %d, but "
|
||||
"this QEMU supports only a minimum version of %d",
|
||||
desc->path, version, QEMU_PLUGIN_MIN_VERSION);
|
||||
goto err_symbol;
|
||||
} else if (version > QEMU_PLUGIN_VERSION) {
|
||||
error_setg(errp, "Could not load plugin %s: plugin requires API version %d, but "
|
||||
"this QEMU supports only up to version %d",
|
||||
desc->path, version, QEMU_PLUGIN_VERSION);
|
||||
goto err_symbol;
|
||||
}
|
||||
}
|
||||
|
||||
qemu_rec_mutex_lock(&plugin.lock);
|
||||
|
||||
/* find an unused random id with &ctx as the seed */
|
||||
ctx->id = (uint64_t)(uintptr_t)ctx;
|
||||
for (;;) {
|
||||
void *existing;
|
||||
|
||||
ctx->id = xorshift64star(ctx->id);
|
||||
existing = g_hash_table_lookup(plugin.id_ht, &ctx->id);
|
||||
if (likely(existing == NULL)) {
|
||||
bool success;
|
||||
|
||||
success = g_hash_table_insert(plugin.id_ht, &ctx->id, &ctx->id);
|
||||
g_assert(success);
|
||||
break;
|
||||
}
|
||||
}
|
||||
QTAILQ_INSERT_TAIL(&plugin.ctxs, ctx, entry);
|
||||
ctx->installing = true;
|
||||
rc = install(ctx->id, info, desc->argc, desc->argv);
|
||||
ctx->installing = false;
|
||||
if (rc) {
|
||||
error_setg(errp, "Could not load plugin %s: qemu_plugin_install returned error code %d",
|
||||
desc->path, rc);
|
||||
/*
|
||||
* we cannot rely on the plugin doing its own cleanup, so
|
||||
* call a full uninstall if the plugin did not yet call it.
|
||||
*/
|
||||
if (!ctx->uninstalling) {
|
||||
plugin_reset_uninstall(ctx->id, NULL, NULL, false);
|
||||
}
|
||||
}
|
||||
|
||||
qemu_rec_mutex_unlock(&plugin.lock);
|
||||
return rc;
|
||||
|
||||
err_symbol:
|
||||
g_module_close(ctx->handle);
|
||||
err_dlopen:
|
||||
qemu_vfree(ctx);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* call after having removed @desc from the list */
|
||||
static void plugin_desc_free(struct qemu_plugin_desc *desc)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < desc->argc; i++) {
|
||||
g_free(desc->argv[i]);
|
||||
}
|
||||
g_free(desc->argv);
|
||||
g_free(desc->path);
|
||||
g_free(desc);
|
||||
}
|
||||
|
||||
/**
|
||||
* qemu_plugin_load_list - load a list of plugins
|
||||
* @head: head of the list of descriptors of the plugins to be loaded
|
||||
*
|
||||
* Returns 0 if all plugins in the list are installed, !0 otherwise.
|
||||
*
|
||||
* Note: the descriptor of each successfully installed plugin is removed
|
||||
* from the list given by @head.
|
||||
*/
|
||||
int qemu_plugin_load_list(QemuPluginList *head, Error **errp)
|
||||
{
|
||||
struct qemu_plugin_desc *desc, *next;
|
||||
g_autofree qemu_info_t *info = g_new0(qemu_info_t, 1);
|
||||
|
||||
info->target_name = target_name();
|
||||
info->version.min = QEMU_PLUGIN_MIN_VERSION;
|
||||
info->version.cur = QEMU_PLUGIN_VERSION;
|
||||
|
||||
qemu_plugin_fillin_mode_info(info);
|
||||
|
||||
QTAILQ_FOREACH_SAFE(desc, head, entry, next) {
|
||||
int err;
|
||||
|
||||
err = plugin_load(desc, info, errp);
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
QTAILQ_REMOVE(head, desc, entry);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct qemu_plugin_reset_data {
|
||||
struct qemu_plugin_ctx *ctx;
|
||||
qemu_plugin_udata_cb_t cb;
|
||||
void *userdata;
|
||||
bool reset;
|
||||
};
|
||||
|
||||
QEMU_DISABLE_CFI
|
||||
static void plugin_reset_destroy__locked(struct qemu_plugin_reset_data *data)
|
||||
{
|
||||
struct qemu_plugin_ctx *ctx = data->ctx;
|
||||
enum qemu_plugin_event ev;
|
||||
bool success;
|
||||
|
||||
/*
|
||||
* After updating the subscription lists there is no need to wait for an RCU
|
||||
* grace period to elapse, because right now we either are in a "safe async"
|
||||
* work environment (i.e. all vCPUs are asleep), or no vCPUs have yet been
|
||||
* created.
|
||||
*/
|
||||
for (ev = 0; ev < QEMU_PLUGIN_EV_MAX; ev++) {
|
||||
plugin_unregister_cb__locked(ctx, ev);
|
||||
}
|
||||
|
||||
if (data->reset) {
|
||||
g_assert(ctx->resetting);
|
||||
if (data->cb) {
|
||||
data->cb(data->userdata);
|
||||
}
|
||||
ctx->resetting = false;
|
||||
g_free(data);
|
||||
return;
|
||||
}
|
||||
|
||||
g_assert(ctx->uninstalling);
|
||||
/* we cannot dlclose if we are going to return to plugin code */
|
||||
if (ctx->installing) {
|
||||
error_report("Calling qemu_plugin_uninstall from the install function "
|
||||
"is a bug. Instead, return !0 from the install function.");
|
||||
abort();
|
||||
}
|
||||
|
||||
success = g_hash_table_remove(plugin.id_ht, &ctx->id);
|
||||
g_assert(success);
|
||||
QTAILQ_REMOVE(&plugin.ctxs, ctx, entry);
|
||||
if (data->cb) {
|
||||
data->cb(data->userdata);
|
||||
}
|
||||
if (!g_module_close(ctx->handle)) {
|
||||
warn_report("%s: %s", __func__, g_module_error());
|
||||
}
|
||||
plugin_desc_free(ctx->desc);
|
||||
qemu_vfree(ctx);
|
||||
g_free(data);
|
||||
}
|
||||
|
||||
static void plugin_reset_destroy(struct qemu_plugin_reset_data *data)
|
||||
{
|
||||
qemu_rec_mutex_lock(&plugin.lock);
|
||||
plugin_reset_destroy__locked(data);
|
||||
qemu_rec_mutex_unlock(&plugin.lock);
|
||||
}
|
||||
|
||||
static void plugin_flush_destroy(CPUState *cpu, run_on_cpu_data arg)
|
||||
{
|
||||
struct qemu_plugin_reset_data *data = arg.host_ptr;
|
||||
|
||||
tb_flush__exclusive_or_serial();
|
||||
plugin_reset_destroy(data);
|
||||
}
|
||||
|
||||
void plugin_reset_uninstall(qemu_plugin_id_t id,
|
||||
qemu_plugin_udata_cb_t cb,
|
||||
void *userdata,
|
||||
bool reset)
|
||||
{
|
||||
struct qemu_plugin_reset_data *data;
|
||||
struct qemu_plugin_ctx *ctx = NULL;
|
||||
|
||||
WITH_QEMU_LOCK_GUARD(&plugin.lock) {
|
||||
ctx = plugin_id_to_ctx_locked(id);
|
||||
if (ctx->uninstalling || (reset && ctx->resetting)) {
|
||||
return;
|
||||
}
|
||||
ctx->resetting = reset;
|
||||
ctx->uninstalling = !reset;
|
||||
}
|
||||
|
||||
data = g_new(struct qemu_plugin_reset_data, 1);
|
||||
data->ctx = ctx;
|
||||
data->cb = cb;
|
||||
data->userdata = userdata;
|
||||
data->reset = reset;
|
||||
/*
|
||||
* Only flush the code cache if the vCPUs have been created. If so,
|
||||
* current_cpu must be non-NULL.
|
||||
*/
|
||||
if (current_cpu) {
|
||||
async_safe_run_on_cpu(current_cpu, plugin_flush_destroy,
|
||||
RUN_ON_CPU_HOST_PTR(data));
|
||||
} else {
|
||||
/*
|
||||
* If current_cpu isn't set, then we don't have yet any vCPU threads
|
||||
* and we therefore can remove the callbacks synchronously.
|
||||
*/
|
||||
plugin_reset_destroy(data);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
if not get_option('plugins')
|
||||
subdir_done()
|
||||
endif
|
||||
|
||||
qemu_plugin_symbols = configure_file(
|
||||
input: files('../include/plugins/qemu-plugin.h'),
|
||||
output: 'qemu-plugin.symbols',
|
||||
capture: true,
|
||||
command: [files('../scripts/qemu-plugin-symbols.py'), '@INPUT@'])
|
||||
|
||||
# Modules need more symbols than just those in plugins/qemu-plugins.symbols
|
||||
if not enable_modules
|
||||
if host_os == 'darwin'
|
||||
configure_file(
|
||||
input: qemu_plugin_symbols,
|
||||
output: 'qemu-plugins-ld64.symbols',
|
||||
capture: true,
|
||||
command: ['sed', '-ne', 's/^[[:space:]]*\\(qemu_.*\\);/_\\1/p', '@INPUT@'])
|
||||
emulator_link_args += ['-Wl,-exported_symbols_list,plugins/qemu-plugins-ld64.symbols']
|
||||
elif host_os == 'windows' and meson.get_compiler('c').get_id() == 'clang'
|
||||
# LLVM/lld does not support exporting specific symbols. However, it works
|
||||
# out of the box with dllexport/dllimport attribute we set in the code.
|
||||
else
|
||||
emulator_link_args += ['-Xlinker', '--dynamic-list=' + qemu_plugin_symbols.full_path()]
|
||||
endif
|
||||
endif
|
||||
|
||||
if host_os == 'windows'
|
||||
# Generate a .lib file for plugins to link against.
|
||||
# First, create a .def file listing all the symbols a plugin should expect to have
|
||||
# available in qemu
|
||||
win32_plugin_def = configure_file(
|
||||
input: qemu_plugin_symbols,
|
||||
output: 'qemu_plugin_api.def',
|
||||
capture: true,
|
||||
command: [python, '-c', 'import fileinput, re; print("EXPORTS", end=""); [print(re.sub(r"[{};]", "", line), end="") for line in fileinput.input()]', '@INPUT@'])
|
||||
|
||||
# then use dlltool to assemble a delaylib.
|
||||
# The delaylib will have an "imaginary" name (qemu.exe), that is used by the
|
||||
# linker file we add with plugins (win32_linker.c) to identify that we want
|
||||
# to find missing symbols in current program.
|
||||
win32_qemu_plugin_api_link_flags = ['-Lplugins', '-lqemu_plugin_api']
|
||||
if meson.get_compiler('c').get_id() == 'clang'
|
||||
if host_machine.cpu() == 'x86_64'
|
||||
dlltool_target = 'i386:x86-64'
|
||||
elif host_machine.cpu() == 'aarch64'
|
||||
dlltool_target = 'arm64'
|
||||
else
|
||||
error('Unknown machine')
|
||||
endif
|
||||
# With LLVM/lld, delaylib is specified at link time (-delayload)
|
||||
dlltool = find_program('llvm-dlltool', required: true)
|
||||
dlltool_cmd = [dlltool, '-m', dlltool_target,'-d', '@INPUT@', '-l', '@OUTPUT@', '-D', 'qemu.exe']
|
||||
win32_qemu_plugin_api_link_flags += ['-Wl,-delayload=qemu.exe']
|
||||
else
|
||||
# With gcc/ld, delay lib is built with a specific delay parameter.
|
||||
dlltool = find_program('dlltool', required: true)
|
||||
dlltool_cmd = [dlltool, '--input-def', '@INPUT@',
|
||||
'--output-delaylib', '@OUTPUT@', '--dllname', 'qemu.exe']
|
||||
endif
|
||||
win32_qemu_plugin_api = configure_file(
|
||||
input: win32_plugin_def,
|
||||
output: 'libqemu_plugin_api.a',
|
||||
command: dlltool_cmd
|
||||
)
|
||||
win32_qemu_plugin_api_lib = static_library('win32_qemu_plugin_api',
|
||||
link_depends: win32_qemu_plugin_api)
|
||||
endif
|
||||
|
||||
if host_os == 'windows'
|
||||
plugins_deps = declare_dependency(sources: [files('win32_linker.c')],
|
||||
include_directories: '../include/plugins',
|
||||
link_with: win32_qemu_plugin_api_lib,
|
||||
link_args: win32_qemu_plugin_api_link_flags,
|
||||
dependencies: glib)
|
||||
else
|
||||
plugins_deps = declare_dependency(include_directories: '../include/plugins',
|
||||
dependencies: glib)
|
||||
endif
|
||||
|
||||
user_ss.add(files('user.c', 'api-user.c'))
|
||||
system_ss.add(files('system.c', 'api-system.c'))
|
||||
|
||||
user_ss.add(files('api.c', 'core.c'))
|
||||
system_ss.add(files('api.c', 'core.c'))
|
||||
|
||||
common_ss.add(files('loader.c'))
|
||||
|
||||
@@ -0,0 +1,129 @@
|
||||
/*
|
||||
* Plugin Shared Internal Functions
|
||||
*
|
||||
* Copyright (C) 2019, Linaro
|
||||
*
|
||||
* License: GNU GPL, version 2 or later.
|
||||
* See the COPYING file in the top-level directory.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
|
||||
#ifndef PLUGIN_H
|
||||
#define PLUGIN_H
|
||||
|
||||
#include <gmodule.h>
|
||||
#include "qemu/queue.h"
|
||||
#include "qemu/qht.h"
|
||||
|
||||
#define QEMU_PLUGIN_MIN_VERSION 7
|
||||
|
||||
/* global state */
|
||||
struct qemu_plugin_state {
|
||||
QTAILQ_HEAD(, qemu_plugin_ctx) ctxs;
|
||||
QLIST_HEAD(, qemu_plugin_cb) cb_lists[QEMU_PLUGIN_EV_MAX];
|
||||
/*
|
||||
* Use the HT as a hash map by inserting k == v, which saves memory as
|
||||
* documented by GLib. The parent struct is obtained with container_of().
|
||||
*/
|
||||
GHashTable *id_ht;
|
||||
/*
|
||||
* Use the HT as a hash map. Note that we could use a list here,
|
||||
* but with the HT we avoid adding a field to CPUState.
|
||||
*/
|
||||
GHashTable *cpu_ht;
|
||||
QLIST_HEAD(, qemu_plugin_scoreboard) scoreboards;
|
||||
size_t scoreboard_alloc_size;
|
||||
DECLARE_BITMAP(mask, QEMU_PLUGIN_EV_MAX);
|
||||
/*
|
||||
* @lock protects the struct as well as ctx->uninstalling.
|
||||
* The lock must be acquired by all API ops.
|
||||
* The lock is recursive, which greatly simplifies things, e.g.
|
||||
* callback registration from qemu_plugin_vcpu_for_each().
|
||||
*/
|
||||
QemuRecMutex lock;
|
||||
/*
|
||||
* HT of callbacks invoked from helpers. All entries are freed when
|
||||
* the code cache is flushed.
|
||||
*/
|
||||
struct qht dyn_cb_arr_ht;
|
||||
/* How many vcpus were started */
|
||||
int num_vcpus;
|
||||
};
|
||||
|
||||
|
||||
struct qemu_plugin_ctx {
|
||||
GModule *handle;
|
||||
qemu_plugin_id_t id;
|
||||
struct qemu_plugin_cb *callbacks[QEMU_PLUGIN_EV_MAX];
|
||||
QTAILQ_ENTRY(qemu_plugin_ctx) entry;
|
||||
/*
|
||||
* keep a reference to @desc until uninstall, so that plugins do not have
|
||||
* to strdup plugin args.
|
||||
*/
|
||||
struct qemu_plugin_desc *desc;
|
||||
bool installing;
|
||||
bool uninstalling;
|
||||
bool resetting;
|
||||
};
|
||||
|
||||
struct qemu_plugin_ctx *plugin_id_to_ctx_locked(qemu_plugin_id_t id);
|
||||
|
||||
void plugin_register_inline_op_on_entry(GArray **arr,
|
||||
enum qemu_plugin_mem_rw rw,
|
||||
enum qemu_plugin_op op,
|
||||
qemu_plugin_u64 entry,
|
||||
uint64_t imm);
|
||||
|
||||
void plugin_reset_uninstall(qemu_plugin_id_t id,
|
||||
qemu_plugin_udata_cb_t cb,
|
||||
void *userdata,
|
||||
bool reset);
|
||||
|
||||
void plugin_register_cb(qemu_plugin_id_t id, enum qemu_plugin_event ev,
|
||||
void *func);
|
||||
|
||||
void plugin_unregister_cb__locked(struct qemu_plugin_ctx *ctx,
|
||||
enum qemu_plugin_event ev);
|
||||
|
||||
void
|
||||
plugin_register_cb_udata(qemu_plugin_id_t id, enum qemu_plugin_event ev,
|
||||
void *func, void *udata);
|
||||
|
||||
void
|
||||
plugin_register_dyn_cb__udata(GArray **arr,
|
||||
qemu_plugin_vcpu_udata_cb_t cb,
|
||||
enum qemu_plugin_cb_flags flags, void *udata);
|
||||
|
||||
void
|
||||
plugin_register_dyn_cond_cb__udata(GArray **arr,
|
||||
qemu_plugin_vcpu_udata_cb_t cb,
|
||||
enum qemu_plugin_cb_flags flags,
|
||||
enum qemu_plugin_cond cond,
|
||||
qemu_plugin_u64 entry,
|
||||
uint64_t imm,
|
||||
void *udata);
|
||||
|
||||
void plugin_register_vcpu_mem_cb(GArray **arr,
|
||||
void *cb,
|
||||
enum qemu_plugin_cb_flags flags,
|
||||
enum qemu_plugin_mem_rw rw,
|
||||
void *udata);
|
||||
|
||||
void exec_inline_op(enum plugin_dyn_cb_type type,
|
||||
struct qemu_plugin_inline_cb *cb,
|
||||
int cpu_index);
|
||||
|
||||
int plugin_num_vcpus(void);
|
||||
|
||||
struct qemu_plugin_scoreboard *plugin_scoreboard_new(size_t element_size);
|
||||
|
||||
void plugin_scoreboard_free(struct qemu_plugin_scoreboard *score);
|
||||
|
||||
/**
|
||||
* qemu_plugin_fillin_mode_info() - populate mode specific info
|
||||
* info: pointer to qemu_info_t structure
|
||||
*/
|
||||
void qemu_plugin_fillin_mode_info(qemu_info_t *info);
|
||||
|
||||
#endif /* PLUGIN_H */
|
||||
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* QEMU Plugin system-emulation helpers
|
||||
*
|
||||
* Helpers that are specific to system emulation.
|
||||
*
|
||||
* Copyright (C) 2017, Emilio G. Cota <[email protected]>
|
||||
* Copyright (C) 2019-2025, Linaro
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
|
||||
#include "qemu/osdep.h"
|
||||
#include "qemu/plugin.h"
|
||||
#include "hw/core/boards.h"
|
||||
|
||||
#include "plugin.h"
|
||||
|
||||
void qemu_plugin_fillin_mode_info(qemu_info_t *info)
|
||||
{
|
||||
MachineState *ms = MACHINE(qdev_get_machine());
|
||||
info->system_emulation = true;
|
||||
info->system.smp_vcpus = ms->smp.cpus;
|
||||
info->system.max_vcpus = ms->smp.max_cpus;
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
/*
|
||||
* QEMU Plugin user-mode helpers
|
||||
*
|
||||
* Helpers that are specific to user-mode.
|
||||
*
|
||||
* Copyright (C) 2017, Emilio G. Cota <[email protected]>
|
||||
* Copyright (C) 2019-2025, Linaro
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
|
||||
#include "qemu/osdep.h"
|
||||
#include "qemu/plugin.h"
|
||||
#include "plugin.h"
|
||||
|
||||
void qemu_plugin_fillin_mode_info(qemu_info_t *info)
|
||||
{
|
||||
info->system_emulation = false;
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright (C) 2023, Greg Manning <[email protected]>
|
||||
*
|
||||
* This hook, __pfnDliFailureHook2, is documented in the microsoft documentation here:
|
||||
* https://learn.microsoft.com/en-us/cpp/build/reference/error-handling-and-notification
|
||||
* It gets called when a delay-loaded DLL encounters various errors.
|
||||
* We handle the specific case of a DLL looking for a "qemu.exe",
|
||||
* and give it the running executable (regardless of what it is named).
|
||||
*
|
||||
* This work is licensed under the terms of the GNU LGPL, version 2 or later.
|
||||
* See the COPYING.LIB file in the top-level directory.
|
||||
*/
|
||||
|
||||
#include <windows.h>
|
||||
#include <delayimp.h>
|
||||
|
||||
FARPROC WINAPI dll_failure_hook(unsigned dliNotify, PDelayLoadInfo pdli);
|
||||
|
||||
|
||||
PfnDliHook __pfnDliFailureHook2 = dll_failure_hook;
|
||||
|
||||
FARPROC WINAPI dll_failure_hook(unsigned dliNotify, PDelayLoadInfo pdli) {
|
||||
if (dliNotify == dliFailLoadLib) {
|
||||
/* If the failing request was for qemu.exe, ... */
|
||||
if (strcmp(pdli->szDll, "qemu.exe") == 0) {
|
||||
/* Then pass back a pointer to the top level module. */
|
||||
HMODULE top = GetModuleHandle(NULL);
|
||||
return (FARPROC) top;
|
||||
}
|
||||
}
|
||||
/* Otherwise we can't do anything special. */
|
||||
return 0;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user