* [PATCH v6 3/9] plugins: Add enforcement of QEMU_PLUGIN_CB flags in register R/W callbacks
2025-06-02 6:08 [PATCH v6 0/9] Add additional plugin API functions to read and write memory and registers Rowan Hart
2025-06-02 6:08 ` [PATCH v6 1/9] gdbstub: Expose gdb_write_register function to consumers of gdbstub Rowan Hart
2025-06-02 6:08 ` [PATCH v6 2/9] plugins: Add register write API Rowan Hart
@ 2025-06-02 6:08 ` Rowan Hart
2025-06-02 6:08 ` [PATCH v6 4/9] plugins: Add memory virtual address write API Rowan Hart
` (5 subsequent siblings)
8 siblings, 0 replies; 10+ messages in thread
From: Rowan Hart @ 2025-06-02 6:08 UTC (permalink / raw)
To: qemu-devel
Cc: Yanan Wang, Richard Henderson, Alexandre Iooss, Zhao Liu,
Eduardo Habkost, Mahmoud Mandour, Alex Bennée,
Pierrick Bouvier, Philippe Mathieu-Daudé, Paolo Bonzini,
Marcel Apfelbaum, Rowan Hart
This patch adds functionality to enforce the requested QEMU_PLUGIN_CB_
flags level passed when registering a callback function using the
plugins API. Each time a callback is about to be invoked, a thread-local
variable will be updated with the level that callback requested. Then,
called API functions (in particular, the register read and write API)
will call qemu_plugin_get_cb_flags() to check the level is at least the
level they require.
Signed-off-by: Rowan Hart <rowanbhart@gmail.com>
---
accel/tcg/plugin-gen.c | 30 ++++++++++++++++++++++++++++++
include/hw/core/cpu.h | 1 +
include/qemu/plugin.h | 4 ++++
include/qemu/qemu-plugin.h | 3 ---
plugins/api.c | 8 ++++++++
plugins/core.c | 32 ++++++++++++++++++++++++++------
6 files changed, 69 insertions(+), 9 deletions(-)
diff --git a/accel/tcg/plugin-gen.c b/accel/tcg/plugin-gen.c
index c1da753894..9920381a84 100644
--- a/accel/tcg/plugin-gen.c
+++ b/accel/tcg/plugin-gen.c
@@ -117,10 +117,20 @@ static TCGv_i32 gen_cpu_index(void)
static void gen_udata_cb(struct qemu_plugin_regular_cb *cb)
{
TCGv_i32 cpu_index = gen_cpu_index();
+ enum qemu_plugin_cb_flags cb_flags =
+ tcg_call_to_qemu_plugin_cb_flags(cb->info->flags);
+ TCGv_i32 flags = tcg_constant_i32(cb_flags);
+ TCGv_i32 clear_flags = tcg_constant_i32(QEMU_PLUGIN_CB_NO_REGS);
+ tcg_gen_st_i32(flags, tcg_env,
+ offsetof(CPUState, neg.plugin_cb_flags) - sizeof(CPUState));
tcg_gen_call2(cb->f.vcpu_udata, cb->info, NULL,
tcgv_i32_temp(cpu_index),
tcgv_ptr_temp(tcg_constant_ptr(cb->userp)));
+ tcg_gen_st_i32(clear_flags, tcg_env,
+ offsetof(CPUState, neg.plugin_cb_flags) - sizeof(CPUState));
tcg_temp_free_i32(cpu_index);
+ tcg_temp_free_i32(flags);
+ tcg_temp_free_i32(clear_flags);
}
static TCGv_ptr gen_plugin_u64_ptr(qemu_plugin_u64 entry)
@@ -173,10 +183,20 @@ static void gen_udata_cond_cb(struct qemu_plugin_conditional_cb *cb)
tcg_gen_ld_i64(val, ptr, 0);
tcg_gen_brcondi_i64(cond, val, cb->imm, after_cb);
TCGv_i32 cpu_index = gen_cpu_index();
+ enum qemu_plugin_cb_flags cb_flags =
+ tcg_call_to_qemu_plugin_cb_flags(cb->info->flags);
+ TCGv_i32 flags = tcg_constant_i32(cb_flags);
+ TCGv_i32 clear_flags = tcg_constant_i32(QEMU_PLUGIN_CB_NO_REGS);
+ tcg_gen_st_i32(flags, tcg_env,
+ offsetof(CPUState, neg.plugin_cb_flags) - sizeof(CPUState));
tcg_gen_call2(cb->f.vcpu_udata, cb->info, NULL,
tcgv_i32_temp(cpu_index),
tcgv_ptr_temp(tcg_constant_ptr(cb->userp)));
+ tcg_gen_st_i32(clear_flags, tcg_env,
+ offsetof(CPUState, neg.plugin_cb_flags) - sizeof(CPUState));
tcg_temp_free_i32(cpu_index);
+ tcg_temp_free_i32(flags);
+ tcg_temp_free_i32(clear_flags);
gen_set_label(after_cb);
tcg_temp_free_i64(val);
@@ -210,12 +230,22 @@ static void gen_mem_cb(struct qemu_plugin_regular_cb *cb,
qemu_plugin_meminfo_t meminfo, TCGv_i64 addr)
{
TCGv_i32 cpu_index = gen_cpu_index();
+ enum qemu_plugin_cb_flags cb_flags =
+ tcg_call_to_qemu_plugin_cb_flags(cb->info->flags);
+ TCGv_i32 flags = tcg_constant_i32(cb_flags);
+ TCGv_i32 clear_flags = tcg_constant_i32(QEMU_PLUGIN_CB_NO_REGS);
+ tcg_gen_st_i32(flags, tcg_env,
+ offsetof(CPUState, neg.plugin_cb_flags) - sizeof(CPUState));
tcg_gen_call4(cb->f.vcpu_mem, cb->info, NULL,
tcgv_i32_temp(cpu_index),
tcgv_i32_temp(tcg_constant_i32(meminfo)),
tcgv_i64_temp(addr),
tcgv_ptr_temp(tcg_constant_ptr(cb->userp)));
+ tcg_gen_st_i32(clear_flags, tcg_env,
+ offsetof(CPUState, neg.plugin_cb_flags) - sizeof(CPUState));
tcg_temp_free_i32(cpu_index);
+ tcg_temp_free_i32(flags);
+ tcg_temp_free_i32(clear_flags);
}
static void inject_cb(struct qemu_plugin_dyn_cb *cb)
diff --git a/include/hw/core/cpu.h b/include/hw/core/cpu.h
index 1e87f7d393..d3cc9a5224 100644
--- a/include/hw/core/cpu.h
+++ b/include/hw/core/cpu.h
@@ -368,6 +368,7 @@ typedef struct CPUNegativeOffsetState {
GArray *plugin_mem_cbs;
uint64_t plugin_mem_value_low;
uint64_t plugin_mem_value_high;
+ int32_t plugin_cb_flags;
#endif
IcountDecr icount_decr;
bool can_do_io;
diff --git a/include/qemu/plugin.h b/include/qemu/plugin.h
index 9726a9ebf3..2fef2e7d71 100644
--- a/include/qemu/plugin.h
+++ b/include/qemu/plugin.h
@@ -209,6 +209,10 @@ void qemu_plugin_user_prefork_lock(void);
*/
void qemu_plugin_user_postfork(bool is_child);
+enum qemu_plugin_cb_flags tcg_call_to_qemu_plugin_cb_flags(int flags);
+
+enum qemu_plugin_cb_flags qemu_plugin_get_cb_flags(void);
+
#else /* !CONFIG_PLUGIN */
static inline void qemu_plugin_add_opts(void)
diff --git a/include/qemu/qemu-plugin.h b/include/qemu/qemu-plugin.h
index cfe1692ecb..120fb626a6 100644
--- a/include/qemu/qemu-plugin.h
+++ b/include/qemu/qemu-plugin.h
@@ -254,9 +254,6 @@ typedef struct {
* @QEMU_PLUGIN_CB_NO_REGS: callback does not access the CPU's regs
* @QEMU_PLUGIN_CB_R_REGS: callback reads the CPU's regs
* @QEMU_PLUGIN_CB_RW_REGS: callback reads and writes the CPU's regs
- *
- * Note: currently QEMU_PLUGIN_CB_RW_REGS is unused, plugins cannot change
- * system register state.
*/
enum qemu_plugin_cb_flags {
QEMU_PLUGIN_CB_NO_REGS,
diff --git a/plugins/api.c b/plugins/api.c
index 3a7add50d2..16141f5c25 100644
--- a/plugins/api.c
+++ b/plugins/api.c
@@ -437,6 +437,10 @@ int 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 -1;
+ }
+
return gdb_read_register(current_cpu, buf, GPOINTER_TO_INT(reg) - 1);
}
@@ -445,6 +449,10 @@ int qemu_plugin_write_register(struct qemu_plugin_register *reg,
{
g_assert(current_cpu);
+ if (buf->len == 0 || qemu_plugin_get_cb_flags() != QEMU_PLUGIN_CB_RW_REGS) {
+ return 0;
+ }
+
return gdb_write_register(current_cpu, buf->data, GPOINTER_TO_INT(reg) - 1);
}
diff --git a/plugins/core.c b/plugins/core.c
index eb9281fe54..34bddb6c1c 100644
--- a/plugins/core.c
+++ b/plugins/core.c
@@ -364,14 +364,15 @@ void plugin_register_dyn_cb__udata(GArray **arr,
enum qemu_plugin_cb_flags flags,
void *udata)
{
- static TCGHelperInfo info[3] = {
+ 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,
/*
* Match qemu_plugin_vcpu_udata_cb_t:
* void (*)(uint32_t, void *)
*/
- [0 ... 2].typemask = (dh_typemask(void, 0) |
+ [0 ... 3].typemask = (dh_typemask(void, 0) |
dh_typemask(i32, 1) |
dh_typemask(ptr, 2))
};
@@ -393,14 +394,15 @@ void plugin_register_dyn_cond_cb__udata(GArray **arr,
uint64_t imm,
void *udata)
{
- static TCGHelperInfo info[3] = {
+ 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,
/*
* Match qemu_plugin_vcpu_udata_cb_t:
* void (*)(uint32_t, void *)
*/
- [0 ... 2].typemask = (dh_typemask(void, 0) |
+ [0 ... 3].typemask = (dh_typemask(void, 0) |
dh_typemask(i32, 1) |
dh_typemask(ptr, 2))
};
@@ -431,14 +433,15 @@ void plugin_register_vcpu_mem_cb(GArray **arr,
!__builtin_types_compatible_p(qemu_plugin_meminfo_t, uint32_t) &&
!__builtin_types_compatible_p(qemu_plugin_meminfo_t, int32_t));
- static TCGHelperInfo info[3] = {
+ 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,
/*
* Match qemu_plugin_vcpu_mem_cb_t:
* void (*)(uint32_t, qemu_plugin_meminfo_t, uint64_t, void *)
*/
- [0 ... 2].typemask =
+ [0 ... 3].typemask =
(dh_typemask(void, 0) |
dh_typemask(i32, 1) |
(__builtin_types_compatible_p(qemu_plugin_meminfo_t, uint32_t)
@@ -760,3 +763,20 @@ void plugin_scoreboard_free(struct qemu_plugin_scoreboard *score)
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;
+ }
+}
+
+enum qemu_plugin_cb_flags qemu_plugin_get_cb_flags(void)
+{
+ assert(current_cpu);
+ return current_cpu->neg.plugin_cb_flags;
+}
--
2.49.0
^ permalink raw reply related [flat|nested] 10+ messages in thread
* [PATCH v6 6/9] plugins: Add patcher plugin and test
2025-06-02 6:08 [PATCH v6 0/9] Add additional plugin API functions to read and write memory and registers Rowan Hart
` (4 preceding siblings ...)
2025-06-02 6:08 ` [PATCH v6 5/9] plugins: Add memory hardware address read/write API Rowan Hart
@ 2025-06-02 6:08 ` Rowan Hart
2025-06-02 6:08 ` [PATCH v6 7/9] plugins: Add hypercalls " Rowan Hart
` (2 subsequent siblings)
8 siblings, 0 replies; 10+ messages in thread
From: Rowan Hart @ 2025-06-02 6:08 UTC (permalink / raw)
To: qemu-devel
Cc: Yanan Wang, Richard Henderson, Alexandre Iooss, Zhao Liu,
Eduardo Habkost, Mahmoud Mandour, Alex Bennée,
Pierrick Bouvier, Philippe Mathieu-Daudé, Paolo Bonzini,
Marcel Apfelbaum, novafacing
From: novafacing <rowanbhart@gmail.com>
This patch adds a plugin that exercises the virtual and hardware memory
read-write API functions added in a previous patch. The plugin takes a
target and patch byte sequence, and will overwrite any instruction
matching the target byte sequence with the patch.
Signed-off-by: novafacing <rowanbhart@gmail.com>
Signed-off-by: Rowan Hart <rowanbhart@gmail.com>
---
tests/tcg/Makefile.target | 1 +
tests/tcg/plugins/meson.build | 2 +-
tests/tcg/plugins/patch.c | 297 ++++++++++++++++++++++
tests/tcg/x86_64/Makefile.softmmu-target | 32 ++-
tests/tcg/x86_64/system/patch-target.c | 27 ++
tests/tcg/x86_64/system/validate-patch.py | 39 +++
6 files changed, 392 insertions(+), 6 deletions(-)
create mode 100644 tests/tcg/plugins/patch.c
create mode 100644 tests/tcg/x86_64/system/patch-target.c
create mode 100755 tests/tcg/x86_64/system/validate-patch.py
diff --git a/tests/tcg/Makefile.target b/tests/tcg/Makefile.target
index 95ff76ea44..4b709a9d18 100644
--- a/tests/tcg/Makefile.target
+++ b/tests/tcg/Makefile.target
@@ -176,6 +176,7 @@ RUN_TESTS+=$(EXTRA_RUNS)
# Some plugins need additional arguments above the default to fully
# exercise things. We can define them on a per-test basis here.
run-plugin-%-with-libmem.so: PLUGIN_ARGS=$(COMMA)inline=true
+run-plugin-%-with-libpatch.so: PLUGIN_ARGS=$(COMMA)target=ffffffff$(COMMA)patch=00000000
ifeq ($(filter %-softmmu, $(TARGET)),)
run-%: %
diff --git a/tests/tcg/plugins/meson.build b/tests/tcg/plugins/meson.build
index 41f02f2c7f..163042e601 100644
--- a/tests/tcg/plugins/meson.build
+++ b/tests/tcg/plugins/meson.build
@@ -1,6 +1,6 @@
t = []
if get_option('plugins')
- foreach i : ['bb', 'empty', 'inline', 'insn', 'mem', 'reset', 'syscall']
+ foreach i : ['bb', 'empty', 'inline', 'insn', 'mem', 'reset', 'syscall', 'patch']
if host_os == 'windows'
t += shared_module(i, files(i + '.c') + '../../../contrib/plugins/win32_linker.c',
include_directories: '../../../include/qemu',
diff --git a/tests/tcg/plugins/patch.c b/tests/tcg/plugins/patch.c
new file mode 100644
index 0000000000..6e83418b85
--- /dev/null
+++ b/tests/tcg/plugins/patch.c
@@ -0,0 +1,297 @@
+/*
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * This plugin patches instructions matching a pattern to a different
+ * instruction as they execute
+ *
+ */
+
+#include "glib.h"
+#include "glibconfig.h"
+
+#include <qemu-plugin.h>
+#include <string.h>
+#include <stdio.h>
+
+QEMU_PLUGIN_EXPORT int qemu_plugin_version = QEMU_PLUGIN_VERSION;
+
+static bool use_hwaddr;
+static bool debug_insns;
+static GByteArray *target_data;
+static GByteArray *patch_data;
+
+/**
+ * Parse a string of hexadecimal digits into a GByteArray. The string must be
+ * even length
+ */
+static GByteArray *str_to_bytes(const char *str)
+{
+ GByteArray *bytes = g_byte_array_new();
+ char byte[3] = {0};
+ size_t len = strlen(str);
+ guint8 value = 0;
+
+ if (len % 2 != 0) {
+ g_byte_array_free(bytes, true);
+ return NULL;
+ }
+
+ for (size_t i = 0; i < len; i += 2) {
+ byte[0] = str[i];
+ byte[1] = str[i + 1];
+ value = (guint8)g_ascii_strtoull(byte, NULL, 16);
+ g_byte_array_append(bytes, &value, 1);
+ }
+
+ return bytes;
+}
+
+static void patch_hwaddr(unsigned int vcpu_index, void *userdata)
+{
+ uint64_t addr = (uint64_t)userdata;
+ GString *str = g_string_new(NULL);
+ g_string_printf(str, "patching: @0x%"
+ PRIx64 "\n",
+ addr);
+ qemu_plugin_outs(str->str);
+ g_string_free(str, true);
+
+ enum qemu_plugin_hwaddr_operation_result result =
+ qemu_plugin_write_memory_hwaddr(addr, patch_data);
+
+
+ if (result != QEMU_PLUGIN_HWADDR_OPERATION_OK) {
+ GString *errmsg = g_string_new(NULL);
+ g_string_printf(errmsg, "Failed to write memory: %d\n", result);
+ qemu_plugin_outs(errmsg->str);
+ g_string_free(errmsg, true);
+ return;
+ }
+
+ GByteArray *read_data = g_byte_array_new();
+
+ result = qemu_plugin_read_memory_hwaddr(addr, read_data,
+ patch_data->len);
+
+ qemu_plugin_outs("Reading memory...\n");
+
+ if (result != QEMU_PLUGIN_HWADDR_OPERATION_OK) {
+ GString *errmsg = g_string_new(NULL);
+ g_string_printf(errmsg, "Failed to read memory: %d\n", result);
+ qemu_plugin_outs(errmsg->str);
+ g_string_free(errmsg, true);
+ return;
+ }
+
+ if (memcmp(patch_data->data, read_data->data, patch_data->len) != 0) {
+ qemu_plugin_outs("Failed to read back written data\n");
+ }
+
+ qemu_plugin_outs("Success!\n");
+
+ return;
+}
+
+static void patch_vaddr(unsigned int vcpu_index, void *userdata)
+{
+ uint64_t addr = (uint64_t)userdata;
+ uint64_t hwaddr = 0;
+ if (!qemu_plugin_translate_vaddr(addr, &hwaddr)) {
+ qemu_plugin_outs("Failed to translate vaddr\n");
+ return;
+ }
+ GString *str = g_string_new(NULL);
+ g_string_printf(str, "patching: @0x%"
+ PRIx64 " hw: @0x%" PRIx64 "\n",
+ addr, hwaddr);
+ qemu_plugin_outs(str->str);
+ g_string_free(str, true);
+
+ qemu_plugin_outs("Writing memory (vaddr)...\n");
+
+ if (!qemu_plugin_write_memory_vaddr(addr, patch_data)) {
+ qemu_plugin_outs("Failed to write memory\n");
+ return;
+ }
+
+ qemu_plugin_outs("Reading memory (vaddr)...\n");
+
+
+ GByteArray *read_data = g_byte_array_new();
+
+ if (!qemu_plugin_read_memory_vaddr(addr, read_data, patch_data->len)) {
+ qemu_plugin_outs("Failed to read memory\n");
+ return;
+ }
+
+ if (memcmp(patch_data->data, read_data->data, patch_data->len) != 0) {
+ qemu_plugin_outs("Failed to read back written data\n");
+ }
+
+ qemu_plugin_outs("Success!\n");
+
+ return;
+}
+
+static void debug_disas(unsigned int vcpu_index, void *userdata)
+{
+ GString *debug_info = (GString *)userdata;
+ qemu_plugin_outs(debug_info->str);
+}
+
+static void debug_print_newline(unsigned int vcpu_index, void *userdata)
+{
+ qemu_plugin_outs("\n");
+}
+
+/*
+ * Callback on translation of a translation block.
+ */
+static void vcpu_tb_trans_cb(qemu_plugin_id_t id, struct qemu_plugin_tb *tb)
+{
+ uint64_t addr = 0;
+ GByteArray *insn_data = g_byte_array_new();
+ for (size_t i = 0; i < qemu_plugin_tb_n_insns(tb); i++) {
+ struct qemu_plugin_insn *insn = qemu_plugin_tb_get_insn(tb, i);
+
+ if (use_hwaddr) {
+ uint64_t vaddr = qemu_plugin_insn_vaddr(insn);
+ if (!qemu_plugin_translate_vaddr(vaddr, &addr)) {
+ qemu_plugin_outs("Failed to translate vaddr\n");
+ continue;
+ }
+ } else {
+ addr = qemu_plugin_insn_vaddr(insn);
+ }
+
+ g_byte_array_set_size(insn_data, qemu_plugin_insn_size(insn));
+ qemu_plugin_insn_data(insn, insn_data->data, insn_data->len);
+
+ if (insn_data->len >= target_data->len &&
+ !memcmp(insn_data->data, target_data->data,
+ MIN(target_data->len, insn_data->len))) {
+ if (use_hwaddr) {
+ qemu_plugin_register_vcpu_tb_exec_cb(tb, patch_hwaddr,
+ QEMU_PLUGIN_CB_NO_REGS,
+ (void *)addr);
+ } else {
+ qemu_plugin_register_vcpu_tb_exec_cb(tb, patch_vaddr,
+ QEMU_PLUGIN_CB_NO_REGS,
+ (void *)addr);
+ }
+ }
+ }
+ for (size_t i = 0; i < qemu_plugin_tb_n_insns(tb); i++) {
+ struct qemu_plugin_insn *insn = qemu_plugin_tb_get_insn(tb, i);
+ uint64_t vaddr = qemu_plugin_insn_vaddr(insn);
+ uint64_t hwaddr = (uint64_t)qemu_plugin_insn_haddr(insn);
+ uint64_t translated_hwaddr = 0;
+ if (!qemu_plugin_translate_vaddr(vaddr, &translated_hwaddr)) {
+ qemu_plugin_outs("Failed to translate vaddr\n");
+ continue;
+ }
+ char *disas = qemu_plugin_insn_disas(insn);
+ GString *str = g_string_new(NULL);
+ g_string_printf(str,
+ "vaddr: 0x%" PRIx64 " hwaddr: 0x%" PRIx64
+ " translated: 0x%" PRIx64 " : %s\n",
+ vaddr, hwaddr, translated_hwaddr, disas);
+ g_free(disas);
+ if (debug_insns) {
+ qemu_plugin_register_vcpu_insn_exec_cb(insn, debug_disas,
+ QEMU_PLUGIN_CB_NO_REGS,
+ str);
+ }
+
+ }
+
+ if (debug_insns) {
+ qemu_plugin_register_vcpu_tb_exec_cb(tb, debug_print_newline,
+ QEMU_PLUGIN_CB_NO_REGS,
+ NULL);
+ }
+
+ g_byte_array_free(insn_data, true);
+}
+
+static void usage(void)
+{
+ fprintf(stderr, "Usage: <lib>,target=<target>,patch=<patch>"
+ "[,use_hwaddr=<use_hwaddr>]"
+ "[,debug_insns=<debug_insns>]\n");
+}
+
+/*
+ * Called when the plugin is installed
+ */
+QEMU_PLUGIN_EXPORT int qemu_plugin_install(qemu_plugin_id_t id,
+ const qemu_info_t *info, int argc,
+ char **argv)
+{
+
+ use_hwaddr = true;
+ debug_insns = false;
+ target_data = NULL;
+ patch_data = NULL;
+
+ if (argc > 4) {
+ usage();
+ return -1;
+ }
+
+ for (size_t i = 0; i < argc; i++) {
+ char *opt = argv[i];
+ g_auto(GStrv) tokens = g_strsplit(opt, "=", 2);
+ if (g_strcmp0(tokens[0], "use_hwaddr") == 0) {
+ if (!qemu_plugin_bool_parse(tokens[0], tokens[1], &use_hwaddr)) {
+ fprintf(stderr,
+ "Failed to parse boolean argument use_hwaddr\n");
+ return -1;
+ }
+ } else if (g_strcmp0(tokens[0], "debug_insns") == 0) {
+ if (!qemu_plugin_bool_parse(tokens[0], tokens[1], &debug_insns)) {
+ fprintf(stderr,
+ "Failed to parse boolean argument debug_insns\n");
+ return -1;
+ }
+ } else if (g_strcmp0(tokens[0], "target") == 0) {
+ target_data = str_to_bytes(tokens[1]);
+ if (!target_data) {
+ fprintf(stderr,
+ "Failed to parse target bytes.\n");
+ return -1;
+ }
+ } else if (g_strcmp0(tokens[0], "patch") == 0) {
+ patch_data = str_to_bytes(tokens[1]);
+ if (!patch_data) {
+ fprintf(stderr, "Failed to parse patch bytes.\n");
+ return -1;
+ }
+ } else {
+ fprintf(stderr, "Unknown argument: %s\n", tokens[0]);
+ usage();
+ return -1;
+ }
+ }
+
+ if (!target_data) {
+ fprintf(stderr, "target argument is required\n");
+ usage();
+ return -1;
+ }
+
+ if (!patch_data) {
+ fprintf(stderr, "patch argument is required\n");
+ usage();
+ return -1;
+ }
+
+ if (target_data->len != patch_data->len) {
+ fprintf(stderr, "Target and patch data must be the same length\n");
+ return -1;
+ }
+
+ qemu_plugin_register_vcpu_tb_trans_cb(id, vcpu_tb_trans_cb);
+
+ return 0;
+}
diff --git a/tests/tcg/x86_64/Makefile.softmmu-target b/tests/tcg/x86_64/Makefile.softmmu-target
index ef6bcb4dc7..8d3a067c33 100644
--- a/tests/tcg/x86_64/Makefile.softmmu-target
+++ b/tests/tcg/x86_64/Makefile.softmmu-target
@@ -7,18 +7,27 @@
#
I386_SYSTEM_SRC=$(SRC_PATH)/tests/tcg/i386/system
-X64_SYSTEM_SRC=$(SRC_PATH)/tests/tcg/x86_64/system
+X86_64_SYSTEM_SRC=$(SRC_PATH)/tests/tcg/x86_64/system
# These objects provide the basic boot code and helper functions for all tests
CRT_OBJS=boot.o
-CRT_PATH=$(X64_SYSTEM_SRC)
-LINK_SCRIPT=$(X64_SYSTEM_SRC)/kernel.ld
+X86_64_TEST_C_SRCS=$(wildcard $(X86_64_SYSTEM_SRC)/*.c)
+X86_64_TEST_S_SRCS=
+
+X86_64_C_TESTS = $(patsubst $(X86_64_SYSTEM_SRC)/%.c, %, $(X86_64_TEST_C_SRCS))
+X86_64_S_TESTS = $(patsubst $(X86_64_SYSTEM_SRC)/%.S, %, $(X86_64_TEST_S_SRCS))
+
+X86_64_TESTS = $(X86_64_C_TESTS)
+X86_64_TESTS += $(X86_64_S_TESTS)
+
+CRT_PATH=$(X86_64_SYSTEM_SRC)
+LINK_SCRIPT=$(X86_64_SYSTEM_SRC)/kernel.ld
LDFLAGS=-Wl,-T$(LINK_SCRIPT) -Wl,-melf_x86_64
CFLAGS+=-nostdlib -ggdb -O0 $(MINILIB_INC)
LDFLAGS+=-static -nostdlib $(CRT_OBJS) $(MINILIB_OBJS) -lgcc
-TESTS+=$(MULTIARCH_TESTS)
+TESTS+=$(X86_64_TESTS) $(MULTIARCH_TESTS)
EXTRA_RUNS+=$(MULTIARCH_RUNS)
# building head blobs
@@ -27,11 +36,24 @@ EXTRA_RUNS+=$(MULTIARCH_RUNS)
%.o: $(CRT_PATH)/%.S
$(CC) $(CFLAGS) $(EXTRA_CFLAGS) -Wa,--noexecstack -c $< -o $@
-# Build and link the tests
+# Build and link the multiarch tests
%: %.c $(LINK_SCRIPT) $(CRT_OBJS) $(MINILIB_OBJS)
$(CC) $(CFLAGS) $(EXTRA_CFLAGS) $< -o $@ $(LDFLAGS)
+# Build and link the arch tests
+%: $(X86_64_SYSTEM_SRC)/%.c $(LINK_SCRIPT) $(CRT_OBJS) $(MINILIB_OBJS)
+ $(CC) $(CFLAGS) $(EXTRA_CFLAGS) $< -o $@ $(LDFLAGS)
+
memory: CFLAGS+=-DCHECK_UNALIGNED=1
+patch-target: CFLAGS+=-O0
# Running
QEMU_OPTS+=-device isa-debugcon,chardev=output -device isa-debug-exit,iobase=0xf4,iosize=0x4 -kernel
+
+# Add patch-target to ADDITIONAL_PLUGINS_TESTS
+ADDITIONAL_PLUGINS_TESTS += patch-target
+
+run-plugin-patch-target-with-libpatch.so: \
+ PLUGIN_ARGS=$(COMMA)target=ffc0$(COMMA)patch=9090$(COMMA)use_hwaddr=true$(COMMA)debug_insns=false
+run-plugin-patch-target-with-libpatch.so: \
+ CHECK_PLUGIN_OUTPUT_COMMAND=$(X86_64_SYSTEM_SRC)/validate-patch.py $@.out
\ No newline at end of file
diff --git a/tests/tcg/x86_64/system/patch-target.c b/tests/tcg/x86_64/system/patch-target.c
new file mode 100644
index 0000000000..8a7c0a0ae8
--- /dev/null
+++ b/tests/tcg/x86_64/system/patch-target.c
@@ -0,0 +1,27 @@
+/*
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * This test target increments a value 100 times. The patcher converts the
+ * inc instruction to a nop, so it only increments the value once.
+ *
+ */
+#include <minilib.h>
+
+int main(void)
+{
+ ml_printf("Running test...\n");
+#if defined(__x86_64__)
+ ml_printf("Testing insn memory read/write...\n");
+ unsigned int x = 0;
+ for (int i = 0; i < 100; i++) {
+ asm volatile (
+ "inc %[x]"
+ : [x] "+a" (x)
+ );
+ }
+ ml_printf("Value: %d\n", x);
+#else
+ #error "This test is only valid for x86_64 architecture."
+#endif
+ return 0;
+}
diff --git a/tests/tcg/x86_64/system/validate-patch.py b/tests/tcg/x86_64/system/validate-patch.py
new file mode 100755
index 0000000000..700950eae5
--- /dev/null
+++ b/tests/tcg/x86_64/system/validate-patch.py
@@ -0,0 +1,39 @@
+#!/usr/bin/env python3
+#
+# validate-patch.py: check the patch applies
+#
+# This program takes two inputs:
+# - the plugin output
+# - the binary output
+#
+# Copyright (C) 2024
+#
+# SPDX-License-Identifier: GPL-2.0-or-later
+
+import sys
+from argparse import ArgumentParser
+
+def main() -> None:
+ """
+ Process the arguments, injest the program and plugin out and
+ verify they match up and report if they do not.
+ """
+ parser = ArgumentParser(description="Validate patch")
+ parser.add_argument('test_output',
+ help="The output from the test itself")
+ parser.add_argument('plugin_output',
+ help="The output from plugin")
+ args = parser.parse_args()
+
+ with open(args.test_output, 'r') as f:
+ test_data = f.read()
+ with open(args.plugin_output, 'r') as f:
+ plugin_data = f.read()
+ if "Value: 1" in test_data:
+ sys.exit(0)
+ else:
+ sys.exit(1)
+
+if __name__ == "__main__":
+ main()
+
--
2.49.0
^ permalink raw reply related [flat|nested] 10+ messages in thread
* [PATCH v6 7/9] plugins: Add hypercalls plugin and test
2025-06-02 6:08 [PATCH v6 0/9] Add additional plugin API functions to read and write memory and registers Rowan Hart
` (5 preceding siblings ...)
2025-06-02 6:08 ` [PATCH v6 6/9] plugins: Add patcher plugin and test Rowan Hart
@ 2025-06-02 6:08 ` Rowan Hart
2025-06-02 6:08 ` [PATCH v6 8/9] plugins: Remove use of qemu_plugin_read_register where it is not permitted Rowan Hart
2025-06-02 6:08 ` [PATCH v6 9/9] plugins: Update plugin version and add notes Rowan Hart
8 siblings, 0 replies; 10+ messages in thread
From: Rowan Hart @ 2025-06-02 6:08 UTC (permalink / raw)
To: qemu-devel
Cc: Yanan Wang, Richard Henderson, Alexandre Iooss, Zhao Liu,
Eduardo Habkost, Mahmoud Mandour, Alex Bennée,
Pierrick Bouvier, Philippe Mathieu-Daudé, Paolo Bonzini,
Marcel Apfelbaum, novafacing
From: novafacing <rowanbhart@gmail.com>
This patch adds a plugin that implements a simple form of hypercalls
from guest code to the plugin by using the register read API. It accepts
only one hypercall, which writes a magic value to guest memory.
Signed-off-by: novafacing <rowanbhart@gmail.com>
Signed-off-by: Rowan Hart <rowanbhart@gmail.com>
---
tests/tcg/Makefile.target | 1 +
tests/tcg/plugins/hypercalls.c | 547 ++++++++++++++++++
tests/tcg/plugins/meson.build | 2 +-
tests/tcg/x86_64/Makefile.softmmu-target | 6 +-
tests/tcg/x86_64/system/hypercalls-target.c | 40 ++
.../tcg/x86_64/system/validate-hypercalls.py | 40 ++
6 files changed, 634 insertions(+), 2 deletions(-)
create mode 100644 tests/tcg/plugins/hypercalls.c
create mode 100644 tests/tcg/x86_64/system/hypercalls-target.c
create mode 100755 tests/tcg/x86_64/system/validate-hypercalls.py
diff --git a/tests/tcg/Makefile.target b/tests/tcg/Makefile.target
index 4b709a9d18..5ac9638102 100644
--- a/tests/tcg/Makefile.target
+++ b/tests/tcg/Makefile.target
@@ -177,6 +177,7 @@ RUN_TESTS+=$(EXTRA_RUNS)
# exercise things. We can define them on a per-test basis here.
run-plugin-%-with-libmem.so: PLUGIN_ARGS=$(COMMA)inline=true
run-plugin-%-with-libpatch.so: PLUGIN_ARGS=$(COMMA)target=ffffffff$(COMMA)patch=00000000
+run-plugin-%-with-libhypercalls.so: PLUGIN_ARGS=$(COMMA)ignore_unsupported=true
ifeq ($(filter %-softmmu, $(TARGET)),)
run-%: %
diff --git a/tests/tcg/plugins/hypercalls.c b/tests/tcg/plugins/hypercalls.c
new file mode 100644
index 0000000000..3214bb8ee5
--- /dev/null
+++ b/tests/tcg/plugins/hypercalls.c
@@ -0,0 +1,547 @@
+/*
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * This plugin implements a simple hypercall interface for guests (both system
+ * and user mode) to call certain operations from the host.
+ */
+#include "glib.h"
+#include "glibconfig.h"
+#include <assert.h>
+#include <inttypes.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <qemu-plugin.h>
+
+QEMU_PLUGIN_EXPORT int qemu_plugin_version = QEMU_PLUGIN_VERSION;
+
+#define AARCH64_N_HYPERCALL_INSNS (28)
+#define AARCH64_HYPERCALL_INSN_LEN (4)
+#define AARCH64_HYPERCALL_MAX (AARCH64_N_HYPERCALL_INSNS)
+#define ARM_N_HYPERCALL_INSNS (12)
+#define ARM_HYPERCALL_INSN_LEN (4)
+#define ARM_HYPERCALL_MAX (ARM_N_HYPERCALL_INSNS)
+#define X86_HYPERCALL_INSN_LEN (2)
+#define X86_HYPERCALL_VALUE_BASE (0x4711)
+#define X86_HYPERCALL_MAX (0x10000)
+#define N_HYPERCALL_ARGS (4)
+
+static bool ignore_unsupported;
+
+static struct qemu_plugin_register *get_register(const char *name);
+static uint64_t byte_array_to_uint64(GByteArray *buf);
+
+enum HypercallInsnType {
+ CONSTANT,
+ CALLBACK,
+};
+
+
+/*
+ * Checks an instruction and returns its hypercall number, if it is
+ * a hypercall instruction, or -1 if it is not. Called at execution
+ * time.
+ */
+typedef int32_t (*hypercall_nr_cb)(GByteArray *);
+
+/*
+ * Checks an instruction and returns whether it is a hypercall, or -1 if it is
+ * not. Called at execution time.
+ */
+typedef bool (*is_hypercall_cb)(GByteArray *);
+
+/*
+ * Specifies a Hypercall for an architecture:
+ *
+ * - Architecture name
+ * - Whether it is enabled
+ * - The hypercall instruction
+ * - The register names to pass the hypercall # and args
+ */
+struct HypercallSpec {
+ const bool enabled;
+ const char *name;
+ const bool le;
+ const char *args[N_HYPERCALL_ARGS];
+ const hypercall_nr_cb hypercall_nr_cb;
+ const is_hypercall_cb is_hypercall_cb;
+};
+
+static int32_t aarch64_hypercall_nr_cb(GByteArray *insn)
+{
+ if (insn->len != AARCH64_HYPERCALL_INSN_LEN) {
+ return -1;
+ }
+
+ static const uint8_t
+ hypercall_insns[AARCH64_N_HYPERCALL_INSNS][AARCH64_HYPERCALL_INSN_LEN] = {
+ { 0xaa, 0x4, 0x0, 0x84 },
+ { 0xaa, 0x5, 0x0, 0xa5 },
+ { 0xaa, 0x6, 0x0, 0xc6 },
+ { 0xaa, 0x7, 0x0, 0xe7 },
+ { 0xaa, 0x8, 0x1, 0x8 },
+ { 0xaa, 0x9, 0x1, 0x29 },
+ { 0xaa, 0xa, 0x1, 0x4a },
+ { 0xaa, 0xb, 0x1, 0x6b },
+ { 0xaa, 0xc, 0x1, 0x8c },
+ { 0xaa, 0xd, 0x1, 0xad },
+ { 0xaa, 0xe, 0x1, 0xce },
+ { 0xaa, 0xf, 0x1, 0xef },
+ { 0xaa, 0x10, 0x2, 0x10 },
+ { 0xaa, 0x11, 0x2, 0x31 },
+ { 0xaa, 0x12, 0x2, 0x52 },
+ { 0xaa, 0x13, 0x2, 0x73 },
+ { 0xaa, 0x14, 0x2, 0x94 },
+ { 0xaa, 0x15, 0x2, 0xb5 },
+ { 0xaa, 0x16, 0x2, 0xd6 },
+ { 0xaa, 0x17, 0x2, 0xf7 },
+ { 0xaa, 0x18, 0x3, 0x18 },
+ { 0xaa, 0x19, 0x3, 0x39 },
+ { 0xaa, 0x1a, 0x3, 0x5a },
+ { 0xaa, 0x1b, 0x3, 0x7b },
+ { 0xaa, 0x1c, 0x3, 0x9c },
+ { 0xaa, 0x1d, 0x3, 0xbd },
+ { 0xaa, 0x1e, 0x3, 0xde },
+ { 0xaa, 0x1f, 0x3, 0xff },
+ };
+
+ for (int32_t i = 0; i < AARCH64_N_HYPERCALL_INSNS; i++) {
+ if (!memcmp(hypercall_insns[i], insn->data, insn->len)) {
+ return i;
+ }
+ }
+ return -1;
+}
+
+static bool aarch64_is_hypercall_cb(GByteArray *insn)
+{
+ return aarch64_hypercall_nr_cb(insn) < 0;
+}
+
+
+static int32_t aarch64_be_hypercall_nr_cb(GByteArray *insn)
+{
+ if (insn->len != AARCH64_HYPERCALL_INSN_LEN) {
+ return -1;
+ }
+
+ static const uint8_t
+ hypercall_insns[AARCH64_N_HYPERCALL_INSNS][AARCH64_HYPERCALL_INSN_LEN] = {
+ {0x84, 0x0, 0x4, 0xaa},
+ {0xa5, 0x0, 0x5, 0xaa},
+ {0xc6, 0x0, 0x6, 0xaa},
+ {0xe7, 0x0, 0x7, 0xaa},
+ {0x8, 0x1, 0x8, 0xaa},
+ {0x29, 0x1, 0x9, 0xaa},
+ {0x4a, 0x1, 0xa, 0xaa},
+ {0x6b, 0x1, 0xb, 0xaa},
+ {0x8c, 0x1, 0xc, 0xaa},
+ {0xad, 0x1, 0xd, 0xaa},
+ {0xce, 0x1, 0xe, 0xaa},
+ {0xef, 0x1, 0xf, 0xaa},
+ {0x10, 0x2, 0x10, 0xaa},
+ {0x31, 0x2, 0x11, 0xaa},
+ {0x52, 0x2, 0x12, 0xaa},
+ {0x73, 0x2, 0x13, 0xaa},
+ {0x94, 0x2, 0x14, 0xaa},
+ {0xb5, 0x2, 0x15, 0xaa},
+ {0xd6, 0x2, 0x16, 0xaa},
+ {0xf7, 0x2, 0x17, 0xaa},
+ {0x18, 0x3, 0x18, 0xaa},
+ {0x39, 0x3, 0x19, 0xaa},
+ {0x5a, 0x3, 0x1a, 0xaa},
+ {0x7b, 0x3, 0x1b, 0xaa},
+ {0x9c, 0x3, 0x1c, 0xaa},
+ {0xbd, 0x3, 0x1d, 0xaa},
+ {0xde, 0x3, 0x1e, 0xaa},
+ {0xff, 0x3, 0x1f, 0xaa},
+ };
+
+ for (int32_t i = 0; i < AARCH64_N_HYPERCALL_INSNS; i++) {
+ if (!memcmp(hypercall_insns[i], insn->data, insn->len)) {
+ return i;
+ }
+ }
+ return -1;
+}
+
+static bool aarch64_be_is_hypercall_cb(GByteArray *insn)
+{
+ return aarch64_be_hypercall_nr_cb(insn) < 0;
+}
+
+
+static int32_t arm_hypercall_nr_cb(GByteArray *insn)
+{
+ if (insn->len != ARM_HYPERCALL_INSN_LEN) {
+ return -1;
+ }
+
+ static const uint8_t
+ hypercall_insns[ARM_N_HYPERCALL_INSNS][ARM_HYPERCALL_INSN_LEN] = {
+ { 0xe1, 0x84, 0x40, 0x4 },
+ { 0xe1, 0x85, 0x50, 0x5 },
+ { 0xe1, 0x86, 0x60, 0x6 },
+ { 0xe1, 0x87, 0x70, 0x7 },
+ { 0xe1, 0x88, 0x80, 0x8 },
+ { 0xe1, 0x89, 0x90, 0x9 },
+ { 0xe1, 0x8a, 0xa0, 0xa },
+ { 0xe1, 0x8b, 0xb0, 0xb },
+ { 0xe1, 0x8c, 0xc0, 0xc },
+ { 0xe1, 0x8d, 0xd0, 0xd },
+ { 0xe1, 0x8e, 0xe0, 0xe },
+ { 0xe1, 0x8f, 0xf0, 0xf },
+ };
+
+ for (int32_t i = 0; i < ARM_N_HYPERCALL_INSNS; i++) {
+ if (!memcmp(hypercall_insns[i], insn->data, insn->len)) {
+ return i;
+ }
+ }
+ return -1;
+}
+
+static bool arm_is_hypercall_cb(GByteArray *insn)
+{
+ return arm_hypercall_nr_cb(insn) < 0;
+}
+
+static int32_t arm_be_hypercall_nr_cb(GByteArray *insn)
+{
+ if (insn->len != ARM_HYPERCALL_INSN_LEN) {
+ return -1;
+ }
+
+ static const uint8_t
+ hypercall_insns[ARM_N_HYPERCALL_INSNS][ARM_HYPERCALL_INSN_LEN] = {
+ {0x4, 0x40, 0x84, 0xe1},
+ {0x5, 0x50, 0x85, 0xe1},
+ {0x6, 0x60, 0x86, 0xe1},
+ {0x7, 0x70, 0x87, 0xe1},
+ {0x8, 0x80, 0x88, 0xe1},
+ {0x9, 0x90, 0x89, 0xe1},
+ {0xa, 0xa0, 0x8a, 0xe1},
+ {0xb, 0xb0, 0x8b, 0xe1},
+ {0xc, 0xc0, 0x8c, 0xe1},
+ {0xd, 0xd0, 0x8d, 0xe1},
+ {0xe, 0xe0, 0x8e, 0xe1},
+ {0xf, 0xf0, 0x8f, 0xe1},
+ };
+
+ for (int32_t i = 0; i < ARM_N_HYPERCALL_INSNS; i++) {
+ if (!memcmp(hypercall_insns[i], insn->data, insn->len)) {
+ return i;
+ }
+ }
+ return -1;
+}
+
+static bool arm_be_is_hypercall_cb(GByteArray *insn)
+{
+ return arm_be_hypercall_nr_cb(insn) < 0;
+}
+
+static int32_t x86_64_hypercall_nr_cb(GByteArray *insn)
+{
+ if (insn->len != X86_HYPERCALL_INSN_LEN) {
+ return -1;
+ }
+
+ uint8_t cpuid[] = { 0x0f, 0xa2 };
+ if (!memcmp(cpuid, insn->data, insn->len)) {
+ GByteArray *reg = g_byte_array_new();
+ qemu_plugin_read_register(get_register("rax"), reg);
+ uint64_t value = byte_array_to_uint64(reg);
+ g_byte_array_free(reg, true);
+
+ if (!(value & X86_HYPERCALL_VALUE_BASE)) {
+ return -1;
+ }
+
+ value = (value >> 16) & 0xffff;
+
+ if (value >= X86_HYPERCALL_MAX) {
+ return -1;
+ }
+
+ return (int32_t)value;
+ }
+
+ return -1;
+}
+
+static bool x86_64_is_hypercall_cb(GByteArray *insn)
+{
+ if (insn->len != X86_HYPERCALL_INSN_LEN) {
+ return false;
+ }
+
+ uint8_t cpuid[] = { 0x0f, 0xa2 };
+ if (!memcmp(cpuid, insn->data, insn->len)) {
+ return true;
+ }
+
+ return false;
+}
+
+static int32_t i386_hypercall_nr_cb(GByteArray *insn)
+{
+ if (insn->len != X86_HYPERCALL_INSN_LEN) {
+ return -1;
+ }
+
+ uint8_t cpuid[] = { 0x0f, 0xa2 };
+ if (!memcmp(cpuid, insn->data, insn->len)) {
+ GByteArray *reg = g_byte_array_new();
+ qemu_plugin_read_register(get_register("eax"), reg);
+ uint64_t value = byte_array_to_uint64(reg);
+ g_byte_array_free(reg, true);
+
+ if (!(value & X86_HYPERCALL_VALUE_BASE)) {
+ return -1;
+ }
+
+ value = (value >> 16) & 0xffff;
+
+ if (value >= X86_HYPERCALL_MAX) {
+ return -1;
+ }
+ return (int32_t)value;
+ }
+
+ return -1;
+
+}
+
+static bool i386_is_hypercall_cb(GByteArray *insn)
+{
+ if (insn->len != X86_HYPERCALL_INSN_LEN) {
+ return false;
+ }
+
+ uint8_t cpuid[] = { 0x0f, 0xa2 };
+ if (!memcmp(cpuid, insn->data, insn->len)) {
+ return true;
+ }
+
+ return false;
+
+}
+
+static const struct HypercallSpec *hypercall_spec;
+
+static const struct HypercallSpec hypercall_specs[] = {
+ { true, "aarch64", true, {
+ "x0", "x1", "x2", "x3",
+ }, aarch64_hypercall_nr_cb, aarch64_is_hypercall_cb
+ },
+ { true, "aarch64_be", false, {
+ "x0", "x1", "x2", "x3",
+ }, aarch64_be_hypercall_nr_cb, aarch64_be_is_hypercall_cb
+ },
+ { true, "arm", true, {
+ "r0", "r1", "r2", "r3",
+ }, arm_hypercall_nr_cb, arm_is_hypercall_cb
+ },
+ { true, "armeb", false, {
+ "r0", "r1", "r2", "r3"
+ }, arm_be_hypercall_nr_cb, arm_be_is_hypercall_cb
+ },
+ { true, "i386", true, {
+ "edi", "esi", "edx", "ecx"
+ }, i386_hypercall_nr_cb, i386_is_hypercall_cb
+ },
+ { true, "x86_64", true, {
+ "rdi", "rsi", "rdx", "rcx"
+
+ }, x86_64_hypercall_nr_cb, x86_64_is_hypercall_cb
+ },
+ { false, NULL, .le = false, {NULL, NULL, NULL, NULL}, NULL},
+};
+
+static GArray *hypercall_insns;
+
+/*
+ * Returns a handle to a register with a given name, or NULL if there is no
+ * such register.
+ */
+static struct qemu_plugin_register *get_register(const char *name)
+{
+ GArray *registers = qemu_plugin_get_registers();
+
+ struct qemu_plugin_register *handle = NULL;
+
+ qemu_plugin_reg_descriptor *reg_descriptors =
+ (qemu_plugin_reg_descriptor *)registers->data;
+
+ for (size_t i = 0; i < registers->len; i++) {
+ if (!strcmp(reg_descriptors[i].name, name)) {
+ handle = reg_descriptors[i].handle;
+ }
+ }
+
+ g_array_free(registers, true);
+
+ return handle;
+}
+
+/*
+ * Transforms a byte array with at most 8 entries into a uint64_t
+ * depending on the target machine's endianness.
+ */
+static uint64_t byte_array_to_uint64(GByteArray *buf)
+{
+ uint64_t value = 0;
+ if (hypercall_spec->le) {
+ for (int i = 0; i < buf->len && i < sizeof(uint64_t); i++) {
+ value |= ((uint64_t)buf->data[i]) << (i * 8);
+ }
+ } else {
+ for (int i = 0; i < buf->len && i < sizeof(uint64_t); i++) {
+ value |= ((uint64_t)buf->data[i]) << ((buf->len - 1 - i) * 8);
+ }
+ }
+ return value;
+}
+
+/*
+ * Handle a "hypercall" instruction, which has some special meaning for this
+ * plugin.
+ */
+static void hypercall(unsigned int vcpu_index, void *userdata)
+{
+ GByteArray *insn_data = (GByteArray *)userdata;
+ int32_t hypercall_nr = hypercall_spec->hypercall_nr_cb(insn_data);
+
+ if (hypercall_nr < 0) {
+ return;
+ }
+
+ uint64_t args[N_HYPERCALL_ARGS] = {0};
+ GByteArray *buf = g_byte_array_new();
+ for (size_t i = 0; i < N_HYPERCALL_ARGS; i++) {
+ g_byte_array_set_size(buf, 0);
+ struct qemu_plugin_register *reg =
+ get_register(hypercall_spec->args[i]);
+ qemu_plugin_read_register(reg, buf);
+ args[i] = byte_array_to_uint64(buf);
+ }
+ g_byte_array_free(buf, true);
+
+ switch (hypercall_nr) {
+ /*
+ * The write hypercall (#0x01) tells the plugin to write random bytes
+ * of a given size into the memory of the emulated system at a particular
+ * vaddr
+ */
+ case 1: {
+ GByteArray *data = g_byte_array_new();
+ g_byte_array_set_size(data, args[1]);
+ for (uint64_t i = 0; i < args[1]; i++) {
+ data->data[i] = (uint8_t)g_random_int();
+ }
+ qemu_plugin_write_memory_vaddr(args[0], data);
+ break;
+ }
+ default:
+ break;
+ }
+}
+
+/*
+ * Callback on translation of a translation block.
+ */
+static void vcpu_tb_trans_cb(qemu_plugin_id_t id, struct qemu_plugin_tb *tb)
+{
+ for (size_t i = 0; i < qemu_plugin_tb_n_insns(tb); i++) {
+ struct qemu_plugin_insn *insn = qemu_plugin_tb_get_insn(tb, i);
+ GByteArray *insn_data = g_byte_array_new();
+ size_t insn_len = qemu_plugin_insn_size(insn);
+ g_byte_array_set_size(insn_data, insn_len);
+ qemu_plugin_insn_data(insn, insn_data->data, insn_data->len);
+
+ if (hypercall_spec->is_hypercall_cb(insn_data)) {
+ g_array_append_val(hypercall_insns, insn_data);
+ qemu_plugin_register_vcpu_insn_exec_cb(insn, hypercall,
+ QEMU_PLUGIN_CB_R_REGS,
+ (void *)insn_data);
+ } else {
+ g_byte_array_free(insn_data, true);
+ }
+
+ }
+}
+
+static void atexit_cb(qemu_plugin_id_t id, void *userdata)
+{
+ for (size_t i = 0; i < hypercall_insns->len; i++) {
+ g_byte_array_free(g_array_index(hypercall_insns, GByteArray *, i),
+ true);
+ }
+
+ g_array_free(hypercall_insns, true);
+}
+
+static void usage(void)
+{
+ fprintf(stderr,
+ "Usage: <lib>,[ignore_unsupported=<ignore_unsupported>]");
+}
+
+/*
+ * Called when the plugin is installed
+ */
+QEMU_PLUGIN_EXPORT int qemu_plugin_install(qemu_plugin_id_t id,
+ const qemu_info_t *info, int argc,
+ char **argv)
+{
+ if (argc > 1) {
+ usage();
+ return -1;
+ }
+
+ for (size_t i = 0; i < argc; i++) {
+ char *opt = argv[i];
+ g_auto(GStrv) tokens = g_strsplit(opt, "=", 2);
+ if (g_strcmp0(tokens[0], "ignore_unsupported") == 0) {
+ if (!qemu_plugin_bool_parse(tokens[0],
+ tokens[1], &ignore_unsupported)) {
+ fprintf(stderr,
+ "Failed to parse argument ignore_unsupported\n");
+ return -1;
+ }
+ } else {
+ fprintf(stderr, "Unknown argument: %s\n", tokens[0]);
+ usage();
+ return -1;
+ }
+ }
+
+
+ hypercall_spec = &hypercall_specs[0];
+
+ while (hypercall_spec->name != NULL) {
+ if (!strcmp(hypercall_spec->name, info->target_name)) {
+ break;
+ }
+ hypercall_spec++;
+ }
+
+ if (hypercall_spec->name == NULL || !hypercall_spec->enabled) {
+ qemu_plugin_outs("Error: no hypercall spec.");
+ if (ignore_unsupported) {
+ return 0;
+ }
+ return -1;
+ }
+
+ hypercall_insns = g_array_new(true, true, sizeof(GByteArray *));
+
+ qemu_plugin_register_vcpu_tb_trans_cb(id, vcpu_tb_trans_cb);
+ qemu_plugin_register_atexit_cb(id, atexit_cb, NULL);
+
+ return 0;
+}
diff --git a/tests/tcg/plugins/meson.build b/tests/tcg/plugins/meson.build
index 163042e601..909bf3005a 100644
--- a/tests/tcg/plugins/meson.build
+++ b/tests/tcg/plugins/meson.build
@@ -1,6 +1,6 @@
t = []
if get_option('plugins')
- foreach i : ['bb', 'empty', 'inline', 'insn', 'mem', 'reset', 'syscall', 'patch']
+ foreach i : ['bb', 'empty', 'inline', 'insn', 'mem', 'reset', 'syscall', 'hypercalls', 'patch']
if host_os == 'windows'
t += shared_module(i, files(i + '.c') + '../../../contrib/plugins/win32_linker.c',
include_directories: '../../../include/qemu',
diff --git a/tests/tcg/x86_64/Makefile.softmmu-target b/tests/tcg/x86_64/Makefile.softmmu-target
index 8d3a067c33..8cb2a19461 100644
--- a/tests/tcg/x86_64/Makefile.softmmu-target
+++ b/tests/tcg/x86_64/Makefile.softmmu-target
@@ -46,14 +46,18 @@ EXTRA_RUNS+=$(MULTIARCH_RUNS)
memory: CFLAGS+=-DCHECK_UNALIGNED=1
patch-target: CFLAGS+=-O0
+hypercalls-target: CFLAGS+=-O0
# Running
QEMU_OPTS+=-device isa-debugcon,chardev=output -device isa-debug-exit,iobase=0xf4,iosize=0x4 -kernel
# Add patch-target to ADDITIONAL_PLUGINS_TESTS
ADDITIONAL_PLUGINS_TESTS += patch-target
+ADDITIONAL_PLUGINS_TESTS += hypercalls-target
run-plugin-patch-target-with-libpatch.so: \
PLUGIN_ARGS=$(COMMA)target=ffc0$(COMMA)patch=9090$(COMMA)use_hwaddr=true$(COMMA)debug_insns=false
run-plugin-patch-target-with-libpatch.so: \
- CHECK_PLUGIN_OUTPUT_COMMAND=$(X86_64_SYSTEM_SRC)/validate-patch.py $@.out
\ No newline at end of file
+ CHECK_PLUGIN_OUTPUT_COMMAND=$(X86_64_SYSTEM_SRC)/validate-patch.py $@.out
+run-plugin-hypercalls-target-with-libhypercalls.so: \
+ CHECK_PLUGIN_OUTPUT_COMMAND=$(X86_64_SYSTEM_SRC)/validate-hypercalls.py $@.out
diff --git a/tests/tcg/x86_64/system/hypercalls-target.c b/tests/tcg/x86_64/system/hypercalls-target.c
new file mode 100644
index 0000000000..acb6d695f2
--- /dev/null
+++ b/tests/tcg/x86_64/system/hypercalls-target.c
@@ -0,0 +1,40 @@
+/*
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * This test target invokes a hypercall to write the value 0x1337 to a
+ * variable.
+ *
+ */
+#include <stddef.h>
+#include <stdint.h>
+#include <minilib.h>
+
+#define _hypercall(num, arg0, arg1, arg2, arg3) \
+ unsigned int a __attribute__((unused)) = 0; \
+ unsigned int b __attribute__((unused)) = 0; \
+ unsigned int c __attribute__((unused)) = 0; \
+ unsigned int d __attribute__((unused)) = 0; \
+ __asm__ __volatile__("cpuid\n\t" \
+ : "=a"(a), "=b"(b), "=c"(c), "=d"(d) \
+ : "a"(num), "D"(arg0), "S"(arg1), \
+ "d"(arg2), "c"(arg3));
+
+#define hypercall(num, arg0, arg1, arg2, arg3) \
+ { \
+ unsigned int __num = 0x4711 | (num << 16); \
+ _hypercall(__num, arg0, arg1, arg2, arg3); \
+ }
+
+int main(void)
+{
+ uint16_t value = 0;
+
+ for (size_t i = 0; i < 1000000; i++) {
+ hypercall(1, &value, sizeof(value), 0, 0);
+ if (value == 0x1337) {
+ ml_printf("Victory!\n");
+ return 0;
+ }
+ }
+ return 0;
+}
diff --git a/tests/tcg/x86_64/system/validate-hypercalls.py b/tests/tcg/x86_64/system/validate-hypercalls.py
new file mode 100755
index 0000000000..6e7c980706
--- /dev/null
+++ b/tests/tcg/x86_64/system/validate-hypercalls.py
@@ -0,0 +1,40 @@
+#!/usr/bin/env python3
+#
+# validate-patch.py: check the patch applies
+#
+# This program takes two inputs:
+# - the plugin output
+# - the binary output
+#
+# Copyright (C) 2024
+#
+# SPDX-License-Identifier: GPL-2.0-or-later
+
+import sys
+from argparse import ArgumentParser
+
+def main() -> None:
+ """
+ Process the arguments, injest the program and plugin out and
+ verify they match up and report if they do not.
+ """
+ parser = ArgumentParser(description="Validate patch")
+ parser.add_argument('test_output',
+ help="The output from the test itself")
+ parser.add_argument('plugin_output',
+ help="The output from plugin")
+ args = parser.parse_args()
+
+ with open(args.test_output, 'r') as f:
+ test_data = f.read()
+ with open(args.plugin_output, 'r') as f:
+ plugin_data = f.read()
+
+ if "Victory" in test_data:
+ sys.exit(0)
+ else:
+ sys.exit(1)
+
+if __name__ == "__main__":
+ main()
+
--
2.49.0
^ permalink raw reply related [flat|nested] 10+ messages in thread