qemu-devel.nongnu.org archive mirror
 help / color / mirror / Atom feed
* [PATCH v2 0/3] Add additional plugin API functions to read and write memory and registers
@ 2024-12-06 10:26 Rowan Hart
  2024-12-06 10:26 ` [PATCH v2 1/3] Expose gdb_write_register function to consumers of gdbstub Rowan Hart
                   ` (3 more replies)
  0 siblings, 4 replies; 14+ messages in thread
From: Rowan Hart @ 2024-12-06 10:26 UTC (permalink / raw)
  To: qemu-devel
  Cc: Richard Henderson, Eduardo Habkost, Alex Bennée,
	Alexandre Iooss, Pierrick Bouvier, Mahmoud Mandour, Paolo Bonzini,
	Philippe Mathieu-Daudé, Rowan Hart

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



^ permalink raw reply	[flat|nested] 14+ messages in thread

* [PATCH v2 1/3] Expose gdb_write_register function to consumers of gdbstub
  2024-12-06 10:26 [PATCH v2 0/3] Add additional plugin API functions to read and write memory and registers Rowan Hart
@ 2024-12-06 10:26 ` Rowan Hart
  2025-01-09 12:03   ` Alex Bennée
  2024-12-06 10:26 ` [PATCH v2 2/3] Add plugin API functions for register R/W, hwaddr R/W, vaddr W Rowan Hart
                   ` (2 subsequent siblings)
  3 siblings, 1 reply; 14+ messages in thread
From: Rowan Hart @ 2024-12-06 10:26 UTC (permalink / raw)
  To: qemu-devel
  Cc: Richard Henderson, Eduardo Habkost, Alex Bennée,
	Alexandre Iooss, Pierrick Bouvier, Mahmoud Mandour, Paolo Bonzini,
	Philippe Mathieu-Daudé, novafacing

From: novafacing <rowanbhart@gmail.com>

---
 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



^ permalink raw reply related	[flat|nested] 14+ messages in thread

* [PATCH v2 2/3] Add plugin API functions for register R/W, hwaddr R/W, vaddr W
  2024-12-06 10:26 [PATCH v2 0/3] Add additional plugin API functions to read and write memory and registers Rowan Hart
  2024-12-06 10:26 ` [PATCH v2 1/3] Expose gdb_write_register function to consumers of gdbstub Rowan Hart
@ 2024-12-06 10:26 ` Rowan Hart
  2025-01-09 12:22   ` Alex Bennée
  2024-12-06 10:26 ` [PATCH v2 3/3] Add inject plugin and x86_64 target for the inject plugin Rowan Hart
  2024-12-06 19:43 ` [PATCH v2 0/3] Add additional plugin API functions to read and write memory and registers Pierrick Bouvier
  3 siblings, 1 reply; 14+ messages in thread
From: Rowan Hart @ 2024-12-06 10:26 UTC (permalink / raw)
  To: qemu-devel
  Cc: Richard Henderson, Eduardo Habkost, Alex Bennée,
	Alexandre Iooss, Pierrick Bouvier, Mahmoud Mandour, Paolo Bonzini,
	Philippe Mathieu-Daudé, novafacing

From: novafacing <rowanbhart@gmail.com>

---
 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 softmmu targets.
+ *
+ * Returns true on success and false on failure.
+ */
+QEMU_PLUGIN_API
+bool qemu_plugin_read_memory_hwaddr(uint64_t addr,
+                                   GByteArray *data, size_t len);
+
+/**
+ * qemu_plugin_write_memory_vaddr() - write to memory using a hardware 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 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.
+ *
+ * This function is only valid for softmmu targets.
+ *
+ * Returns true on success and false on failure.
+ */
+QEMU_PLUGIN_API
+bool qemu_plugin_write_memory_hwaddr(uint64_t addr,
+                                   GByteArray *data);
 
 /**
  * qemu_plugin_scoreboard_new() - alloc a new scoreboard
diff --git a/plugins/api.c b/plugins/api.c
index 24ea64e2de..4a84cf4dfe 100644
--- a/plugins/api.c
+++ b/plugins/api.c
@@ -560,6 +560,24 @@ 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(vaddr addr, GByteArray *data, size_t len)
 {
     g_assert(current_cpu);
@@ -580,13 +598,57 @@ bool qemu_plugin_read_memory_vaddr(vaddr addr, GByteArray *data, size_t len)
     return true;
 }
 
-int qemu_plugin_read_register(struct qemu_plugin_register *reg, GByteArray *buf)
+bool qemu_plugin_write_memory_vaddr(vaddr addr, GByteArray *data)
 {
     g_assert(current_cpu);
 
-    return gdb_read_register(current_cpu, buf, GPOINTER_TO_INT(reg) - 1);
+    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;
+}
+
+bool qemu_plugin_read_memory_hwaddr(hwaddr addr, GByteArray *data, size_t len)
+{
+#ifdef CONFIG_SOFTMMU
+    if (len == 0) {
+        return false;
+    }
+
+    g_byte_array_set_size(data, len);
+
+    cpu_physical_memory_rw(addr, data->data, data->len, false);
+
+    return true;
+#else
+    return false;
+#endif
 }
 
+bool qemu_plugin_write_memory_hwaddr(hwaddr addr, GByteArray *data)
+{
+#ifdef CONFIG_SOFTMMU
+    if (data->len == 0) {
+        return false;
+    }
+
+    cpu_physical_memory_rw(addr, data->data, data->len, true);
+
+    return true;
+#else
+    return false;
+#endif
+}
+
+
 struct qemu_plugin_scoreboard *qemu_plugin_scoreboard_new(size_t element_size)
 {
     return plugin_scoreboard_new(element_size);
-- 
2.46.1



^ permalink raw reply related	[flat|nested] 14+ messages in thread

* [PATCH v2 3/3] Add inject plugin and x86_64 target for the inject plugin
  2024-12-06 10:26 [PATCH v2 0/3] Add additional plugin API functions to read and write memory and registers Rowan Hart
  2024-12-06 10:26 ` [PATCH v2 1/3] Expose gdb_write_register function to consumers of gdbstub Rowan Hart
  2024-12-06 10:26 ` [PATCH v2 2/3] Add plugin API functions for register R/W, hwaddr R/W, vaddr W Rowan Hart
@ 2024-12-06 10:26 ` Rowan Hart
  2024-12-06 19:57   ` Pierrick Bouvier
  2024-12-06 19:43 ` [PATCH v2 0/3] Add additional plugin API functions to read and write memory and registers Pierrick Bouvier
  3 siblings, 1 reply; 14+ messages in thread
