QEMU Rust Crates + Plugins in Rust
Hi all, this is my first post to the mailing list! I've spent the last couple weeks building QEMU Rust crates to enable a couple goals: - Install QEMU binaries using cargo, the Rust package manager - Use git-latest QEMU binaries in Rust projects as a dependency - Write QEMU TCG plugins entirely in Rust The main crate is https://crates.io/crates/qemu, a thin wrapper around the existing QEMU build system, using crate features to toggle configure options. There are also crates for each binary build target, for example https://crates.io/crates/qemu-system-x86_64. These crates allow users to run `cargo install qemu-system-x86_64`, which installs a small wrapper binary with a dependency on the built binaries from the qemu crate. These binary installs run exactly as a normal QEMU installation via package manager would, and essentially just provide another alternative distro-agnostic packaging mechanism. More interesting (I think) is the crate https://github.com/novafacing/cannonball, which depends on the qemu crate and enables developers to write QEMU TCG plugins entirely in Rust. This enables a very nice workflow for Rust developers interested in building tracers, profilers, and other tooling already supported by the TCG Plugin API. The cannonball repository has one example of such a tracer that outputs various events as JSON. This is a pretty rough implementation I've hacked together over a week or two, but I've heard from IRC there is some interest in experimenting with Rust as part of QEMU and I'm interested in feedback on this attempt in that direction. There's a bit more information in my post about this on cohost: https://cohost.org/novafacing/post/240422-some-qemu-and-plugi Finally, I am acutely aware in doing this I've taken control of almost 70 crate names the QEMU project might like to use at some point. I'm not here to step on toes, so if the decision makers would like the QEMU project to get control of these crate names, just let me know and I'll email the crates.io team about getting them moved. Otherwise, comments, discussion about Rust-ifying QEMU and its plugins, ideas, and PRs welcome, as an author of a decent number of TCG plugins the last few years I'm pretty excited about what even this rudimentary approach has enabled with regards to ease of use and development speed. -Rowan
[PATCH] plugins: add plugin API to read guest memory
Signed-off-by: Rowan Hart --- docs/about/emulation.rst | 16 - include/qemu/qemu-plugin.h | 24 +++- plugins/api.c| 21 +++ plugins/qemu-plugins.symbols | 1 + tests/tcg/plugins/mem.c | 37 +++- tests/tcg/plugins/syscall.c | 113 +++ 6 files changed, 208 insertions(+), 4 deletions(-) diff --git a/docs/about/emulation.rst b/docs/about/emulation.rst index eea1261baa..9c68e37887 100644 --- a/docs/about/emulation.rst +++ b/docs/about/emulation.rst @@ -354,6 +354,8 @@ Behaviour can be tweaked with the following arguments: - Use callbacks on each memory instrumentation. * - hwaddr=true|false - Count IO accesses (only for system emulation) + * - read=true|false +- Read the memory content of each access and display it System Calls @@ -388,6 +390,19 @@ run:: 160 1 0 135 1 0 +Behaviour can be tweaked with the following arguments: + +.. list-table:: Syscall plugin arguments + :widths: 20 80 + :header-rows: 1 + + * - Option +- Description + * - print=true|false +- Print the number of times each syscall is called + * - log_writes=true|false +- Log the buffer of each write syscall in hexdump format + Test inline operations .. @@ -777,4 +792,3 @@ Other emulation features When running system emulation you can also enable deterministic execution which allows for repeatable record/replay debugging. See :ref:`Record/Replay` for more details. - diff --git a/include/qemu/qemu-plugin.h b/include/qemu/qemu-plugin.h index c71c705b69..d4ec73574b 100644 --- a/include/qemu/qemu-plugin.h +++ b/include/qemu/qemu-plugin.h @@ -57,11 +57,19 @@ typedef uint64_t qemu_plugin_id_t; * - Remove qemu_plugin_register_vcpu_{tb, insn, mem}_exec_inline. * Those functions are replaced by *_per_vcpu variants, which guarantee * thread-safety for operations. + * + * version 3: + * - modified arguments and return value of qemu_plugin_insn_data to copy + * the data into a user-provided buffer instead of returning a pointer + * to the data. + * + * version 4: + * - added qemu_plugin_read_memory_vaddr */ extern QEMU_PLUGIN_EXPORT int qemu_plugin_version; -#define QEMU_PLUGIN_VERSION 3 +#define QEMU_PLUGIN_VERSION 4 /** * struct qemu_info_t - system information for plugins @@ -852,6 +860,20 @@ typedef struct { QEMU_PLUGIN_API GArray *qemu_plugin_get_registers(void); +/** + * qemu_plugin_read_memory_vaddr() - read from memory using a virtual address + * + * @addr: A virtual address to read from + * @len: The number of bytes to read, starting from @addr + * + * Returns a GByteArray with the read memory. Ownership of the GByteArray is + * transferred to the caller, which is responsible for deallocating it after + * use. On failure returns NULL. + */ +QEMU_PLUGIN_API +GByteArray *qemu_plugin_read_memory_vaddr(uint64_t addr, + size_t len); + /** * qemu_plugin_read_register() - read register for current vCPU * diff --git a/plugins/api.c b/plugins/api.c index 2ff13d09de..f210ca166a 100644 --- a/plugins/api.c +++ b/plugins/api.c @@ -527,6 +527,27 @@ GArray *qemu_plugin_get_registers(void) return create_register_handles(regs); } +GByteArray *qemu_plugin_read_memory_vaddr(vaddr addr, size_t len) +{ +g_assert(current_cpu); + +if (len == 0) { +return NULL; +} + +GByteArray *buf = g_byte_array_sized_new(len); +g_byte_array_set_size(buf, len); + +int result = cpu_memory_rw_debug(current_cpu, addr, buf->data, buf->len, 0); + +if (result < 0) { +g_byte_array_unref(buf); +return NULL; +} + +return buf; +} + int qemu_plugin_read_register(struct qemu_plugin_register *reg, GByteArray *buf) { g_assert(current_cpu); diff --git a/plugins/qemu-plugins.symbols b/plugins/qemu-plugins.symbols index ca773d8d9f..3ad479a924 100644 --- a/plugins/qemu-plugins.symbols +++ b/plugins/qemu-plugins.symbols @@ -20,6 +20,7 @@ qemu_plugin_num_vcpus; qemu_plugin_outs; qemu_plugin_path_to_binary; + qemu_plugin_read_memory_vaddr; qemu_plugin_read_register; qemu_plugin_register_atexit_cb; qemu_plugin_register_flush_cb; diff --git a/tests/tcg/plugins/mem.c b/tests/tcg/plugins/mem.c index b650dddcce..c65d48680b 100644 --- a/tests/tcg/plugins/mem.c +++ b/tests/tcg/plugins/mem.c @@ -24,7 +24,7 @@ typedef struct { static struct qemu_plugin_scoreboard *counts; static qemu_plugin_u64 mem_count; static qemu_plugin_u64 io_count; -static bool do_inline, do_callback; +static bool do_inline, do_callback, do_read; static bool do_haddr; static enum qemu_plugin_mem_rw rw = QEMU_PLUGIN_MEM_RW; @@ -58,6 +58,30 @@ static void vcpu_mem(unsigned int cpu_index, qemu_plugin_meminfo_t meminfo, } else { qemu_plugin_u64_add(mem_count, cpu_index, 1); } + +if (do_read) { +size_t size = qemu_plugin_mem_s
Re: [PATCH] plugins: add plugin API to read guest memory
Alex & Pierrick, Thank you for the feedback! This is my first contribution to QEMU, so I'm glad it at least passes the initial smell test :) > I'll make my comments in this patch, but for v2, please split those individual > commits, and a cover letter, describing your changes (https://github.com/ > stefanha/git-publish is a great tool if you want to easily push series). Will do! Mailing list dev is new to me but I will do a practice run to try and not mess it up. >> +QEMU_PLUGIN_API >> +GByteArray *qemu_plugin_read_memory_vaddr(uint64_t addr, >> + size_t len); >> + > > IMHO, it would be better to pass the buffer as a parameter, instead of > allocating a new one everytime. > > bool qemu_plugin_read_memory_vaddr(uint64_t addr, GByteArray *buf, size_t > len). Sure, this makes perfect sense. I considered both and picked wrong so not hard to change. >> /** >>* qemu_plugin_read_register() - read register for current vCPU >>* >> diff --git a/plugins/api.c b/plugins/api.c >> index 2ff13d09de..f210ca166a 100644 >> --- a/plugins/api.c >> +++ b/plugins/api.c >> @@ -527,6 +527,27 @@ GArray *qemu_plugin_get_registers(void) >> return create_register_handles(regs); >> } >> +GByteArray *qemu_plugin_read_memory_vaddr(vaddr addr, size_t len) >> +{ >> +g_assert(current_cpu); >> + >> +if (len == 0) { >> +return NULL; >> +} >> + >> +GByteArray *buf = g_byte_array_sized_new(len); >> +g_byte_array_set_size(buf, len); >> + >> +int result = cpu_memory_rw_debug(current_cpu, addr, buf->data, >> buf->len, 0); >> + >> +if (result < 0) { >> +g_byte_array_unref(buf); >> +return NULL; >> +} >> + >> +return buf; >> +} >> + >> int qemu_plugin_read_register(struct qemu_plugin_register *reg, GByteArray >> *buf) >> { >> g_assert(current_cpu); >> diff --git a/plugins/qemu-plugins.symbols b/plugins/qemu-plugins.symbols >> index ca773d8d9f..3ad479a924 100644 >> --- a/plugins/qemu-plugins.symbols >> +++ b/plugins/qemu-plugins.symbols >> @@ -20,6 +20,7 @@ >> qemu_plugin_num_vcpus; >> qemu_plugin_outs; >> qemu_plugin_path_to_binary; >> + qemu_plugin_read_memory_vaddr; >> qemu_plugin_read_register; >> qemu_plugin_register_atexit_cb; >> qemu_plugin_register_flush_cb; >> diff --git a/tests/tcg/plugins/mem.c b/tests/tcg/plugins/mem.c >> index b650dddcce..c65d48680b 100644 >> --- a/tests/tcg/plugins/mem.c >> +++ b/tests/tcg/plugins/mem.c >> @@ -24,7 +24,7 @@ typedef struct { >> static struct qemu_plugin_scoreboard *counts; >> static qemu_plugin_u64 mem_count; >> static qemu_plugin_u64 io_count; >> -static bool do_inline, do_callback; >> +static bool do_inline, do_callback, do_read; >> static bool do_haddr; >> static enum qemu_plugin_mem_rw rw = QEMU_PLUGIN_MEM_RW; >> @@ -58,6 +58,30 @@ static void vcpu_mem(unsigned int cpu_index, >> qemu_plugin_meminfo_t meminfo, >> } else { >> qemu_plugin_u64_add(mem_count, cpu_index, 1); >> } >> + >> +if (do_read) { >> +size_t size = qemu_plugin_mem_size_shift(meminfo); >> +GByteArray *data = qemu_plugin_read_memory_vaddr(vaddr, size); >> + >> +if (data) { >> +g_autoptr(GString) out = g_string_new(""); >> + >> +if (qemu_plugin_mem_is_store(meminfo)) { >> +g_string_append(out, "store: "); >> +} else { >> +g_string_append(out, "load: "); >> +} >> + >> +g_string_append_printf(out, "vaddr: 0x%" PRIx64 " data: 0x", >> + vaddr); >> +for (size_t i = 0; i < data->len; i++) { >> +g_string_append_printf(out, "%02x", data->data[i]); >> +} >> +g_string_append(out, "\n"); >> +qemu_plugin_outs(out->str); >> +g_byte_array_free(data, TRUE); >> +} >> +} > > See other series, merging an API for getting values read on a memory access. > It's a better fit to implement this. So I think it's better to drop this > plugin modification. Ok, will drop this one and keep the modification to the syscalls plugin only as an example of how to use the API. >> } >> static void vcpu_tb_trans(qemu_plugin_id_t id, struct qemu_plugin_tb *tb) >> @@ -86,7 +110,6 @@ QEMU_PLUGIN_EXPORT int >> qemu_plugin_install(qemu_plugin_id_t id, >> const qemu_info_t *info, >> int argc, char **argv) >> { >> - >> for (int i = 0; i < argc; i++) { >> char *opt = argv[i]; >> g_auto(GStrv) tokens = g_strsplit(opt, "=", 2); >> @@ -117,6 +140,11 @@ QEMU_PLUGIN_EXPORT int >> qemu_plugin_install(qemu_plugin_id_t id, >> fprintf(stderr, "boolean argument parsing failed: %s\n", >> opt); >> return -1; >> } >> +} else if (g_strcmp0(tokens[0], "read") == 0) { >>
Re: [PATCH] plugins: add plugin API to read guest memory
Alex, Thanks for the additional information. >> >> A key aspect of what you propose here, is that the memory may have >> changed during the write time, and when you read it, while what we >> propose guarantees to track every change correctly. >> >> It's not a bad thing, and both API are definitely complementary. When >> talking to Alex, he was keen to add a global read_memory API (like you >> propose), after we merge the first one. >> >> @Alex: any thought on this? > > I'd like to get the memory callback API merged first - mostly because > that is the API that will absolutely reflect what was loaded or stored > to a given memory location. For precise instrumentation that is the one > to use. > > However I agree the ability to read the state of memory outside of loads > and stores is useful especially for something like this. It's not > unreasonable to assume the memory state of arguments going into a > syscall isn't being messed with and it is easier to track pointers and > strings with a more general purpose API. > I agree, I considered the absolute load/store question and poked around the code a bit, but I didn't find what looked like a solid way to either: A) Ensure that all writes are flushed before the read happens (which sounds like a hefty performance penalty anyway) or B) Check whether there are outstanding writes and return an error It sounds like essentially use cases where that level of per-insn write granularity matters should utilize your upcoming API instead of this one, and I will add a call-out to the doc of this one to alert users of the potential pitfall. >>> qemu_plugin_register_vcpu_syscall_cb(id, vcpu_syscall); >>> qemu_plugin_register_vcpu_syscall_ret_cb(id, vcpu_syscall_ret); >>> qemu_plugin_register_atexit_cb(id, plugin_exit, NULL); > > There was someone on IRC looking to trace system calls in system mode > (by tracking the syscall instruction and reading the registers at the > time). I wonder if we could make this plugin do the right thing in both > modes? > Cool! I think this should be doable. -Rowan
[PATCH v2 1/2] plugins: add plugin API to read guest memory
Signed-off-by: Rowan Hart --- include/qemu/qemu-plugin.h | 32 +++- plugins/api.c| 20 plugins/qemu-plugins.symbols | 1 + 3 files changed, 52 insertions(+), 1 deletion(-) diff --git a/include/qemu/qemu-plugin.h b/include/qemu/qemu-plugin.h index c71c705b69..10f9db8cdc 100644 --- a/include/qemu/qemu-plugin.h +++ b/include/qemu/qemu-plugin.h @@ -57,11 +57,19 @@ typedef uint64_t qemu_plugin_id_t; * - Remove qemu_plugin_register_vcpu_{tb, insn, mem}_exec_inline. * Those functions are replaced by *_per_vcpu variants, which guarantee * thread-safety for operations. + * + * version 3: + * - modified arguments and return value of qemu_plugin_insn_data to copy + * the data into a user-provided buffer instead of returning a pointer + * to the data. + * + * version 4: + * - added qemu_plugin_read_memory_vaddr */ extern QEMU_PLUGIN_EXPORT int qemu_plugin_version; -#define QEMU_PLUGIN_VERSION 3 +#define QEMU_PLUGIN_VERSION 4 /** * struct qemu_info_t - system information for plugins @@ -852,6 +860,28 @@ typedef struct { QEMU_PLUGIN_API GArray *qemu_plugin_get_registers(void); +/** + * qemu_plugin_read_memory_vaddr() - read from memory using a virtual address + * + * @addr: A virtual address to read from + * @data: A byte array to store data into + * @len: The number of bytes to read, starting from @addr + + * @len bytes of data is read starting at @addr and stored into @data. If @data + * is not large enough to hold @len bytes, it will be expanded to the necessary + * size, reallocating if necessary. @len must be greater than 0. + * + * This function does not ensure writes are flushed prior to reading, so + * callers should take care when calling this function in plugin callbacks to + * avoid attempting to read data which may not yet be written and should use + * the memory callback API instead. + * + * Returns true on success and false on failure. + */ +QEMU_PLUGIN_API +bool qemu_plugin_read_memory_vaddr(uint64_t addr, + GByteArray *data, size_t len); + /** * qemu_plugin_read_register() - read register for current vCPU * diff --git a/plugins/api.c b/plugins/api.c index 2ff13d09de..cb7d818918 100644 --- a/plugins/api.c +++ b/plugins/api.c @@ -527,6 +527,26 @@ GArray *qemu_plugin_get_registers(void) return create_register_handles(regs); } +bool qemu_plugin_read_memory_vaddr(vaddr 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, 0); + +if (result < 0) { +return false; +} + +return true; +} + int qemu_plugin_read_register(struct qemu_plugin_register *reg, GByteArray *buf) { g_assert(current_cpu); diff --git a/plugins/qemu-plugins.symbols b/plugins/qemu-plugins.symbols index ca773d8d9f..3ad479a924 100644 --- a/plugins/qemu-plugins.symbols +++ b/plugins/qemu-plugins.symbols @@ -20,6 +20,7 @@ qemu_plugin_num_vcpus; qemu_plugin_outs; qemu_plugin_path_to_binary; + qemu_plugin_read_memory_vaddr; qemu_plugin_read_register; qemu_plugin_register_atexit_cb; qemu_plugin_register_flush_cb; -- 2.46.0
[PATCH v2 0/2] plugins: add plugin API to read guest memory
This patch adds one API function to the QEMU plugin API bool qemu_plugin_read_memory_vaddr(vaddr, GByteArray *, size_t); The API allows reading memory from an arbitrary guest virtual address, which is useful for many things but the motivating examples are: * Virtual Machine Introspection (VMI) * Accurate and easier execution trace extraction * Debugging and logging tools An example of its use is added to the existing syscalls plugin, which now has an option to hexdump the buf argument to any write(2) syscalls which occur. Rowan Hart (2): plugins: add plugin API to read guest memory plugins: add option to dump write argument to syscall plugin docs/about/emulation.rst | 14 - include/qemu/qemu-plugin.h | 32 +- plugins/api.c| 20 ++ plugins/qemu-plugins.symbols | 1 + tests/tcg/plugins/syscall.c | 117 +++ 5 files changed, 182 insertions(+), 2 deletions(-) -- 2.46.0
[PATCH v2 2/2] plugins: add option to dump write argument to syscall plugin
Signed-off-by: Rowan Hart --- docs/about/emulation.rst| 14 - tests/tcg/plugins/syscall.c | 117 2 files changed, 130 insertions(+), 1 deletion(-) diff --git a/docs/about/emulation.rst b/docs/about/emulation.rst index eea1261baa..e85d494ff0 100644 --- a/docs/about/emulation.rst +++ b/docs/about/emulation.rst @@ -388,6 +388,19 @@ run:: 160 1 0 135 1 0 +Behaviour can be tweaked with the following arguments: + +.. list-table:: Syscall plugin arguments + :widths: 20 80 + :header-rows: 1 + + * - Option +- Description + * - print=true|false +- Print the number of times each syscall is called + * - log_writes=true|false +- Log the buffer of each write syscall in hexdump format + Test inline operations .. @@ -777,4 +790,3 @@ Other emulation features When running system emulation you can also enable deterministic execution which allows for repeatable record/replay debugging. See :ref:`Record/Replay` for more details. - diff --git a/tests/tcg/plugins/syscall.c b/tests/tcg/plugins/syscall.c index 72e1a5bf90..7c92f798b5 100644 --- a/tests/tcg/plugins/syscall.c +++ b/tests/tcg/plugins/syscall.c @@ -22,8 +22,57 @@ typedef struct { int64_t errors; } SyscallStats; +struct SyscallInfo { +const char *name; +int64_t write_sysno; +}; + +const struct SyscallInfo arch_syscall_info[] = { +{ "aarch64", 64 }, +{ "aarch64_be", 64 }, +{ "alpha", 4 }, +{ "arm", 4 }, +{ "armeb", 4 }, +{ "avr", -1 }, +{ "cris", -1 }, +{ "hexagon", 64 }, +{ "hppa", -1 }, +{ "i386", 4 }, +{ "loongarch64", -1 }, +{ "m68k", 4 }, +{ "microblaze", 4 }, +{ "microblazeel", 4 }, +{ "mips", 1 }, +{ "mips64", 1 }, +{ "mips64el", 1 }, +{ "mipsel", 1 }, +{ "mipsn32", 1 }, +{ "mipsn32el", 1 }, +{ "or1k", -1 }, +{ "ppc", 4 }, +{ "ppc64", 4 }, +{ "ppc64le", 4 }, +{ "riscv32", 64 }, +{ "riscv64", 64 }, +{ "rx", -1 }, +{ "s390x", -1 }, +{ "sh4", -1 }, +{ "sh4eb", -1 }, +{ "sparc", 4 }, +{ "sparc32plus", 4 }, +{ "sparc64", 4 }, +{ "tricore", -1 }, +{ "x86_64", 1 }, +{ "xtensa", 13 }, +{ "xtensaeb", 13 }, +{ NULL, -1 }, +}; + static GMutex lock; static GHashTable *statistics; +static GByteArray *memory_buffer; +static bool do_log_writes; +static int64_t write_sysno = -1; static SyscallStats *get_or_create_entry(int64_t num) { @@ -39,6 +88,44 @@ static SyscallStats *get_or_create_entry(int64_t num) return entry; } +/* + * Hex-dump a GByteArray to the QEMU plugin output in the format: + * 61 63 63 65 6c 09 09 20 20 20 66 70 75 09 09 09 | accel.fpu... + * 20 6d 6f 64 75 6c 65 2d 63 6f 6d 6d 6f 6e 2e 63 | .module-common.c + */ +static void hexdump(const GByteArray *data) +{ +g_autoptr(GString) out = g_string_new(""); + +for (guint index = 0; index < data->len; index += 16) { +for (guint col = 0; col < 16; col++) { +if (index + col < data->len) { +g_string_append_printf(out, "%02x ", data->data[index + col]); +} else { +g_string_append(out, " "); +} +} + +g_string_append(out, " | "); + +for (guint col = 0; col < 16; col++) { +if (index + col >= data->len) { +break; +} + +if (g_ascii_isgraph(data->data[index + col])) { +g_string_append_printf(out, "%c", data->data[index + col]); +} else { +g_string_append(out, "."); +} +} + +g_string_append(out, "\n"); +} + +qemu_plugin_outs(out->str); +} + static void vcpu_syscall(qemu_plugin_id_t id, unsigned int vcpu_index, int64_t num, uint64_t a1, uint64_t a2, uint64_t a3, uint64_t a4, uint64_t a5, @@ -54,6 +141,14 @@ static void vcpu_syscall(qemu_plugin_id_t id, unsigned int vcpu_index, g_autofree gchar *out = g_strdup_printf("syscall #%" PRIi64 "\n", num); qemu_plugin_outs(out); } + +if (do_log_writes && num == write_sysno) { +if (qemu_plugin_read_memory_vaddr(a2, memory_buffer, a3)) { +hexdump(memory_buffer); +} else { +fprintf(stderr, "Error reading memory from vaddr %lu\n", a2); +} +} } static void vcpu_syscall_ret(qemu_plugin
[PATCH v3 1/2] plugins: add plugin API to read guest memory
Signed-off-by: Rowan Hart Reviewed-by: Pierrick Bouvier --- include/qemu/qemu-plugin.h | 32 +++- plugins/api.c| 20 plugins/qemu-plugins.symbols | 1 + 3 files changed, 52 insertions(+), 1 deletion(-) diff --git a/include/qemu/qemu-plugin.h b/include/qemu/qemu-plugin.h index c71c705b69..e4068f823b 100644 --- a/include/qemu/qemu-plugin.h +++ b/include/qemu/qemu-plugin.h @@ -57,11 +57,19 @@ typedef uint64_t qemu_plugin_id_t; * - Remove qemu_plugin_register_vcpu_{tb, insn, mem}_exec_inline. * Those functions are replaced by *_per_vcpu variants, which guarantee * thread-safety for operations. + * + * version 3: + * - modified arguments and return value of qemu_plugin_insn_data to copy + * the data into a user-provided buffer instead of returning a pointer + * to the data. + * + * version 4: + * - added qemu_plugin_read_memory_vaddr */ extern QEMU_PLUGIN_EXPORT int qemu_plugin_version; -#define QEMU_PLUGIN_VERSION 3 +#define QEMU_PLUGIN_VERSION 4 /** * struct qemu_info_t - system information for plugins @@ -852,6 +860,28 @@ typedef struct { QEMU_PLUGIN_API GArray *qemu_plugin_get_registers(void); +/** + * qemu_plugin_read_memory_vaddr() - read from memory using a virtual address + * + * @addr: A virtual address to read from + * @data: A byte array to store data into + * @len: The number of bytes to read, starting from @addr + * + * @len bytes of data is read starting at @addr and stored into @data. If @data + * is not large enough to hold @len bytes, it will be expanded to the necessary + * size, reallocating if necessary. @len must be greater than 0. + * + * This function does not ensure writes are flushed prior to reading, so + * callers should take care when calling this function in plugin callbacks to + * avoid attempting to read data which may not yet be written and should use + * the memory callback API instead. + * + * Returns true on success and false on failure. + */ +QEMU_PLUGIN_API +bool qemu_plugin_read_memory_vaddr(uint64_t addr, + GByteArray *data, size_t len); + /** * qemu_plugin_read_register() - read register for current vCPU * diff --git a/plugins/api.c b/plugins/api.c index 2ff13d09de..cb7d818918 100644 --- a/plugins/api.c +++ b/plugins/api.c @@ -527,6 +527,26 @@ GArray *qemu_plugin_get_registers(void) return create_register_handles(regs); } +bool qemu_plugin_read_memory_vaddr(vaddr 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, 0); + +if (result < 0) { +return false; +} + +return true; +} + int qemu_plugin_read_register(struct qemu_plugin_register *reg, GByteArray *buf) { g_assert(current_cpu); diff --git a/plugins/qemu-plugins.symbols b/plugins/qemu-plugins.symbols index ca773d8d9f..3ad479a924 100644 --- a/plugins/qemu-plugins.symbols +++ b/plugins/qemu-plugins.symbols @@ -20,6 +20,7 @@ qemu_plugin_num_vcpus; qemu_plugin_outs; qemu_plugin_path_to_binary; + qemu_plugin_read_memory_vaddr; qemu_plugin_read_register; qemu_plugin_register_atexit_cb; qemu_plugin_register_flush_cb; -- 2.46.0
[PATCH v3 0/2] plugins: add plugin API to read guest memory
This patch adds one API function to the QEMU plugin API bool qemu_plugin_read_memory_vaddr(vaddr, GByteArray *, size_t); The API allows reading memory from an arbitrary guest virtual address, which is useful for many things but the motivating examples are: * Virtual Machine Introspection (VMI) * Accurate and easier execution trace extraction * Debugging and logging tools An example of its use is added to the existing syscalls plugin, which now has an option to hexdump the buf argument to any write(2) syscalls which occur. For v3, fixed a missing '*' in a comment which caused a doc build issue. Rowan Hart (2): plugins: add plugin API to read guest memory plugins: add option to dump write argument to syscall plugin docs/about/emulation.rst | 14 - include/qemu/qemu-plugin.h | 32 +- plugins/api.c| 20 ++ plugins/qemu-plugins.symbols | 1 + tests/tcg/plugins/syscall.c | 117 +++ 5 files changed, 182 insertions(+), 2 deletions(-) -- 2.46.0
[PATCH v3 2/2] plugins: add option to dump write argument to syscall plugin
Signed-off-by: Rowan Hart Reviewed-by: Pierrick Bouvier Tested-by: Pierrick Bouvier --- docs/about/emulation.rst| 14 - tests/tcg/plugins/syscall.c | 117 2 files changed, 130 insertions(+), 1 deletion(-) diff --git a/docs/about/emulation.rst b/docs/about/emulation.rst index eea1261baa..e85d494ff0 100644 --- a/docs/about/emulation.rst +++ b/docs/about/emulation.rst @@ -388,6 +388,19 @@ run:: 160 1 0 135 1 0 +Behaviour can be tweaked with the following arguments: + +.. list-table:: Syscall plugin arguments + :widths: 20 80 + :header-rows: 1 + + * - Option +- Description + * - print=true|false +- Print the number of times each syscall is called + * - log_writes=true|false +- Log the buffer of each write syscall in hexdump format + Test inline operations .. @@ -777,4 +790,3 @@ Other emulation features When running system emulation you can also enable deterministic execution which allows for repeatable record/replay debugging. See :ref:`Record/Replay` for more details. - diff --git a/tests/tcg/plugins/syscall.c b/tests/tcg/plugins/syscall.c index 72e1a5bf90..7c92f798b5 100644 --- a/tests/tcg/plugins/syscall.c +++ b/tests/tcg/plugins/syscall.c @@ -22,8 +22,57 @@ typedef struct { int64_t errors; } SyscallStats; +struct SyscallInfo { +const char *name; +int64_t write_sysno; +}; + +const struct SyscallInfo arch_syscall_info[] = { +{ "aarch64", 64 }, +{ "aarch64_be", 64 }, +{ "alpha", 4 }, +{ "arm", 4 }, +{ "armeb", 4 }, +{ "avr", -1 }, +{ "cris", -1 }, +{ "hexagon", 64 }, +{ "hppa", -1 }, +{ "i386", 4 }, +{ "loongarch64", -1 }, +{ "m68k", 4 }, +{ "microblaze", 4 }, +{ "microblazeel", 4 }, +{ "mips", 1 }, +{ "mips64", 1 }, +{ "mips64el", 1 }, +{ "mipsel", 1 }, +{ "mipsn32", 1 }, +{ "mipsn32el", 1 }, +{ "or1k", -1 }, +{ "ppc", 4 }, +{ "ppc64", 4 }, +{ "ppc64le", 4 }, +{ "riscv32", 64 }, +{ "riscv64", 64 }, +{ "rx", -1 }, +{ "s390x", -1 }, +{ "sh4", -1 }, +{ "sh4eb", -1 }, +{ "sparc", 4 }, +{ "sparc32plus", 4 }, +{ "sparc64", 4 }, +{ "tricore", -1 }, +{ "x86_64", 1 }, +{ "xtensa", 13 }, +{ "xtensaeb", 13 }, +{ NULL, -1 }, +}; + static GMutex lock; static GHashTable *statistics; +static GByteArray *memory_buffer; +static bool do_log_writes; +static int64_t write_sysno = -1; static SyscallStats *get_or_create_entry(int64_t num) { @@ -39,6 +88,44 @@ static SyscallStats *get_or_create_entry(int64_t num) return entry; } +/* + * Hex-dump a GByteArray to the QEMU plugin output in the format: + * 61 63 63 65 6c 09 09 20 20 20 66 70 75 09 09 09 | accel.fpu... + * 20 6d 6f 64 75 6c 65 2d 63 6f 6d 6d 6f 6e 2e 63 | .module-common.c + */ +static void hexdump(const GByteArray *data) +{ +g_autoptr(GString) out = g_string_new(""); + +for (guint index = 0; index < data->len; index += 16) { +for (guint col = 0; col < 16; col++) { +if (index + col < data->len) { +g_string_append_printf(out, "%02x ", data->data[index + col]); +} else { +g_string_append(out, " "); +} +} + +g_string_append(out, " | "); + +for (guint col = 0; col < 16; col++) { +if (index + col >= data->len) { +break; +} + +if (g_ascii_isgraph(data->data[index + col])) { +g_string_append_printf(out, "%c", data->data[index + col]); +} else { +g_string_append(out, "."); +} +} + +g_string_append(out, "\n"); +} + +qemu_plugin_outs(out->str); +} + static void vcpu_syscall(qemu_plugin_id_t id, unsigned int vcpu_index, int64_t num, uint64_t a1, uint64_t a2, uint64_t a3, uint64_t a4, uint64_t a5, @@ -54,6 +141,14 @@ static void vcpu_syscall(qemu_plugin_id_t id, unsigned int vcpu_index, g_autofree gchar *out = g_strdup_printf("syscall #%" PRIi64 "\n", num); qemu_plugin_outs(out); } + +if (do_log_writes && num == write_sysno) { +if (qemu_plugin_read_memory_vaddr(a2, memory_buffer, a3)) { +hexdump(memory_buffer); +} else { +fprintf(stderr, "Error reading memory from vaddr %lu\n", a2); +} +}
[PATCH 0/1] plugins: add API to read guest CPU memory from hwaddr
This patch adds a single API function which allows reading from a guest CPU physical address. I don't know of a good way to add a self-contained test for this feature to tests/tcg/plugins, but I did come up with a small test case to demonstrate the functionality using peiyuanix/riscv-os: First, grab and build the firmware code: curl -o firmware.S https://raw.githubusercontent.com/peiyuanix/riscv-os/main/03-Bare-Metal-Hello-RISC-V/firmware.s curl -o firmware.x https://raw.githubusercontent.com/peiyuanix/riscv-os/main/03-Bare-Metal-Hello-RISC-V/firmware.ld riscv64-linux-gnu-as firmware.S -o firmware.o riscv64-linux-gnu-ld -T firmware.x -o firmare firmware.o riscv64-linux-gnu-objcopy -O binary -S firmware firmware.bin Next, grab and build the plugin (just dumps from phys address on first instruction executed): curl -o dump-riscv-firmware.c https://gist.githubusercontent.com/novafacing/5abc08052fab671a0fb26547810b4c55/raw/33772d614d6e36eae30e3405af34f149d7cc608b/dump-riscv-firmware.c gcc -rdynamic -shared -fPIC -Iinclude/qemu $(pkg-config --cflags --libs glib-2.0) -o libdump-riscv-firmware.so dump-riscv-firmware.c Finally, run the plugin: qemu-system-riscv64 -display none -machine virt -serial stdio -bios firmware.bin -plugin $(pwd)libdump-riscv-firmware.so -d plugin This outputs as expected -- the hexdump of the running firmware: b7 01 00 10 a3 80 01 00 93 02 00 08 a3 81 51 00 | ..Q. 93 02 50 00 23 80 51 00 93 02 00 00 a3 80 51 00 | ..P.#.Q...Q. 93 02 30 00 a3 81 51 00 93 02 10 00 23 81 51 00 | ..0...Q.#.Q. 23 82 01 00 83 82 51 00 83 82 01 00 a3 83 01 00 | #.Q. 93 02 80 04 23 80 51 00 93 02 50 06 23 80 51 00 | #.Q...P.#.Q. 93 02 c0 06 23 80 51 00 93 02 c0 06 23 80 51 00 | #.Q.#.Q. 93 02 f0 06 23 80 51 00 93 02 c0 02 23 80 51 00 | #.Q.#.Q. 93 02 00 02 23 80 51 00 93 02 20 05 23 80 51 00 | #.Q.#.Q. 93 02 90 04 23 80 51 00 93 02 30 05 23 80 51 00 | #.Q...0.#.Q. 93 02 30 04 23 80 51 00 93 02 d0 02 23 80 51 00 | ..0.#.Q.#.Q. 93 02 60 05 23 80 51 00 93 02 10 02 23 80 51 00 | ..`.#.Q.#.Q. 93 02 a0 00 23 80 51 00 6f 00 00 00 00 00 00 00 | #.Q.o... 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | Hello, RISC-V! Rowan Hart (1): plugins: add API to read guest CPU memory from hwaddr include/qemu/qemu-plugin.h | 22 ++ plugins/api.c| 17 + plugins/qemu-plugins.symbols | 2 ++ 3 files changed, 41 insertions(+) -- 2.46.0
[PATCH 1/1] plugins: add API to read guest CPU memory from hwaddr
Signed-off-by: Rowan Hart --- include/qemu/qemu-plugin.h | 22 ++ plugins/api.c| 17 + plugins/qemu-plugins.symbols | 2 ++ 3 files changed, 41 insertions(+) diff --git a/include/qemu/qemu-plugin.h b/include/qemu/qemu-plugin.h index c71c705b69..25f39c0960 100644 --- a/include/qemu/qemu-plugin.h +++ b/include/qemu/qemu-plugin.h @@ -868,6 +868,28 @@ QEMU_PLUGIN_API int qemu_plugin_read_register(struct qemu_plugin_register *handle, GByteArray *buf); +/** + * qemu_plugin_read_cpu_memory_hwaddr() - read CPU memory from hwaddr + * + * @addr: A virtual address to read from + * @data: A byte array to store data into + * @len: The number of bytes to read, starting from @addr + * + * @len bytes of data is read starting at @addr and stored into @data. If @data + * is not large enough to hold @len bytes, it will be expanded to the necessary + * size, reallocating if necessary. @len must be greater than 0. + * + * This function does not ensure writes are flushed prior to reading, so + * callers should take care when calling this function in plugin callbacks to + * avoid attempting to read data which may not yet be written and should use + * the memory callback API instead. + * + * Returns true on success and false on failure. + */ +QEMU_PLUGIN_API +bool qemu_plugin_read_cpu_memory_hwaddr(uint64_t addr, + GByteArray *data, size_t len); + /** * qemu_plugin_scoreboard_new() - alloc a new scoreboard * diff --git a/plugins/api.c b/plugins/api.c index 2ff13d09de..c87bed6641 100644 --- a/plugins/api.c +++ b/plugins/api.c @@ -527,6 +527,22 @@ GArray *qemu_plugin_get_registers(void) return create_register_handles(regs); } +bool qemu_plugin_read_cpu_memory_hwaddr(uint64_t addr, +GByteArray *data, uint64_t len) +{ +#ifndef CONFIG_USER_ONLY +if (len == 0) { +return false; +} + +g_byte_array_set_size(data, len); +cpu_physical_memory_rw(addr, (void *)data->data, len, 0); +return true; +#else +return false; +#endif +} + int qemu_plugin_read_register(struct qemu_plugin_register *reg, GByteArray *buf) { g_assert(current_cpu); @@ -534,6 +550,7 @@ int qemu_plugin_read_register(struct qemu_plugin_register *reg, GByteArray *buf) return gdb_read_register(current_cpu, buf, GPOINTER_TO_INT(reg) - 1); } + struct qemu_plugin_scoreboard *qemu_plugin_scoreboard_new(size_t element_size) { return plugin_scoreboard_new(element_size); diff --git a/plugins/qemu-plugins.symbols b/plugins/qemu-plugins.symbols index ca773d8d9f..5d9cfd71bb 100644 --- a/plugins/qemu-plugins.symbols +++ b/plugins/qemu-plugins.symbols @@ -20,6 +20,8 @@ qemu_plugin_num_vcpus; qemu_plugin_outs; qemu_plugin_path_to_binary; + qemu_plugin_read_cpu_memory_hwaddr; + qemu_plugin_read_io_memory_hwaddr; qemu_plugin_read_register; qemu_plugin_register_atexit_cb; qemu_plugin_register_flush_cb; -- 2.46.0
Re: [PATCH 1/1] plugins: add API to read guest CPU memory from hwaddr
> + qemu_plugin_read_cpu_memory_hwaddr; > + qemu_plugin_read_io_memory_hwaddr; This second symbol name should be removed, I initially wanted to implement for IO as well but there is no good generic way I can see to access a list of IO AddressSpace to read from.
[PATCH v2 1/1] plugins: add API to read guest CPU memory from hwaddr
Signed-off-by: Rowan Hart --- include/qemu/qemu-plugin.h | 22 ++ plugins/api.c| 17 + plugins/qemu-plugins.symbols | 1 + 3 files changed, 40 insertions(+) diff --git a/include/qemu/qemu-plugin.h b/include/qemu/qemu-plugin.h index c71c705b69..25f39c0960 100644 --- a/include/qemu/qemu-plugin.h +++ b/include/qemu/qemu-plugin.h @@ -868,6 +868,28 @@ QEMU_PLUGIN_API int qemu_plugin_read_register(struct qemu_plugin_register *handle, GByteArray *buf); +/** + * qemu_plugin_read_cpu_memory_hwaddr() - read CPU memory from hwaddr + * + * @addr: A virtual address to read from + * @data: A byte array to store data into + * @len: The number of bytes to read, starting from @addr + * + * @len bytes of data is read starting at @addr and stored into @data. If @data + * is not large enough to hold @len bytes, it will be expanded to the necessary + * size, reallocating if necessary. @len must be greater than 0. + * + * This function does not ensure writes are flushed prior to reading, so + * callers should take care when calling this function in plugin callbacks to + * avoid attempting to read data which may not yet be written and should use + * the memory callback API instead. + * + * Returns true on success and false on failure. + */ +QEMU_PLUGIN_API +bool qemu_plugin_read_cpu_memory_hwaddr(uint64_t addr, + GByteArray *data, size_t len); + /** * qemu_plugin_scoreboard_new() - alloc a new scoreboard * diff --git a/plugins/api.c b/plugins/api.c index 2ff13d09de..c87bed6641 100644 --- a/plugins/api.c +++ b/plugins/api.c @@ -527,6 +527,22 @@ GArray *qemu_plugin_get_registers(void) return create_register_handles(regs); } +bool qemu_plugin_read_cpu_memory_hwaddr(uint64_t addr, +GByteArray *data, uint64_t len) +{ +#ifndef CONFIG_USER_ONLY +if (len == 0) { +return false; +} + +g_byte_array_set_size(data, len); +cpu_physical_memory_rw(addr, (void *)data->data, len, 0); +return true; +#else +return false; +#endif +} + int qemu_plugin_read_register(struct qemu_plugin_register *reg, GByteArray *buf) { g_assert(current_cpu); @@ -534,6 +550,7 @@ int qemu_plugin_read_register(struct qemu_plugin_register *reg, GByteArray *buf) return gdb_read_register(current_cpu, buf, GPOINTER_TO_INT(reg) - 1); } + struct qemu_plugin_scoreboard *qemu_plugin_scoreboard_new(size_t element_size) { return plugin_scoreboard_new(element_size); diff --git a/plugins/qemu-plugins.symbols b/plugins/qemu-plugins.symbols index ca773d8d9f..616cae1e7f 100644 --- a/plugins/qemu-plugins.symbols +++ b/plugins/qemu-plugins.symbols @@ -20,6 +20,7 @@ qemu_plugin_num_vcpus; qemu_plugin_outs; qemu_plugin_path_to_binary; + qemu_plugin_read_cpu_memory_hwaddr; qemu_plugin_read_register; qemu_plugin_register_atexit_cb; qemu_plugin_register_flush_cb; -- 2.46.0
[PATCH v2 0/1] plugins: add API to read guest CPU memory from hwaddr
This patch adds a single API function which allows reading from a guest CPU physical address. I don't know of a good way to add a self-contained test for this feature to tests/tcg/plugins, but I did come up with a small test case to demonstrate the functionality using peiyuanix/riscv-os: First, grab and build the firmware code: curl -o firmware.S https://raw.githubusercontent.com/peiyuanix/riscv-os/main/03-Bare-Metal-Hello-RISC-V/firmware.s curl -o firmware.x https://raw.githubusercontent.com/peiyuanix/riscv-os/main/03-Bare-Metal-Hello-RISC-V/firmware.ld riscv64-linux-gnu-as firmware.S -o firmware.o riscv64-linux-gnu-ld -T firmware.x -o firmare firmware.o riscv64-linux-gnu-objcopy -O binary -S firmware firmware.bin Next, grab and build the plugin (just dumps from phys address on first instruction executed): curl -o dump-riscv-firmware.c https://gist.githubusercontent.com/novafacing/5abc08052fab671a0fb26547810b4c55/raw/33772d614d6e36eae30e3405af34f149d7cc608b/dump-riscv-firmware.c gcc -rdynamic -shared -fPIC -Iinclude/qemu $(pkg-config --cflags --libs glib-2.0) -o libdump-riscv-firmware.so dump-riscv-firmware.c Finally, run the plugin: qemu-system-riscv64 -display none -machine virt -serial stdio -bios firmware.bin -plugin $(pwd)libdump-riscv-firmware.so -d plugin This outputs as expected -- the hexdump of the running firmware: b7 01 00 10 a3 80 01 00 93 02 00 08 a3 81 51 00 | ..Q. 93 02 50 00 23 80 51 00 93 02 00 00 a3 80 51 00 | ..P.#.Q...Q. 93 02 30 00 a3 81 51 00 93 02 10 00 23 81 51 00 | ..0...Q.#.Q. 23 82 01 00 83 82 51 00 83 82 01 00 a3 83 01 00 | #.Q. 93 02 80 04 23 80 51 00 93 02 50 06 23 80 51 00 | #.Q...P.#.Q. 93 02 c0 06 23 80 51 00 93 02 c0 06 23 80 51 00 | #.Q.#.Q. 93 02 f0 06 23 80 51 00 93 02 c0 02 23 80 51 00 | #.Q.#.Q. 93 02 00 02 23 80 51 00 93 02 20 05 23 80 51 00 | #.Q.#.Q. 93 02 90 04 23 80 51 00 93 02 30 05 23 80 51 00 | #.Q...0.#.Q. 93 02 30 04 23 80 51 00 93 02 d0 02 23 80 51 00 | ..0.#.Q.#.Q. 93 02 60 05 23 80 51 00 93 02 10 02 23 80 51 00 | ..`.#.Q.#.Q. 93 02 a0 00 23 80 51 00 6f 00 00 00 00 00 00 00 | #.Q.o... 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | Hello, RISC-V! For v2, removes a symbol left in qemu-plugins.symbols accidentally. Rowan Hart (1): plugins: add API to read guest CPU memory from hwaddr include/qemu/qemu-plugin.h | 22 ++ plugins/api.c| 17 + plugins/qemu-plugins.symbols | 1 + 3 files changed, 40 insertions(+) -- 2.46.0
Re: [PATCH 0/1] plugins: add API to read guest CPU memory from hwaddr
> > See: > > tests/tcg/i386/system/boot.S > tests/tcg/alpha/system/boot.S > tests/tcg/loongarch64/system/boot.S > tests/tcg/aarch64/system/boot.S > tests/tcg/x86_64/system/boot.S > tests/tcg/arm/system/boot.S > > for what is needed (basically a MMU-enabled flat memory map and some > sort of emit char helper, probably using semihosting in this case) Sounds good! Sorry for the long pause, had some stuff going on :) Investigating this now! I've never messed with semihosting, should be fun.
[PATCH v2 2/3] Add plugin API functions for register R/W, hwaddr R/W, vaddr W
From: novafacing --- include/qemu/qemu-plugin.h | 116 + plugins/api.c | 66 - 2 files changed, 168 insertions(+), 14 deletions(-) diff --git a/include/qemu/qemu-plugin.h b/include/qemu/qemu-plugin.h index 0fba36ae02..b812593e7f 100644 --- a/include/qemu/qemu-plugin.h +++ b/include/qemu/qemu-plugin.h @@ -65,11 +65,18 @@ typedef uint64_t qemu_plugin_id_t; * * version 4: * - added qemu_plugin_read_memory_vaddr + * + * version 5: + * - added qemu_plugin_write_memory_vaddr + * - added qemu_plugin_read_memory_hwaddr + * - added qemu_plugin_write_memory_hwaddr + * - added qemu_plugin_write_register + * */ extern QEMU_PLUGIN_EXPORT int qemu_plugin_version; -#define QEMU_PLUGIN_VERSION 4 +#define QEMU_PLUGIN_VERSION 5 /** * struct qemu_info_t - system information for plugins @@ -255,8 +262,6 @@ typedef struct { * @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, @@ -893,6 +898,41 @@ typedef struct { QEMU_PLUGIN_API GArray *qemu_plugin_get_registers(void); +/** + * qemu_plugin_read_register() - read register for current vCPU + * + * @handle: a @qemu_plugin_reg_handle handle + * @buf: A GByteArray for the data owned by the plugin + * + * This function is only available in a context that register read access is + * explicitly requested via the QEMU_PLUGIN_CB_R_REGS flag. + * + * Returns the size of the read register. The content of @buf is in target byte + * order. On failure returns -1. + */ +QEMU_PLUGIN_API +int qemu_plugin_read_register(struct qemu_plugin_register *handle, + GByteArray *buf); + +/** + * qemu_plugin_write_register() - write register for current vCPU + * + * @handle: a @qemu_plugin_reg_handle handle + * @buf: A GByteArray for the data owned by the plugin + * + * This function is only available in a context that register write access is + * explicitly requested via the QEMU_PLUGIN_CB_W_REGS flag. + * + * The size of @buf must be at least the size of the requested register. + * Attempting to write a register with @buf smaller than the register size + * will result in a crash or other undesired behavior. + * + * Returns the number of bytes written. On failure returns 0. + */ +QEMU_PLUGIN_API +int qemu_plugin_write_register(struct qemu_plugin_register *handle, + GByteArray *buf); + /** * qemu_plugin_read_memory_vaddr() - read from memory using a virtual address * @@ -916,20 +956,72 @@ bool qemu_plugin_read_memory_vaddr(uint64_t addr, GByteArray *data, size_t len); /** - * qemu_plugin_read_register() - read register for current vCPU + * qemu_plugin_write_memory_vaddr() - write to memory using a virtual address * - * @handle: a @qemu_plugin_reg_handle handle - * @buf: A GByteArray for the data owned by the plugin + * @addr: A virtual address to write to + * @data: A byte array containing the data to write * - * This function is only available in a context that register read access is - * explicitly requested via the QEMU_PLUGIN_CB_R_REGS flag. + * The contents of @data will be written to memory starting at the virtual + * address @addr. * - * Returns the size of the read register. The content of @buf is in target byte - * order. On failure returns -1. + * This function does not guarantee consistency of writes, nor does it ensure + * that pending writes are flushed either before or after the write takes + * place, so callers should take care when calling this function in plugin + * callbacks to avoid depending on the existence of data written using this + * function which may be overwritten afterward. + * + * Returns true on success and false on failure. */ QEMU_PLUGIN_API -int qemu_plugin_read_register(struct qemu_plugin_register *handle, - GByteArray *buf); +bool qemu_plugin_write_memory_vaddr(uint64_t addr, + GByteArray *data); + +/** + * qemu_plugin_read_memory_vaddr() - read from memory using a hardware address + * + * @addr: A virtual address to read from + * @data: A byte array to store data into + * @len: The number of bytes to read, starting from @addr + * + * @len bytes of data is read starting at @addr and stored into @data. If @data + * is not large enough to hold @len bytes, it will be expanded to the necessary + * size, reallocating if necessary. @len must be greater than 0. + * + * This function does not ensure writes are flushed prior to reading, so + * callers should take care when calling this function in plugin callbacks to + * avoid attempting to read data which may not yet be written and should use + * the memory callback API instead. + * + * This function is only valid for soft
[PATCH v2 0/3] Add additional plugin API functions to read and write memory and registers
This patch set follows a previous patch which added the qemu_plugin_read_memory_vaddr function and adds a set of similar functions to read and write registers, virtual memory, and physical memory. The use case I have in mind is for use of QEMU for program analysis and testing. For example, a fuzzer which uses QEMU for emulation might wish to inject test data into a program at runtime using qemu_plugin_write_memory_vaddr (and likewise if testing an operating system or bare metal application using qemu_plugin_write_memory_hwaddr). It may also wish to read the initial contents of memory using qemu_plugin_read_memory_vaddr/hwaddr. Similarly, a testing framework may wish to fake register values, perhaps to simulate a device failure, perhaps by using qemu_plugin_write_register to set a register value to an error code. I think all this functionality works together to make QEMU plugins more powerful and versatile, hopefully removing barriers to using upstream QEMU for these tasks which have historically required maintaining a QEMU fork downstream (like QEMUAFL https://github.com/AFLplusplus/qemuafl), which is tedious, error prone, and results in users missing out on enhancements to QEMU. A test is provided, compile: gcc -o tests/tcg/x86_64/inject-target tests/tcg/x86_64/inject-target.c And run: ./build/qemu-x86_64 -d plugin --plugin build/tests/tcg/plugins/libinject.so tests/tcg/x86_64/inject-target Hopefully after a number of tries, the inject plugin will inject the right value into the target program, leading to a victory message. This plugin handles simple "hypercalls", only one of which is implemented and injects data into guest memory. novafacing (3): Expose gdb_write_register function to consumers of gdbstub Add plugin API functions for register R/W, hwaddr R/W, vaddr W Add inject plugin and x86_64 target for the inject plugin gdbstub/gdbstub.c| 2 +- include/exec/gdbstub.h | 14 +++ include/qemu/qemu-plugin.h | 116 +++-- plugins/api.c| 66 +- tests/tcg/plugins/inject.c | 206 +++ tests/tcg/plugins/meson.build| 2 +- tests/tcg/x86_64/Makefile.target | 1 + tests/tcg/x86_64/inject-target.c | 27 8 files changed, 418 insertions(+), 16 deletions(-) create mode 100644 tests/tcg/plugins/inject.c create mode 100644 tests/tcg/x86_64/inject-target.c -- 2.46.1
[PATCH v2 3/3] Add inject plugin and x86_64 target for the inject plugin
From: novafacing --- tests/tcg/plugins/inject.c | 206 +++ tests/tcg/plugins/meson.build| 2 +- tests/tcg/x86_64/Makefile.target | 1 + tests/tcg/x86_64/inject-target.c | 27 4 files changed, 235 insertions(+), 1 deletion(-) create mode 100644 tests/tcg/plugins/inject.c create mode 100644 tests/tcg/x86_64/inject-target.c diff --git a/tests/tcg/plugins/inject.c b/tests/tcg/plugins/inject.c new file mode 100644 index 00..9edc2cd34e --- /dev/null +++ b/tests/tcg/plugins/inject.c @@ -0,0 +1,206 @@ +/* + * Copyright (C) 2024, Rowan Hart + * + * License: GNU GPL, version 2 or later. + * See the COPYING file in the top-level directory. + */ +#include "glib.h" +#include +#include +#include +#include +#include +#include + +#include + +/* + * 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 char *name; +const bool enabled; +const char *hypercall; +const bool little_endian; +const char *num_reg; +const char *arg0_reg; +const char *arg1_reg; +}; + +static const struct HypercallSpec *hypercall_spec; + +static const struct HypercallSpec hypercall_specs[] = { +{ "aarch64", false, NULL, true, 0, 0, 0 }, +{ "aarch64_be", false, NULL, false, 0, 0, 0 }, +{ "alpha", false, NULL, true, 0, 0, 0 }, +{ "arm", false, NULL, true, 0, 0, 0 }, +{ "armeb", false, NULL, false, 0, 0, 0 }, +{ "avr", false, NULL, true, 0, 0, 0 }, +{ "hexagon", false, NULL, true, 0, 0, 0 }, +{ "hppa", false, NULL, false, 0, 0, 0 }, +{ "i386", false, NULL, true, 0, 0, 0 }, +{ "loongarch64", false, NULL, true, 0, 0, 0 }, +{ "m68k", false, NULL, false, 0, 0, 0 }, +{ "microblaze", false, NULL, false, 0, 0, 0 }, +{ "microblazeel", false, NULL, true, 0, 0, 0 }, +{ "mips", false, NULL, false, 0, 0, 0 }, +{ "mips64", false, NULL, false, 0, 0, 0 }, +{ "mips64el", false, NULL, true, 0, 0, 0 }, +{ "mipsel", false, NULL, true, 0, 0, 0 }, +{ "mipsn32", false, NULL, false, 0, 0, 0 }, +{ "mipsn32el", false, NULL, true, 0, 0, 0 }, +{ "or1k", false, NULL, false, 0, 0, 0 }, +{ "ppc", false, NULL, false, 0, 0, 0 }, +{ "ppc64", false, NULL, false, 0, 0, 0 }, +{ "ppc64le", false, NULL, true, 0, 0, 0 }, +{ "riscv32", false, NULL, true, 0, 0, 0 }, +{ "riscv64", false, NULL, true, 0, 0, 0 }, +{ "rx", false, NULL, true, 0, 0, 0 }, +{ "s390x", false, NULL, false, 0, 0, 0 }, +{ "sh4", false, NULL, true, 0, 0, 0 }, +{ "sh4eb", false, NULL, false, 0, 0, 0 }, +{ "sparc", false, NULL, false, 0, 0, 0 }, +{ "sparc32plus", false, NULL, false, 0, 0, 0 }, +{ "sparc64", false, NULL, false, 0, 0, 0 }, +{ "tricore", false, NULL, true, 0, 0, 0 }, +{ "x86_64", true, "\x0f\xa2", true, "rax", "rdi", "rsi" }, +{ "xtensa", false, NULL, true, 0, 0, 0 }, +{ "xtensaeb", false, NULL, false, 0, 0, 0 }, +{ NULL, false, NULL, false, 0, 0, 0 }, +}; + +QEMU_PLUGIN_EXPORT int qemu_plugin_version = QEMU_PLUGIN_VERSION; + +/* + * 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->little_endian) { +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 "hyperacll" instruction, which has some specia
[PATCH v2 1/3] Expose gdb_write_register function to consumers of gdbstub
From: novafacing --- gdbstub/gdbstub.c | 2 +- include/exec/gdbstub.h | 14 ++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/gdbstub/gdbstub.c b/gdbstub/gdbstub.c index b1def7e71d..7d87a3324c 100644 --- a/gdbstub/gdbstub.c +++ b/gdbstub/gdbstub.c @@ -536,7 +536,7 @@ int gdb_read_register(CPUState *cpu, GByteArray *buf, int reg) return 0; } -static int gdb_write_register(CPUState *cpu, uint8_t *mem_buf, int reg) +int gdb_write_register(CPUState *cpu, uint8_t *mem_buf, int reg) { CPUClass *cc = CPU_GET_CLASS(cpu); GDBRegisterState *r; diff --git a/include/exec/gdbstub.h b/include/exec/gdbstub.h index d73f424f56..584ed73fc9 100644 --- a/include/exec/gdbstub.h +++ b/include/exec/gdbstub.h @@ -118,6 +118,20 @@ const GDBFeature *gdb_find_static_feature(const char *xmlname); */ int gdb_read_register(CPUState *cpu, GByteArray *buf, int reg); +/** + * gdb_write_register() - Write a register associated with a CPU. + * @cpu: The CPU associated with the register. + * @buf: The buffer that the register contents will be set to. + * @reg: The register's number returned by gdb_find_feature_register(). + * + * The size of @buf must be at least the size of the register being + * written. + * + * Return: The number of written bytes, or 0 if an error occurred (for + * example, an unknown register was provided). + */ +int gdb_write_register(CPUState *cpu, uint8_t *mem_buf, int reg); + /** * typedef GDBRegDesc - a register description from gdbstub */ -- 2.46.1
[PATCH 0/2] Add additional plugin API functions to read and write memory and registers
This patch set follows a previous patch which added the qemu_plugin_read_memory_vaddr function and adds a set of similar functions to read and write registers, virtual memory, and physical memory. The use case I have in mind is for use of QEMU for program analysis and testing. For example, a fuzzer which uses QEMU for emulation might wish to inject test data into a program at runtime using qemu_plugin_write_memory_vaddr (and likewise if testing an operating system or bare metal application using qemu_plugin_write_memory_hwaddr). It may also wish to read the initial contents of memory using qemu_plugin_read_memory_vaddr/hwaddr. Similarly, a testing framework may wish to fake register values, perhaps to simulate a device failure, perhaps by using qemu_plugin_write_register to set a register value to an error code. I think all this functionality works together to make QEMU plugins more powerful and versatile, hopefully removing barriers to using upstream QEMU for these tasks which have historically required maintaining a QEMU fork downstream (like QEMUAFL https://github.com/AFLplusplus/qemuafl), which is tedious, error prone, and results in users missing out on enhancements to QEMU. novafacing (2): Expose gdb_write_register function to consumers of gdbstub Add plugin API functions for register R/W, hwaddr R/W, vaddr W gdbstub/gdbstub.c | 2 +- include/exec/gdbstub.h | 14 + include/qemu/qemu-plugin.h | 116 + plugins/api.c | 66 - 4 files changed, 183 insertions(+), 15 deletions(-) -- 2.46.1
[PATCH 1/2] Expose gdb_write_register function to consumers of gdbstub
From: novafacing --- gdbstub/gdbstub.c | 2 +- include/exec/gdbstub.h | 14 ++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/gdbstub/gdbstub.c b/gdbstub/gdbstub.c index b1def7e71d..7d87a3324c 100644 --- a/gdbstub/gdbstub.c +++ b/gdbstub/gdbstub.c @@ -536,7 +536,7 @@ int gdb_read_register(CPUState *cpu, GByteArray *buf, int reg) return 0; } -static int gdb_write_register(CPUState *cpu, uint8_t *mem_buf, int reg) +int gdb_write_register(CPUState *cpu, uint8_t *mem_buf, int reg) { CPUClass *cc = CPU_GET_CLASS(cpu); GDBRegisterState *r; diff --git a/include/exec/gdbstub.h b/include/exec/gdbstub.h index d73f424f56..584ed73fc9 100644 --- a/include/exec/gdbstub.h +++ b/include/exec/gdbstub.h @@ -118,6 +118,20 @@ const GDBFeature *gdb_find_static_feature(const char *xmlname); */ int gdb_read_register(CPUState *cpu, GByteArray *buf, int reg); +/** + * gdb_write_register() - Write a register associated with a CPU. + * @cpu: The CPU associated with the register. + * @buf: The buffer that the register contents will be set to. + * @reg: The register's number returned by gdb_find_feature_register(). + * + * The size of @buf must be at least the size of the register being + * written. + * + * Return: The number of written bytes, or 0 if an error occurred (for + * example, an unknown register was provided). + */ +int gdb_write_register(CPUState *cpu, uint8_t *mem_buf, int reg); + /** * typedef GDBRegDesc - a register description from gdbstub */ -- 2.46.1
[PATCH 2/2] Add plugin API functions for register R/W, hwaddr R/W, vaddr W
From: novafacing --- include/qemu/qemu-plugin.h | 116 + plugins/api.c | 66 - 2 files changed, 168 insertions(+), 14 deletions(-) diff --git a/include/qemu/qemu-plugin.h b/include/qemu/qemu-plugin.h index 0fba36ae02..b812593e7f 100644 --- a/include/qemu/qemu-plugin.h +++ b/include/qemu/qemu-plugin.h @@ -65,11 +65,18 @@ typedef uint64_t qemu_plugin_id_t; * * version 4: * - added qemu_plugin_read_memory_vaddr + * + * version 5: + * - added qemu_plugin_write_memory_vaddr + * - added qemu_plugin_read_memory_hwaddr + * - added qemu_plugin_write_memory_hwaddr + * - added qemu_plugin_write_register + * */ extern QEMU_PLUGIN_EXPORT int qemu_plugin_version; -#define QEMU_PLUGIN_VERSION 4 +#define QEMU_PLUGIN_VERSION 5 /** * struct qemu_info_t - system information for plugins @@ -255,8 +262,6 @@ typedef struct { * @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, @@ -893,6 +898,41 @@ typedef struct { QEMU_PLUGIN_API GArray *qemu_plugin_get_registers(void); +/** + * qemu_plugin_read_register() - read register for current vCPU + * + * @handle: a @qemu_plugin_reg_handle handle + * @buf: A GByteArray for the data owned by the plugin + * + * This function is only available in a context that register read access is + * explicitly requested via the QEMU_PLUGIN_CB_R_REGS flag. + * + * Returns the size of the read register. The content of @buf is in target byte + * order. On failure returns -1. + */ +QEMU_PLUGIN_API +int qemu_plugin_read_register(struct qemu_plugin_register *handle, + GByteArray *buf); + +/** + * qemu_plugin_write_register() - write register for current vCPU + * + * @handle: a @qemu_plugin_reg_handle handle + * @buf: A GByteArray for the data owned by the plugin + * + * This function is only available in a context that register write access is + * explicitly requested via the QEMU_PLUGIN_CB_W_REGS flag. + * + * The size of @buf must be at least the size of the requested register. + * Attempting to write a register with @buf smaller than the register size + * will result in a crash or other undesired behavior. + * + * Returns the number of bytes written. On failure returns 0. + */ +QEMU_PLUGIN_API +int qemu_plugin_write_register(struct qemu_plugin_register *handle, + GByteArray *buf); + /** * qemu_plugin_read_memory_vaddr() - read from memory using a virtual address * @@ -916,20 +956,72 @@ bool qemu_plugin_read_memory_vaddr(uint64_t addr, GByteArray *data, size_t len); /** - * qemu_plugin_read_register() - read register for current vCPU + * qemu_plugin_write_memory_vaddr() - write to memory using a virtual address * - * @handle: a @qemu_plugin_reg_handle handle - * @buf: A GByteArray for the data owned by the plugin + * @addr: A virtual address to write to + * @data: A byte array containing the data to write * - * This function is only available in a context that register read access is - * explicitly requested via the QEMU_PLUGIN_CB_R_REGS flag. + * The contents of @data will be written to memory starting at the virtual + * address @addr. * - * Returns the size of the read register. The content of @buf is in target byte - * order. On failure returns -1. + * This function does not guarantee consistency of writes, nor does it ensure + * that pending writes are flushed either before or after the write takes + * place, so callers should take care when calling this function in plugin + * callbacks to avoid depending on the existence of data written using this + * function which may be overwritten afterward. + * + * Returns true on success and false on failure. */ QEMU_PLUGIN_API -int qemu_plugin_read_register(struct qemu_plugin_register *handle, - GByteArray *buf); +bool qemu_plugin_write_memory_vaddr(uint64_t addr, + GByteArray *data); + +/** + * qemu_plugin_read_memory_vaddr() - read from memory using a hardware address + * + * @addr: A virtual address to read from + * @data: A byte array to store data into + * @len: The number of bytes to read, starting from @addr + * + * @len bytes of data is read starting at @addr and stored into @data. If @data + * is not large enough to hold @len bytes, it will be expanded to the necessary + * size, reallocating if necessary. @len must be greater than 0. + * + * This function does not ensure writes are flushed prior to reading, so + * callers should take care when calling this function in plugin callbacks to + * avoid attempting to read data which may not yet be written and should use + * the memory callback API instead. + * + * This function is only valid for soft
Re: [PATCH v2 3/3] Add inject plugin and x86_64 target for the inject plugin
>> +++ b/tests/tcg/plugins/inject.c > > Could we find a better name? For sure, maybe "hypercalls.c" since that's really what it's mostly about. >> @@ -0,0 +1,206 @@ >> +/* >> + * Copyright (C) 2024, Rowan Hart >> + * >> + * License: GNU GPL, version 2 or later. >> + * See the COPYING file in the top-level directory. >> + */ > > We can add a comment here about what the plugin is doing. Will do! > One challenge with picking a random value, is how to ensure this pattern has > no other meaning for all architectures? I'm not sure we can find a single > pattern of bytes that works for all arch, even though that would be > definitely stylish :). > > In more, it seems that we are reinventing the syscall interface, while we > already have it. But as the current instrumentation only works for user-mode, > having a specific hypercall interface might be worth it for plugins, so > system mode could benefit from it too. > > The work done here could serve later to define a proper interface. I'll see what I can do about this. SIMICS supports many architectures and has a "magic instruction" interface[0] (basically hypercalls) and has these instructions defined per-architecture in a way that at minimum there are 12 values available which work on every architecture the simulator supports. QEMU supports more architectures than SIMICS but I think we could start there and follow a similar approach. [0]: https://intel.github.io/tsffs/simics/simics-user-guide/breakpoints.html#Magic-Breakpoints -Rowan
Re: [PATCH v2 0/3] Add additional plugin API functions to read and write memory and registers
> I am personally in favor to adding such features in upstream QEMU, but we > should discuss it with the maintainers, because it would allow to change the > state of execution, which is something qemu plugins actively didn't try to > do. It's a real paradigm shift for plugins. > > By writing to memory/registers, we can start replacing instructions and > control flow, and there is a whole set of consequences to that. > Totally agree! As much as I really want this functionality for plugins, I think alignment on it is quite important. I can see very cool use cases for being able to replace instructions and control flow to allow hooking functions, hotpatching, and so forth. I don't really know the edge cases here so your expertise will be helpful. In the worst case I can imagine: what would happen if a callback overwrote the next insn? I'm not sure what behavior I would expect if that insn has already been translated as part of the same tb. That said, the plugin is aware of which insns have already been translated, so maybe it is not unreasonable to just document this as a "don't do that". Let me know what you think. > The hypercall functionality would be useful for plugins as a whole. And I > think it definitely deserves to be worked on, if maintainers are open to that > as well. Sure, I'd be happy to work on this some more. At least on the fuzzing side of things, the way hypercalls are done across hypervisors (QEMU, Bochs, etc) is pretty consistent so I think we could provide a useful common set of functionality. The reason I did the bare minimum here is I'm not familiar with every architecture, and a good NOP needs to be chosen for each one along with a reasonable way to pass some arguments -- I don't know if I'm the right person to make that call. Glad to hear you're for this idea! -Rowan
[PATCH v3 4/8] Add memory virtual address write API
From: novafacing Signed-off-by: novafacing Signed-off-by: Rowan Hart --- include/qemu/qemu-plugin.h | 21 + plugins/api.c | 18 ++ 2 files changed, 39 insertions(+) diff --git a/include/qemu/qemu-plugin.h b/include/qemu/qemu-plugin.h index 1380f7d441..eff8430b4a 100644 --- a/include/qemu/qemu-plugin.h +++ b/include/qemu/qemu-plugin.h @@ -993,6 +993,27 @@ QEMU_PLUGIN_API bool qemu_plugin_read_memory_vaddr(uint64_t addr, GByteArray *data, size_t len); +/** + * qemu_plugin_write_memory_vaddr() - write to memory using a virtual address + * + * @addr: A virtual address to write to + * @data: A byte array containing the data to write + * + * The contents of @data will be written to memory starting at the virtual + * address @addr. + * + * This function does not guarantee consistency of writes, nor does it ensure + * that pending writes are flushed either before or after the write takes place, + * so callers should take care to only call this function in vCPU context (i.e. + * in callbacks) and avoid depending on the existence of data written using this + * function which may be overwritten afterward. + * + * Returns true on success and false on failure. + */ +QEMU_PLUGIN_API +bool qemu_plugin_write_memory_vaddr(uint64_t addr, + GByteArray *data); + /** * qemu_plugin_scoreboard_new() - alloc a new scoreboard * diff --git a/plugins/api.c b/plugins/api.c index d1cc6ff86e..19c10bb39e 100644 --- a/plugins/api.c +++ b/plugins/api.c @@ -551,6 +551,24 @@ bool qemu_plugin_read_memory_vaddr(uint64_t addr, GByteArray *data, size_t len) 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; +} + struct qemu_plugin_scoreboard *qemu_plugin_scoreboard_new(size_t element_size) { return plugin_scoreboard_new(element_size); -- 2.49.0
[PATCH v3 0/8] Add additional plugin API functions to read and write memory and registers
This patch series adds several new API functions focused on enabling use cases around reading and writing guest memory from QEMU plugins. To support these new APIs, some utility functionality around retrieving information about address spaces is added as well. The new qemu_plugin_write_register utilizes gdb_write_register, which is now declared in gdbstub.h for this purpose instead of being static. qemu_plugin_write_memory_vaddr utilizes cpu_memory_rw_debug much the same as the existing read_memory_vaddr function does. The read and write_hwaddr functions are the most different. These functions use address_space_rw, which works well in most cases. There is an important caveat that for writes, the page being written will be set dirty by the write operation. This dirty setting requires locking the page range, which can contend with an already held lock in page_collection_lock when called in a tb translate callback with a write to the instruction memory in the tb. The doc comments warn against doing this, and it's unlikely anyone would want to do this. I've also added two test plugins: one that implements a simple hypercall interface that guest code can use to communicate with the plugin in a structured way with a test to ensure that this hypercall works and writing virtual memory works. And one that implements a simple patch utility to patch memory at runtime. The test for the second plugin ensures the patch applies successfully to instruction memory, and can use both hw and vaddr methods. novafacing (8): Expose gdb_write_register function to consumers of gdbstub Add register write API Add address space API Add memory virtual address write API Add memory hardware address read/write API Add patcher plugin and test Add hypercalls plugin and test Update plugin version and add notes gdbstub/gdbstub.c | 2 +- include/exec/gdbstub.h| 14 + include/qemu/plugin.h | 6 + include/qemu/qemu-plugin.h| 217 ++- plugins/api.c | 213 ++- tests/tcg/Makefile.target | 2 + tests/tcg/plugins/hypercalls.c| 552 ++ tests/tcg/plugins/meson.build | 2 +- tests/tcg/plugins/patch.c | 324 ++ tests/tcg/x86_64/Makefile.softmmu-target | 36 +- tests/tcg/x86_64/system/hypercalls-target.c | 45 ++ tests/tcg/x86_64/system/patch-target.c| 32 + .../tcg/x86_64/system/validate-hypercalls.py | 40 ++ tests/tcg/x86_64/system/validate-patch.py | 39 ++ 14 files changed, 1501 insertions(+), 23 deletions(-) create mode 100644 tests/tcg/plugins/hypercalls.c create mode 100644 tests/tcg/plugins/patch.c create mode 100644 tests/tcg/x86_64/system/hypercalls-target.c create mode 100644 tests/tcg/x86_64/system/patch-target.c create mode 100755 tests/tcg/x86_64/system/validate-hypercalls.py create mode 100755 tests/tcg/x86_64/system/validate-patch.py -- 2.49.0
[PATCH v3 5/8] Add memory hardware address read/write API
From: novafacing Signed-off-by: novafacing Signed-off-by: Rowan Hart --- include/qemu/qemu-plugin.h | 96 +++ plugins/api.c | 100 + 2 files changed, 196 insertions(+) diff --git a/include/qemu/qemu-plugin.h b/include/qemu/qemu-plugin.h index eff8430b4a..d4f229abd9 100644 --- a/include/qemu/qemu-plugin.h +++ b/include/qemu/qemu-plugin.h @@ -1014,6 +1014,102 @@ QEMU_PLUGIN_API bool qemu_plugin_write_memory_vaddr(uint64_t addr, GByteArray *data); +/** + * enum qemu_plugin_hwaddr_operation_result - result of a memory operation + * + * @QEMU_PLUGIN_HWADDR_OPERATION_OK: hwaddr operation succeeded + * @QEMU_PLUGIN_HWADDR_OPERATION_ERROR: unexpected error occurred + * @QEMU_PLUGIN_HWADDR_OPERATION_DEVICE_ERROR: error in memory device + * @QEMU_PLUGIN_HWADDR_OPERATION_ACCESS_DENIED: permission error + * @QEMU_PLUGIN_HWADDR_OPERATION_INVALID_ADDRESS: address was invalid + * @QEMU_PLUGIN_HWADDR_OPERATION_INVALID_ADDRESS_SPACE: invalid address space + */ +enum qemu_plugin_hwaddr_operation_result { +QEMU_PLUGIN_HWADDR_OPERATION_OK, +QEMU_PLUGIN_HWADDR_OPERATION_ERROR, +QEMU_PLUGIN_HWADDR_OPERATION_DEVICE_ERROR, +QEMU_PLUGIN_HWADDR_OPERATION_ACCESS_DENIED, +QEMU_PLUGIN_HWADDR_OPERATION_INVALID_ADDRESS, +QEMU_PLUGIN_HWADDR_OPERATION_INVALID_ADDRESS_SPACE, +}; + +/** + * qemu_plugin_read_memory_hwaddr() - read from memory using a hardware address + * + * @as_idx: The index of the address space to read from + * @addr: A physical address to read from + * @data: A byte array to store data into + * @len: The number of bytes to read, starting from @addr + * + * @len bytes of data is read starting at @addr and stored into @data. If @data + * is not large enough to hold @len bytes, it will be expanded to the necessary + * size, reallocating if necessary. @len must be greater than 0. + * + * This function does not ensure writes are flushed prior to reading, so + * callers should take care when calling this function in plugin callbacks to + * avoid attempting to read data which may not yet be written and should use + * the memory callback API instead. + * + * This function is only valid for softmmu targets. + * + * Returns a qemu_plugin_hwaddr_operation_result indicating the result of the + * operation. + */ +QEMU_PLUGIN_API +enum qemu_plugin_hwaddr_operation_result +qemu_plugin_read_memory_hwaddr(unsigned int as_idx, uint64_t addr, +GByteArray *data, size_t len); + +/** + * qemu_plugin_write_memory_hwaddr() - write to memory using a hardware address + * + * @as_idx: The index of the address space to write to + * @addr: A physical address to write to + * @data: A byte array containing the data to write + * + * The contents of @data will be written to memory starting at the hardware + * address @addr. + * + * This function does not guarantee consistency of writes, nor does it ensure + * that pending writes are flushed either before or after the write takes place, + * so callers should take care when calling this function in plugin callbacks to + * avoid depending on the existence of data written using this function which + * may be overwritten afterward. In addition, this function requires that the + * pages containing the address are not locked. Practically, this means that you + * should not write instruction memory in a current translation block inside a + * callback registered with qemu_plugin_register_vcpu_tb_trans_cb. + * + * You can, for example, write instruction memory in a current translation block + * in a callback registered with qemu_plugin_register_vcpu_tb_exec_cb, although + * be aware that the write will not be flushed until after the translation block + * has finished executing. In general, this function should be used to write + * data memory or to patch code at a known address, not in a current translation + * block. + * + * This function is only valid for softmmu targets. + * + * Returns a qemu_plugin_hwaddr_operation_result indicating the result of the + * operation. + */ +QEMU_PLUGIN_API +enum qemu_plugin_hwaddr_operation_result +qemu_plugin_write_memory_hwaddr(unsigned int as_idx, uint64_t addr, + GByteArray *data); + +/** + * qemu_plugin_translate_vaddr() - translate a virtual address to a physical one + * + * @vaddr: virtual address to translate + * @hwaddr: pointer to store the physical address + * + * This function is only valid in vCPU context (i.e. in callbacks) and is only + * valid for softmmu targets. + * + * Returns true on success and false on failure. + */ +QEMU_PLUGIN_API +bool qemu_plugin_translate_vaddr(uint64_t vaddr, uint64_t *hwaddr); + /** * qemu_plugin_scoreboard_new() - alloc a new scoreboard * diff --git a/plugins/api.c b/plugins/api.c index 19c10bb39e..5983768783 100644 --- a/plugins/api.c +++ b/plugins/api.c @@ -569,6 +569,106 @@ bool
[PATCH v3 8/8] Update plugin version and add notes
From: novafacing Signed-off-by: Rowan Hart --- include/qemu/qemu-plugin.h | 12 +++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/include/qemu/qemu-plugin.h b/include/qemu/qemu-plugin.h index d4f229abd9..4cf2955560 100644 --- a/include/qemu/qemu-plugin.h +++ b/include/qemu/qemu-plugin.h @@ -65,11 +65,21 @@ typedef uint64_t qemu_plugin_id_t; * * version 4: * - added qemu_plugin_read_memory_vaddr + * + * version 5: + * - added qemu_plugin_write_memory_vaddr + * - added qemu_plugin_read_memory_hwaddr + * - added qemu_plugin_write_memory_hwaddr + * - added qemu_plugin_write_register + * - added qemu_plugin_get_current_vcpu_address_spaces + * - added qemu_plugin_n_address_spaces + * - added qemu_plugin_address_space_name + * - added qemu_plugin_translate_vaddr */ extern QEMU_PLUGIN_EXPORT int qemu_plugin_version; -#define QEMU_PLUGIN_VERSION 4 +#define QEMU_PLUGIN_VERSION 5 /** * struct qemu_info_t - system information for plugins -- 2.49.0
[PATCH v3 6/8] Add patcher plugin and test
From: novafacing Signed-off-by: novafacing Signed-off-by: Rowan Hart --- tests/tcg/Makefile.target | 1 + tests/tcg/plugins/meson.build | 2 +- tests/tcg/plugins/patch.c | 324 ++ tests/tcg/x86_64/Makefile.softmmu-target | 32 ++- tests/tcg/x86_64/system/patch-target.c| 32 +++ tests/tcg/x86_64/system/validate-patch.py | 39 +++ 6 files changed, 424 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=$(COMMA)patch= 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 00..6f2bc3688e --- /dev/null +++ b/tests/tcg/plugins/patch.c @@ -0,0 +1,324 @@ +/* + * SPDX-License-Identifier: GPL-2.0-or-later + * + * Copyright (C) 2025, Rowan Hart + * + * License: GNU GPL, version 2 or later. + * See the COPYING file in the top-level directory. + * + * This plugin patches instructions matching a pattern to a different + * instruction as they execute + * + */ + +#include "glib.h" +#include "glibconfig.h" + +#include +#include +#include + +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); + +struct qemu_plugin_address_space_info *info = +qemu_plugin_get_current_vcpu_address_spaces(); + +if (!info) { +qemu_plugin_outs("Failed to get address spaces\n"); +return; +} + +qemu_plugin_outs("Got address spaces\n"); + +int n_address_spaces = qemu_plugin_n_address_spaces(info); +unsigned int memory_idx; + +for (memory_idx = 0; memory_idx < n_address_spaces; memory_idx++) { +const char *name = qemu_plugin_address_space_name(info, memory_idx); +if (!g_strcmp0(name, "cpu-memory-0")) { +break; +} +} + +if (memory_idx >= n_address_spaces) { +qemu_plugin_outs("No matching address space\n"); +return; +} + +qemu_plugin_outs("Got address space cpu-memory-0\n"); + +qemu_plugin_outs("Writing memory (hwaddr)...\n"); +enum qemu_plugin_hwaddr_operation_result result = +qemu_plugin_write_memory_hwaddr(memory_idx, 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); +
[PATCH v3 3/8] Add address space API
From: novafacing Signed-off-by: novafacing Signed-off-by: Rowan Hart --- include/qemu/plugin.h | 6 +++ include/qemu/qemu-plugin.h | 45 ++ plugins/api.c | 79 ++ 3 files changed, 130 insertions(+) diff --git a/include/qemu/plugin.h b/include/qemu/plugin.h index 9726a9ebf3..38439a37fa 100644 --- a/include/qemu/plugin.h +++ b/include/qemu/plugin.h @@ -139,6 +139,12 @@ struct qemu_plugin_tb { GArray *cbs; }; +/* Internal context for address space information */ +struct qemu_plugin_address_space_info { +CPUState *cpu; +GPtrArray *names; +}; + /** * struct CPUPluginState - per-CPU state for plugins * @event_mask: plugin event bitmap. Modified only via async work. diff --git a/include/qemu/qemu-plugin.h b/include/qemu/qemu-plugin.h index 68c8632fd7..1380f7d441 100644 --- a/include/qemu/qemu-plugin.h +++ b/include/qemu/qemu-plugin.h @@ -926,6 +926,51 @@ QEMU_PLUGIN_API int qemu_plugin_write_register(struct qemu_plugin_register *handle, GByteArray *buf); +/** struct qemu_plugin_address_space_info - Opaque handle for space info */ +struct qemu_plugin_address_space_info; + +/** + * qemu_plugin_get_current_vcpu_address_spaces() - get a list of address spaces + * for the current vCPU + * + * This function should be called in vCPU context, i.e. from a vCPU, translation + * block, or operation callback. + * + * This function is only valid for softmmu targets. + * + * Returns an opaque qemu_plugin_address_space* handle that is only valid for + * the duration of the callback. The caller is not responsible for freeing the + * result. + */ +QEMU_PLUGIN_API +struct qemu_plugin_address_space_info* +qemu_plugin_get_current_vcpu_address_spaces(void); + +/** + * qemu_plugin_n_address_spaces() - get the number of address spaces + * + * @info: opaque handle to address space information + * + * Returns the number of address spaces, or -1 if the handle is invalid. + */ +QEMU_PLUGIN_API +int qemu_plugin_n_address_spaces(struct qemu_plugin_address_space_info *info); + +/** + * qemu_plugin_address_space_name() - get the name of an address space + * + * @info: opaque handle to address space information + * @idx: index of the address space + * + * Returns the name of the address space, or NULL if the handle is invalid. The + * caller is responsible for freeing the result. + * + */ +QEMU_PLUGIN_API +const char* +qemu_plugin_address_space_name(struct qemu_plugin_address_space_info *info, + unsigned int idx); + /** * qemu_plugin_read_memory_vaddr() - read from memory using a virtual address * diff --git a/plugins/api.c b/plugins/api.c index 79b2dc20b8..d1cc6ff86e 100644 --- a/plugins/api.c +++ b/plugins/api.c @@ -39,6 +39,7 @@ #include "qemu/main-loop.h" #include "qemu/plugin.h" #include "qemu/log.h" +#include "system/memory.h" #include "tcg/tcg.h" #include "exec/gdbstub.h" #include "exec/target_page.h" @@ -452,6 +453,84 @@ int qemu_plugin_write_register(struct qemu_plugin_register *reg, return gdb_write_register(current_cpu, buf->data, GPOINTER_TO_INT(reg) - 1); } +#ifdef CONFIG_SOFTMMU +static __thread struct qemu_plugin_address_space_info address_space_info = { +NULL, NULL +}; +static void free_g_string_and_data(gpointer data) +{ +g_string_free(data, true); +} +#endif + +struct qemu_plugin_address_space_info* +qemu_plugin_get_current_vcpu_address_spaces(void) +{ +#ifdef CONFIG_SOFTMMU +CPUState *cpu = current_cpu; + +if (address_space_info.names == NULL) { +address_space_info.cpu = NULL; +address_space_info.names = g_ptr_array_new(); +g_ptr_array_set_free_func(address_space_info.names, + free_g_string_and_data); +} + +g_ptr_array_set_size(address_space_info.names, 0); + +for (size_t i = 0; i < cpu->cpu_ases_count; i++) { +AddressSpace *as = cpu_get_address_space(cpu, i); + +if (as == NULL || as->name == NULL) { +return NULL; +} + +g_ptr_array_add(address_space_info.names, +g_string_new(as->name)); +} + +address_space_info.cpu = cpu; + +return &address_space_info; +#else +return NULL; +#endif +} + +int qemu_plugin_n_address_spaces(struct qemu_plugin_address_space_info *info) +{ +#ifdef CONFIG_SOFTMMU +if (info->cpu != current_cpu) { +address_space_info.cpu = NULL; +g_ptr_array_set_size(address_space_info.names, 0); +return -1; +} + +return info->names->len; +#else +return -1; +#endif +} + +const char * +qemu_plugin_address_space_name(struct qemu_plugin_address_space_info *info, + unsigned int idx) +{ +#ifdef CONFIG_SOFTMMU +if (info->cpu != current_cpu) { +address_space_info.cpu = NULL; +g_ptr_a
[PATCH v3 7/8] Add hypercalls plugin and test
From: novafacing Signed-off-by: novafacing Signed-off-by: Rowan Hart --- tests/tcg/Makefile.target | 1 + tests/tcg/plugins/hypercalls.c| 552 ++ tests/tcg/plugins/meson.build | 2 +- tests/tcg/x86_64/Makefile.softmmu-target | 6 +- tests/tcg/x86_64/system/hypercalls-target.c | 45 ++ .../tcg/x86_64/system/validate-hypercalls.py | 40 ++ 6 files changed, 644 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=$(COMMA)patch= +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 00..ece2716ae8 --- /dev/null +++ b/tests/tcg/plugins/hypercalls.c @@ -0,0 +1,552 @@ +/* + * SPDX-License-Identifier: GPL-2.0-or-later + * + * Copyright (C) 2024, Rowan Hart + * + * License: GNU GPL, version 2 or later. + * See the COPYING file in the top-level directory. + * + * 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 +#include +#include +#include +#include +#include +#include + +#include + +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 (0x1) +#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(i
[PATCH v3 1/8] Expose gdb_write_register function to consumers of gdbstub
From: novafacing Signed-off-by: novafacing Signed-off-by: Rowan Hart --- gdbstub/gdbstub.c | 2 +- include/exec/gdbstub.h | 14 ++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/gdbstub/gdbstub.c b/gdbstub/gdbstub.c index 565f6b33a9..5846e481be 100644 --- a/gdbstub/gdbstub.c +++ b/gdbstub/gdbstub.c @@ -534,7 +534,7 @@ int gdb_read_register(CPUState *cpu, GByteArray *buf, int reg) return 0; } -static int gdb_write_register(CPUState *cpu, uint8_t *mem_buf, int reg) +int gdb_write_register(CPUState *cpu, uint8_t *mem_buf, int reg) { GDBRegisterState *r; diff --git a/include/exec/gdbstub.h b/include/exec/gdbstub.h index 0675b0b646..a16c0051ce 100644 --- a/include/exec/gdbstub.h +++ b/include/exec/gdbstub.h @@ -124,6 +124,20 @@ const GDBFeature *gdb_find_static_feature(const char *xmlname); */ int gdb_read_register(CPUState *cpu, GByteArray *buf, int reg); +/** + * gdb_write_register() - Write a register associated with a CPU. + * @cpu: The CPU associated with the register. + * @buf: The buffer that the register contents will be set to. + * @reg: The register's number returned by gdb_find_feature_register(). + * + * The size of @buf must be at least the size of the register being + * written. + * + * Return: The number of written bytes, or 0 if an error occurred (for + * example, an unknown register was provided). + */ +int gdb_write_register(CPUState *cpu, uint8_t *mem_buf, int reg); + /** * typedef GDBRegDesc - a register description from gdbstub */ -- 2.49.0
[PATCH v3 2/8] Add register write API
From: novafacing Signed-off-by: novafacing Signed-off-by: Rowan Hart --- include/qemu/qemu-plugin.h | 57 +- plugins/api.c | 26 - 2 files changed, 56 insertions(+), 27 deletions(-) diff --git a/include/qemu/qemu-plugin.h b/include/qemu/qemu-plugin.h index 3a850aa216..68c8632fd7 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, @@ -871,7 +868,8 @@ struct qemu_plugin_register; /** * typedef qemu_plugin_reg_descriptor - register descriptions * - * @handle: opaque handle for retrieving value with qemu_plugin_read_register + * @handle: opaque handle for retrieving value with qemu_plugin_read_register or + * writing value with qemu_plugin_write_register * @name: register name * @feature: optional feature descriptor, can be NULL */ @@ -893,6 +891,41 @@ typedef struct { QEMU_PLUGIN_API GArray *qemu_plugin_get_registers(void); +/** + * qemu_plugin_read_register() - read register for current vCPU + * + * @handle: a @qemu_plugin_reg_handle handle + * @buf: A GByteArray for the data owned by the plugin + * + * This function is only available in a context that register read access is + * explicitly requested via the QEMU_PLUGIN_CB_R_REGS flag. + * + * Returns the size of the read register. The content of @buf is in target byte + * order. On failure returns -1. + */ +QEMU_PLUGIN_API +int qemu_plugin_read_register(struct qemu_plugin_register *handle, + GByteArray *buf); + +/** + * qemu_plugin_write_register() - write register for current vCPU + * + * @handle: a @qemu_plugin_reg_handle handle + * @buf: A GByteArray for the data owned by the plugin + * + * This function is only available in a context that register write access is + * explicitly requested via the QEMU_PLUGIN_CB_W_REGS flag. + * + * The size of @buf must be at least the size of the requested register. + * Attempting to write a register with @buf smaller than the register size + * will result in a crash or other undesired behavior. + * + * Returns the number of bytes written. On failure returns 0. + */ +QEMU_PLUGIN_API +int qemu_plugin_write_register(struct qemu_plugin_register *handle, + GByteArray *buf); + /** * qemu_plugin_read_memory_vaddr() - read from memory using a virtual address * @@ -915,22 +948,6 @@ QEMU_PLUGIN_API bool qemu_plugin_read_memory_vaddr(uint64_t addr, GByteArray *data, size_t len); -/** - * qemu_plugin_read_register() - read register for current vCPU - * - * @handle: a @qemu_plugin_reg_handle handle - * @buf: A GByteArray for the data owned by the plugin - * - * This function is only available in a context that register read access is - * explicitly requested via the QEMU_PLUGIN_CB_R_REGS flag. - * - * Returns the size of the read register. The content of @buf is in target byte - * order. On failure returns -1. - */ -QEMU_PLUGIN_API -int qemu_plugin_read_register(struct qemu_plugin_register *handle, - GByteArray *buf); - /** * qemu_plugin_scoreboard_new() - alloc a new scoreboard * diff --git a/plugins/api.c b/plugins/api.c index 3c9d4832e9..79b2dc20b8 100644 --- a/plugins/api.c +++ b/plugins/api.c @@ -433,6 +433,25 @@ GArray *qemu_plugin_get_registers(void) return create_register_handles(regs); } +int qemu_plugin_read_register(struct qemu_plugin_register *reg, GByteArray *buf) +{ +g_assert(current_cpu); + +return gdb_read_register(current_cpu, buf, GPOINTER_TO_INT(reg) - 1); +} + +int qemu_plugin_write_register(struct qemu_plugin_register *reg, + GByteArray *buf) +{ +g_assert(current_cpu); + +if (buf->len == 0) { +return 0; +} + +return gdb_write_register(current_cpu, buf->data, GPOINTER_TO_INT(reg) - 1); +} + bool qemu_plugin_read_memory_vaddr(uint64_t addr, GByteArray *data, size_t len) { g_assert(current_cpu); @@ -453,13 +472,6 @@ bool qemu_plugin_read_memory_vaddr(uint64_t addr, GByteArray *data, size_t len) return true; } -int qemu_plugin_read_register(struct qemu_plugin_register *reg, GByteArray *buf) -{ -g_assert(current_cpu); - -return gdb_read_register(current_cpu, buf, GPOINTER_TO_INT(reg) - 1); -} - struct qemu_plugin_scoreboard *qemu_plugin_scoreboard_new(size_t element_size) { return plugin_scoreboard_new(element_size); -- 2.49.0
Re: [PATCH v3 2/8] Add register write API
Hi Julian, > Again, what was the reason for moving `qemu_plugin_read_register`? I moved it so it's grouped with get_registers above instead of being separated below the memory functions. I can move it back, just seemed nicer that way. -Rowan On Thu, May 22, 2025, 4:59 AM Julian Ganz wrote: > Hi Rowan, > > > diff --git a/include/qemu/qemu-plugin.h b/include/qemu/qemu-plugin.h > > index 3a850aa216..68c8632fd7 100644 > > --- a/include/qemu/qemu-plugin.h > > +++ b/include/qemu/qemu-plugin.h > > @@ -893,6 +891,41 @@ typedef struct { > > QEMU_PLUGIN_API > > GArray *qemu_plugin_get_registers(void); > > > > +/** > > + * qemu_plugin_read_register() - read register for current vCPU > > + * > > + * @handle: a @qemu_plugin_reg_handle handle > > + * @buf: A GByteArray for the data owned by the plugin > > + * > > + * This function is only available in a context that register read > access is > > + * explicitly requested via the QEMU_PLUGIN_CB_R_REGS flag. > > + * > > + * Returns the size of the read register. The content of @buf is in > target byte > > + * order. On failure returns -1. > > + */ > > +QEMU_PLUGIN_API > > +int qemu_plugin_read_register(struct qemu_plugin_register *handle, > > + GByteArray *buf); > > + > > +/** > > + * qemu_plugin_write_register() - write register for current vCPU > > + * > > + * @handle: a @qemu_plugin_reg_handle handle > > + * @buf: A GByteArray for the data owned by the plugin > > + * > > + * This function is only available in a context that register write > access is > > + * explicitly requested via the QEMU_PLUGIN_CB_W_REGS flag. > > + * > > + * The size of @buf must be at least the size of the requested register. > > + * Attempting to write a register with @buf smaller than the register > size > > + * will result in a crash or other undesired behavior. > > + * > > + * Returns the number of bytes written. On failure returns 0. > > + */ > > +QEMU_PLUGIN_API > > +int qemu_plugin_write_register(struct qemu_plugin_register *handle, > > + GByteArray *buf); > > + > > /** > > * qemu_plugin_read_memory_vaddr() - read from memory using a virtual > address > > * > > @@ -915,22 +948,6 @@ QEMU_PLUGIN_API > > bool qemu_plugin_read_memory_vaddr(uint64_t addr, > > GByteArray *data, size_t len); > > > > -/** > > - * qemu_plugin_read_register() - read register for current vCPU > > - * > > - * @handle: a @qemu_plugin_reg_handle handle > > - * @buf: A GByteArray for the data owned by the plugin > > - * > > - * This function is only available in a context that register read > access is > > - * explicitly requested via the QEMU_PLUGIN_CB_R_REGS flag. > > - * > > - * Returns the size of the read register. The content of @buf is in > target byte > > - * order. On failure returns -1. > > - */ > > -QEMU_PLUGIN_API > > -int qemu_plugin_read_register(struct qemu_plugin_register *handle, > > - GByteArray *buf); > > - > > Why the code move? > > > diff --git a/plugins/api.c b/plugins/api.c > > index 3c9d4832e9..79b2dc20b8 100644 > > --- a/plugins/api.c > > +++ b/plugins/api.c > > @@ -433,6 +433,25 @@ GArray *qemu_plugin_get_registers(void) > > return create_register_handles(regs); > > } > > > > +int qemu_plugin_read_register(struct qemu_plugin_register *reg, > GByteArray *buf) > > +{ > > +g_assert(current_cpu); > > + > > +return gdb_read_register(current_cpu, buf, GPOINTER_TO_INT(reg) - > 1); > > +} > > + > > +int qemu_plugin_write_register(struct qemu_plugin_register *reg, > > + GByteArray *buf) > > +{ > > +g_assert(current_cpu); > > + > > +if (buf->len == 0) { > > +return 0; > > +} > > + > > +return gdb_write_register(current_cpu, buf->data, > GPOINTER_TO_INT(reg) - 1); > > +} > > + > > bool qemu_plugin_read_memory_vaddr(uint64_t addr, GByteArray *data, > size_t len) > > { > > g_assert(current_cpu); > > @@ -453,13 +472,6 @@ bool qemu_plugin_read_memory_vaddr(uint64_t addr, > GByteArray *data, size_t len) > > return true; > > } > > > > -int qemu_plugin_read_register(struct qemu_plugin_register *reg, > GByteArray *buf) > > -{ > > -g_assert(current_cpu); > > - > > -return gdb_read_register(current_cpu, buf, GPOINTER_TO_INT(reg) - > 1); > > -} > > - > > Again, what was the reason for moving `qemu_plugin_read_register`? > > Reviewed-By: Julian Ganz > > Regards, > Julian >
Re: [PATCH v3 2/8] Add register write API
> a) handle the QEMU_PLUGIN_CB_RW_REGS I missed that this was not already handled. I'll fix that. > b) try and enforce we are only being called from such callbacks Sure, beyond documentation I suppose we can add and check a flag to ensure this. I think it's a good idea to reduce foot guns from just calling it from any old place. -Rowan
Re: [PATCH v3 5/8] Add memory hardware address read/write API
> > > > This definition strikes me as odd. What was your reason to assert > > `current_cpu` here, but not in the other two functions? Also a bit > > surprising is the declaration of `cpu` if you use it in just one place > > (rather than just use `current_cpu` directly as for the assertion). > > > > And there is no reason in particular why the vCPU could not be a > > function parameter of `qemu_plugin_translate_vaddr`, right? You don't > > have the same restrictions as in `qemu_plugin_read_memory_hwaddr` or > > `qemu_plugin_hwaddr_operation_result` where you actually touch memory? > > > > That's a good point, adding a "unsigned int vcpu_index" to the signature > should be enough to query current or any other vcpu easily. > This is a really nice idea, it might be nice to make a vcpu version of read/write register too. For memory, I'd think going with the current memory is probably fine, I don't see any configs with different memory per vcpu? >
Re: [PATCH v3 5/8] Add memory hardware address read/write API
Well, first I just noticed that I left a debug print in this function! So I'll fix that. Reading this patch, and patch 3 (Add address space API), I am not sure AddressSpace is something we want to leak in plugins interface. It is a concept *very* internal to QEMU, and not reflecting directly something concerning the emulated architecture (it is related, but not officially described for it). The same way qemu_plugin_write_memory_vaddr is only valid in the current page table setup, we could assume the same for current address space, and return an error if memory is not mapped with current AS. Eventually, we could read/write a given hwaddr in all existing address spaces (starting with current mapped one), if it makes sense to do this, which I'm not sure about. What are your thoughts on this? I definitely see the arguments for not exposing it even as an opaque struct, internality not withstanding it also adds some complexity for plugin authors. My thought with exposing it is kind of twofold. First, there are specific address spaces like the secure address space on ARM or I/O memory on x86 that plugins might like to access and I think it's easiest to facilitate that if we just let them choose which one they want to r/w to. Second, I wanted to lean towards being less restrictive now to avoid having to go back and remove restrictions later since even though it's very internal, it doesn't seem very likely to change. That said, if you think it's more trouble than it's worth I'm totally fine with just defaulting to writing to the current AS (or to cpu-memory, whichever's more reasonable). Your call, just let me know which way you think is best for v4 :) qemu_plugin_translate_vaddr is fine for me. I did have a question about this -- one of the test plugins prints out vaddr, haddr from qemu_plugin_insn_haddr, and the translated haddr from qemu_plugin_translate_vaddr. When running with direct memory mappings in a system test, the vaddr = translated haddr, which is correct, but the haddr from qemu_plugin_insn_haddr was incorrect (it was 0x7ffaddress>). Is this expected behavior? Thanks for the feedback!
[PATCH v4 7/9] plugins: Add hypercalls plugin and test
From: novafacing 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 Signed-off-by: Rowan Hart --- tests/tcg/Makefile.target | 1 + tests/tcg/plugins/hypercalls.c| 552 ++ tests/tcg/plugins/meson.build | 2 +- tests/tcg/x86_64/Makefile.softmmu-target | 6 +- tests/tcg/x86_64/system/hypercalls-target.c | 45 ++ .../tcg/x86_64/system/validate-hypercalls.py | 40 ++ 6 files changed, 644 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=$(COMMA)patch= +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 00..3abbe1bf30 --- /dev/null +++ b/tests/tcg/plugins/hypercalls.c @@ -0,0 +1,552 @@ +/* + * SPDX-License-Identifier: GPL-2.0-or-later + * + * Copyright (C) 2024, Rowan Hart + * + * License: GNU GPL, version 2 or later. + * See the COPYING file in the top-level directory. + * + * 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 +#include +#include +#include +#include +#include +#include + +#include + +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 (0x1) +#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(hype
[PATCH v4 6/9] plugins: Add patcher plugin and test
From: novafacing 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 Signed-off-by: Rowan Hart --- tests/tcg/Makefile.target | 1 + tests/tcg/plugins/meson.build | 2 +- tests/tcg/plugins/patch.c | 302 ++ tests/tcg/x86_64/Makefile.softmmu-target | 32 ++- tests/tcg/x86_64/system/patch-target.c| 32 +++ tests/tcg/x86_64/system/validate-patch.py | 39 +++ 6 files changed, 402 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=$(COMMA)patch= 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 00..3767d14a53 --- /dev/null +++ b/tests/tcg/plugins/patch.c @@ -0,0 +1,302 @@ +/* + * SPDX-License-Identifier: GPL-2.0-or-later + * + * Copyright (C) 2025, Rowan Hart + * + * License: GNU GPL, version 2 or later. + * See the COPYING file in the top-level directory. + * + * This plugin patches instructions matching a pattern to a different + * instruction as they execute + * + */ + +#include "glib.h" +#include "glibconfig.h" + +#include +#include +#include + +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, patc
[PATCH v4 8/9] plugins: Remove use of qemu_plugin_read_register where it is not permitted
This patch is required to make the insn plugin work after adding enforcement of QEMU_PLUGIN_CB_ flags in calls to read or write registers. Previously, these flags were not enforced and the API could be called from anywhere, but this was not intended as described by the documentation. Now, the flags are enforced and qemu_plugin_read_register can no longer be called from a vcpu_init callback because it does not request the QEMU_PLUGIN_CB_ flag (nor does it have a mechanism to do so). Signed-off-by: Rowan Hart --- tests/tcg/plugins/insn.c | 22 +- 1 file changed, 1 insertion(+), 21 deletions(-) diff --git a/tests/tcg/plugins/insn.c b/tests/tcg/plugins/insn.c index 0c723cb9ed..265d3ebe9e 100644 --- a/tests/tcg/plugins/insn.c +++ b/tests/tcg/plugins/insn.c @@ -81,25 +81,6 @@ static Instruction * get_insn_record(const char *disas, uint64_t vaddr, Match *m return record; } -/* - * Initialise a new vcpu with reading the register list - */ -static void vcpu_init(qemu_plugin_id_t id, unsigned int vcpu_index) -{ -g_autoptr(GArray) reg_list = qemu_plugin_get_registers(); -g_autoptr(GByteArray) reg_value = g_byte_array_new(); - -if (reg_list) { -for (int i = 0; i < reg_list->len; i++) { -qemu_plugin_reg_descriptor *rd = &g_array_index( -reg_list, qemu_plugin_reg_descriptor, i); -int count = qemu_plugin_read_register(rd->handle, reg_value); -g_assert(count > 0); -} -} -} - - static void vcpu_insn_exec_before(unsigned int cpu_index, void *udata) { qemu_plugin_u64_add(insn_count, cpu_index, 1); @@ -295,8 +276,7 @@ QEMU_PLUGIN_EXPORT int qemu_plugin_install(qemu_plugin_id_t id, insn_count = qemu_plugin_scoreboard_u64( qemu_plugin_scoreboard_new(sizeof(uint64_t))); -/* Register init, translation block and exit callbacks */ -qemu_plugin_register_vcpu_init_cb(id, vcpu_init); +/* Register translation block and exit callbacks */ qemu_plugin_register_vcpu_tb_trans_cb(id, vcpu_tb_trans); qemu_plugin_register_atexit_cb(id, plugin_exit, NULL); return 0; -- 2.49.0
[PATCH v4 3/9] plugins: Add enforcement of QEMU_PLUGIN_CB flags in register R/W callbacks
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 --- accel/tcg/plugin-gen.c | 27 + include/qemu/plugin.h | 12 include/qemu/qemu-plugin.h | 3 -- plugins/api.c | 8 + plugins/core.c | 60 ++ 5 files changed, 101 insertions(+), 9 deletions(-) diff --git a/accel/tcg/plugin-gen.c b/accel/tcg/plugin-gen.c index c1da753894..ceb2314bc1 100644 --- a/accel/tcg/plugin-gen.c +++ b/accel/tcg/plugin-gen.c @@ -117,10 +117,19 @@ 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); +tcg_gen_call1(qemu_plugin_set_cb_flags, +qemu_plugin_get_set_cb_flags_helper_info(), NULL, +tcgv_i32_temp(flags)); 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_call0(qemu_plugin_clear_cb_flags, + qemu_plugin_get_clear_cb_flags_helper_info(), NULL); tcg_temp_free_i32(cpu_index); +tcg_temp_free_i32(flags); } static TCGv_ptr gen_plugin_u64_ptr(qemu_plugin_u64 entry) @@ -173,10 +182,19 @@ 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); +tcg_gen_call1(qemu_plugin_set_cb_flags, + qemu_plugin_get_set_cb_flags_helper_info(), NULL, + tcgv_i32_temp(flags)); 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_call0(qemu_plugin_clear_cb_flags, + qemu_plugin_get_clear_cb_flags_helper_info(), NULL); tcg_temp_free_i32(cpu_index); +tcg_temp_free_i32(flags); gen_set_label(after_cb); tcg_temp_free_i64(val); @@ -210,12 +228,21 @@ 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); +tcg_gen_call1(qemu_plugin_set_cb_flags, + qemu_plugin_get_set_cb_flags_helper_info(), NULL, + tcgv_i32_temp(flags)); 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_call0(qemu_plugin_clear_cb_flags, + qemu_plugin_get_clear_cb_flags_helper_info(), NULL); tcg_temp_free_i32(cpu_index); +tcg_temp_free_i32(flags); } static void inject_cb(struct qemu_plugin_dyn_cb *cb) diff --git a/include/qemu/plugin.h b/include/qemu/plugin.h index 9726a9ebf3..f312cc1a72 100644 --- a/include/qemu/plugin.h +++ b/include/qemu/plugin.h @@ -209,6 +209,18 @@ 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); + +void qemu_plugin_set_cb_flags(enum qemu_plugin_cb_flags flags); + +enum qemu_plugin_cb_flags qemu_plugin_get_cb_flags(void); + +void qemu_plugin_clear_cb_flags(void); + +TCGHelperInfo *qemu_plugin_get_set_cb_flags_helper_info(void); + +TCGHelperInfo *qemu_plugin_get_clear_cb_flags_helper_info(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_
[PATCH v4 5/9] plugins: Add memory hardware address read/write API
From: novafacing This patch adds functions to the plugins API to allow plugins to read and write memory via hardware addresses. The functions use the current address space of the current CPU in order to avoid exposing address space information to users. A later patch may want to add a function to permit a specified address space, for example to facilitate architecture-specific plugins that want to operate on them, for example reading ARM secure memory. Signed-off-by: novafacing Signed-off-by: Rowan Hart --- include/qemu/qemu-plugin.h | 93 plugins/api.c | 97 ++ 2 files changed, 190 insertions(+) diff --git a/include/qemu/qemu-plugin.h b/include/qemu/qemu-plugin.h index 8ae7758b95..2cb5de9f64 100644 --- a/include/qemu/qemu-plugin.h +++ b/include/qemu/qemu-plugin.h @@ -969,6 +969,99 @@ QEMU_PLUGIN_API bool qemu_plugin_write_memory_vaddr(uint64_t addr, GByteArray *data); +/** + * enum qemu_plugin_hwaddr_operation_result - result of a memory operation + * + * @QEMU_PLUGIN_HWADDR_OPERATION_OK: hwaddr operation succeeded + * @QEMU_PLUGIN_HWADDR_OPERATION_ERROR: unexpected error occurred + * @QEMU_PLUGIN_HWADDR_OPERATION_DEVICE_ERROR: error in memory device + * @QEMU_PLUGIN_HWADDR_OPERATION_ACCESS_DENIED: permission error + * @QEMU_PLUGIN_HWADDR_OPERATION_INVALID_ADDRESS: address was invalid + * @QEMU_PLUGIN_HWADDR_OPERATION_INVALID_ADDRESS_SPACE: invalid address space + */ +enum qemu_plugin_hwaddr_operation_result { +QEMU_PLUGIN_HWADDR_OPERATION_OK, +QEMU_PLUGIN_HWADDR_OPERATION_ERROR, +QEMU_PLUGIN_HWADDR_OPERATION_DEVICE_ERROR, +QEMU_PLUGIN_HWADDR_OPERATION_ACCESS_DENIED, +QEMU_PLUGIN_HWADDR_OPERATION_INVALID_ADDRESS, +QEMU_PLUGIN_HWADDR_OPERATION_INVALID_ADDRESS_SPACE, +}; + +/** + * qemu_plugin_read_memory_hwaddr() - read from memory using a hardware address + * + * @addr: The physical address to read from + * @data: A byte array to store data into + * @len: The number of bytes to read, starting from @addr + * + * @len bytes of data is read from the current memory space for the current + * vCPU starting at @addr and stored into @data. If @data is not large enough to + * hold @len bytes, it will be expanded to the necessary size, reallocating if + * necessary. @len must be greater than 0. + * + * This function does not ensure writes are flushed prior to reading, so + * callers should take care when calling this function in plugin callbacks to + * avoid attempting to read data which may not yet be written and should use + * the memory callback API instead. + * + * This function is only valid for softmmu targets. + * + * Returns a qemu_plugin_hwaddr_operation_result indicating the result of the + * operation. + */ +QEMU_PLUGIN_API +enum qemu_plugin_hwaddr_operation_result +qemu_plugin_read_memory_hwaddr(uint64_t addr, GByteArray *data, size_t len); + +/** + * qemu_plugin_write_memory_hwaddr() - write to memory using a hardware address + * + * @addr: A physical address to write to + * @data: A byte array containing the data to write + * + * The contents of @data will be written to memory starting at the hardware + * address @addr in the current address space for the current vCPU. + * + * This function does not guarantee consistency of writes, nor does it ensure + * that pending writes are flushed either before or after the write takes place, + * so callers should take care when calling this function in plugin callbacks to + * avoid depending on the existence of data written using this function which + * may be overwritten afterward. In addition, this function requires that the + * pages containing the address are not locked. Practically, this means that you + * should not write instruction memory in a current translation block inside a + * callback registered with qemu_plugin_register_vcpu_tb_trans_cb. + * + * You can, for example, write instruction memory in a current translation block + * in a callback registered with qemu_plugin_register_vcpu_tb_exec_cb, although + * be aware that the write will not be flushed until after the translation block + * has finished executing. In general, this function should be used to write + * data memory or to patch code at a known address, not in a current translation + * block. + * + * This function is only valid for softmmu targets. + * + * Returns a qemu_plugin_hwaddr_operation_result indicating the result of the + * operation. + */ +QEMU_PLUGIN_API +enum qemu_plugin_hwaddr_operation_result +qemu_plugin_write_memory_hwaddr(uint64_t addr, GByteArray *data); + +/** + * qemu_plugin_translate_vaddr() - translate virtual address for current vCPU + * + * @vaddr: virtual address to translate + * @hwaddr: pointer to store the physical address + * + * This function is only valid in vCPU context (i.e. in callbacks) and is only + * valid for softmmu targets. + * + * Returns true on success and false on failure
[PATCH v4 0/9] Add additional plugin API functions to read and write memory and registers
This patch series adds several new API functions focused on enabling use cases around reading and writing guest memory from QEMU plugins. To support these new APIs, some utility functionality around retrieving information about address spaces is added as well. The new qemu_plugin_write_register utilizes gdb_write_register, which is now declared in gdbstub.h for this purpose instead of being static. qemu_plugin_write_memory_vaddr utilizes cpu_memory_rw_debug much the same as the existing read_memory_vaddr function does. The read and write_hwaddr functions are the most different. These functions use address_space_rw, which works well in most cases. There is an important caveat that for writes, the page being written will be set dirty by the write operation. This dirty setting requires locking the page range, which can contend with an already held lock in page_collection_lock when called in a tb translate callback with a write to the instruction memory in the tb. The doc comments warn against doing this, and it's unlikely anyone would want to do this. I've also added two test plugins: one that implements a simple hypercall interface that guest code can use to communicate with the plugin in a structured way with a test to ensure that this hypercall works and writing virtual memory works. And one that implements a simple patch utility to patch memory at runtime. The test for the second plugin ensures the patch applies successfully to instruction memory, and can use both hw and vaddr methods. I've had a few comments from the last submission that I've addressed, and some that I haven't for one reason or another: - Enforce QEMU_PLUGIN_CB_ flags in register read/write operations: done! - Fix my commit messages and add long messages describing commits: done! - Un-expose AS internals: done! Functions operate on current vCPU, current AS. - Clean up use of current_cpu: done! - Make functions take a vcpu_idx: not done. May revisit but it allows footguns. Even for translation, seems best to not do this now. We can easily add _vcpu versions of these functions in the future if we change our minds! Rowan Hart (2): plugins: Add enforcement of QEMU_PLUGIN_CB flags in register R/W callbacks plugins: Remove use of qemu_plugin_read_register where it is not permitted novafacing (7): gdbstub: Expose gdb_write_register function to consumers of gdbstub plugins: Add register write API plugins: Add memory virtual address write API plugins: Add memory hardware address read/write API plugins: Add patcher plugin and test plugins: Add hypercalls plugin and test plugins: Update plugin version and add notes accel/tcg/plugin-gen.c| 27 + gdbstub/gdbstub.c | 2 +- include/exec/gdbstub.h| 14 + include/qemu/plugin.h | 12 + include/qemu/qemu-plugin.h| 166 +- plugins/api.c | 135 - plugins/core.c| 60 +- tests/tcg/Makefile.target | 2 + tests/tcg/plugins/hypercalls.c| 552 ++ tests/tcg/plugins/insn.c | 22 +- tests/tcg/plugins/meson.build | 2 +- tests/tcg/plugins/patch.c | 302 ++ tests/tcg/x86_64/Makefile.softmmu-target | 36 +- tests/tcg/x86_64/system/hypercalls-target.c | 45 ++ tests/tcg/x86_64/system/patch-target.c| 32 + .../tcg/x86_64/system/validate-hypercalls.py | 40 ++ tests/tcg/x86_64/system/validate-patch.py | 39 ++ 17 files changed, 1438 insertions(+), 50 deletions(-) create mode 100644 tests/tcg/plugins/hypercalls.c create mode 100644 tests/tcg/plugins/patch.c create mode 100644 tests/tcg/x86_64/system/hypercalls-target.c create mode 100644 tests/tcg/x86_64/system/patch-target.c create mode 100755 tests/tcg/x86_64/system/validate-hypercalls.py create mode 100755 tests/tcg/x86_64/system/validate-patch.py -- 2.49.0
[PATCH v4 4/9] plugins: Add memory virtual address write API
From: novafacing This patch adds functions to the plugins API to allow reading and writing memory via virtual addresses. These functions only permit doing so on the current CPU, because there is no way to ensure consistency if plugins are allowed to read or write to other CPUs that aren't currently in the context of the plugin. Signed-off-by: novafacing Signed-off-by: Rowan Hart --- include/qemu/qemu-plugin.h | 21 + plugins/api.c | 18 ++ 2 files changed, 39 insertions(+) diff --git a/include/qemu/qemu-plugin.h b/include/qemu/qemu-plugin.h index 120fb626a6..8ae7758b95 100644 --- a/include/qemu/qemu-plugin.h +++ b/include/qemu/qemu-plugin.h @@ -948,6 +948,27 @@ QEMU_PLUGIN_API bool qemu_plugin_read_memory_vaddr(uint64_t addr, GByteArray *data, size_t len); +/** + * qemu_plugin_write_memory_vaddr() - write to memory using a virtual address + * + * @addr: A virtual address to write to + * @data: A byte array containing the data to write + * + * The contents of @data will be written to memory starting at the virtual + * address @addr. + * + * This function does not guarantee consistency of writes, nor does it ensure + * that pending writes are flushed either before or after the write takes place, + * so callers should take care to only call this function in vCPU context (i.e. + * in callbacks) and avoid depending on the existence of data written using this + * function which may be overwritten afterward. + * + * Returns true on success and false on failure. + */ +QEMU_PLUGIN_API +bool qemu_plugin_write_memory_vaddr(uint64_t addr, + GByteArray *data); + /** * qemu_plugin_scoreboard_new() - alloc a new scoreboard * diff --git a/plugins/api.c b/plugins/api.c index 16141f5c25..7258b6590b 100644 --- a/plugins/api.c +++ b/plugins/api.c @@ -476,6 +476,24 @@ bool qemu_plugin_read_memory_vaddr(uint64_t addr, GByteArray *data, size_t len) 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; +} + struct qemu_plugin_scoreboard *qemu_plugin_scoreboard_new(size_t element_size) { return plugin_scoreboard_new(element_size); -- 2.49.0
[PATCH v4 2/9] plugins: Add register write API
From: novafacing This patch adds a function to the plugins API to allow plugins to write register contents. It also moves the qemu_plugin_read_register function so all the register-related functions are grouped together in the file. Signed-off-by: novafacing Signed-off-by: Rowan Hart --- include/qemu/qemu-plugin.h | 54 ++ plugins/api.c | 22 +++- 2 files changed, 52 insertions(+), 24 deletions(-) diff --git a/include/qemu/qemu-plugin.h b/include/qemu/qemu-plugin.h index 3a850aa216..cfe1692ecb 100644 --- a/include/qemu/qemu-plugin.h +++ b/include/qemu/qemu-plugin.h @@ -871,7 +871,8 @@ struct qemu_plugin_register; /** * typedef qemu_plugin_reg_descriptor - register descriptions * - * @handle: opaque handle for retrieving value with qemu_plugin_read_register + * @handle: opaque handle for retrieving value with qemu_plugin_read_register or + * writing value with qemu_plugin_write_register * @name: register name * @feature: optional feature descriptor, can be NULL */ @@ -893,6 +894,41 @@ typedef struct { QEMU_PLUGIN_API GArray *qemu_plugin_get_registers(void); +/** + * qemu_plugin_read_register() - read register for current vCPU + * + * @handle: a @qemu_plugin_reg_handle handle + * @buf: A GByteArray for the data owned by the plugin + * + * This function is only available in a context that register read access is + * explicitly requested via the QEMU_PLUGIN_CB_R_REGS flag. + * + * Returns the size of the read register. The content of @buf is in target byte + * order. On failure returns -1. + */ +QEMU_PLUGIN_API +int qemu_plugin_read_register(struct qemu_plugin_register *handle, + GByteArray *buf); + +/** + * qemu_plugin_write_register() - write register for current vCPU + * + * @handle: a @qemu_plugin_reg_handle handle + * @buf: A GByteArray for the data owned by the plugin + * + * This function is only available in a context that register write access is + * explicitly requested via the QEMU_PLUGIN_CB_RW_REGS flag. + * + * The size of @buf must be at least the size of the requested register. + * Attempting to write a register with @buf smaller than the register size + * will result in a crash or other undesired behavior. + * + * Returns the number of bytes written. On failure returns 0. + */ +QEMU_PLUGIN_API +int qemu_plugin_write_register(struct qemu_plugin_register *handle, + GByteArray *buf); + /** * qemu_plugin_read_memory_vaddr() - read from memory using a virtual address * @@ -915,22 +951,6 @@ QEMU_PLUGIN_API bool qemu_plugin_read_memory_vaddr(uint64_t addr, GByteArray *data, size_t len); -/** - * qemu_plugin_read_register() - read register for current vCPU - * - * @handle: a @qemu_plugin_reg_handle handle - * @buf: A GByteArray for the data owned by the plugin - * - * This function is only available in a context that register read access is - * explicitly requested via the QEMU_PLUGIN_CB_R_REGS flag. - * - * Returns the size of the read register. The content of @buf is in target byte - * order. On failure returns -1. - */ -QEMU_PLUGIN_API -int qemu_plugin_read_register(struct qemu_plugin_register *handle, - GByteArray *buf); - /** * qemu_plugin_scoreboard_new() - alloc a new scoreboard * diff --git a/plugins/api.c b/plugins/api.c index 3c9d4832e9..3a7add50d2 100644 --- a/plugins/api.c +++ b/plugins/api.c @@ -433,6 +433,21 @@ GArray *qemu_plugin_get_registers(void) return create_register_handles(regs); } +int qemu_plugin_read_register(struct qemu_plugin_register *reg, GByteArray *buf) +{ +g_assert(current_cpu); + +return gdb_read_register(current_cpu, buf, GPOINTER_TO_INT(reg) - 1); +} + +int qemu_plugin_write_register(struct qemu_plugin_register *reg, + GByteArray *buf) +{ +g_assert(current_cpu); + +return gdb_write_register(current_cpu, buf->data, GPOINTER_TO_INT(reg) - 1); +} + bool qemu_plugin_read_memory_vaddr(uint64_t addr, GByteArray *data, size_t len) { g_assert(current_cpu); @@ -453,13 +468,6 @@ bool qemu_plugin_read_memory_vaddr(uint64_t addr, GByteArray *data, size_t len) return true; } -int qemu_plugin_read_register(struct qemu_plugin_register *reg, GByteArray *buf) -{ -g_assert(current_cpu); - -return gdb_read_register(current_cpu, buf, GPOINTER_TO_INT(reg) - 1); -} - struct qemu_plugin_scoreboard *qemu_plugin_scoreboard_new(size_t element_size) { return plugin_scoreboard_new(element_size); -- 2.49.0
[PATCH v4 1/9] gdbstub: Expose gdb_write_register function to consumers of gdbstub
From: novafacing This patch exposes the gdb_write_register function from gdbstub/gdbstub.c via the exec/gdbstub.h header file to support use in plugins to write register contents. Signed-off-by: novafacing Signed-off-by: Rowan Hart --- gdbstub/gdbstub.c | 2 +- include/exec/gdbstub.h | 14 ++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/gdbstub/gdbstub.c b/gdbstub/gdbstub.c index 565f6b33a9..5846e481be 100644 --- a/gdbstub/gdbstub.c +++ b/gdbstub/gdbstub.c @@ -534,7 +534,7 @@ int gdb_read_register(CPUState *cpu, GByteArray *buf, int reg) return 0; } -static int gdb_write_register(CPUState *cpu, uint8_t *mem_buf, int reg) +int gdb_write_register(CPUState *cpu, uint8_t *mem_buf, int reg) { GDBRegisterState *r; diff --git a/include/exec/gdbstub.h b/include/exec/gdbstub.h index 0675b0b646..a16c0051ce 100644 --- a/include/exec/gdbstub.h +++ b/include/exec/gdbstub.h @@ -124,6 +124,20 @@ const GDBFeature *gdb_find_static_feature(const char *xmlname); */ int gdb_read_register(CPUState *cpu, GByteArray *buf, int reg); +/** + * gdb_write_register() - Write a register associated with a CPU. + * @cpu: The CPU associated with the register. + * @buf: The buffer that the register contents will be set to. + * @reg: The register's number returned by gdb_find_feature_register(). + * + * The size of @buf must be at least the size of the register being + * written. + * + * Return: The number of written bytes, or 0 if an error occurred (for + * example, an unknown register was provided). + */ +int gdb_write_register(CPUState *cpu, uint8_t *mem_buf, int reg); + /** * typedef GDBRegDesc - a register description from gdbstub */ -- 2.49.0
[PATCH v4 9/9] plugins: Update plugin version and add notes
From: novafacing This patch updates the plugin version to gate new APIs and adds notes describing what has been added. Signed-off-by: Rowan Hart --- include/qemu/qemu-plugin.h | 9 - 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/include/qemu/qemu-plugin.h b/include/qemu/qemu-plugin.h index 2cb5de9f64..170a79e667 100644 --- a/include/qemu/qemu-plugin.h +++ b/include/qemu/qemu-plugin.h @@ -65,11 +65,18 @@ typedef uint64_t qemu_plugin_id_t; * * version 4: * - added qemu_plugin_read_memory_vaddr + * + * version 5: + * - added qemu_plugin_write_memory_vaddr + * - added qemu_plugin_read_memory_hwaddr + * - added qemu_plugin_write_memory_hwaddr + * - added qemu_plugin_write_register + * - added qemu_plugin_translate_vaddr */ extern QEMU_PLUGIN_EXPORT int qemu_plugin_version; -#define QEMU_PLUGIN_VERSION 4 +#define QEMU_PLUGIN_VERSION 5 /** * struct qemu_info_t - system information for plugins -- 2.49.0
[PATCH v5 2/9] plugins: Add register write API
From: novafacing This patch adds a function to the plugins API to allow plugins to write register contents. It also moves the qemu_plugin_read_register function so all the register-related functions are grouped together in the file. Signed-off-by: novafacing Signed-off-by: Rowan Hart --- include/qemu/qemu-plugin.h | 54 ++ plugins/api.c | 22 +++- 2 files changed, 52 insertions(+), 24 deletions(-) diff --git a/include/qemu/qemu-plugin.h b/include/qemu/qemu-plugin.h index 3a850aa216..cfe1692ecb 100644 --- a/include/qemu/qemu-plugin.h +++ b/include/qemu/qemu-plugin.h @@ -871,7 +871,8 @@ struct qemu_plugin_register; /** * typedef qemu_plugin_reg_descriptor - register descriptions * - * @handle: opaque handle for retrieving value with qemu_plugin_read_register + * @handle: opaque handle for retrieving value with qemu_plugin_read_register or + * writing value with qemu_plugin_write_register * @name: register name * @feature: optional feature descriptor, can be NULL */ @@ -893,6 +894,41 @@ typedef struct { QEMU_PLUGIN_API GArray *qemu_plugin_get_registers(void); +/** + * qemu_plugin_read_register() - read register for current vCPU + * + * @handle: a @qemu_plugin_reg_handle handle + * @buf: A GByteArray for the data owned by the plugin + * + * This function is only available in a context that register read access is + * explicitly requested via the QEMU_PLUGIN_CB_R_REGS flag. + * + * Returns the size of the read register. The content of @buf is in target byte + * order. On failure returns -1. + */ +QEMU_PLUGIN_API +int qemu_plugin_read_register(struct qemu_plugin_register *handle, + GByteArray *buf); + +/** + * qemu_plugin_write_register() - write register for current vCPU + * + * @handle: a @qemu_plugin_reg_handle handle + * @buf: A GByteArray for the data owned by the plugin + * + * This function is only available in a context that register write access is + * explicitly requested via the QEMU_PLUGIN_CB_RW_REGS flag. + * + * The size of @buf must be at least the size of the requested register. + * Attempting to write a register with @buf smaller than the register size + * will result in a crash or other undesired behavior. + * + * Returns the number of bytes written. On failure returns 0. + */ +QEMU_PLUGIN_API +int qemu_plugin_write_register(struct qemu_plugin_register *handle, + GByteArray *buf); + /** * qemu_plugin_read_memory_vaddr() - read from memory using a virtual address * @@ -915,22 +951,6 @@ QEMU_PLUGIN_API bool qemu_plugin_read_memory_vaddr(uint64_t addr, GByteArray *data, size_t len); -/** - * qemu_plugin_read_register() - read register for current vCPU - * - * @handle: a @qemu_plugin_reg_handle handle - * @buf: A GByteArray for the data owned by the plugin - * - * This function is only available in a context that register read access is - * explicitly requested via the QEMU_PLUGIN_CB_R_REGS flag. - * - * Returns the size of the read register. The content of @buf is in target byte - * order. On failure returns -1. - */ -QEMU_PLUGIN_API -int qemu_plugin_read_register(struct qemu_plugin_register *handle, - GByteArray *buf); - /** * qemu_plugin_scoreboard_new() - alloc a new scoreboard * diff --git a/plugins/api.c b/plugins/api.c index 3c9d4832e9..3a7add50d2 100644 --- a/plugins/api.c +++ b/plugins/api.c @@ -433,6 +433,21 @@ GArray *qemu_plugin_get_registers(void) return create_register_handles(regs); } +int qemu_plugin_read_register(struct qemu_plugin_register *reg, GByteArray *buf) +{ +g_assert(current_cpu); + +return gdb_read_register(current_cpu, buf, GPOINTER_TO_INT(reg) - 1); +} + +int qemu_plugin_write_register(struct qemu_plugin_register *reg, + GByteArray *buf) +{ +g_assert(current_cpu); + +return gdb_write_register(current_cpu, buf->data, GPOINTER_TO_INT(reg) - 1); +} + bool qemu_plugin_read_memory_vaddr(uint64_t addr, GByteArray *data, size_t len) { g_assert(current_cpu); @@ -453,13 +468,6 @@ bool qemu_plugin_read_memory_vaddr(uint64_t addr, GByteArray *data, size_t len) return true; } -int qemu_plugin_read_register(struct qemu_plugin_register *reg, GByteArray *buf) -{ -g_assert(current_cpu); - -return gdb_read_register(current_cpu, buf, GPOINTER_TO_INT(reg) - 1); -} - struct qemu_plugin_scoreboard *qemu_plugin_scoreboard_new(size_t element_size) { return plugin_scoreboard_new(element_size); -- 2.49.0
[PATCH v5 8/9] plugins: Remove use of qemu_plugin_read_register where it is not permitted
This patch is required to make the insn plugin work after adding enforcement of QEMU_PLUGIN_CB_ flags in calls to read or write registers. Previously, these flags were not enforced and the API could be called from anywhere, but this was not intended as described by the documentation. Now, the flags are enforced and qemu_plugin_read_register can no longer be called from a vcpu_init callback because it does not request the QEMU_PLUGIN_CB_ flag (nor does it have a mechanism to do so). Signed-off-by: Rowan Hart --- tests/tcg/plugins/insn.c | 22 +- 1 file changed, 1 insertion(+), 21 deletions(-) diff --git a/tests/tcg/plugins/insn.c b/tests/tcg/plugins/insn.c index 0c723cb9ed..265d3ebe9e 100644 --- a/tests/tcg/plugins/insn.c +++ b/tests/tcg/plugins/insn.c @@ -81,25 +81,6 @@ static Instruction * get_insn_record(const char *disas, uint64_t vaddr, Match *m return record; } -/* - * Initialise a new vcpu with reading the register list - */ -static void vcpu_init(qemu_plugin_id_t id, unsigned int vcpu_index) -{ -g_autoptr(GArray) reg_list = qemu_plugin_get_registers(); -g_autoptr(GByteArray) reg_value = g_byte_array_new(); - -if (reg_list) { -for (int i = 0; i < reg_list->len; i++) { -qemu_plugin_reg_descriptor *rd = &g_array_index( -reg_list, qemu_plugin_reg_descriptor, i); -int count = qemu_plugin_read_register(rd->handle, reg_value); -g_assert(count > 0); -} -} -} - - static void vcpu_insn_exec_before(unsigned int cpu_index, void *udata) { qemu_plugin_u64_add(insn_count, cpu_index, 1); @@ -295,8 +276,7 @@ QEMU_PLUGIN_EXPORT int qemu_plugin_install(qemu_plugin_id_t id, insn_count = qemu_plugin_scoreboard_u64( qemu_plugin_scoreboard_new(sizeof(uint64_t))); -/* Register init, translation block and exit callbacks */ -qemu_plugin_register_vcpu_init_cb(id, vcpu_init); +/* Register translation block and exit callbacks */ qemu_plugin_register_vcpu_tb_trans_cb(id, vcpu_tb_trans); qemu_plugin_register_atexit_cb(id, plugin_exit, NULL); return 0; -- 2.49.0
[PATCH v5 3/9] plugins: Add enforcement of QEMU_PLUGIN_CB flags in register R/W callbacks
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 --- 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..1d330a1c03 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..120fb626
[PATCH v5 6/9] plugins: Add patcher plugin and test
From: novafacing 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 Signed-off-by: Rowan Hart --- tests/tcg/Makefile.target | 1 + tests/tcg/plugins/meson.build | 2 +- tests/tcg/plugins/patch.c | 302 ++ tests/tcg/x86_64/Makefile.softmmu-target | 32 ++- tests/tcg/x86_64/system/patch-target.c| 32 +++ tests/tcg/x86_64/system/validate-patch.py | 39 +++ 6 files changed, 402 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=$(COMMA)patch= 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 00..3767d14a53 --- /dev/null +++ b/tests/tcg/plugins/patch.c @@ -0,0 +1,302 @@ +/* + * SPDX-License-Identifier: GPL-2.0-or-later + * + * Copyright (C) 2025, Rowan Hart + * + * License: GNU GPL, version 2 or later. + * See the COPYING file in the top-level directory. + * + * This plugin patches instructions matching a pattern to a different + * instruction as they execute + * + */ + +#include "glib.h" +#include "glibconfig.h" + +#include +#include +#include + +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, patc
[PATCH v5 0/9] Add additional plugin API functions to read and write memory and registers
This patch series adds several new API functions focused on enabling use cases around reading and writing guest memory from QEMU plugins. To support these new APIs, some utility functionality around retrieving information about address spaces is added as well. The new qemu_plugin_write_register utilizes gdb_write_register, which is now declared in gdbstub.h for this purpose instead of being static. qemu_plugin_write_memory_vaddr utilizes cpu_memory_rw_debug much the same as the existing read_memory_vaddr function does. The read and write_hwaddr functions are the most different. These functions use address_space_rw, which works well in most cases. There is an important caveat that for writes, the page being written will be set dirty by the write operation. This dirty setting requires locking the page range, which can contend with an already held lock in page_collection_lock when called in a tb translate callback with a write to the instruction memory in the tb. The doc comments warn against doing this, and it's unlikely anyone would want to do this. I've also added two test plugins: one that implements a simple hypercall interface that guest code can use to communicate with the plugin in a structured way with a test to ensure that this hypercall works and writing virtual memory works. And one that implements a simple patch utility to patch memory at runtime. The test for the second plugin ensures the patch applies successfully to instruction memory, and can use both hw and vaddr methods. For v3, I've had a few comments from the last submission that I've addressed, and some that I haven't for one reason or another: - Enforce QEMU_PLUGIN_CB_ flags in register read/write operations: done! - Fix my commit messages and add long messages describing commits: done! - Un-expose AS internals: done! Functions operate on current vCPU, current AS. - Clean up use of current_cpu: done! - Make functions take a vcpu_idx: not done. May revisit but it allows footguns. Even for translation, seems best to not do this now. We can easily add _vcpu versions of these functions in the future if we change our minds! For v4, I've just updated the enforcement of the QEMU_PLUGIN_CB_ flags to just use immediate stores, which simplifies the implementation quite a lot and should be more efficient too. Thanks Pierrick for the suggestion! Rowan Hart (2): plugins: Add enforcement of QEMU_PLUGIN_CB flags in register R/W callbacks plugins: Remove use of qemu_plugin_read_register where it is not permitted novafacing (7): gdbstub: Expose gdb_write_register function to consumers of gdbstub plugins: Add register write API plugins: Add memory virtual address write API plugins: Add memory hardware address read/write API plugins: Add patcher plugin and test plugins: Add hypercalls plugin and test plugins: Update plugin version and add notes accel/tcg/plugin-gen.c| 30 + gdbstub/gdbstub.c | 2 +- include/exec/gdbstub.h| 14 + include/hw/core/cpu.h | 1 + include/qemu/plugin.h | 4 + include/qemu/qemu-plugin.h| 166 +- plugins/api.c | 135 - plugins/core.c| 32 +- tests/tcg/Makefile.target | 2 + tests/tcg/plugins/hypercalls.c| 552 ++ tests/tcg/plugins/insn.c | 22 +- tests/tcg/plugins/meson.build | 2 +- tests/tcg/plugins/patch.c | 302 ++ tests/tcg/x86_64/Makefile.softmmu-target | 36 +- tests/tcg/x86_64/system/hypercalls-target.c | 45 ++ tests/tcg/x86_64/system/patch-target.c| 32 + .../tcg/x86_64/system/validate-hypercalls.py | 40 ++ tests/tcg/x86_64/system/validate-patch.py | 39 ++ 18 files changed, 1406 insertions(+), 50 deletions(-) create mode 100644 tests/tcg/plugins/hypercalls.c create mode 100644 tests/tcg/plugins/patch.c create mode 100644 tests/tcg/x86_64/system/hypercalls-target.c create mode 100644 tests/tcg/x86_64/system/patch-target.c create mode 100755 tests/tcg/x86_64/system/validate-hypercalls.py create mode 100755 tests/tcg/x86_64/system/validate-patch.py -- 2.49.0
[PATCH v5 7/9] plugins: Add hypercalls plugin and test
From: novafacing 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 Signed-off-by: Rowan Hart --- tests/tcg/Makefile.target | 1 + tests/tcg/plugins/hypercalls.c| 552 ++ tests/tcg/plugins/meson.build | 2 +- tests/tcg/x86_64/Makefile.softmmu-target | 6 +- tests/tcg/x86_64/system/hypercalls-target.c | 45 ++ .../tcg/x86_64/system/validate-hypercalls.py | 40 ++ 6 files changed, 644 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=$(COMMA)patch= +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 00..3abbe1bf30 --- /dev/null +++ b/tests/tcg/plugins/hypercalls.c @@ -0,0 +1,552 @@ +/* + * SPDX-License-Identifier: GPL-2.0-or-later + * + * Copyright (C) 2024, Rowan Hart + * + * License: GNU GPL, version 2 or later. + * See the COPYING file in the top-level directory. + * + * 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 +#include +#include +#include +#include +#include +#include + +#include + +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 (0x1) +#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(hype
[PATCH v5 5/9] plugins: Add memory hardware address read/write API
From: novafacing This patch adds functions to the plugins API to allow plugins to read and write memory via hardware addresses. The functions use the current address space of the current CPU in order to avoid exposing address space information to users. A later patch may want to add a function to permit a specified address space, for example to facilitate architecture-specific plugins that want to operate on them, for example reading ARM secure memory. Signed-off-by: novafacing Signed-off-by: Rowan Hart --- include/qemu/qemu-plugin.h | 93 plugins/api.c | 97 ++ 2 files changed, 190 insertions(+) diff --git a/include/qemu/qemu-plugin.h b/include/qemu/qemu-plugin.h index 8ae7758b95..2cb5de9f64 100644 --- a/include/qemu/qemu-plugin.h +++ b/include/qemu/qemu-plugin.h @@ -969,6 +969,99 @@ QEMU_PLUGIN_API bool qemu_plugin_write_memory_vaddr(uint64_t addr, GByteArray *data); +/** + * enum qemu_plugin_hwaddr_operation_result - result of a memory operation + * + * @QEMU_PLUGIN_HWADDR_OPERATION_OK: hwaddr operation succeeded + * @QEMU_PLUGIN_HWADDR_OPERATION_ERROR: unexpected error occurred + * @QEMU_PLUGIN_HWADDR_OPERATION_DEVICE_ERROR: error in memory device + * @QEMU_PLUGIN_HWADDR_OPERATION_ACCESS_DENIED: permission error + * @QEMU_PLUGIN_HWADDR_OPERATION_INVALID_ADDRESS: address was invalid + * @QEMU_PLUGIN_HWADDR_OPERATION_INVALID_ADDRESS_SPACE: invalid address space + */ +enum qemu_plugin_hwaddr_operation_result { +QEMU_PLUGIN_HWADDR_OPERATION_OK, +QEMU_PLUGIN_HWADDR_OPERATION_ERROR, +QEMU_PLUGIN_HWADDR_OPERATION_DEVICE_ERROR, +QEMU_PLUGIN_HWADDR_OPERATION_ACCESS_DENIED, +QEMU_PLUGIN_HWADDR_OPERATION_INVALID_ADDRESS, +QEMU_PLUGIN_HWADDR_OPERATION_INVALID_ADDRESS_SPACE, +}; + +/** + * qemu_plugin_read_memory_hwaddr() - read from memory using a hardware address + * + * @addr: The physical address to read from + * @data: A byte array to store data into + * @len: The number of bytes to read, starting from @addr + * + * @len bytes of data is read from the current memory space for the current + * vCPU starting at @addr and stored into @data. If @data is not large enough to + * hold @len bytes, it will be expanded to the necessary size, reallocating if + * necessary. @len must be greater than 0. + * + * This function does not ensure writes are flushed prior to reading, so + * callers should take care when calling this function in plugin callbacks to + * avoid attempting to read data which may not yet be written and should use + * the memory callback API instead. + * + * This function is only valid for softmmu targets. + * + * Returns a qemu_plugin_hwaddr_operation_result indicating the result of the + * operation. + */ +QEMU_PLUGIN_API +enum qemu_plugin_hwaddr_operation_result +qemu_plugin_read_memory_hwaddr(uint64_t addr, GByteArray *data, size_t len); + +/** + * qemu_plugin_write_memory_hwaddr() - write to memory using a hardware address + * + * @addr: A physical address to write to + * @data: A byte array containing the data to write + * + * The contents of @data will be written to memory starting at the hardware + * address @addr in the current address space for the current vCPU. + * + * This function does not guarantee consistency of writes, nor does it ensure + * that pending writes are flushed either before or after the write takes place, + * so callers should take care when calling this function in plugin callbacks to + * avoid depending on the existence of data written using this function which + * may be overwritten afterward. In addition, this function requires that the + * pages containing the address are not locked. Practically, this means that you + * should not write instruction memory in a current translation block inside a + * callback registered with qemu_plugin_register_vcpu_tb_trans_cb. + * + * You can, for example, write instruction memory in a current translation block + * in a callback registered with qemu_plugin_register_vcpu_tb_exec_cb, although + * be aware that the write will not be flushed until after the translation block + * has finished executing. In general, this function should be used to write + * data memory or to patch code at a known address, not in a current translation + * block. + * + * This function is only valid for softmmu targets. + * + * Returns a qemu_plugin_hwaddr_operation_result indicating the result of the + * operation. + */ +QEMU_PLUGIN_API +enum qemu_plugin_hwaddr_operation_result +qemu_plugin_write_memory_hwaddr(uint64_t addr, GByteArray *data); + +/** + * qemu_plugin_translate_vaddr() - translate virtual address for current vCPU + * + * @vaddr: virtual address to translate + * @hwaddr: pointer to store the physical address + * + * This function is only valid in vCPU context (i.e. in callbacks) and is only + * valid for softmmu targets. + * + * Returns true on success and false on failure
[PATCH v5 4/9] plugins: Add memory virtual address write API
From: novafacing This patch adds functions to the plugins API to allow reading and writing memory via virtual addresses. These functions only permit doing so on the current CPU, because there is no way to ensure consistency if plugins are allowed to read or write to other CPUs that aren't currently in the context of the plugin. Signed-off-by: novafacing Signed-off-by: Rowan Hart --- include/qemu/qemu-plugin.h | 21 + plugins/api.c | 18 ++ 2 files changed, 39 insertions(+) diff --git a/include/qemu/qemu-plugin.h b/include/qemu/qemu-plugin.h index 120fb626a6..8ae7758b95 100644 --- a/include/qemu/qemu-plugin.h +++ b/include/qemu/qemu-plugin.h @@ -948,6 +948,27 @@ QEMU_PLUGIN_API bool qemu_plugin_read_memory_vaddr(uint64_t addr, GByteArray *data, size_t len); +/** + * qemu_plugin_write_memory_vaddr() - write to memory using a virtual address + * + * @addr: A virtual address to write to + * @data: A byte array containing the data to write + * + * The contents of @data will be written to memory starting at the virtual + * address @addr. + * + * This function does not guarantee consistency of writes, nor does it ensure + * that pending writes are flushed either before or after the write takes place, + * so callers should take care to only call this function in vCPU context (i.e. + * in callbacks) and avoid depending on the existence of data written using this + * function which may be overwritten afterward. + * + * Returns true on success and false on failure. + */ +QEMU_PLUGIN_API +bool qemu_plugin_write_memory_vaddr(uint64_t addr, + GByteArray *data); + /** * qemu_plugin_scoreboard_new() - alloc a new scoreboard * diff --git a/plugins/api.c b/plugins/api.c index 16141f5c25..7258b6590b 100644 --- a/plugins/api.c +++ b/plugins/api.c @@ -476,6 +476,24 @@ bool qemu_plugin_read_memory_vaddr(uint64_t addr, GByteArray *data, size_t len) 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; +} + struct qemu_plugin_scoreboard *qemu_plugin_scoreboard_new(size_t element_size) { return plugin_scoreboard_new(element_size); -- 2.49.0
[PATCH v6 4/9] plugins: Add memory virtual address write API
From: novafacing This patch adds functions to the plugins API to allow reading and writing memory via virtual addresses. These functions only permit doing so on the current CPU, because there is no way to ensure consistency if plugins are allowed to read or write to other CPUs that aren't currently in the context of the plugin. Signed-off-by: novafacing Signed-off-by: Rowan Hart --- include/qemu/qemu-plugin.h | 21 + plugins/api.c | 18 ++ 2 files changed, 39 insertions(+) diff --git a/include/qemu/qemu-plugin.h b/include/qemu/qemu-plugin.h index 120fb626a6..8ae7758b95 100644 --- a/include/qemu/qemu-plugin.h +++ b/include/qemu/qemu-plugin.h @@ -948,6 +948,27 @@ QEMU_PLUGIN_API bool qemu_plugin_read_memory_vaddr(uint64_t addr, GByteArray *data, size_t len); +/** + * qemu_plugin_write_memory_vaddr() - write to memory using a virtual address + * + * @addr: A virtual address to write to + * @data: A byte array containing the data to write + * + * The contents of @data will be written to memory starting at the virtual + * address @addr. + * + * This function does not guarantee consistency of writes, nor does it ensure + * that pending writes are flushed either before or after the write takes place, + * so callers should take care to only call this function in vCPU context (i.e. + * in callbacks) and avoid depending on the existence of data written using this + * function which may be overwritten afterward. + * + * Returns true on success and false on failure. + */ +QEMU_PLUGIN_API +bool qemu_plugin_write_memory_vaddr(uint64_t addr, + GByteArray *data); + /** * qemu_plugin_scoreboard_new() - alloc a new scoreboard * diff --git a/plugins/api.c b/plugins/api.c index 16141f5c25..7258b6590b 100644 --- a/plugins/api.c +++ b/plugins/api.c @@ -476,6 +476,24 @@ bool qemu_plugin_read_memory_vaddr(uint64_t addr, GByteArray *data, size_t len) 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; +} + struct qemu_plugin_scoreboard *qemu_plugin_scoreboard_new(size_t element_size) { return plugin_scoreboard_new(element_size); -- 2.49.0
[PATCH v5 9/9] plugins: Update plugin version and add notes
From: novafacing This patch updates the plugin version to gate new APIs and adds notes describing what has been added. Signed-off-by: Rowan Hart --- include/qemu/qemu-plugin.h | 9 - 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/include/qemu/qemu-plugin.h b/include/qemu/qemu-plugin.h index 2cb5de9f64..170a79e667 100644 --- a/include/qemu/qemu-plugin.h +++ b/include/qemu/qemu-plugin.h @@ -65,11 +65,18 @@ typedef uint64_t qemu_plugin_id_t; * * version 4: * - added qemu_plugin_read_memory_vaddr + * + * version 5: + * - added qemu_plugin_write_memory_vaddr + * - added qemu_plugin_read_memory_hwaddr + * - added qemu_plugin_write_memory_hwaddr + * - added qemu_plugin_write_register + * - added qemu_plugin_translate_vaddr */ extern QEMU_PLUGIN_EXPORT int qemu_plugin_version; -#define QEMU_PLUGIN_VERSION 4 +#define QEMU_PLUGIN_VERSION 5 /** * struct qemu_info_t - system information for plugins -- 2.49.0
[PATCH v6 8/9] plugins: Remove use of qemu_plugin_read_register where it is not permitted
This patch is required to make the insn plugin work after adding enforcement of QEMU_PLUGIN_CB_ flags in calls to read or write registers. Previously, these flags were not enforced and the API could be called from anywhere, but this was not intended as described by the documentation. Now, the flags are enforced and qemu_plugin_read_register can no longer be called from a vcpu_init callback because it does not request the QEMU_PLUGIN_CB_ flag (nor does it have a mechanism to do so). Signed-off-by: Rowan Hart --- tests/tcg/plugins/insn.c | 22 +- 1 file changed, 1 insertion(+), 21 deletions(-) diff --git a/tests/tcg/plugins/insn.c b/tests/tcg/plugins/insn.c index 0c723cb9ed..265d3ebe9e 100644 --- a/tests/tcg/plugins/insn.c +++ b/tests/tcg/plugins/insn.c @@ -81,25 +81,6 @@ static Instruction * get_insn_record(const char *disas, uint64_t vaddr, Match *m return record; } -/* - * Initialise a new vcpu with reading the register list - */ -static void vcpu_init(qemu_plugin_id_t id, unsigned int vcpu_index) -{ -g_autoptr(GArray) reg_list = qemu_plugin_get_registers(); -g_autoptr(GByteArray) reg_value = g_byte_array_new(); - -if (reg_list) { -for (int i = 0; i < reg_list->len; i++) { -qemu_plugin_reg_descriptor *rd = &g_array_index( -reg_list, qemu_plugin_reg_descriptor, i); -int count = qemu_plugin_read_register(rd->handle, reg_value); -g_assert(count > 0); -} -} -} - - static void vcpu_insn_exec_before(unsigned int cpu_index, void *udata) { qemu_plugin_u64_add(insn_count, cpu_index, 1); @@ -295,8 +276,7 @@ QEMU_PLUGIN_EXPORT int qemu_plugin_install(qemu_plugin_id_t id, insn_count = qemu_plugin_scoreboard_u64( qemu_plugin_scoreboard_new(sizeof(uint64_t))); -/* Register init, translation block and exit callbacks */ -qemu_plugin_register_vcpu_init_cb(id, vcpu_init); +/* Register translation block and exit callbacks */ qemu_plugin_register_vcpu_tb_trans_cb(id, vcpu_tb_trans); qemu_plugin_register_atexit_cb(id, plugin_exit, NULL); return 0; -- 2.49.0
[PATCH v6 0/9] Add additional plugin API functions to read and write memory and registers
This patch series adds several new API functions focused on enabling use cases around reading and writing guest memory from QEMU plugins. To support these new APIs, some utility functionality around retrieving information about address spaces is added as well. The new qemu_plugin_write_register utilizes gdb_write_register, which is now declared in gdbstub.h for this purpose instead of being static. qemu_plugin_write_memory_vaddr utilizes cpu_memory_rw_debug much the same as the existing read_memory_vaddr function does. The read and write_hwaddr functions are the most different. These functions use address_space_rw, which works well in most cases. There is an important caveat that for writes, the page being written will be set dirty by the write operation. This dirty setting requires locking the page range, which can contend with an already held lock in page_collection_lock when called in a tb translate callback with a write to the instruction memory in the tb. The doc comments warn against doing this, and it's unlikely anyone would want to do this. I've also added two test plugins: one that implements a simple hypercall interface that guest code can use to communicate with the plugin in a structured way with a test to ensure that this hypercall works and writing virtual memory works. And one that implements a simple patch utility to patch memory at runtime. The test for the second plugin ensures the patch applies successfully to instruction memory, and can use both hw and vaddr methods. For v3, I've had a few comments from the last submission that I've addressed, and some that I haven't for one reason or another: - Enforce QEMU_PLUGIN_CB_ flags in register read/write operations: done! - Fix my commit messages and add long messages describing commits: done! - Un-expose AS internals: done! Functions operate on current vCPU, current AS. - Clean up use of current_cpu: done! - Make functions take a vcpu_idx: not done. May revisit but it allows footguns. Even for translation, seems best to not do this now. We can easily add _vcpu versions of these functions in the future if we change our minds! For v5, I've just updated the enforcement of the QEMU_PLUGIN_CB_ flags to just use immediate stores, which simplifies the implementation quite a lot and should be more efficient too. Thanks Pierrick for the suggestion! v6 is a formatting pass, I left some whitespace that needed removal, some license text was wrong, and so forth. Rowan Hart (2): plugins: Add enforcement of QEMU_PLUGIN_CB flags in register R/W callbacks plugins: Remove use of qemu_plugin_read_register where it is not permitted novafacing (7): gdbstub: Expose gdb_write_register function to consumers of gdbstub plugins: Add register write API plugins: Add memory virtual address write API plugins: Add memory hardware address read/write API plugins: Add patcher plugin and test plugins: Add hypercalls plugin and test plugins: Update plugin version and add notes accel/tcg/plugin-gen.c| 30 + gdbstub/gdbstub.c | 2 +- include/exec/gdbstub.h| 14 + include/hw/core/cpu.h | 1 + include/qemu/plugin.h | 4 + include/qemu/qemu-plugin.h| 166 +- plugins/api.c | 135 - plugins/core.c| 32 +- tests/tcg/Makefile.target | 2 + tests/tcg/plugins/hypercalls.c| 547 ++ tests/tcg/plugins/insn.c | 22 +- tests/tcg/plugins/meson.build | 2 +- tests/tcg/plugins/patch.c | 297 ++ tests/tcg/x86_64/Makefile.softmmu-target | 36 +- tests/tcg/x86_64/system/hypercalls-target.c | 40 ++ tests/tcg/x86_64/system/patch-target.c| 27 + .../tcg/x86_64/system/validate-hypercalls.py | 40 ++ tests/tcg/x86_64/system/validate-patch.py | 39 ++ 18 files changed, 1386 insertions(+), 50 deletions(-) create mode 100644 tests/tcg/plugins/hypercalls.c create mode 100644 tests/tcg/plugins/patch.c create mode 100644 tests/tcg/x86_64/system/hypercalls-target.c create mode 100644 tests/tcg/x86_64/system/patch-target.c create mode 100755 tests/tcg/x86_64/system/validate-hypercalls.py create mode 100755 tests/tcg/x86_64/system/validate-patch.py -- 2.49.0
[PATCH v6 3/9] plugins: Add enforcement of QEMU_PLUGIN_CB flags in register R/W callbacks
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 --- 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..120fb626
[PATCH v6 7/9] plugins: Add hypercalls plugin and test
From: novafacing 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 Signed-off-by: Rowan Hart --- 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=$(COMMA)patch= +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 00..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 +#include +#include +#include +#include +#include +#include + +#include + +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 (0x1) +#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_
[PATCH v6 2/9] plugins: Add register write API
From: novafacing This patch adds a function to the plugins API to allow plugins to write register contents. It also moves the qemu_plugin_read_register function so all the register-related functions are grouped together in the file. Signed-off-by: novafacing Signed-off-by: Rowan Hart --- include/qemu/qemu-plugin.h | 54 ++ plugins/api.c | 22 +++- 2 files changed, 52 insertions(+), 24 deletions(-) diff --git a/include/qemu/qemu-plugin.h b/include/qemu/qemu-plugin.h index 3a850aa216..cfe1692ecb 100644 --- a/include/qemu/qemu-plugin.h +++ b/include/qemu/qemu-plugin.h @@ -871,7 +871,8 @@ struct qemu_plugin_register; /** * typedef qemu_plugin_reg_descriptor - register descriptions * - * @handle: opaque handle for retrieving value with qemu_plugin_read_register + * @handle: opaque handle for retrieving value with qemu_plugin_read_register or + * writing value with qemu_plugin_write_register * @name: register name * @feature: optional feature descriptor, can be NULL */ @@ -893,6 +894,41 @@ typedef struct { QEMU_PLUGIN_API GArray *qemu_plugin_get_registers(void); +/** + * qemu_plugin_read_register() - read register for current vCPU + * + * @handle: a @qemu_plugin_reg_handle handle + * @buf: A GByteArray for the data owned by the plugin + * + * This function is only available in a context that register read access is + * explicitly requested via the QEMU_PLUGIN_CB_R_REGS flag. + * + * Returns the size of the read register. The content of @buf is in target byte + * order. On failure returns -1. + */ +QEMU_PLUGIN_API +int qemu_plugin_read_register(struct qemu_plugin_register *handle, + GByteArray *buf); + +/** + * qemu_plugin_write_register() - write register for current vCPU + * + * @handle: a @qemu_plugin_reg_handle handle + * @buf: A GByteArray for the data owned by the plugin + * + * This function is only available in a context that register write access is + * explicitly requested via the QEMU_PLUGIN_CB_RW_REGS flag. + * + * The size of @buf must be at least the size of the requested register. + * Attempting to write a register with @buf smaller than the register size + * will result in a crash or other undesired behavior. + * + * Returns the number of bytes written. On failure returns 0. + */ +QEMU_PLUGIN_API +int qemu_plugin_write_register(struct qemu_plugin_register *handle, + GByteArray *buf); + /** * qemu_plugin_read_memory_vaddr() - read from memory using a virtual address * @@ -915,22 +951,6 @@ QEMU_PLUGIN_API bool qemu_plugin_read_memory_vaddr(uint64_t addr, GByteArray *data, size_t len); -/** - * qemu_plugin_read_register() - read register for current vCPU - * - * @handle: a @qemu_plugin_reg_handle handle - * @buf: A GByteArray for the data owned by the plugin - * - * This function is only available in a context that register read access is - * explicitly requested via the QEMU_PLUGIN_CB_R_REGS flag. - * - * Returns the size of the read register. The content of @buf is in target byte - * order. On failure returns -1. - */ -QEMU_PLUGIN_API -int qemu_plugin_read_register(struct qemu_plugin_register *handle, - GByteArray *buf); - /** * qemu_plugin_scoreboard_new() - alloc a new scoreboard * diff --git a/plugins/api.c b/plugins/api.c index 3c9d4832e9..3a7add50d2 100644 --- a/plugins/api.c +++ b/plugins/api.c @@ -433,6 +433,21 @@ GArray *qemu_plugin_get_registers(void) return create_register_handles(regs); } +int qemu_plugin_read_register(struct qemu_plugin_register *reg, GByteArray *buf) +{ +g_assert(current_cpu); + +return gdb_read_register(current_cpu, buf, GPOINTER_TO_INT(reg) - 1); +} + +int qemu_plugin_write_register(struct qemu_plugin_register *reg, + GByteArray *buf) +{ +g_assert(current_cpu); + +return gdb_write_register(current_cpu, buf->data, GPOINTER_TO_INT(reg) - 1); +} + bool qemu_plugin_read_memory_vaddr(uint64_t addr, GByteArray *data, size_t len) { g_assert(current_cpu); @@ -453,13 +468,6 @@ bool qemu_plugin_read_memory_vaddr(uint64_t addr, GByteArray *data, size_t len) return true; } -int qemu_plugin_read_register(struct qemu_plugin_register *reg, GByteArray *buf) -{ -g_assert(current_cpu); - -return gdb_read_register(current_cpu, buf, GPOINTER_TO_INT(reg) - 1); -} - struct qemu_plugin_scoreboard *qemu_plugin_scoreboard_new(size_t element_size) { return plugin_scoreboard_new(element_size); -- 2.49.0
[PATCH v6 9/9] plugins: Update plugin version and add notes
From: novafacing This patch updates the plugin version to gate new APIs and adds notes describing what has been added. Signed-off-by: Rowan Hart --- include/qemu/qemu-plugin.h | 9 - 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/include/qemu/qemu-plugin.h b/include/qemu/qemu-plugin.h index 2cb5de9f64..170a79e667 100644 --- a/include/qemu/qemu-plugin.h +++ b/include/qemu/qemu-plugin.h @@ -65,11 +65,18 @@ typedef uint64_t qemu_plugin_id_t; * * version 4: * - added qemu_plugin_read_memory_vaddr + * + * version 5: + * - added qemu_plugin_write_memory_vaddr + * - added qemu_plugin_read_memory_hwaddr + * - added qemu_plugin_write_memory_hwaddr + * - added qemu_plugin_write_register + * - added qemu_plugin_translate_vaddr */ extern QEMU_PLUGIN_EXPORT int qemu_plugin_version; -#define QEMU_PLUGIN_VERSION 4 +#define QEMU_PLUGIN_VERSION 5 /** * struct qemu_info_t - system information for plugins -- 2.49.0
[PATCH v6 1/9] gdbstub: Expose gdb_write_register function to consumers of gdbstub
From: novafacing This patch exposes the gdb_write_register function from gdbstub/gdbstub.c via the exec/gdbstub.h header file to support use in plugins to write register contents. Signed-off-by: novafacing Signed-off-by: Rowan Hart --- gdbstub/gdbstub.c | 2 +- include/exec/gdbstub.h | 14 ++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/gdbstub/gdbstub.c b/gdbstub/gdbstub.c index 565f6b33a9..5846e481be 100644 --- a/gdbstub/gdbstub.c +++ b/gdbstub/gdbstub.c @@ -534,7 +534,7 @@ int gdb_read_register(CPUState *cpu, GByteArray *buf, int reg) return 0; } -static int gdb_write_register(CPUState *cpu, uint8_t *mem_buf, int reg) +int gdb_write_register(CPUState *cpu, uint8_t *mem_buf, int reg) { GDBRegisterState *r; diff --git a/include/exec/gdbstub.h b/include/exec/gdbstub.h index 0675b0b646..a16c0051ce 100644 --- a/include/exec/gdbstub.h +++ b/include/exec/gdbstub.h @@ -124,6 +124,20 @@ const GDBFeature *gdb_find_static_feature(const char *xmlname); */ int gdb_read_register(CPUState *cpu, GByteArray *buf, int reg); +/** + * gdb_write_register() - Write a register associated with a CPU. + * @cpu: The CPU associated with the register. + * @buf: The buffer that the register contents will be set to. + * @reg: The register's number returned by gdb_find_feature_register(). + * + * The size of @buf must be at least the size of the register being + * written. + * + * Return: The number of written bytes, or 0 if an error occurred (for + * example, an unknown register was provided). + */ +int gdb_write_register(CPUState *cpu, uint8_t *mem_buf, int reg); + /** * typedef GDBRegDesc - a register description from gdbstub */ -- 2.49.0
[PATCH v6 6/9] plugins: Add patcher plugin and test
From: novafacing 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 Signed-off-by: Rowan Hart --- 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=$(COMMA)patch= 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 00..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 +#include +#include + +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&q
[PATCH v5 1/9] gdbstub: Expose gdb_write_register function to consumers of gdbstub
From: novafacing This patch exposes the gdb_write_register function from gdbstub/gdbstub.c via the exec/gdbstub.h header file to support use in plugins to write register contents. Signed-off-by: novafacing Signed-off-by: Rowan Hart --- gdbstub/gdbstub.c | 2 +- include/exec/gdbstub.h | 14 ++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/gdbstub/gdbstub.c b/gdbstub/gdbstub.c index 565f6b33a9..5846e481be 100644 --- a/gdbstub/gdbstub.c +++ b/gdbstub/gdbstub.c @@ -534,7 +534,7 @@ int gdb_read_register(CPUState *cpu, GByteArray *buf, int reg) return 0; } -static int gdb_write_register(CPUState *cpu, uint8_t *mem_buf, int reg) +int gdb_write_register(CPUState *cpu, uint8_t *mem_buf, int reg) { GDBRegisterState *r; diff --git a/include/exec/gdbstub.h b/include/exec/gdbstub.h index 0675b0b646..a16c0051ce 100644 --- a/include/exec/gdbstub.h +++ b/include/exec/gdbstub.h @@ -124,6 +124,20 @@ const GDBFeature *gdb_find_static_feature(const char *xmlname); */ int gdb_read_register(CPUState *cpu, GByteArray *buf, int reg); +/** + * gdb_write_register() - Write a register associated with a CPU. + * @cpu: The CPU associated with the register. + * @buf: The buffer that the register contents will be set to. + * @reg: The register's number returned by gdb_find_feature_register(). + * + * The size of @buf must be at least the size of the register being + * written. + * + * Return: The number of written bytes, or 0 if an error occurred (for + * example, an unknown register was provided). + */ +int gdb_write_register(CPUState *cpu, uint8_t *mem_buf, int reg); + /** * typedef GDBRegDesc - a register description from gdbstub */ -- 2.49.0
[PATCH v6 5/9] plugins: Add memory hardware address read/write API
From: novafacing This patch adds functions to the plugins API to allow plugins to read and write memory via hardware addresses. The functions use the current address space of the current CPU in order to avoid exposing address space information to users. A later patch may want to add a function to permit a specified address space, for example to facilitate architecture-specific plugins that want to operate on them, for example reading ARM secure memory. Signed-off-by: novafacing Signed-off-by: Rowan Hart --- include/qemu/qemu-plugin.h | 93 plugins/api.c | 97 ++ 2 files changed, 190 insertions(+) diff --git a/include/qemu/qemu-plugin.h b/include/qemu/qemu-plugin.h index 8ae7758b95..2cb5de9f64 100644 --- a/include/qemu/qemu-plugin.h +++ b/include/qemu/qemu-plugin.h @@ -969,6 +969,99 @@ QEMU_PLUGIN_API bool qemu_plugin_write_memory_vaddr(uint64_t addr, GByteArray *data); +/** + * enum qemu_plugin_hwaddr_operation_result - result of a memory operation + * + * @QEMU_PLUGIN_HWADDR_OPERATION_OK: hwaddr operation succeeded + * @QEMU_PLUGIN_HWADDR_OPERATION_ERROR: unexpected error occurred + * @QEMU_PLUGIN_HWADDR_OPERATION_DEVICE_ERROR: error in memory device + * @QEMU_PLUGIN_HWADDR_OPERATION_ACCESS_DENIED: permission error + * @QEMU_PLUGIN_HWADDR_OPERATION_INVALID_ADDRESS: address was invalid + * @QEMU_PLUGIN_HWADDR_OPERATION_INVALID_ADDRESS_SPACE: invalid address space + */ +enum qemu_plugin_hwaddr_operation_result { +QEMU_PLUGIN_HWADDR_OPERATION_OK, +QEMU_PLUGIN_HWADDR_OPERATION_ERROR, +QEMU_PLUGIN_HWADDR_OPERATION_DEVICE_ERROR, +QEMU_PLUGIN_HWADDR_OPERATION_ACCESS_DENIED, +QEMU_PLUGIN_HWADDR_OPERATION_INVALID_ADDRESS, +QEMU_PLUGIN_HWADDR_OPERATION_INVALID_ADDRESS_SPACE, +}; + +/** + * qemu_plugin_read_memory_hwaddr() - read from memory using a hardware address + * + * @addr: The physical address to read from + * @data: A byte array to store data into + * @len: The number of bytes to read, starting from @addr + * + * @len bytes of data is read from the current memory space for the current + * vCPU starting at @addr and stored into @data. If @data is not large enough to + * hold @len bytes, it will be expanded to the necessary size, reallocating if + * necessary. @len must be greater than 0. + * + * This function does not ensure writes are flushed prior to reading, so + * callers should take care when calling this function in plugin callbacks to + * avoid attempting to read data which may not yet be written and should use + * the memory callback API instead. + * + * This function is only valid for softmmu targets. + * + * Returns a qemu_plugin_hwaddr_operation_result indicating the result of the + * operation. + */ +QEMU_PLUGIN_API +enum qemu_plugin_hwaddr_operation_result +qemu_plugin_read_memory_hwaddr(uint64_t addr, GByteArray *data, size_t len); + +/** + * qemu_plugin_write_memory_hwaddr() - write to memory using a hardware address + * + * @addr: A physical address to write to + * @data: A byte array containing the data to write + * + * The contents of @data will be written to memory starting at the hardware + * address @addr in the current address space for the current vCPU. + * + * This function does not guarantee consistency of writes, nor does it ensure + * that pending writes are flushed either before or after the write takes place, + * so callers should take care when calling this function in plugin callbacks to + * avoid depending on the existence of data written using this function which + * may be overwritten afterward. In addition, this function requires that the + * pages containing the address are not locked. Practically, this means that you + * should not write instruction memory in a current translation block inside a + * callback registered with qemu_plugin_register_vcpu_tb_trans_cb. + * + * You can, for example, write instruction memory in a current translation block + * in a callback registered with qemu_plugin_register_vcpu_tb_exec_cb, although + * be aware that the write will not be flushed until after the translation block + * has finished executing. In general, this function should be used to write + * data memory or to patch code at a known address, not in a current translation + * block. + * + * This function is only valid for softmmu targets. + * + * Returns a qemu_plugin_hwaddr_operation_result indicating the result of the + * operation. + */ +QEMU_PLUGIN_API +enum qemu_plugin_hwaddr_operation_result +qemu_plugin_write_memory_hwaddr(uint64_t addr, GByteArray *data); + +/** + * qemu_plugin_translate_vaddr() - translate virtual address for current vCPU + * + * @vaddr: virtual address to translate + * @hwaddr: pointer to store the physical address + * + * This function is only valid in vCPU context (i.e. in callbacks) and is only + * valid for softmmu targets. + * + * Returns true on success and false on failure
[PATCH v7 2/9] plugins: Add register write API
From: novafacing This patch adds a function to the plugins API to allow plugins to write register contents. It also moves the qemu_plugin_read_register function so all the register-related functions are grouped together in the file. Signed-off-by: novafacing Signed-off-by: Rowan Hart Reviewed-by: Pierrick Bouvier Signed-off-by: Rowan Hart --- include/qemu/qemu-plugin.h | 54 ++ plugins/api.c | 22 +++- 2 files changed, 52 insertions(+), 24 deletions(-) diff --git a/include/qemu/qemu-plugin.h b/include/qemu/qemu-plugin.h index 3a850aa216..cfe1692ecb 100644 --- a/include/qemu/qemu-plugin.h +++ b/include/qemu/qemu-plugin.h @@ -871,7 +871,8 @@ struct qemu_plugin_register; /** * typedef qemu_plugin_reg_descriptor - register descriptions * - * @handle: opaque handle for retrieving value with qemu_plugin_read_register + * @handle: opaque handle for retrieving value with qemu_plugin_read_register or + * writing value with qemu_plugin_write_register * @name: register name * @feature: optional feature descriptor, can be NULL */ @@ -893,6 +894,41 @@ typedef struct { QEMU_PLUGIN_API GArray *qemu_plugin_get_registers(void); +/** + * qemu_plugin_read_register() - read register for current vCPU + * + * @handle: a @qemu_plugin_reg_handle handle + * @buf: A GByteArray for the data owned by the plugin + * + * This function is only available in a context that register read access is + * explicitly requested via the QEMU_PLUGIN_CB_R_REGS flag. + * + * Returns the size of the read register. The content of @buf is in target byte + * order. On failure returns -1. + */ +QEMU_PLUGIN_API +int qemu_plugin_read_register(struct qemu_plugin_register *handle, + GByteArray *buf); + +/** + * qemu_plugin_write_register() - write register for current vCPU + * + * @handle: a @qemu_plugin_reg_handle handle + * @buf: A GByteArray for the data owned by the plugin + * + * This function is only available in a context that register write access is + * explicitly requested via the QEMU_PLUGIN_CB_RW_REGS flag. + * + * The size of @buf must be at least the size of the requested register. + * Attempting to write a register with @buf smaller than the register size + * will result in a crash or other undesired behavior. + * + * Returns the number of bytes written. On failure returns 0. + */ +QEMU_PLUGIN_API +int qemu_plugin_write_register(struct qemu_plugin_register *handle, + GByteArray *buf); + /** * qemu_plugin_read_memory_vaddr() - read from memory using a virtual address * @@ -915,22 +951,6 @@ QEMU_PLUGIN_API bool qemu_plugin_read_memory_vaddr(uint64_t addr, GByteArray *data, size_t len); -/** - * qemu_plugin_read_register() - read register for current vCPU - * - * @handle: a @qemu_plugin_reg_handle handle - * @buf: A GByteArray for the data owned by the plugin - * - * This function is only available in a context that register read access is - * explicitly requested via the QEMU_PLUGIN_CB_R_REGS flag. - * - * Returns the size of the read register. The content of @buf is in target byte - * order. On failure returns -1. - */ -QEMU_PLUGIN_API -int qemu_plugin_read_register(struct qemu_plugin_register *handle, - GByteArray *buf); - /** * qemu_plugin_scoreboard_new() - alloc a new scoreboard * diff --git a/plugins/api.c b/plugins/api.c index 3c9d4832e9..3a7add50d2 100644 --- a/plugins/api.c +++ b/plugins/api.c @@ -433,6 +433,21 @@ GArray *qemu_plugin_get_registers(void) return create_register_handles(regs); } +int qemu_plugin_read_register(struct qemu_plugin_register *reg, GByteArray *buf) +{ +g_assert(current_cpu); + +return gdb_read_register(current_cpu, buf, GPOINTER_TO_INT(reg) - 1); +} + +int qemu_plugin_write_register(struct qemu_plugin_register *reg, + GByteArray *buf) +{ +g_assert(current_cpu); + +return gdb_write_register(current_cpu, buf->data, GPOINTER_TO_INT(reg) - 1); +} + bool qemu_plugin_read_memory_vaddr(uint64_t addr, GByteArray *data, size_t len) { g_assert(current_cpu); @@ -453,13 +468,6 @@ bool qemu_plugin_read_memory_vaddr(uint64_t addr, GByteArray *data, size_t len) return true; } -int qemu_plugin_read_register(struct qemu_plugin_register *reg, GByteArray *buf) -{ -g_assert(current_cpu); - -return gdb_read_register(current_cpu, buf, GPOINTER_TO_INT(reg) - 1); -} - struct qemu_plugin_scoreboard *qemu_plugin_scoreboard_new(size_t element_size) { return plugin_scoreboard_new(element_size); -- 2.49.0
[PATCH v7 6/9] plugins: Add patcher plugin and test
From: novafacing 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 Signed-off-by: Rowan Hart --- 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=$(COMMA)patch= 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 00..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 +#include +#include + +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&q
[PATCH v7 3/9] plugins: Add enforcement of QEMU_PLUGIN_CB flags in register R/W callbacks
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 --- 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..120fb626
[PATCH v7 9/9] plugins: Update plugin version and add notes
From: novafacing This patch updates the plugin version to gate new APIs and adds notes describing what has been added. Signed-off-by: Rowan Hart --- include/qemu/qemu-plugin.h | 9 - 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/include/qemu/qemu-plugin.h b/include/qemu/qemu-plugin.h index 2cb5de9f64..170a79e667 100644 --- a/include/qemu/qemu-plugin.h +++ b/include/qemu/qemu-plugin.h @@ -65,11 +65,18 @@ typedef uint64_t qemu_plugin_id_t; * * version 4: * - added qemu_plugin_read_memory_vaddr + * + * version 5: + * - added qemu_plugin_write_memory_vaddr + * - added qemu_plugin_read_memory_hwaddr + * - added qemu_plugin_write_memory_hwaddr + * - added qemu_plugin_write_register + * - added qemu_plugin_translate_vaddr */ extern QEMU_PLUGIN_EXPORT int qemu_plugin_version; -#define QEMU_PLUGIN_VERSION 4 +#define QEMU_PLUGIN_VERSION 5 /** * struct qemu_info_t - system information for plugins -- 2.49.0
[PATCH v7 7/9] plugins: Add hypercalls plugin and test
From: novafacing 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 Signed-off-by: Rowan Hart --- 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=$(COMMA)patch= +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 00..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 +#include +#include +#include +#include +#include +#include + +#include + +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 (0x1) +#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_
[PATCH v7 0/9] Add additional plugin API functions to read and write memory and registers
This patch series adds several new API functions focused on enabling use cases around reading and writing guest memory from QEMU plugins. To support these new APIs, some utility functionality around retrieving information about address spaces is added as well. The new qemu_plugin_write_register utilizes gdb_write_register, which is now declared in gdbstub.h for this purpose instead of being static. qemu_plugin_write_memory_vaddr utilizes cpu_memory_rw_debug much the same as the existing read_memory_vaddr function does. The read and write_hwaddr functions are the most different. These functions use address_space_rw, which works well in most cases. There is an important caveat that for writes, the page being written will be set dirty by the write operation. This dirty setting requires locking the page range, which can contend with an already held lock in page_collection_lock when called in a tb translate callback with a write to the instruction memory in the tb. The doc comments warn against doing this, and it's unlikely anyone would want to do this. I've also added two test plugins: one that implements a simple hypercall interface that guest code can use to communicate with the plugin in a structured way with a test to ensure that this hypercall works and writing virtual memory works. And one that implements a simple patch utility to patch memory at runtime. The test for the second plugin ensures the patch applies successfully to instruction memory, and can use both hw and vaddr methods. For v3, I've had a few comments from the last submission that I've addressed, and some that I haven't for one reason or another: - Enforce QEMU_PLUGIN_CB_ flags in register read/write operations: done! - Fix my commit messages and add long messages describing commits: done! - Un-expose AS internals: done! Functions operate on current vCPU, current AS. - Clean up use of current_cpu: done! - Make functions take a vcpu_idx: not done. May revisit but it allows footguns. Even for translation, seems best to not do this now. We can easily add _vcpu versions of these functions in the future if we change our minds! For v5, I've just updated the enforcement of the QEMU_PLUGIN_CB_ flags to just use immediate stores, which simplifies the implementation quite a lot and should be more efficient too. Thanks Pierrick for the suggestion! v6 is a formatting pass, I left some whitespace that needed removal, some license text was wrong, and so forth. Rowan Hart (2): plugins: Add enforcement of QEMU_PLUGIN_CB flags in register R/W callbacks plugins: Remove use of qemu_plugin_read_register where it is not permitted novafacing (7): gdbstub: Expose gdb_write_register function to consumers of gdbstub plugins: Add register write API plugins: Add memory virtual address write API plugins: Add memory hardware address read/write API plugins: Add patcher plugin and test plugins: Add hypercalls plugin and test plugins: Update plugin version and add notes accel/tcg/plugin-gen.c| 30 + gdbstub/gdbstub.c | 2 +- include/exec/gdbstub.h| 14 + include/hw/core/cpu.h | 1 + include/qemu/plugin.h | 4 + include/qemu/qemu-plugin.h| 166 +- plugins/api.c | 135 - plugins/core.c| 32 +- tests/tcg/Makefile.target | 2 + tests/tcg/plugins/hypercalls.c| 547 ++ tests/tcg/plugins/insn.c | 22 +- tests/tcg/plugins/meson.build | 2 +- tests/tcg/plugins/patch.c | 297 ++ tests/tcg/x86_64/Makefile.softmmu-target | 36 +- tests/tcg/x86_64/system/hypercalls-target.c | 40 ++ tests/tcg/x86_64/system/patch-target.c| 27 + .../tcg/x86_64/system/validate-hypercalls.py | 40 ++ tests/tcg/x86_64/system/validate-patch.py | 39 ++ 18 files changed, 1386 insertions(+), 50 deletions(-) create mode 100644 tests/tcg/plugins/hypercalls.c create mode 100644 tests/tcg/plugins/patch.c create mode 100644 tests/tcg/x86_64/system/hypercalls-target.c create mode 100644 tests/tcg/x86_64/system/patch-target.c create mode 100755 tests/tcg/x86_64/system/validate-hypercalls.py create mode 100755 tests/tcg/x86_64/system/validate-patch.py -- 2.49.0
[PATCH v7 4/9] plugins: Add memory virtual address write API
From: novafacing This patch adds functions to the plugins API to allow reading and writing memory via virtual addresses. These functions only permit doing so on the current CPU, because there is no way to ensure consistency if plugins are allowed to read or write to other CPUs that aren't currently in the context of the plugin. Signed-off-by: novafacing Signed-off-by: Rowan Hart Reviewed-by: Pierrick Bouvier Signed-off-by: Rowan Hart --- include/qemu/qemu-plugin.h | 21 + plugins/api.c | 18 ++ 2 files changed, 39 insertions(+) diff --git a/include/qemu/qemu-plugin.h b/include/qemu/qemu-plugin.h index 120fb626a6..8ae7758b95 100644 --- a/include/qemu/qemu-plugin.h +++ b/include/qemu/qemu-plugin.h @@ -948,6 +948,27 @@ QEMU_PLUGIN_API bool qemu_plugin_read_memory_vaddr(uint64_t addr, GByteArray *data, size_t len); +/** + * qemu_plugin_write_memory_vaddr() - write to memory using a virtual address + * + * @addr: A virtual address to write to + * @data: A byte array containing the data to write + * + * The contents of @data will be written to memory starting at the virtual + * address @addr. + * + * This function does not guarantee consistency of writes, nor does it ensure + * that pending writes are flushed either before or after the write takes place, + * so callers should take care to only call this function in vCPU context (i.e. + * in callbacks) and avoid depending on the existence of data written using this + * function which may be overwritten afterward. + * + * Returns true on success and false on failure. + */ +QEMU_PLUGIN_API +bool qemu_plugin_write_memory_vaddr(uint64_t addr, + GByteArray *data); + /** * qemu_plugin_scoreboard_new() - alloc a new scoreboard * diff --git a/plugins/api.c b/plugins/api.c index 16141f5c25..7258b6590b 100644 --- a/plugins/api.c +++ b/plugins/api.c @@ -476,6 +476,24 @@ bool qemu_plugin_read_memory_vaddr(uint64_t addr, GByteArray *data, size_t len) 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; +} + struct qemu_plugin_scoreboard *qemu_plugin_scoreboard_new(size_t element_size) { return plugin_scoreboard_new(element_size); -- 2.49.0
[PATCH v7 8/9] plugins: Remove use of qemu_plugin_read_register where it is not permitted
This patch is required to make the insn plugin work after adding enforcement of QEMU_PLUGIN_CB_ flags in calls to read or write registers. Previously, these flags were not enforced and the API could be called from anywhere, but this was not intended as described by the documentation. Now, the flags are enforced and qemu_plugin_read_register can no longer be called from a vcpu_init callback because it does not request the QEMU_PLUGIN_CB_ flag (nor does it have a mechanism to do so). Signed-off-by: Rowan Hart --- tests/tcg/plugins/insn.c | 22 +- 1 file changed, 1 insertion(+), 21 deletions(-) diff --git a/tests/tcg/plugins/insn.c b/tests/tcg/plugins/insn.c index 0c723cb9ed..265d3ebe9e 100644 --- a/tests/tcg/plugins/insn.c +++ b/tests/tcg/plugins/insn.c @@ -81,25 +81,6 @@ static Instruction * get_insn_record(const char *disas, uint64_t vaddr, Match *m return record; } -/* - * Initialise a new vcpu with reading the register list - */ -static void vcpu_init(qemu_plugin_id_t id, unsigned int vcpu_index) -{ -g_autoptr(GArray) reg_list = qemu_plugin_get_registers(); -g_autoptr(GByteArray) reg_value = g_byte_array_new(); - -if (reg_list) { -for (int i = 0; i < reg_list->len; i++) { -qemu_plugin_reg_descriptor *rd = &g_array_index( -reg_list, qemu_plugin_reg_descriptor, i); -int count = qemu_plugin_read_register(rd->handle, reg_value); -g_assert(count > 0); -} -} -} - - static void vcpu_insn_exec_before(unsigned int cpu_index, void *udata) { qemu_plugin_u64_add(insn_count, cpu_index, 1); @@ -295,8 +276,7 @@ QEMU_PLUGIN_EXPORT int qemu_plugin_install(qemu_plugin_id_t id, insn_count = qemu_plugin_scoreboard_u64( qemu_plugin_scoreboard_new(sizeof(uint64_t))); -/* Register init, translation block and exit callbacks */ -qemu_plugin_register_vcpu_init_cb(id, vcpu_init); +/* Register translation block and exit callbacks */ qemu_plugin_register_vcpu_tb_trans_cb(id, vcpu_tb_trans); qemu_plugin_register_atexit_cb(id, plugin_exit, NULL); return 0; -- 2.49.0
[PATCH v7 1/9] gdbstub: Expose gdb_write_register function to consumers of gdbstub
From: novafacing This patch exposes the gdb_write_register function from gdbstub/gdbstub.c via the exec/gdbstub.h header file to support use in plugins to write register contents. Signed-off-by: novafacing Signed-off-by: Rowan Hart Reviewed-by: Alex Benée Reviewed-by: Julian Ganz Reviewed-by: Pierrick Bouvier Signed-off-by: Rowan Hart --- gdbstub/gdbstub.c | 2 +- include/exec/gdbstub.h | 14 ++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/gdbstub/gdbstub.c b/gdbstub/gdbstub.c index 565f6b33a9..5846e481be 100644 --- a/gdbstub/gdbstub.c +++ b/gdbstub/gdbstub.c @@ -534,7 +534,7 @@ int gdb_read_register(CPUState *cpu, GByteArray *buf, int reg) return 0; } -static int gdb_write_register(CPUState *cpu, uint8_t *mem_buf, int reg) +int gdb_write_register(CPUState *cpu, uint8_t *mem_buf, int reg) { GDBRegisterState *r; diff --git a/include/exec/gdbstub.h b/include/exec/gdbstub.h index 0675b0b646..a16c0051ce 100644 --- a/include/exec/gdbstub.h +++ b/include/exec/gdbstub.h @@ -124,6 +124,20 @@ const GDBFeature *gdb_find_static_feature(const char *xmlname); */ int gdb_read_register(CPUState *cpu, GByteArray *buf, int reg); +/** + * gdb_write_register() - Write a register associated with a CPU. + * @cpu: The CPU associated with the register. + * @buf: The buffer that the register contents will be set to. + * @reg: The register's number returned by gdb_find_feature_register(). + * + * The size of @buf must be at least the size of the register being + * written. + * + * Return: The number of written bytes, or 0 if an error occurred (for + * example, an unknown register was provided). + */ +int gdb_write_register(CPUState *cpu, uint8_t *mem_buf, int reg); + /** * typedef GDBRegDesc - a register description from gdbstub */ -- 2.49.0
Re: [PATCH v7 3/9] plugins: Add enforcement of QEMU_PLUGIN_CB flags in register R/W callbacks
@@ -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; +Â Â Â } + Would it be better to return -1 for "qemu_plugin_get_cb_flags() != QEMU_PLUGIN_CB_RW_REGS", so user can notice there is something wrong with flags? Sure would, typo on my part here. Â 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)) Â }; [QEMU_PLUGIN_CB_RW_REGS].flags = 0 was already set implicitly, as all elements not explicit set are initialized to 0. As you can see, [0 ... 2].typemask was set, which shows we initialized the third element. Adding [QEMU_PLUGIN_CB_RW_REGS].flags = 0 does not hurt, and is more explicit, but you don't need to increase array size. This static array is used to set info field in callback struct, as .info = &info[flags]. Flags being an enum qemu_plugin_cb_flags, its value is 0,1 or 2, so adding one entry is useless. Got it, for some reason I assumed this array needed a sentinel value and simultaneously didn't recognize the [0 ... n] sytnax was inclusive. Thanks for pointing that out! I'll make these two changes.
[PATCH v7 5/9] plugins: Add memory hardware address read/write API
From: novafacing This patch adds functions to the plugins API to allow plugins to read and write memory via hardware addresses. The functions use the current address space of the current CPU in order to avoid exposing address space information to users. A later patch may want to add a function to permit a specified address space, for example to facilitate architecture-specific plugins that want to operate on them, for example reading ARM secure memory. Signed-off-by: novafacing Signed-off-by: Rowan Hart Reviewed-by: Pierrick Bouvier Signed-off-by: Rowan Hart --- include/qemu/qemu-plugin.h | 93 plugins/api.c | 97 ++ 2 files changed, 190 insertions(+) diff --git a/include/qemu/qemu-plugin.h b/include/qemu/qemu-plugin.h index 8ae7758b95..2cb5de9f64 100644 --- a/include/qemu/qemu-plugin.h +++ b/include/qemu/qemu-plugin.h @@ -969,6 +969,99 @@ QEMU_PLUGIN_API bool qemu_plugin_write_memory_vaddr(uint64_t addr, GByteArray *data); +/** + * enum qemu_plugin_hwaddr_operation_result - result of a memory operation + * + * @QEMU_PLUGIN_HWADDR_OPERATION_OK: hwaddr operation succeeded + * @QEMU_PLUGIN_HWADDR_OPERATION_ERROR: unexpected error occurred + * @QEMU_PLUGIN_HWADDR_OPERATION_DEVICE_ERROR: error in memory device + * @QEMU_PLUGIN_HWADDR_OPERATION_ACCESS_DENIED: permission error + * @QEMU_PLUGIN_HWADDR_OPERATION_INVALID_ADDRESS: address was invalid + * @QEMU_PLUGIN_HWADDR_OPERATION_INVALID_ADDRESS_SPACE: invalid address space + */ +enum qemu_plugin_hwaddr_operation_result { +QEMU_PLUGIN_HWADDR_OPERATION_OK, +QEMU_PLUGIN_HWADDR_OPERATION_ERROR, +QEMU_PLUGIN_HWADDR_OPERATION_DEVICE_ERROR, +QEMU_PLUGIN_HWADDR_OPERATION_ACCESS_DENIED, +QEMU_PLUGIN_HWADDR_OPERATION_INVALID_ADDRESS, +QEMU_PLUGIN_HWADDR_OPERATION_INVALID_ADDRESS_SPACE, +}; + +/** + * qemu_plugin_read_memory_hwaddr() - read from memory using a hardware address + * + * @addr: The physical address to read from + * @data: A byte array to store data into + * @len: The number of bytes to read, starting from @addr + * + * @len bytes of data is read from the current memory space for the current + * vCPU starting at @addr and stored into @data. If @data is not large enough to + * hold @len bytes, it will be expanded to the necessary size, reallocating if + * necessary. @len must be greater than 0. + * + * This function does not ensure writes are flushed prior to reading, so + * callers should take care when calling this function in plugin callbacks to + * avoid attempting to read data which may not yet be written and should use + * the memory callback API instead. + * + * This function is only valid for softmmu targets. + * + * Returns a qemu_plugin_hwaddr_operation_result indicating the result of the + * operation. + */ +QEMU_PLUGIN_API +enum qemu_plugin_hwaddr_operation_result +qemu_plugin_read_memory_hwaddr(uint64_t addr, GByteArray *data, size_t len); + +/** + * qemu_plugin_write_memory_hwaddr() - write to memory using a hardware address + * + * @addr: A physical address to write to + * @data: A byte array containing the data to write + * + * The contents of @data will be written to memory starting at the hardware + * address @addr in the current address space for the current vCPU. + * + * This function does not guarantee consistency of writes, nor does it ensure + * that pending writes are flushed either before or after the write takes place, + * so callers should take care when calling this function in plugin callbacks to + * avoid depending on the existence of data written using this function which + * may be overwritten afterward. In addition, this function requires that the + * pages containing the address are not locked. Practically, this means that you + * should not write instruction memory in a current translation block inside a + * callback registered with qemu_plugin_register_vcpu_tb_trans_cb. + * + * You can, for example, write instruction memory in a current translation block + * in a callback registered with qemu_plugin_register_vcpu_tb_exec_cb, although + * be aware that the write will not be flushed until after the translation block + * has finished executing. In general, this function should be used to write + * data memory or to patch code at a known address, not in a current translation + * block. + * + * This function is only valid for softmmu targets. + * + * Returns a qemu_plugin_hwaddr_operation_result indicating the result of the + * operation. + */ +QEMU_PLUGIN_API +enum qemu_plugin_hwaddr_operation_result +qemu_plugin_write_memory_hwaddr(uint64_t addr, GByteArray *data); + +/** + * qemu_plugin_translate_vaddr() - translate virtual address for current vCPU + * + * @vaddr: virtual address to translate + * @hwaddr: pointer to store the physical address + * + * This function is only valid in vCPU context (i.e. in callbacks) and is only + * valid for softmmu targets
[PATCH v13 4/7] plugins: Add memory virtual address write API
From: novafacing This patch adds functions to the plugins API to allow reading and writing memory via virtual addresses. These functions only permit doing so on the current CPU, because there is no way to ensure consistency if plugins are allowed to read or write to other CPUs that aren't currently in the context of the plugin. Reviewed-by: Pierrick Bouvier Signed-off-by: Rowan Hart --- include/qemu/qemu-plugin.h | 21 + plugins/api.c | 18 ++ 2 files changed, 39 insertions(+) diff --git a/include/qemu/qemu-plugin.h b/include/qemu/qemu-plugin.h index 9c9ebf6ce0..4167c46c2a 100644 --- a/include/qemu/qemu-plugin.h +++ b/include/qemu/qemu-plugin.h @@ -958,6 +958,27 @@ QEMU_PLUGIN_API bool qemu_plugin_read_memory_vaddr(uint64_t addr, GByteArray *data, size_t len); +/** + * qemu_plugin_write_memory_vaddr() - write to memory using a virtual address + * + * @addr: A virtual address to write to + * @data: A byte array containing the data to write + * + * The contents of @data will be written to memory starting at the virtual + * address @addr. + * + * This function does not guarantee consistency of writes, nor does it ensure + * that pending writes are flushed either before or after the write takes place, + * so callers should take care to only call this function in vCPU context (i.e. + * in callbacks) and avoid depending on the existence of data written using this + * function which may be overwritten afterward. + * + * Returns true on success and false on failure. + */ +QEMU_PLUGIN_API +bool qemu_plugin_write_memory_vaddr(uint64_t addr, + GByteArray *data); + /** * qemu_plugin_scoreboard_new() - alloc a new scoreboard * diff --git a/plugins/api.c b/plugins/api.c index 3f04399c26..1f64a9ea64 100644 --- a/plugins/api.c +++ b/plugins/api.c @@ -476,6 +476,24 @@ bool qemu_plugin_read_memory_vaddr(uint64_t addr, GByteArray *data, size_t len) 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; +} + struct qemu_plugin_scoreboard *qemu_plugin_scoreboard_new(size_t element_size) { return plugin_scoreboard_new(element_size); -- 2.49.0
[PATCH v13 6/7] plugins: Add patcher plugin and test
From: novafacing 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: Rowan Hart --- tests/tcg/Makefile.target | 7 +- tests/tcg/plugins/meson.build | 2 +- tests/tcg/plugins/patch.c | 241 ++ tests/tcg/x86_64/Makefile.softmmu-target | 7 + tests/tcg/x86_64/system/patch-target.c| 22 ++ tests/tcg/x86_64/system/validate-patch.py | 39 6 files changed, 316 insertions(+), 2 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..af68f11664 100644 --- a/tests/tcg/Makefile.target +++ b/tests/tcg/Makefile.target @@ -151,7 +151,12 @@ ifeq ($(CONFIG_PLUGIN),y) PLUGIN_SRC=$(SRC_PATH)/tests/tcg/plugins PLUGIN_LIB=../plugins VPATH+=$(PLUGIN_LIB) -PLUGINS=$(patsubst %.c, lib%.so, $(notdir $(wildcard $(PLUGIN_SRC)/*.c))) +# Some plugins need to be disabled for all tests to avoid exponential explosion. +# For example, libpatch.so only needs to run against the arch-specific patch +# target test, so we explicitly run it in the arch-specific Makefile. +DISABLE_PLUGINS=libpatch.so +PLUGINS=$(filter-out $(DISABLE_PLUGINS), \ + $(patsubst %.c, lib%.so, $(notdir $(wildcard $(PLUGIN_SRC)/*.c # We need to ensure expand the run-plugin-TEST-with-PLUGIN # pre-requistes manually here as we can't use stems to handle it. We 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 00..450fc51c88 --- /dev/null +++ b/tests/tcg/plugins/patch.c @@ -0,0 +1,241 @@ +/* + * 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 +#include +#include + +QEMU_PLUGIN_EXPORT int qemu_plugin_version = QEMU_PLUGIN_VERSION; + +static bool use_hwaddr; +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) +{ +size_t len = strlen(str); + +if (len == 0 || len % 2 != 0) { +return NULL; +} + +GByteArray *bytes = g_byte_array_new(); +char byte[3] = {0}; +guint8 value = 0; + +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; +g_autoptr(GString) str = g_string_new(NULL); +g_string_printf(str, "patching: @0x%" +PRIx64 "\n", +addr); +qemu_plugin_outs(str->str); + +enum qemu_plugin_hwaddr_operation_result result = +qemu_plugin_write_memory_hwaddr(addr, patch_data); + + +if (result != QEMU_PLUGIN_HWADDR_OPERATION_OK) { +g_autoptr(GString) errmsg = g_string_new(NULL); +g_string_printf(errmsg, "Failed to write memory: %d\n", result); +qemu_plugin_outs(errmsg->str); +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) { +g_autoptr(GString) errmsg = g_string_new(NULL); +g_string_printf(errmsg, "Failed to read memory: %d\n", result); +qemu_plugin_outs(errmsg->str); +return; +} + +if (memcmp(patch
[PATCH v13 2/7] plugins: Add register write API
From: novafacing This patch adds a function to the plugins API to allow plugins to write register contents. It also moves the qemu_plugin_read_register function so all the register-related functions are grouped together in the file. Reviewed-by: Pierrick Bouvier Signed-off-by: Rowan Hart --- include/qemu/qemu-plugin.h | 54 ++ plugins/api.c | 26 +- 2 files changed, 56 insertions(+), 24 deletions(-) diff --git a/include/qemu/qemu-plugin.h b/include/qemu/qemu-plugin.h index 3a850aa216..cfe1692ecb 100644 --- a/include/qemu/qemu-plugin.h +++ b/include/qemu/qemu-plugin.h @@ -871,7 +871,8 @@ struct qemu_plugin_register; /** * typedef qemu_plugin_reg_descriptor - register descriptions * - * @handle: opaque handle for retrieving value with qemu_plugin_read_register + * @handle: opaque handle for retrieving value with qemu_plugin_read_register or + * writing value with qemu_plugin_write_register * @name: register name * @feature: optional feature descriptor, can be NULL */ @@ -893,6 +894,41 @@ typedef struct { QEMU_PLUGIN_API GArray *qemu_plugin_get_registers(void); +/** + * qemu_plugin_read_register() - read register for current vCPU + * + * @handle: a @qemu_plugin_reg_handle handle + * @buf: A GByteArray for the data owned by the plugin + * + * This function is only available in a context that register read access is + * explicitly requested via the QEMU_PLUGIN_CB_R_REGS flag. + * + * Returns the size of the read register. The content of @buf is in target byte + * order. On failure returns -1. + */ +QEMU_PLUGIN_API +int qemu_plugin_read_register(struct qemu_plugin_register *handle, + GByteArray *buf); + +/** + * qemu_plugin_write_register() - write register for current vCPU + * + * @handle: a @qemu_plugin_reg_handle handle + * @buf: A GByteArray for the data owned by the plugin + * + * This function is only available in a context that register write access is + * explicitly requested via the QEMU_PLUGIN_CB_RW_REGS flag. + * + * The size of @buf must be at least the size of the requested register. + * Attempting to write a register with @buf smaller than the register size + * will result in a crash or other undesired behavior. + * + * Returns the number of bytes written. On failure returns 0. + */ +QEMU_PLUGIN_API +int qemu_plugin_write_register(struct qemu_plugin_register *handle, + GByteArray *buf); + /** * qemu_plugin_read_memory_vaddr() - read from memory using a virtual address * @@ -915,22 +951,6 @@ QEMU_PLUGIN_API bool qemu_plugin_read_memory_vaddr(uint64_t addr, GByteArray *data, size_t len); -/** - * qemu_plugin_read_register() - read register for current vCPU - * - * @handle: a @qemu_plugin_reg_handle handle - * @buf: A GByteArray for the data owned by the plugin - * - * This function is only available in a context that register read access is - * explicitly requested via the QEMU_PLUGIN_CB_R_REGS flag. - * - * Returns the size of the read register. The content of @buf is in target byte - * order. On failure returns -1. - */ -QEMU_PLUGIN_API -int qemu_plugin_read_register(struct qemu_plugin_register *handle, - GByteArray *buf); - /** * qemu_plugin_scoreboard_new() - alloc a new scoreboard * diff --git a/plugins/api.c b/plugins/api.c index 3c9d4832e9..6514f2c76a 100644 --- a/plugins/api.c +++ b/plugins/api.c @@ -433,6 +433,25 @@ GArray *qemu_plugin_get_registers(void) return create_register_handles(regs); } +int qemu_plugin_read_register(struct qemu_plugin_register *reg, GByteArray *buf) +{ +g_assert(current_cpu); + +return gdb_read_register(current_cpu, buf, GPOINTER_TO_INT(reg) - 1); +} + +int qemu_plugin_write_register(struct qemu_plugin_register *reg, + GByteArray *buf) +{ +g_assert(current_cpu); + +if (buf->len == 0 || qemu_plugin_get_cb_flags() != QEMU_PLUGIN_CB_RW_REGS) { +return -1; +} + +return gdb_write_register(current_cpu, buf->data, GPOINTER_TO_INT(reg) - 1); +} + bool qemu_plugin_read_memory_vaddr(uint64_t addr, GByteArray *data, size_t len) { g_assert(current_cpu); @@ -453,13 +472,6 @@ bool qemu_plugin_read_memory_vaddr(uint64_t addr, GByteArray *data, size_t len) return true; } -int qemu_plugin_read_register(struct qemu_plugin_register *reg, GByteArray *buf) -{ -g_assert(current_cpu); - -return gdb_read_register(current_cpu, buf, GPOINTER_TO_INT(reg) - 1); -} - struct qemu_plugin_scoreboard *qemu_plugin_scoreboard_new(size_t element_size) { return plugin_scoreboard_new(element_size); -- 2.49.0
[PATCH v13 3/7] plugins: Add enforcement of QEMU_PLUGIN_CB flags in register R/W callbacks
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. Reviewed-by: Pierrick Bouvier Signed-off-by: Rowan Hart --- accel/tcg/plugin-gen.c | 30 ++ include/hw/core/cpu.h | 1 + include/qemu/plugin.h | 15 +++ include/qemu/qemu-plugin.h | 19 +-- plugins/api.c | 4 plugins/core.c | 33 + 6 files changed, 96 insertions(+), 6 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 33296a1c08..162a56a5da 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..f355c7cb8a 100644 --- a/include/qemu/plugin.h +++ b/include/qemu/plugin.h @@ -209,6 +209,21 @@ 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); + +static inline void qemu_plugin_set_cb_flags(CPUState *cpu, +enum qemu_plugin_cb_flags flags) +{ +assert(cpu); +cpu->ne
[PATCH v13 1/7] gdbstub: Expose gdb_write_register function to consumers of gdbstub
From: novafacing This patch exposes the gdb_write_register function from gdbstub/gdbstub.c via the exec/gdbstub.h header file to support use in plugins to write register contents. Reviewed-by: Alex Bennée Reviewed-by: Julian Ganz Reviewed-by: Pierrick Bouvier Signed-off-by: Rowan Hart --- gdbstub/gdbstub.c | 2 +- include/exec/gdbstub.h | 14 ++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/gdbstub/gdbstub.c b/gdbstub/gdbstub.c index def0b7e877..dd5fb5667c 100644 --- a/gdbstub/gdbstub.c +++ b/gdbstub/gdbstub.c @@ -535,7 +535,7 @@ int gdb_read_register(CPUState *cpu, GByteArray *buf, int reg) return 0; } -static int gdb_write_register(CPUState *cpu, uint8_t *mem_buf, int reg) +int gdb_write_register(CPUState *cpu, uint8_t *mem_buf, int reg) { GDBRegisterState *r; diff --git a/include/exec/gdbstub.h b/include/exec/gdbstub.h index 0675b0b646..a16c0051ce 100644 --- a/include/exec/gdbstub.h +++ b/include/exec/gdbstub.h @@ -124,6 +124,20 @@ const GDBFeature *gdb_find_static_feature(const char *xmlname); */ int gdb_read_register(CPUState *cpu, GByteArray *buf, int reg); +/** + * gdb_write_register() - Write a register associated with a CPU. + * @cpu: The CPU associated with the register. + * @buf: The buffer that the register contents will be set to. + * @reg: The register's number returned by gdb_find_feature_register(). + * + * The size of @buf must be at least the size of the register being + * written. + * + * Return: The number of written bytes, or 0 if an error occurred (for + * example, an unknown register was provided). + */ +int gdb_write_register(CPUState *cpu, uint8_t *mem_buf, int reg); + /** * typedef GDBRegDesc - a register description from gdbstub */ -- 2.49.0
[PATCH v13 5/7] plugins: Add memory hardware address read/write API
From: novafacing This patch adds functions to the plugins API to allow plugins to read and write memory via hardware addresses. The functions use the current address space of the current CPU in order to avoid exposing address space information to users. A later patch may want to add a function to permit a specified address space, for example to facilitate architecture-specific plugins that want to operate on them, for example reading ARM secure memory. Reviewed-by: Pierrick Bouvier Signed-off-by: Rowan Hart --- include/qemu/qemu-plugin.h | 93 plugins/api.c | 97 ++ 2 files changed, 190 insertions(+) diff --git a/include/qemu/qemu-plugin.h b/include/qemu/qemu-plugin.h index 4167c46c2a..5eecdccc67 100644 --- a/include/qemu/qemu-plugin.h +++ b/include/qemu/qemu-plugin.h @@ -979,6 +979,99 @@ QEMU_PLUGIN_API bool qemu_plugin_write_memory_vaddr(uint64_t addr, GByteArray *data); +/** + * enum qemu_plugin_hwaddr_operation_result - result of a memory operation + * + * @QEMU_PLUGIN_HWADDR_OPERATION_OK: hwaddr operation succeeded + * @QEMU_PLUGIN_HWADDR_OPERATION_ERROR: unexpected error occurred + * @QEMU_PLUGIN_HWADDR_OPERATION_DEVICE_ERROR: error in memory device + * @QEMU_PLUGIN_HWADDR_OPERATION_ACCESS_DENIED: permission error + * @QEMU_PLUGIN_HWADDR_OPERATION_INVALID_ADDRESS: address was invalid + * @QEMU_PLUGIN_HWADDR_OPERATION_INVALID_ADDRESS_SPACE: invalid address space + */ +enum qemu_plugin_hwaddr_operation_result { +QEMU_PLUGIN_HWADDR_OPERATION_OK, +QEMU_PLUGIN_HWADDR_OPERATION_ERROR, +QEMU_PLUGIN_HWADDR_OPERATION_DEVICE_ERROR, +QEMU_PLUGIN_HWADDR_OPERATION_ACCESS_DENIED, +QEMU_PLUGIN_HWADDR_OPERATION_INVALID_ADDRESS, +QEMU_PLUGIN_HWADDR_OPERATION_INVALID_ADDRESS_SPACE, +}; + +/** + * qemu_plugin_read_memory_hwaddr() - read from memory using a hardware address + * + * @addr: The physical address to read from + * @data: A byte array to store data into + * @len: The number of bytes to read, starting from @addr + * + * @len bytes of data is read from the current memory space for the current + * vCPU starting at @addr and stored into @data. If @data is not large enough to + * hold @len bytes, it will be expanded to the necessary size, reallocating if + * necessary. @len must be greater than 0. + * + * This function does not ensure writes are flushed prior to reading, so + * callers should take care when calling this function in plugin callbacks to + * avoid attempting to read data which may not yet be written and should use + * the memory callback API instead. + * + * This function is only valid for softmmu targets. + * + * Returns a qemu_plugin_hwaddr_operation_result indicating the result of the + * operation. + */ +QEMU_PLUGIN_API +enum qemu_plugin_hwaddr_operation_result +qemu_plugin_read_memory_hwaddr(uint64_t addr, GByteArray *data, size_t len); + +/** + * qemu_plugin_write_memory_hwaddr() - write to memory using a hardware address + * + * @addr: A physical address to write to + * @data: A byte array containing the data to write + * + * The contents of @data will be written to memory starting at the hardware + * address @addr in the current address space for the current vCPU. + * + * This function does not guarantee consistency of writes, nor does it ensure + * that pending writes are flushed either before or after the write takes place, + * so callers should take care when calling this function in plugin callbacks to + * avoid depending on the existence of data written using this function which + * may be overwritten afterward. In addition, this function requires that the + * pages containing the address are not locked. Practically, this means that you + * should not write instruction memory in a current translation block inside a + * callback registered with qemu_plugin_register_vcpu_tb_trans_cb. + * + * You can, for example, write instruction memory in a current translation block + * in a callback registered with qemu_plugin_register_vcpu_tb_exec_cb, although + * be aware that the write will not be flushed until after the translation block + * has finished executing. In general, this function should be used to write + * data memory or to patch code at a known address, not in a current translation + * block. + * + * This function is only valid for softmmu targets. + * + * Returns a qemu_plugin_hwaddr_operation_result indicating the result of the + * operation. + */ +QEMU_PLUGIN_API +enum qemu_plugin_hwaddr_operation_result +qemu_plugin_write_memory_hwaddr(uint64_t addr, GByteArray *data); + +/** + * qemu_plugin_translate_vaddr() - translate virtual address for current vCPU + * + * @vaddr: virtual address to translate + * @hwaddr: pointer to store the physical address + * + * This function is only valid in vCPU context (i.e. in callbacks) and is only + * valid for softmmu targets. + * + * Returns true on success and false on failure
[PATCH v13 0/7] Add additional plugin API functions to read and write memory and registers
This patch series adds several new API functions focused on enabling use cases around reading and writing guest memory from QEMU plugins. To support these new APIs, some utility functionality around retrieving information about address spaces is added as well. The new qemu_plugin_write_register utilizes gdb_write_register, which is now declared in gdbstub.h for this purpose instead of being static. qemu_plugin_write_memory_vaddr utilizes cpu_memory_rw_debug much the same as the existing read_memory_vaddr function does. The read and write_hwaddr functions are the most different. These functions use address_space_rw, which works well in most cases. There is an important caveat that for writes, the page being written will be set dirty by the write operation. This dirty setting requires locking the page range, which can contend with an already held lock in page_collection_lock when called in a tb translate callback with a write to the instruction memory in the tb. The doc comments warn against doing this, and it's unlikely anyone would want to do this. I've also added two test plugins: one that implements a simple hypercall interface that guest code can use to communicate with the plugin in a structured way with a test to ensure that this hypercall works and writing virtual memory works. And one that implements a simple patch utility to patch memory at runtime. The test for the second plugin ensures the patch applies successfully to instruction memory, and can use both hw and vaddr methods. For v3, I've had a few comments from the last submission that I've addressed, and some that I haven't for one reason or another: - Enforce QEMU_PLUGIN_CB_ flags in register read/write operations: done! - Fix my commit messages and add long messages describing commits: done! - Un-expose AS internals: done! Functions operate on current vCPU, current AS. - Clean up use of current_cpu: done! - Make functions take a vcpu_idx: not done. May revisit but it allows footguns. Even for translation, seems best to not do this now. We can easily add _vcpu versions of these functions in the future if we change our minds! For v5, I've just updated the enforcement of the QEMU_PLUGIN_CB_ flags to just use immediate stores, which simplifies the implementation quite a lot and should be more efficient too. Thanks Pierrick for the suggestion! v6 is a formatting pass, I left some whitespace that needed removal, some license text was wrong, and so forth. v8 reverts a mistake I made extending the size of arrays of TCGHelperInfo structs, as I misunderstood their sizes. It preserves adding an explicit zero as the last entry for clarity, however. v9 fixes qemu_plugin_read_register to return -1 on parameter or flag state error instead of 0. In v10, I relaxed the restriction on when the register r/w functions can be called, allowing all them to be used from any callback where the CPU is not currently executing, with additional notes in the documentation for exceptions (atexit and flush, which do not operate on a specific CPU and in which current_cpu is not set). v11 makes the cb flags functions inline and fixes a typo where cpu was asserted but current_cpu was actually accessed. v12 removes the hypercalls plugin because the functions it tested are also tested by the patcher plugin, making it redundant. We'll circle back on a hypercalls API in the future as a part of the plugin API, not as a plugin itself. v13 fixes up some issues Alex pointed out with the patch test. Now, the patch target only runs with libpatch.so, and libpatch.so only runs with the patch test. Rowan Hart (1): plugins: Add enforcement of QEMU_PLUGIN_CB flags in register R/W callbacks novafacing (6): gdbstub: Expose gdb_write_register function to consumers of gdbstub plugins: Add register write API plugins: Add memory virtual address write API plugins: Add memory hardware address read/write API plugins: Add patcher plugin and test plugins: Update plugin version and add notes accel/tcg/plugin-gen.c| 30 +++ gdbstub/gdbstub.c | 2 +- include/exec/gdbstub.h| 14 ++ include/hw/core/cpu.h | 1 + include/qemu/plugin.h | 15 ++ include/qemu/qemu-plugin.h| 176 ++-- plugins/api.c | 135 +++- plugins/core.c| 33 +++ tests/tcg/Makefile.target | 7 +- tests/tcg/plugins/meson.build | 2 +- tests/tcg/plugins/patch.c | 241 ++ tests/tcg/x86_64/Makefile.softmmu-target | 7 + tests/tcg/x86_64/system/patch-target.c| 22 ++ tests/tcg/x86_64/system/validate-patch.py | 39 14 files changed, 705 insertions(+), 19 deletions(-) create mode 100644 tests/tcg/plugins/patch.c create mode 100644 tests/tcg/x86_64/system/patch-target.c create mode 1007
[PATCH v13 7/7] plugins: Update plugin version and add notes
From: novafacing This patch updates the plugin version to gate new APIs and adds notes describing what has been added. Reviewed-by: Pierrick Bouvier Signed-off-by: Rowan Hart --- include/qemu/qemu-plugin.h | 9 - 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/include/qemu/qemu-plugin.h b/include/qemu/qemu-plugin.h index 5eecdccc67..c450106af1 100644 --- a/include/qemu/qemu-plugin.h +++ b/include/qemu/qemu-plugin.h @@ -65,11 +65,18 @@ typedef uint64_t qemu_plugin_id_t; * * version 4: * - added qemu_plugin_read_memory_vaddr + * + * version 5: + * - added qemu_plugin_write_memory_vaddr + * - added qemu_plugin_read_memory_hwaddr + * - added qemu_plugin_write_memory_hwaddr + * - added qemu_plugin_write_register + * - added qemu_plugin_translate_vaddr */ extern QEMU_PLUGIN_EXPORT int qemu_plugin_version; -#define QEMU_PLUGIN_VERSION 4 +#define QEMU_PLUGIN_VERSION 5 /** * struct qemu_info_t - system information for plugins -- 2.49.0
Re: [PATCH v12 0/7] Add additional plugin API functions to read and write memory and registers
I've updated this patch to address some notes about the build/test configuration for the patch plugin. Please check https://lore.kernel.org/qemu-devel/20250619161547.1401448-1-rowanbh...@gmail.com/T/#t instead. On 6/11/25 4:24 PM, Rowan Hart wrote: This patch series adds several new API functions focused on enabling use cases around reading and writing guest memory from QEMU plugins. To support these new APIs, some utility functionality around retrieving information about address spaces is added as well. The new qemu_plugin_write_register utilizes gdb_write_register, which is now declared in gdbstub.h for this purpose instead of being static. qemu_plugin_write_memory_vaddr utilizes cpu_memory_rw_debug much the same as the existing read_memory_vaddr function does. The read and write_hwaddr functions are the most different. These functions use address_space_rw, which works well in most cases. There is an important caveat that for writes, the page being written will be set dirty by the write operation. This dirty setting requires locking the page range, which can contend with an already held lock in page_collection_lock when called in a tb translate callback with a write to the instruction memory in the tb. The doc comments warn against doing this, and it's unlikely anyone would want to do this. I've also added two test plugins: one that implements a simple hypercall interface that guest code can use to communicate with the plugin in a structured way with a test to ensure that this hypercall works and writing virtual memory works. And one that implements a simple patch utility to patch memory at runtime. The test for the second plugin ensures the patch applies successfully to instruction memory, and can use both hw and vaddr methods. For v3, I've had a few comments from the last submission that I've addressed, and some that I haven't for one reason or another: - Enforce QEMU_PLUGIN_CB_ flags in register read/write operations: done! - Fix my commit messages and add long messages describing commits: done! - Un-expose AS internals: done! Functions operate on current vCPU, current AS. - Clean up use of current_cpu: done! - Make functions take a vcpu_idx: not done. May revisit but it allows footguns. Even for translation, seems best to not do this now. We can easily add _vcpu versions of these functions in the future if we change our minds! For v5, I've just updated the enforcement of the QEMU_PLUGIN_CB_ flags to just use immediate stores, which simplifies the implementation quite a lot and should be more efficient too. Thanks Pierrick for the suggestion! v6 is a formatting pass, I left some whitespace that needed removal, some license text was wrong, and so forth. v8 reverts a mistake I made extending the size of arrays of TCGHelperInfo structs, as I misunderstood their sizes. It preserves adding an explicit zero as the last entry for clarity, however. v9 fixes qemu_plugin_read_register to return -1 on parameter or flag state error instead of 0. In v10, I relaxed the restriction on when the register r/w functions can be called, allowing all them to be used from any callback where the CPU is not currently executing, with additional notes in the documentation for exceptions (atexit and flush, which do not operate on a specific CPU and in which current_cpu is not set). v11 makes the cb flags functions inline and fixes a typo where cpu was asserted but current_cpu was actually accessed. v12 removes the hypercalls plugin because the functions it tested are also tested by the patcher plugin, making it redundant. We'll circle back on a hypercalls API in the future as a part of the plugin API, not as a plugin itself. Rowan Hart (1): plugins: Add enforcement of QEMU_PLUGIN_CB flags in register R/W callbacks novafacing (6): gdbstub: Expose gdb_write_register function to consumers of gdbstub plugins: Add register write API plugins: Add memory virtual address write API plugins: Add memory hardware address read/write API plugins: Add patcher plugin and test plugins: Update plugin version and add notes accel/tcg/plugin-gen.c| 30 +++ gdbstub/gdbstub.c | 2 +- include/exec/gdbstub.h| 14 ++ include/hw/core/cpu.h | 1 + include/qemu/plugin.h | 15 ++ include/qemu/qemu-plugin.h| 176 ++-- plugins/api.c | 135 +++- plugins/core.c| 33 +++ tests/tcg/Makefile.target | 1 + tests/tcg/plugins/meson.build | 2 +- tests/tcg/plugins/patch.c | 241 ++ 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 14 files changed, 725 insertions(+), 23 deletions(-
[PATCH v14 7/8] plugins: Add patcher plugin and test
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. Reviewed-by: Pierrick Bouvier Signed-off-by: Rowan Hart --- tests/tcg/Makefile.target | 7 +- tests/tcg/plugins/meson.build | 2 +- tests/tcg/plugins/patch.c | 241 ++ tests/tcg/x86_64/Makefile.softmmu-target | 7 + tests/tcg/x86_64/system/patch-target.c| 22 ++ tests/tcg/x86_64/system/validate-patch.py | 39 6 files changed, 316 insertions(+), 2 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..af68f11664 100644 --- a/tests/tcg/Makefile.target +++ b/tests/tcg/Makefile.target @@ -151,7 +151,12 @@ ifeq ($(CONFIG_PLUGIN),y) PLUGIN_SRC=$(SRC_PATH)/tests/tcg/plugins PLUGIN_LIB=../plugins VPATH+=$(PLUGIN_LIB) -PLUGINS=$(patsubst %.c, lib%.so, $(notdir $(wildcard $(PLUGIN_SRC)/*.c))) +# Some plugins need to be disabled for all tests to avoid exponential explosion. +# For example, libpatch.so only needs to run against the arch-specific patch +# target test, so we explicitly run it in the arch-specific Makefile. +DISABLE_PLUGINS=libpatch.so +PLUGINS=$(filter-out $(DISABLE_PLUGINS), \ + $(patsubst %.c, lib%.so, $(notdir $(wildcard $(PLUGIN_SRC)/*.c # We need to ensure expand the run-plugin-TEST-with-PLUGIN # pre-requistes manually here as we can't use stems to handle it. We diff --git a/tests/tcg/plugins/meson.build b/tests/tcg/plugins/meson.build index 029342282a..61a007d9e7 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 00..450fc51c88 --- /dev/null +++ b/tests/tcg/plugins/patch.c @@ -0,0 +1,241 @@ +/* + * 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 +#include +#include + +QEMU_PLUGIN_EXPORT int qemu_plugin_version = QEMU_PLUGIN_VERSION; + +static bool use_hwaddr; +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) +{ +size_t len = strlen(str); + +if (len == 0 || len % 2 != 0) { +return NULL; +} + +GByteArray *bytes = g_byte_array_new(); +char byte[3] = {0}; +guint8 value = 0; + +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; +g_autoptr(GString) str = g_string_new(NULL); +g_string_printf(str, "patching: @0x%" +PRIx64 "\n", +addr); +qemu_plugin_outs(str->str); + +enum qemu_plugin_hwaddr_operation_result result = +qemu_plugin_write_memory_hwaddr(addr, patch_data); + + +if (result != QEMU_PLUGIN_HWADDR_OPERATION_OK) { +g_autoptr(GString) errmsg = g_string_new(NULL); +g_string_printf(errmsg, "Failed to write memory: %d\n", result); +qemu_plugin_outs(errmsg->str); +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) { +g_autoptr(GString) errmsg = g_string_new(NULL); +g_string_printf(errmsg, "Failed to read memory: %d\n", result); +qemu_plugin_outs(errmsg->str); +return; +} + +if (memcmp(patch
[PATCH v14 8/8] plugins: Update plugin version and add notes
This patch updates the plugin version to gate new APIs and adds notes describing what has been added. Reviewed-by: Pierrick Bouvier Signed-off-by: Rowan Hart --- include/qemu/qemu-plugin.h | 9 - 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/include/qemu/qemu-plugin.h b/include/qemu/qemu-plugin.h index 5eecdccc67..c450106af1 100644 --- a/include/qemu/qemu-plugin.h +++ b/include/qemu/qemu-plugin.h @@ -65,11 +65,18 @@ typedef uint64_t qemu_plugin_id_t; * * version 4: * - added qemu_plugin_read_memory_vaddr + * + * version 5: + * - added qemu_plugin_write_memory_vaddr + * - added qemu_plugin_read_memory_hwaddr + * - added qemu_plugin_write_memory_hwaddr + * - added qemu_plugin_write_register + * - added qemu_plugin_translate_vaddr */ extern QEMU_PLUGIN_EXPORT int qemu_plugin_version; -#define QEMU_PLUGIN_VERSION 4 +#define QEMU_PLUGIN_VERSION 5 /** * struct qemu_info_t - system information for plugins -- 2.49.0
[PATCH v14 2/8] plugins: Add register write API
This patch adds a function to the plugins API to allow plugins to write register contents. It also moves the qemu_plugin_read_register function so all the register-related functions are grouped together in the file. Reviewed-by: Pierrick Bouvier Signed-off-by: Rowan Hart --- include/qemu/qemu-plugin.h | 54 ++ plugins/api.c | 26 +- 2 files changed, 56 insertions(+), 24 deletions(-) diff --git a/include/qemu/qemu-plugin.h b/include/qemu/qemu-plugin.h index 3a850aa216..cfe1692ecb 100644 --- a/include/qemu/qemu-plugin.h +++ b/include/qemu/qemu-plugin.h @@ -871,7 +871,8 @@ struct qemu_plugin_register; /** * typedef qemu_plugin_reg_descriptor - register descriptions * - * @handle: opaque handle for retrieving value with qemu_plugin_read_register + * @handle: opaque handle for retrieving value with qemu_plugin_read_register or + * writing value with qemu_plugin_write_register * @name: register name * @feature: optional feature descriptor, can be NULL */ @@ -893,6 +894,41 @@ typedef struct { QEMU_PLUGIN_API GArray *qemu_plugin_get_registers(void); +/** + * qemu_plugin_read_register() - read register for current vCPU + * + * @handle: a @qemu_plugin_reg_handle handle + * @buf: A GByteArray for the data owned by the plugin + * + * This function is only available in a context that register read access is + * explicitly requested via the QEMU_PLUGIN_CB_R_REGS flag. + * + * Returns the size of the read register. The content of @buf is in target byte + * order. On failure returns -1. + */ +QEMU_PLUGIN_API +int qemu_plugin_read_register(struct qemu_plugin_register *handle, + GByteArray *buf); + +/** + * qemu_plugin_write_register() - write register for current vCPU + * + * @handle: a @qemu_plugin_reg_handle handle + * @buf: A GByteArray for the data owned by the plugin + * + * This function is only available in a context that register write access is + * explicitly requested via the QEMU_PLUGIN_CB_RW_REGS flag. + * + * The size of @buf must be at least the size of the requested register. + * Attempting to write a register with @buf smaller than the register size + * will result in a crash or other undesired behavior. + * + * Returns the number of bytes written. On failure returns 0. + */ +QEMU_PLUGIN_API +int qemu_plugin_write_register(struct qemu_plugin_register *handle, + GByteArray *buf); + /** * qemu_plugin_read_memory_vaddr() - read from memory using a virtual address * @@ -915,22 +951,6 @@ QEMU_PLUGIN_API bool qemu_plugin_read_memory_vaddr(uint64_t addr, GByteArray *data, size_t len); -/** - * qemu_plugin_read_register() - read register for current vCPU - * - * @handle: a @qemu_plugin_reg_handle handle - * @buf: A GByteArray for the data owned by the plugin - * - * This function is only available in a context that register read access is - * explicitly requested via the QEMU_PLUGIN_CB_R_REGS flag. - * - * Returns the size of the read register. The content of @buf is in target byte - * order. On failure returns -1. - */ -QEMU_PLUGIN_API -int qemu_plugin_read_register(struct qemu_plugin_register *handle, - GByteArray *buf); - /** * qemu_plugin_scoreboard_new() - alloc a new scoreboard * diff --git a/plugins/api.c b/plugins/api.c index 3c9d4832e9..6514f2c76a 100644 --- a/plugins/api.c +++ b/plugins/api.c @@ -433,6 +433,25 @@ GArray *qemu_plugin_get_registers(void) return create_register_handles(regs); } +int qemu_plugin_read_register(struct qemu_plugin_register *reg, GByteArray *buf) +{ +g_assert(current_cpu); + +return gdb_read_register(current_cpu, buf, GPOINTER_TO_INT(reg) - 1); +} + +int qemu_plugin_write_register(struct qemu_plugin_register *reg, + GByteArray *buf) +{ +g_assert(current_cpu); + +if (buf->len == 0 || qemu_plugin_get_cb_flags() != QEMU_PLUGIN_CB_RW_REGS) { +return -1; +} + +return gdb_write_register(current_cpu, buf->data, GPOINTER_TO_INT(reg) - 1); +} + bool qemu_plugin_read_memory_vaddr(uint64_t addr, GByteArray *data, size_t len) { g_assert(current_cpu); @@ -453,13 +472,6 @@ bool qemu_plugin_read_memory_vaddr(uint64_t addr, GByteArray *data, size_t len) return true; } -int qemu_plugin_read_register(struct qemu_plugin_register *reg, GByteArray *buf) -{ -g_assert(current_cpu); - -return gdb_read_register(current_cpu, buf, GPOINTER_TO_INT(reg) - 1); -} - struct qemu_plugin_scoreboard *qemu_plugin_scoreboard_new(size_t element_size) { return plugin_scoreboard_new(element_size); -- 2.49.0
[PATCH v14 4/8] plugins: Add memory virtual address write API
This patch adds functions to the plugins API to allow reading and writing memory via virtual addresses. These functions only permit doing so on the current CPU, because there is no way to ensure consistency if plugins are allowed to read or write to other CPUs that aren't currently in the context of the plugin. Reviewed-by: Pierrick Bouvier Signed-off-by: Rowan Hart --- include/qemu/qemu-plugin.h | 21 + plugins/api.c | 18 ++ 2 files changed, 39 insertions(+) diff --git a/include/qemu/qemu-plugin.h b/include/qemu/qemu-plugin.h index 9c9ebf6ce0..4167c46c2a 100644 --- a/include/qemu/qemu-plugin.h +++ b/include/qemu/qemu-plugin.h @@ -958,6 +958,27 @@ QEMU_PLUGIN_API bool qemu_plugin_read_memory_vaddr(uint64_t addr, GByteArray *data, size_t len); +/** + * qemu_plugin_write_memory_vaddr() - write to memory using a virtual address + * + * @addr: A virtual address to write to + * @data: A byte array containing the data to write + * + * The contents of @data will be written to memory starting at the virtual + * address @addr. + * + * This function does not guarantee consistency of writes, nor does it ensure + * that pending writes are flushed either before or after the write takes place, + * so callers should take care to only call this function in vCPU context (i.e. + * in callbacks) and avoid depending on the existence of data written using this + * function which may be overwritten afterward. + * + * Returns true on success and false on failure. + */ +QEMU_PLUGIN_API +bool qemu_plugin_write_memory_vaddr(uint64_t addr, + GByteArray *data); + /** * qemu_plugin_scoreboard_new() - alloc a new scoreboard * diff --git a/plugins/api.c b/plugins/api.c index 3f04399c26..1f64a9ea64 100644 --- a/plugins/api.c +++ b/plugins/api.c @@ -476,6 +476,24 @@ bool qemu_plugin_read_memory_vaddr(uint64_t addr, GByteArray *data, size_t len) 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; +} + struct qemu_plugin_scoreboard *qemu_plugin_scoreboard_new(size_t element_size) { return plugin_scoreboard_new(element_size); -- 2.49.0
[PATCH v14 6/8] tests/tcg: Remove copy-pasted notes and from i386 and add x86_64 system tests to tests
The x86_64-softmmu Makefile seems to have been copy-pasted from the i386 Makefile at some point in the past. Cleaning up a vestigial unused variable and removing some outdated comments. Signed-off-by: Rowan Hart --- tests/tcg/x86_64/Makefile.softmmu-target | 12 +--- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/tests/tcg/x86_64/Makefile.softmmu-target b/tests/tcg/x86_64/Makefile.softmmu-target index ef6bcb4dc7..d3e09708a5 100644 --- a/tests/tcg/x86_64/Makefile.softmmu-target +++ b/tests/tcg/x86_64/Makefile.softmmu-target @@ -1,13 +1,11 @@ # -# x86 system tests -# -# This currently builds only for i386. The common C code is built -# with standard compiler flags however so we can support both by -# adding additional boot files for x86_64. +# x86_64 system tests # -I386_SYSTEM_SRC=$(SRC_PATH)/tests/tcg/i386/system X64_SYSTEM_SRC=$(SRC_PATH)/tests/tcg/x86_64/system +X64_SYSTEM_TESTS=$(patsubst $(X64_SYSTEM_SRC)/%.c, %, $(wildcard $(X64_SYSTEM_SRC)/*.c)) + +VPATH+=$(X64_SYSTEM_SRC) # These objects provide the basic boot code and helper functions for all tests CRT_OBJS=boot.o @@ -18,7 +16,7 @@ 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+=$(MULTIARCH_TESTS) $(X64_SYSTEM_TESTS) EXTRA_RUNS+=$(MULTIARCH_RUNS) # building head blobs -- 2.49.0
[PATCH v14 5/8] plugins: Add memory hardware address read/write API
This patch adds functions to the plugins API to allow plugins to read and write memory via hardware addresses. The functions use the current address space of the current CPU in order to avoid exposing address space information to users. A later patch may want to add a function to permit a specified address space, for example to facilitate architecture-specific plugins that want to operate on them, for example reading ARM secure memory. Reviewed-by: Pierrick Bouvier Signed-off-by: Rowan Hart --- include/qemu/qemu-plugin.h | 93 plugins/api.c | 97 ++ 2 files changed, 190 insertions(+) diff --git a/include/qemu/qemu-plugin.h b/include/qemu/qemu-plugin.h index 4167c46c2a..5eecdccc67 100644 --- a/include/qemu/qemu-plugin.h +++ b/include/qemu/qemu-plugin.h @@ -979,6 +979,99 @@ QEMU_PLUGIN_API bool qemu_plugin_write_memory_vaddr(uint64_t addr, GByteArray *data); +/** + * enum qemu_plugin_hwaddr_operation_result - result of a memory operation + * + * @QEMU_PLUGIN_HWADDR_OPERATION_OK: hwaddr operation succeeded + * @QEMU_PLUGIN_HWADDR_OPERATION_ERROR: unexpected error occurred + * @QEMU_PLUGIN_HWADDR_OPERATION_DEVICE_ERROR: error in memory device + * @QEMU_PLUGIN_HWADDR_OPERATION_ACCESS_DENIED: permission error + * @QEMU_PLUGIN_HWADDR_OPERATION_INVALID_ADDRESS: address was invalid + * @QEMU_PLUGIN_HWADDR_OPERATION_INVALID_ADDRESS_SPACE: invalid address space + */ +enum qemu_plugin_hwaddr_operation_result { +QEMU_PLUGIN_HWADDR_OPERATION_OK, +QEMU_PLUGIN_HWADDR_OPERATION_ERROR, +QEMU_PLUGIN_HWADDR_OPERATION_DEVICE_ERROR, +QEMU_PLUGIN_HWADDR_OPERATION_ACCESS_DENIED, +QEMU_PLUGIN_HWADDR_OPERATION_INVALID_ADDRESS, +QEMU_PLUGIN_HWADDR_OPERATION_INVALID_ADDRESS_SPACE, +}; + +/** + * qemu_plugin_read_memory_hwaddr() - read from memory using a hardware address + * + * @addr: The physical address to read from + * @data: A byte array to store data into + * @len: The number of bytes to read, starting from @addr + * + * @len bytes of data is read from the current memory space for the current + * vCPU starting at @addr and stored into @data. If @data is not large enough to + * hold @len bytes, it will be expanded to the necessary size, reallocating if + * necessary. @len must be greater than 0. + * + * This function does not ensure writes are flushed prior to reading, so + * callers should take care when calling this function in plugin callbacks to + * avoid attempting to read data which may not yet be written and should use + * the memory callback API instead. + * + * This function is only valid for softmmu targets. + * + * Returns a qemu_plugin_hwaddr_operation_result indicating the result of the + * operation. + */ +QEMU_PLUGIN_API +enum qemu_plugin_hwaddr_operation_result +qemu_plugin_read_memory_hwaddr(uint64_t addr, GByteArray *data, size_t len); + +/** + * qemu_plugin_write_memory_hwaddr() - write to memory using a hardware address + * + * @addr: A physical address to write to + * @data: A byte array containing the data to write + * + * The contents of @data will be written to memory starting at the hardware + * address @addr in the current address space for the current vCPU. + * + * This function does not guarantee consistency of writes, nor does it ensure + * that pending writes are flushed either before or after the write takes place, + * so callers should take care when calling this function in plugin callbacks to + * avoid depending on the existence of data written using this function which + * may be overwritten afterward. In addition, this function requires that the + * pages containing the address are not locked. Practically, this means that you + * should not write instruction memory in a current translation block inside a + * callback registered with qemu_plugin_register_vcpu_tb_trans_cb. + * + * You can, for example, write instruction memory in a current translation block + * in a callback registered with qemu_plugin_register_vcpu_tb_exec_cb, although + * be aware that the write will not be flushed until after the translation block + * has finished executing. In general, this function should be used to write + * data memory or to patch code at a known address, not in a current translation + * block. + * + * This function is only valid for softmmu targets. + * + * Returns a qemu_plugin_hwaddr_operation_result indicating the result of the + * operation. + */ +QEMU_PLUGIN_API +enum qemu_plugin_hwaddr_operation_result +qemu_plugin_write_memory_hwaddr(uint64_t addr, GByteArray *data); + +/** + * qemu_plugin_translate_vaddr() - translate virtual address for current vCPU + * + * @vaddr: virtual address to translate + * @hwaddr: pointer to store the physical address + * + * This function is only valid in vCPU context (i.e. in callbacks) and is only + * valid for softmmu targets. + * + * Returns true on success and false on failure. + */ +QEMU_PLUGIN_API
Re: [PATCH v13 0/7] Add additional plugin API functions to read and write memory and registers
make[1]: *** No rule to make target 'patch-target', needed by 'run-plugin-patch-target-with-libpatch.so'. Stop. make: *** [/home/alex/lsrc/qemu.git/tests/Makefile.include:56: run-tcg-tests-x86_64-softmmu] Error 2 You need to ensure vpath is set, something like: Thanks for the note Alex. It turns out I'd been testing like `make -C build -j$(nproc) run-tcg-tests-x86_64-softmmu`, making this succeed when it shouldn't have. I updated it in v14. novafacing (6): gdbstub: Expose gdb_write_register function to consumers of gdbstub plugins: Add register write API plugins: Add memory virtual address write API plugins: Add memory hardware address read/write API plugins: Add patcher plugin and test plugins: Update plugin version and add notes Could you update the Author fields so the Author matches the s-o-b tags please and is consistent please. Absolutely, fixed this in v14 as well. One of my computers must have had my alias set instead of real name, apologies! -Rowan
[PATCH v14 3/8] plugins: Add enforcement of QEMU_PLUGIN_CB flags in register R/W callbacks
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. Reviewed-by: Pierrick Bouvier Signed-off-by: Rowan Hart --- accel/tcg/plugin-gen.c | 30 ++ include/hw/core/cpu.h | 1 + include/qemu/plugin.h | 15 +++ include/qemu/qemu-plugin.h | 19 +-- plugins/api.c | 4 plugins/core.c | 33 + 6 files changed, 96 insertions(+), 6 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 33296a1c08..162a56a5da 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..f355c7cb8a 100644 --- a/include/qemu/plugin.h +++ b/include/qemu/plugin.h @@ -209,6 +209,21 @@ 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); + +static inline void qemu_plugin_set_cb_flags(CPUState *cpu, +enum qemu_plugin_cb_flags flags) +{ +assert(cpu); +cpu->ne
[PATCH v14 1/8] gdbstub: Expose gdb_write_register function to consumers of gdbstub
This patch exposes the gdb_write_register function from gdbstub/gdbstub.c via the exec/gdbstub.h header file to support use in plugins to write register contents. Reviewed-by: Alex Bennée Reviewed-by: Julian Ganz Reviewed-by: Pierrick Bouvier Signed-off-by: Rowan Hart --- gdbstub/gdbstub.c | 2 +- include/exec/gdbstub.h | 14 ++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/gdbstub/gdbstub.c b/gdbstub/gdbstub.c index def0b7e877..dd5fb5667c 100644 --- a/gdbstub/gdbstub.c +++ b/gdbstub/gdbstub.c @@ -535,7 +535,7 @@ int gdb_read_register(CPUState *cpu, GByteArray *buf, int reg) return 0; } -static int gdb_write_register(CPUState *cpu, uint8_t *mem_buf, int reg) +int gdb_write_register(CPUState *cpu, uint8_t *mem_buf, int reg) { GDBRegisterState *r; diff --git a/include/exec/gdbstub.h b/include/exec/gdbstub.h index 0675b0b646..a16c0051ce 100644 --- a/include/exec/gdbstub.h +++ b/include/exec/gdbstub.h @@ -124,6 +124,20 @@ const GDBFeature *gdb_find_static_feature(const char *xmlname); */ int gdb_read_register(CPUState *cpu, GByteArray *buf, int reg); +/** + * gdb_write_register() - Write a register associated with a CPU. + * @cpu: The CPU associated with the register. + * @buf: The buffer that the register contents will be set to. + * @reg: The register's number returned by gdb_find_feature_register(). + * + * The size of @buf must be at least the size of the register being + * written. + * + * Return: The number of written bytes, or 0 if an error occurred (for + * example, an unknown register was provided). + */ +int gdb_write_register(CPUState *cpu, uint8_t *mem_buf, int reg); + /** * typedef GDBRegDesc - a register description from gdbstub */ -- 2.49.0
Re: [PATCH v12 5/7] plugins: Add memory hardware address read/write API
My main concern about the long list of caveats for writing memory is the user will almost certainly cause weird things to happen which will then be hard to debug. I can see the patcher example however it would be useful to know what other practical uses this interface provides. Of course! My main personal intent here is to facilitate introspection and manipulation of guest state for security analysis. Some examples of why the memory/register R/W primitives are necessary here include: Fuzzing: - Read registers and memory for tracing control flow, comparison operands, and profiled values (e.g. memcmp arguments) - Write memory to inject test cases into the guest (for me and other fuzzer developers, this is the biggest reason!) - Write registers to reset execution or skip over complex checks like checksums - Read and write memory, and read and write registers, to do basic snapshot/restore by tracking dirty pages and resetting them Virtual Machine Introspection (for malware analysis and reverse engineering): - Read memory and registers to find kernel, analyze kernel structures, and retrieve info like process lists, memory mappings - Read memory and registers to quickly trace malware execution in VM - Write memory and registers to test behavior under various conditions, like skipping into checks (motivating example: what happens if you skip into the kill switch statement for WannaCry) Runtime patching (as in the example): - Writing memory to patch critical legacy code in production often can no longer be built or patched via means other than by applying binary patches (this is a real problem for e.g. the government, to the point where DARPA ran a program https://www.darpa.mil/research/programs/assured-micropatching to work on it!) - Writing registers to skip over broken code, redirect to patch code, etc. Ultimately, the caveats boil down to "don't modify stuff that's touched by currently executing code". I personally don't think that's unreasonable (as long as it's in the doc-strings) because for any plugin that needs to write memory, ensuring the write consistency is probably the easiest problem to solve and people working in this space are used to having way worse and jankier workarounds. These plugin functions make life way easier for them. I have been in touch with 20+ people from various companies and projects (including Microsoft, where I work, as well as other big and small tech) all working on plugins that could be better if this feature existed, so there is definitely a user-base and appetite for it! The last cool use-case is that this moves us a long way towards cleaning up the large number of QEMU forks out there designed for RE and security testing like QEMU-Nyx, qemuafl, symqemu, and many more. Instead of maintaining forks of QEMU (many of these are based on 4.2.0 or older!) folks can just maintain a plugin, which lets them take advantage of updates and fixes without giant rebases. My goal is to kill these forks and have these projects write small, maintainable plugins instead, and the authors are on board :)
[PATCH v10 2/8] plugins: Add register write API
From: novafacing This patch adds a function to the plugins API to allow plugins to write register contents. It also moves the qemu_plugin_read_register function so all the register-related functions are grouped together in the file. Reviewed-by: Pierrick Bouvier Signed-off-by: Rowan Hart --- include/qemu/qemu-plugin.h | 54 ++ plugins/api.c | 26 +- 2 files changed, 56 insertions(+), 24 deletions(-) diff --git a/include/qemu/qemu-plugin.h b/include/qemu/qemu-plugin.h index 3a850aa216..cfe1692ecb 100644 --- a/include/qemu/qemu-plugin.h +++ b/include/qemu/qemu-plugin.h @@ -871,7 +871,8 @@ struct qemu_plugin_register; /** * typedef qemu_plugin_reg_descriptor - register descriptions * - * @handle: opaque handle for retrieving value with qemu_plugin_read_register + * @handle: opaque handle for retrieving value with qemu_plugin_read_register or + * writing value with qemu_plugin_write_register * @name: register name * @feature: optional feature descriptor, can be NULL */ @@ -893,6 +894,41 @@ typedef struct { QEMU_PLUGIN_API GArray *qemu_plugin_get_registers(void); +/** + * qemu_plugin_read_register() - read register for current vCPU + * + * @handle: a @qemu_plugin_reg_handle handle + * @buf: A GByteArray for the data owned by the plugin + * + * This function is only available in a context that register read access is + * explicitly requested via the QEMU_PLUGIN_CB_R_REGS flag. + * + * Returns the size of the read register. The content of @buf is in target byte + * order. On failure returns -1. + */ +QEMU_PLUGIN_API +int qemu_plugin_read_register(struct qemu_plugin_register *handle, + GByteArray *buf); + +/** + * qemu_plugin_write_register() - write register for current vCPU + * + * @handle: a @qemu_plugin_reg_handle handle + * @buf: A GByteArray for the data owned by the plugin + * + * This function is only available in a context that register write access is + * explicitly requested via the QEMU_PLUGIN_CB_RW_REGS flag. + * + * The size of @buf must be at least the size of the requested register. + * Attempting to write a register with @buf smaller than the register size + * will result in a crash or other undesired behavior. + * + * Returns the number of bytes written. On failure returns 0. + */ +QEMU_PLUGIN_API +int qemu_plugin_write_register(struct qemu_plugin_register *handle, + GByteArray *buf); + /** * qemu_plugin_read_memory_vaddr() - read from memory using a virtual address * @@ -915,22 +951,6 @@ QEMU_PLUGIN_API bool qemu_plugin_read_memory_vaddr(uint64_t addr, GByteArray *data, size_t len); -/** - * qemu_plugin_read_register() - read register for current vCPU - * - * @handle: a @qemu_plugin_reg_handle handle - * @buf: A GByteArray for the data owned by the plugin - * - * This function is only available in a context that register read access is - * explicitly requested via the QEMU_PLUGIN_CB_R_REGS flag. - * - * Returns the size of the read register. The content of @buf is in target byte - * order. On failure returns -1. - */ -QEMU_PLUGIN_API -int qemu_plugin_read_register(struct qemu_plugin_register *handle, - GByteArray *buf); - /** * qemu_plugin_scoreboard_new() - alloc a new scoreboard * diff --git a/plugins/api.c b/plugins/api.c index 3c9d4832e9..6514f2c76a 100644 --- a/plugins/api.c +++ b/plugins/api.c @@ -433,6 +433,25 @@ GArray *qemu_plugin_get_registers(void) return create_register_handles(regs); } +int qemu_plugin_read_register(struct qemu_plugin_register *reg, GByteArray *buf) +{ +g_assert(current_cpu); + +return gdb_read_register(current_cpu, buf, GPOINTER_TO_INT(reg) - 1); +} + +int qemu_plugin_write_register(struct qemu_plugin_register *reg, + GByteArray *buf) +{ +g_assert(current_cpu); + +if (buf->len == 0 || qemu_plugin_get_cb_flags() != QEMU_PLUGIN_CB_RW_REGS) { +return -1; +} + +return gdb_write_register(current_cpu, buf->data, GPOINTER_TO_INT(reg) - 1); +} + bool qemu_plugin_read_memory_vaddr(uint64_t addr, GByteArray *data, size_t len) { g_assert(current_cpu); @@ -453,13 +472,6 @@ bool qemu_plugin_read_memory_vaddr(uint64_t addr, GByteArray *data, size_t len) return true; } -int qemu_plugin_read_register(struct qemu_plugin_register *reg, GByteArray *buf) -{ -g_assert(current_cpu); - -return gdb_read_register(current_cpu, buf, GPOINTER_TO_INT(reg) - 1); -} - struct qemu_plugin_scoreboard *qemu_plugin_scoreboard_new(size_t element_size) { return plugin_scoreboard_new(element_size); -- 2.49.0
[PATCH v10 4/8] plugins: Add memory virtual address write API
From: novafacing This patch adds functions to the plugins API to allow reading and writing memory via virtual addresses. These functions only permit doing so on the current CPU, because there is no way to ensure consistency if plugins are allowed to read or write to other CPUs that aren't currently in the context of the plugin. Reviewed-by: Pierrick Bouvier Signed-off-by: Rowan Hart --- include/qemu/qemu-plugin.h | 21 + plugins/api.c | 18 ++ 2 files changed, 39 insertions(+) diff --git a/include/qemu/qemu-plugin.h b/include/qemu/qemu-plugin.h index 9c9ebf6ce0..4167c46c2a 100644 --- a/include/qemu/qemu-plugin.h +++ b/include/qemu/qemu-plugin.h @@ -958,6 +958,27 @@ QEMU_PLUGIN_API bool qemu_plugin_read_memory_vaddr(uint64_t addr, GByteArray *data, size_t len); +/** + * qemu_plugin_write_memory_vaddr() - write to memory using a virtual address + * + * @addr: A virtual address to write to + * @data: A byte array containing the data to write + * + * The contents of @data will be written to memory starting at the virtual + * address @addr. + * + * This function does not guarantee consistency of writes, nor does it ensure + * that pending writes are flushed either before or after the write takes place, + * so callers should take care to only call this function in vCPU context (i.e. + * in callbacks) and avoid depending on the existence of data written using this + * function which may be overwritten afterward. + * + * Returns true on success and false on failure. + */ +QEMU_PLUGIN_API +bool qemu_plugin_write_memory_vaddr(uint64_t addr, + GByteArray *data); + /** * qemu_plugin_scoreboard_new() - alloc a new scoreboard * diff --git a/plugins/api.c b/plugins/api.c index 3f04399c26..1f64a9ea64 100644 --- a/plugins/api.c +++ b/plugins/api.c @@ -476,6 +476,24 @@ bool qemu_plugin_read_memory_vaddr(uint64_t addr, GByteArray *data, size_t len) 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; +} + struct qemu_plugin_scoreboard *qemu_plugin_scoreboard_new(size_t element_size) { return plugin_scoreboard_new(element_size); -- 2.49.0