Import QEMU upstream snapshot d2e570c

Upstream: https://gitlab.com/qemu-project/qemu.git

Upstream-Commit: d2e570cc0f97b936902a5b1b86b73c0f5998b475
This commit is contained in:
2026-08-31 02:15:30 +02:00
commit cf256aa081
11315 changed files with 3598369 additions and 0 deletions
+131
View File
@@ -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));
}
}
+57
View File
@@ -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
View File
@@ -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
View File
@@ -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;
}
}
+422
View File
@@ -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);
}
}
+88
View File
@@ -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'))
+129
View File
@@ -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 */
+24
View File
@@ -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;
}
+19
View File
@@ -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;
}
+34
View File
@@ -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;
}