From: Rowan Hart @ 2024-12-06 10:26 UTC (permalink / raw)
  To: qemu-devel
  Cc: Richard Henderson, Eduardo Habkost, Alex Bennée,
	Alexandre Iooss, Pierrick Bouvier, Mahmoud Mandour, Paolo Bonzini,
	Philippe Mathieu-Daudé, novafacing

From: novafacing <rowanbhart@gmail.com>

---
 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 0000000000..9edc2cd34e
--- /dev/null
+++ b/tests/tcg/plugins/inject.c
@@ -0,0 +1,206 @@
+/*
+ * Copyright (C) 2024, Rowan Hart <rowanbhart@gmail.com>
+ *
+ * License: GNU GPL, version 2 or later.
+ *   See the COPYING file in the top-level directory.
+ */
+#include "glib.h"
+#include <assert.h>
+#include <inttypes.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <qemu-plugin.h>
+
+/*
+ * 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 special meaning for this
+ * plugin.
+ */
+static void hypercall(unsigned int vcpu_index, void *userdata)
+{
+    uint64_t num = 0, arg0 = 0, arg1 = 0;
+    GByteArray *buf = g_byte_array_new();
+    qemu_plugin_read_register(get_register(hypercall_spec->num_reg), buf);
+    num = byte_array_to_uint64(buf);
+
+    g_byte_array_set_size(buf, 0);
+    qemu_plugin_read_register(get_register(hypercall_spec->arg0_reg), buf);
+    arg0 = byte_array_to_uint64(buf);
+
+    g_byte_array_set_size(buf, 0);
+    qemu_plugin_read_register(get_register(hypercall_spec->arg1_reg), buf);
+    arg1 = byte_array_to_uint64(buf);
+
+    switch (num) {
+    /*
+     * The write hypercall (#0x13371337) tells the plugin to write random bytes
+     * of a given size into the memory of the emulated system at a particular
+     * vaddr
+     */
+    case 0x13371337: {
+        GByteArray *data = g_byte_array_new();
+        g_byte_array_set_size(data, arg1);
+        for (uint64_t i = 0; i < arg1; i++) {
+            data->data[i] = (uint8_t)g_random_int();
+        }
+        qemu_plugin_write_memory_vaddr(arg0, data);
+        break;
+    }
+    default:
+        break;
+    }
+
+    g_byte_array_free(buf, TRUE);
+}
+
+/*
+ * Callback on translation of a translation block.
+ */
+static void vcpu_tb_trans(qemu_plugin_id_t id, struct qemu_plugin_tb *tb)
+{
+    for (size_t i = 0; i < qemu_plugin_tb_n_insns(tb); i++) {
+        struct qemu_plugin_insn *insn = qemu_plugin_tb_get_insn(tb, i);
+        GByteArray *insn_data = g_byte_array_new();
+        size_t insn_len = qemu_plugin_insn_size(insn);
+        g_byte_array_set_size(insn_data, insn_len);
+        qemu_plugin_insn_data(insn, insn_data->data, insn_data->len);
+        if (!memcmp(insn_data->data, hypercall_spec->hypercall, insn_data->len)) {
+            qemu_plugin_register_vcpu_insn_exec_cb(insn, hypercall,
+                                                   QEMU_PLUGIN_CB_R_REGS, NULL);
+        }
+        g_byte_array_free(insn_data, true);
+    }
+}
+
+
+/*
+ * Called when the plugin is installed
+ */
+QEMU_PLUGIN_EXPORT int qemu_plugin_install(qemu_plugin_id_t id,
+                                           const qemu_info_t *info, int argc,
+                                           char **argv)
+{
+    hypercall_spec = &hypercall_specs[0];
+    while (hypercall_spec->name != NULL) {
+        if (!strcmp(hypercall_spec->name, info->target_name)) {
+            break;
+        }
+        hypercall_spec++;
+    }
+
+    if (hypercall_spec->name == NULL) {
+        qemu_plugin_outs("Error: no hypercall spec.");
+        return -1;
+    }
+
+    qemu_plugin_register_vcpu_tb_trans_cb(id, vcpu_tb_trans);
+
+    return 0;
+}
diff --git a/tests/tcg/plugins/meson.build b/tests/tcg/plugins/meson.build
index f847849b1b..96782416d3 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', 'syscall']
+  foreach i : ['bb', 'empty', 'inline', 'insn', 'mem', 'syscall', 'inject']
     if host_os == 'windows'
       t += shared_module(i, files(i + '.c') + '../../../contrib/plugins/win32_linker.c',
                         include_directories: '../../../include/qemu',
diff --git a/tests/tcg/x86_64/Makefile.target b/tests/tcg/x86_64/Makefile.target
index d6dff559c7..7c8e21636d 100644
--- a/tests/tcg/x86_64/Makefile.target
+++ b/tests/tcg/x86_64/Makefile.target
@@ -18,6 +18,7 @@ X86_64_TESTS += adox
 X86_64_TESTS += test-1648
 X86_64_TESTS += test-2175
 X86_64_TESTS += cross-modifying-code
+X86_64_TESTS += inject-target
 TESTS=$(MULTIARCH_TESTS) $(X86_64_TESTS) test-x86_64
 else
 TESTS=$(MULTIARCH_TESTS)
diff --git a/tests/tcg/x86_64/inject-target.c b/tests/tcg/x86_64/inject-target.c
new file mode 100644
index 0000000000..c886e5ab8b
--- /dev/null
+++ b/tests/tcg/x86_64/inject-target.c
@@ -0,0 +1,27 @@
+#include <stddef.h>
+#include <stdint.h>
+#include <stdio.h>
+
+#define hypercall(num, arg0, arg1)                                \
+    unsigned int _a __attribute__((unused)) = 0;                  \
+    unsigned int _b __attribute__((unused)) = 0;                  \
+    unsigned int _c __attribute__((unused)) = 0;                  \
+    unsigned int _d __attribute__((unused)) = 0;                  \
+    __asm__ __volatile__("cpuid\n\t"                              \
+                         : "=a"(_a), "=b"(_b), "=c"(_c), "=d"(_d) \
+                         : "a"(num), "D"(arg0), "S"(arg1));
+
+int main(void)
+{
+    uint16_t value;
+
+    for (size_t i = 0; i < 1000000; i++) {
+        hypercall(0x13371337, &value, sizeof(value));
+        if (value == 0x1337) {
+            printf("Victory!\n");
+            return 0;
+        }
+    }
+    return 1;
+}
+
-- 
2.46.1



^ permalink raw reply related	[flat|nested] 14+ messages in thread

* Re: [PATCH v2 0/3] Add additional plugin API functions to read and write memory and registers
  2024-12-06 10:26 [PATCH v2 0/3] Add additional plugin API functions to read and write memory and registers Rowan Hart
                   ` (2 preceding siblings ...)
  2024-12-06 10:26 ` [PATCH v2 3/3] Add inject plugin and x86_64 target for the inject plugin Rowan Hart
@ 2024-12-06 19:43 ` Pierrick Bouvier
  2024-12-07  0:57   ` Rowan Hart
  3 siblings, 1 reply; 14+ messages in thread
From: Pierrick Bouvier @ 2024-12-06 19:43 UTC (permalink / raw)
  To: Rowan Hart, qemu-devel
  Cc: Richard Henderson, Eduardo Habkost, Alex Bennée,
	Alexandre Iooss, Mahmoud Mandour, Paolo Bonzini,
	Philippe Mathieu-Daudé, Peter Maydell

Hi Rowan,

thanks for this submission!

On 12/6/24 02:26, Rowan Hart wrote:
> 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.
> 

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.

> 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.
> 

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.

> 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
> 

Regards,
Pierrick


^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [PATCH v2 3/3] Add inject plugin and x86_64 target for the inject plugin
  2024-12-06 10:26 ` [PATCH v2 3/3] Add inject plugin and x86_64 target for the inject plugin Rowan Hart
@ 2024-12-06 19:57   ` Pierrick Bouvier
  2024-12-07  1:02     ` Rowan Hart
  0 siblings, 1 reply; 14+ messages in thread
From: Pierrick Bouvier @ 2024-12-06 19:57 UTC (permalink / raw)
  To: Rowan Hart, qemu-devel
  Cc: Richard Henderson, Eduardo Habkost, Alex Bennée,
	Alexandre Iooss, Mahmoud Mandour, Paolo Bonzini,
	Philippe Mathieu-Daudé

On 12/6/24 02:26, Rowan Hart wrote:
> From: novafacing <rowanbhart@gmail.com>
> 
> ---
>   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 0000000000..9edc2cd34e
> --- /dev/null
> +++ b/tests/tcg/plugins/inject.c

Could we find a better name?

> @@ -0,0 +1,206 @@
> +/*
> + * Copyright (C) 2024, Rowan Hart <rowanbhart@gmail.com>
> + *
> + * 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.

> +#include "glib.h"
> +#include <assert.h>
> +#include <inttypes.h>
> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <string.h>
> +#include <unistd.h>
> +
> +#include <qemu-plugin.h>
> +
> +/*
> + * 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 special meaning for this
> + * plugin.
> + */
> +static void hypercall(unsigned int vcpu_index, void *userdata)
> +{
> +    uint64_t num = 0, arg0 = 0, arg1 = 0;
> +    GByteArray *buf = g_byte_array_new();
> +    qemu_plugin_read_register(get_register(hypercall_spec->num_reg), buf);
> +    num = byte_array_to_uint64(buf);
> +
> +    g_byte_array_set_size(buf, 0);
> +    qemu_plugin_read_register(get_register(hypercall_spec->arg0_reg), buf);
> +    arg0 = byte_array_to_uint64(buf);
> +
> +    g_byte_array_set_size(buf, 0);
> +    qemu_plugin_read_register(get_register(hypercall_spec->arg1_reg), buf);
> +    arg1 = byte_array_to_uint64(buf);
> +
> +    switch (num) {
> +    /*
> +     * The write hypercall (#0x13371337) tells the plugin to write random bytes
> +     * of a given size into the memory of the emulated system at a particular
> +     * vaddr
> +     */

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.

> +    case 0x13371337: {
> +        GByteArray *data = g_byte_array_new();
> +        g_byte_array_set_size(data, arg1);
> +        for (uint64_t i = 0; i < arg1; i++) {
> +            data->data[i] = (uint8_t)g_random_int();
> +        }
> +        qemu_plugin_write_memory_vaddr(arg0, data);
> +        break;
> +    }
> +    default:
> +        break;
> +    }
> +
> +    g_byte_array_free(buf, TRUE);
> +}
> +
> +/*
> + * Callback on translation of a translation block.
> + */
> +static void vcpu_tb_trans(qemu_plugin_id_t id, struct qemu_plugin_tb *tb)
> +{
> +    for (size_t i = 0; i < qemu_plugin_tb_n_insns(tb); i++) {
> +        struct qemu_plugin_insn *insn = qemu_plugin_tb_get_insn(tb, i);
> +        GByteArray *insn_data = g_byte_array_new();
> +        size_t insn_len = qemu_plugin_insn_size(insn);
> +        g_byte_array_set_size(insn_data, insn_len);
> +        qemu_plugin_insn_data(insn, insn_data->data, insn_data->len);
> +        if (!memcmp(insn_data->data, hypercall_spec->hypercall, insn_data->len)) {
> +            qemu_plugin_register_vcpu_insn_exec_cb(insn, hypercall,
> +                                                   QEMU_PLUGIN_CB_R_REGS, NULL);
> +        }
> +        g_byte_array_free(insn_data, true);
> +    }
> +}
> +
> +
> +/*
> + * Called when the plugin is installed
> + */
> +QEMU_PLUGIN_EXPORT int qemu_plugin_install(qemu_plugin_id_t id,
> +                                           const qemu_info_t *info, int argc,
> +                                           char **argv)
> +{
> +    hypercall_spec = &hypercall_specs[0];
> +    while (hypercall_spec->name != NULL) {
> +        if (!strcmp(hypercall_spec->name, info->target_name)) {
> +            break;
> +        }
> +        hypercall_spec++;
> +    }
> +
> +    if (hypercall_spec->name == NULL) {
> +        qemu_plugin_outs("Error: no hypercall spec.");
> +        return -1;
> +    }
> +
> +    qemu_plugin_register_vcpu_tb_trans_cb(id, vcpu_tb_trans);
> +
> +    return 0;
> +}
> diff --git a/tests/tcg/plugins/meson.build b/tests/tcg/plugins/meson.build
> index f847849b1b..96782416d3 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', 'syscall']
> +  foreach i : ['bb', 'empty', 'inline', 'insn', 'mem', 'syscall', 'inject']
>       if host_os == 'windows'
>         t += shared_module(i, files(i + '.c') + '../../../contrib/plugins/win32_linker.c',
>                           include_directories: '../../../include/qemu',
> diff --git a/tests/tcg/x86_64/Makefile.target b/tests/tcg/x86_64/Makefile.target
> index d6dff559c7..7c8e21636d 100644
> --- a/tests/tcg/x86_64/Makefile.target
> +++ b/tests/tcg/x86_64/Makefile.target
> @@ -18,6 +18,7 @@ X86_64_TESTS += adox
>   X86_64_TESTS += test-1648
>   X86_64_TESTS += test-2175
>   X86_64_TESTS += cross-modifying-code
> +X86_64_TESTS += inject-target
>   TESTS=$(MULTIARCH_TESTS) $(X86_64_TESTS) test-x86_64
>   else
>   TESTS=$(MULTIARCH_TESTS)
> diff --git a/tests/tcg/x86_64/inject-target.c b/tests/tcg/x86_64/inject-target.c
> new file mode 100644
> index 0000000000..c886e5ab8b
> --- /dev/null
> +++ b/tests/tcg/x86_64/inject-target.c
> @@ -0,0 +1,27 @@
> +#include <stddef.h>
> +#include <stdint.h>
> +#include <stdio.h>
> +
> +#define hypercall(num, arg0, arg1)                                \
> +    unsigned int _a __attribute__((unused)) = 0;                  \
> +    unsigned int _b __attribute__((unused)) = 0;                  \
> +    unsigned int _c __attribute__((unused)) = 0;                  \
> +    unsigned int _d __attribute__((unused)) = 0;                  \
> +    __asm__ __volatile__("cpuid\n\t"                              \
> +                         : "=a"(_a), "=b"(_b), "=c"(_c), "=d"(_d) \
> +                         : "a"(num), "D"(arg0), "S"(arg1));
> +
> +int main(void)
> +{
> +    uint16_t value;
> +
> +    for (size_t i = 0; i < 1000000; i++) {
> +        hypercall(0x13371337, &value, sizeof(value));
> +        if (value == 0x1337) {
> +            printf("Victory!\n");
> +            return 0;
> +        }
> +    }
> +    return 1;
> +}
> +



^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [PATCH v2 0/3] Add additional plugin API functions to read and write memory and registers
  2024-12-06 19:43 ` [PATCH v2 0/3] Add additional plugin API functions to read and write memory and registers Pierrick Bouvier
@ 2024-12-07  0:57   ` Rowan Hart
  2024-12-09 18:45     ` Pierrick Bouvier
  0 siblings, 1 reply; 14+ messages in thread
From: Rowan Hart @ 2024-12-07  0:57 UTC (permalink / raw)
  To: Pierrick Bouvier, qemu-devel
  Cc: Richard Henderson, Eduardo Habkost, Alex Bennée,
	Alexandre Iooss, Mahmoud Mandour, Paolo Bonzini,
	Philippe Mathieu-Daudé, Peter Maydell

> 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


^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [PATCH v2 3/3] Add inject plugin and x86_64 target for the inject plugin
  2024-12-06 19:57   ` Pierrick Bouvier
@ 2024-12-07  1:02     ` Rowan Hart
  2024-12-09 18:38       ` Pierrick Bouvier
  0 siblings, 1 reply; 14+ messages in thread
From: Rowan Hart @ 2024-12-07  1:02 UTC (permalink / raw)
  To: Pierrick Bouvier, qemu-devel
  Cc: Richard Henderson, Eduardo Habkost, Alex Bennée,
	Alexandre Iooss, Mahmoud Mandour, Paolo Bonzini,
	Philippe Mathieu-Daudé

>> +++ 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 <rowanbhart@gmail.com>
>> + *
>> + * 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


^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [PATCH v2 3/3] Add inject plugin and x86_64 target for the inject plugin
  2024-12-07  1:02     ` Rowan Hart
@ 2024-12-09 18:38       ` Pierrick Bouvier
  0 siblings, 0 replies; 14+ messages in thread
From: Pierrick Bouvier @ 2024-12-09 18:38 UTC (permalink / raw)
  To: Rowan Hart, qemu-devel
  Cc: Richard Henderson, Eduardo Habkost, Alex Bennée,
	Alexandre Iooss, Mahmoud Mandour, Paolo Bonzini,
	Philippe Mathieu-Daudé

On 12/6/24 17:02, Rowan Hart wrote:
>>> +++ 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.
> 

Sounds good.

>>> @@ -0,0 +1,206 @@
>>> +/*
>>> + * Copyright (C) 2024, Rowan Hart <rowanbhart@gmail.com>
>>> + *
>>> + * 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
> 

Looks like a good model to reuse if we want to implement something similar.

> -Rowan



^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [PATCH v2 0/3] Add additional plugin API functions to read and write memory and registers
  2024-12-07  0:57   ` Rowan Hart
@ 2024-12-09 18:45     ` Pierrick Bouvier
  2024-12-10 11:38       ` Alex Bennée
  0 siblings, 1 reply; 14+ messages in thread
From: Pierrick Bouvier @ 2024-12-09 18:45 UTC (permalink / raw)
  To: Rowan Hart, qemu-devel
  Cc: Richard Henderson, Eduardo Habkost, Alex Bennée,
	Alexandre Iooss, Mahmoud Mandour, Paolo Bonzini,
	Philippe Mathieu-Daudé, Peter Maydell

On 12/6/24 16:57, Rowan Hart wrote:
>> 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.
> 

In the end, if we implement something to modify running code, we should 
make sure it works as expected (flushing the related tb). I am not sure 
about the current status, and all the changes that would be needed, but 
it's something we should discuss before implementing.

More globally, let's wait to hear feedback from maintainers to see if 
they are open to the idea or not. A "hard" no would end it there.

>> 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.
> 

We have been discussing something like that for system mode recently, so 
it would definitely be useful.

IMHO, it's open for anyone to contribute this, the plugins area is not a 
private garden where only chosen ones can change things. Just be 
prepared for change requests, and multiple versions before the final one.

Same on this one, we'll see if maintainers are ok with the idea.

> Glad to hear you're for this idea!
> 
> -Rowan

Thanks,
Pierrick


^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [PATCH v2 0/3] Add additional plugin API functions to read and write memory and registers
  2024-12-09 18:45     ` Pierrick Bouvier
@ 2024-12-10 11:38       ` Alex Bennée
  2024-12-10 18:40         ` Pierrick Bouvier
  0 siblings, 1 reply; 14+ messages in thread
From: Alex Bennée @ 2024-12-10 11:38 UTC (permalink / raw)
  To: Pierrick Bouvier
  Cc: Rowan Hart, qemu-devel, Richard Henderson, Eduardo Habkost,
	Alexandre Iooss, Mahmoud Mandour, Paolo Bonzini,
	Philippe Mathieu-Daudé, Peter Maydell

Pierrick Bouvier <pierrick.bouvier@linaro.org> writes:

> On 12/6/24 16:57, Rowan Hart wrote:
>>> 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 think currently that makes quite a lot of demands on the translator to
make sure things are kept consistent.

We have been talking about maybe enabling hypercalls of some sort to
allow for hooking explicit function boundaries in linux-user. A natural
extension of that would be for host library bypass functions. I'm unsure
of how that would apply in system emulation mode though where things are
handled on a lot more granular level.

>> 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.
>> 
>
> In the end, if we implement something to modify running code, we
> should make sure it works as expected (flushing the related tb). I am
> not sure about the current status, and all the changes that would be
> needed, but it's something we should discuss before implementing.

If the right access helpers are used we eventually end up in cputlb and
the code modification detection code will kick in. But that detection
mechanism relies on the guest controlled page tables marking executable
code and honouring rw permissions. If plugins don't honour those
permissions you'll become unstuck quite quickly.

> More globally, let's wait to hear feedback from maintainers to see if
> they are open to the idea or not. A "hard" no would end it there.

It's not a hard no - but I think any such patching ability would need a
quite a bit of thought to make sure edge cases are covered. However I do
expect there will be downstream forks that go further than the upstream
is currently comfortable with and if the code is structured in the right
way we can minimise the pain of re-basing.

>>> 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.
>> 
>
> We have been discussing something like that for system mode recently,
> so it would definitely be useful.
>
> IMHO, it's open for anyone to contribute this, the plugins area is not
> a private garden where only chosen ones can change things. Just be
> prepared for change requests, and multiple versions before the final
> one.
>
> Same on this one, we'll see if maintainers are ok with the idea.
>
>> Glad to hear you're for this idea!
>> -Rowan
>
> Thanks,
> Pierrick

-- 
Alex Bennée
Virtualisation Tech Lead @ Linaro


^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [PATCH v2 0/3] Add additional plugin API functions to read and write memory and registers
  2024-12-10 11:38       ` Alex Bennée
@ 2024-12-10 18:40         ` Pierrick Bouvier
  0 siblings, 0 replies; 14+ messages in thread
From: Pierrick Bouvier @ 2024-12-10 18:40 UTC (permalink / raw)
  To: Alex Bennée
  Cc: Rowan Hart, qemu-devel, Richard Henderson, Eduardo Habkost,
	Alexandre Iooss, Mahmoud Mandour, Paolo Bonzini,
	Philippe Mathieu-Daudé, Peter Maydell

On 12/10/24 03:38, Alex Bennée wrote:
> Pierrick Bouvier <pierrick.bouvier@linaro.org> writes:
> 
>> On 12/6/24 16:57, Rowan Hart wrote:
>>>> 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 think currently that makes quite a lot of demands on the translator to
> make sure things are kept consistent.
> 
> We have been talking about maybe enabling hypercalls of some sort to
> allow for hooking explicit function boundaries in linux-user. A natural
> extension of that would be for host library bypass functions. I'm unsure
> of how that would apply in system emulation mode though where things are
> handled on a lot more granular level.
>

If we are talking about replacing library function calls with host 
variants, I'm not sure it's connected to the hypercalls we are talking 
about here.

>>> 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.
>>>
>>
>> In the end, if we implement something to modify running code, we
>> should make sure it works as expected (flushing the related tb). I am
>> not sure about the current status, and all the changes that would be
>> needed, but it's something we should discuss before implementing.
> 
> If the right access helpers are used we eventually end up in cputlb and
> the code modification detection code will kick in. But that detection
> mechanism relies on the guest controlled page tables marking executable
> code and honouring rw permissions. If plugins don't honour those
> permissions you'll become unstuck quite quickly.
> 
>> More globally, let's wait to hear feedback from maintainers to see if
>> they are open to the idea or not. A "hard" no would end it there.
> 
> It's not a hard no - but I think any such patching ability would need a
> quite a bit of thought to make sure edge cases are covered. However I do
> expect there will be downstream forks that go further than the upstream
> is currently comfortable with and if the code is structured in the right
> way we can minimise the pain of re-basing.
> 

In a more straightforward way, does it mean you are open to change state 
of execution through plugins?

Out of the technical aspect and getting all the corner cases right, we 
should first discuss if we want to go there, or if we decide it does not 
have its place upstream, and should belong to downstream forks instead.

>>>> 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.
>>>
>>
>> We have been discussing something like that for system mode recently,
>> so it would definitely be useful.
>>
>> IMHO, it's open for anyone to contribute this, the plugins area is not
>> a private garden where only chosen ones can change things. Just be
>> prepared for change requests, and multiple versions before the final
>> one.
>>
>> Same on this one, we'll see if maintainers are ok with the idea.
>>
>>> Glad to hear you're for this idea!
>>> -Rowan
>>
>> Thanks,
>> Pierrick
> 


^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [PATCH v2 1/3] Expose gdb_write_register function to consumers of gdbstub
  2024-12-06 10:26 ` [PATCH v2 1/3] Expose gdb_write_register function to consumers of gdbstub Rowan Hart
@ 2025-01-09 12:03   ` Alex Bennée
  0 siblings, 0 replies; 14+ messages in thread
From: Alex Bennée @ 2025-01-09 12:03 UTC (permalink / raw)
  To: Rowan Hart
  Cc: qemu-devel, Richard Henderson, Eduardo Habkost, Alexandre Iooss,
	Pierrick Bouvier, Mahmoud Mandour, Paolo Bonzini,
	Philippe Mathieu-Daudé

Rowan Hart <rowanbhart@gmail.com> writes:

> From: novafacing <rowanbhart@gmail.com>

You need to include Signed-of-by tags for anything to get accepted.

>
> ---
>  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
>   */

Reviewed-by: Alex Bennée <alex.bennee@linaro.org>

-- 
Alex Bennée
Virtualisation Tech Lead @ Linaro


^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [PATCH v2 2/3] Add plugin API functions for register R/W, hwaddr R/W, vaddr W
  2024-12-06 10:26 ` [PATCH v2 2/3] Add plugin API functions for register R/W, hwaddr R/W, vaddr W Rowan Hart
@ 2025-01-09 12:22   ` Alex Bennée
  0 siblings, 0 replies; 14+ messages in thread
From: Alex Bennée @ 2025-01-09 12:22 UTC (permalink / raw)
  To: Rowan Hart
  Cc: qemu-devel, Richard Henderson, Eduardo Habkost, Alexandre Iooss,
	Pierrick Bouvier, Mahmoud Mandour, Paolo Bonzini,
	Philippe Mathieu-Daudé

Rowan Hart <rowanbhart@gmail.com> writes:

> From: novafacing <rowanbhart@gmail.com>
>
> ---
>  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
> + *
>   */

In the next version please split up the API additions to make it easier
to review. For now w.r.t. hwaddr I refer to:

  Subject: Re: [PATCH 1/1] plugins: add API to read guest CPU memory from hwaddr
  In-Reply-To: <20240828063224.291503-2-rowanbhart@gmail.com> (Rowan Hart's message of "Tue, 27 Aug 2024 23:32:24 -0700")
  References: <20240828063224.291503-1-rowanbhart@gmail.com> <20240828063224.291503-2-rowanbhart@gmail.com>
  Date: Thu, 09 Jan 2025 11:38:48 +0000
  Message-ID: <877c749pyv.fsf@draig.linaro.org>

-- 
Alex Bennée
Virtualisation Tech Lead @ Linaro


^ permalink raw reply	[flat|nested] 14+ messages in thread

end of thread, other threads:[~2025-01-09 12:23 UTC | newest]

Thread overview: 14+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2024-12-06 10:26 [PATCH v2 0/3] Add additional plugin API functions to read and write memory and registers Rowan Hart
2024-12-06 10:26 ` [PATCH v2 1/3] Expose gdb_write_register function to consumers of gdbstub Rowan Hart
2025-01-09 12:03   ` Alex Bennée
2024-12-06 10:26 ` [PATCH v2 2/3] Add plugin API functions for register R/W, hwaddr R/W, vaddr W Rowan Hart
2025-01-09 12:22   ` Alex Bennée
2024-12-06 10:26 ` [PATCH v2 3/3] Add inject plugin and x86_64 target for the inject plugin Rowan Hart
2024-12-06 19:57   ` Pierrick Bouvier
2024-12-07  1:02     ` Rowan Hart
2024-12-09 18:38       ` Pierrick Bouvier
2024-12-06 19:43 ` [PATCH v2 0/3] Add additional plugin API functions to read and write memory and registers Pierrick Bouvier
2024-12-07  0:57   ` Rowan Hart
2024-12-09 18:45     ` Pierrick Bouvier
2024-12-10 11:38       ` Alex Bennée
2024-12-10 18:40         ` Pierrick Bouvier

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).