Browse Source

Maintainer updates for June (gitlab, semihosting, plugins, virtio-gpu)

- mark s390x runner system tests as allow_fail
  - build semihosting once
  - add register write support to plugins
  - add virtual memory write support to plugins
  - add harder memory read/write support to plugins
  - add patcher plugin and tests
  - re-stock virtio-gpu MAINTAINERS
  - fix context init for Venus fences
 -----BEGIN PGP SIGNATURE-----
 
 iQEzBAABCgAdFiEEZoWumedRZ7yvyN81+9DbCVqeKkQFAmhk+PkACgkQ+9DbCVqe
 KkRMbAgAgs7sufzfJF+408BpYfciArU/XL0ZbfZnvRdayJaw6hIe9q4ySe+w5xVe
 jzSnc/eI3ESl7+Id0eUlC4p7faxgjj/+FoAgCcFSx2Dzt6VQ1WRCMFxc5Z+h/P5i
 vm5Zf2vr38RHyIC/A8BRJWmmpF0NtXwYDaVyV92r+qYClBi6RyJ6+Ooio0MEVdzl
 62/2bGIsf+BzXQOiHHIyki86GS/9svBNMDbCt+4X1cTtoSArsTj9qJ0CSBNM3BFS
 /EJ2e6Hbc9p/8lNPal48/P5L/jN3LJDiQvG+PmxH91gYjsAb6kD4GvKvgT/L6Iai
 lD1YrL7vzgiPJWveESe3i7PWMolnCw==
 =q7Vs
 -----END PGP SIGNATURE-----

Merge tag 'pull-10.1-maintainer-june-2025-020725-1' of https://gitlab.com/stsquad/qemu into staging

Maintainer updates for June (gitlab, semihosting, plugins, virtio-gpu)

 - mark s390x runner system tests as allow_fail
 - build semihosting once
 - add register write support to plugins
 - add virtual memory write support to plugins
 - add harder memory read/write support to plugins
 - add patcher plugin and tests
 - re-stock virtio-gpu MAINTAINERS
 - fix context init for Venus fences

* tag 'pull-10.1-maintainer-june-2025-020725-1' of https://gitlab.com/stsquad/qemu:
  virtio-gpu: support context init multiple timeline
  MAINTAINERS: add Akihiko and Dmitry as reviewers
  MAINTAINERS: add myself to virtio-gpu for Odd Fixes
  plugins: Update plugin version and add notes
  plugins: Add patcher plugin and test
  tests/tcg: Remove copy-pasted notes and from i386 and add x86_64 system tests to tests
  plugins: Add memory hardware address read/write API
  plugins: Add memory virtual address write API
  plugins: Add enforcement of QEMU_PLUGIN_CB flags in register R/W callbacks
  plugins: Add register write API
  gdbstub: Expose gdb_write_register function to consumers of gdbstub
  semihosting/uaccess: Compile once
  semihosting/uaccess: Remove uses of target_ulong type
  tests/functional: Add PCI hotplug test for aarch64
  gitlab: mark s390x-system to allow failures

Conflicts:
  tests/functional/meson.build

  Context conflict with commit 7bc86ccbb5 ("tests/functional: test
  device passthrough on aarch64"), keep both changes to
  tests_aarch64_system_thorough[].
pull/292/head
Stefan Hajnoczi 9 months ago
parent
commit
597639c427
  1. 2
      .gitlab-ci.d/custom-runners/ubuntu-22.04-s390x.yml
  2. 11
      MAINTAINERS
  3. 30
      accel/tcg/plugin-gen.c
  4. 2
      gdbstub/gdbstub.c
  5. 44
      hw/display/virtio-gpu-virgl.c
  6. 14
      include/exec/gdbstub.h
  7. 1
      include/hw/core/cpu.h
  8. 15
      include/qemu/plugin.h
  9. 176
      include/qemu/qemu-plugin.h
  10. 12
      include/semihosting/uaccess.h
  11. 135
      plugins/api.c
  12. 33
      plugins/core.c
  13. 5
      semihosting/meson.build
  14. 10
      semihosting/uaccess.c
  15. 1
      tests/functional/meson.build
  16. 72
      tests/functional/test_aarch64_hotplug_pci.py
  17. 7
      tests/tcg/Makefile.target
  18. 2
      tests/tcg/plugins/meson.build
  19. 251
      tests/tcg/plugins/patch.c
  20. 21
      tests/tcg/x86_64/Makefile.softmmu-target
  21. 22
      tests/tcg/x86_64/system/patch-target.c
  22. 39
      tests/tcg/x86_64/system/validate-patch.py

2
.gitlab-ci.d/custom-runners/ubuntu-22.04-s390x.yml

@ -31,7 +31,9 @@ ubuntu-22.04-s390x-all-system:
timeout: 75m
rules:
- if: '$CI_PROJECT_NAMESPACE == "qemu-project" && $CI_COMMIT_BRANCH =~ /^staging/'
allow_failure: true
- if: "$S390X_RUNNER_AVAILABLE"
allow_failure: true
script:
- mkdir build
- cd build

11
MAINTAINERS

@ -2091,6 +2091,12 @@ S: Supported
F: include/hw/pci/pcie_doe.h
F: hw/pci/pcie_doe.c
ARM PCI Hotplug
M: Gustavo Romero <gustavo.romero@linaro.org>
L: qemu-arm@nongnu.org
S: Supported
F: tests/functional/test_aarch64_hotplug_pci.py
ACPI/SMBIOS
M: Michael S. Tsirkin <mst@redhat.com>
M: Igor Mammedov <imammedo@redhat.com>
@ -2680,7 +2686,10 @@ F: hw/display/ramfb*.c
F: include/hw/display/ramfb.h
virtio-gpu
S: Orphan
M: Alex Bennée <alex.bennee@linaro.org>
R: Akihiko Odaki <odaki@rsg.ci.i.u-tokyo.ac.jp>
R: Dmitry Osipenko <dmitry.osipenko@collabora.com>
S: Odd Fixes
F: hw/display/virtio-gpu*
F: hw/display/virtio-vga.*
F: include/hw/virtio/virtio-gpu.h

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

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

44
hw/display/virtio-gpu-virgl.c

@ -970,6 +970,15 @@ void virtio_gpu_virgl_process_cmd(VirtIOGPU *g,
}
trace_virtio_gpu_fence_ctrl(cmd->cmd_hdr.fence_id, cmd->cmd_hdr.type);
#if VIRGL_VERSION_MAJOR >= 1
if (cmd->cmd_hdr.flags & VIRTIO_GPU_FLAG_INFO_RING_IDX) {
virgl_renderer_context_create_fence(cmd->cmd_hdr.ctx_id,
VIRGL_RENDERER_FENCE_FLAG_MERGEABLE,
cmd->cmd_hdr.ring_idx,
cmd->cmd_hdr.fence_id);
return;
}
#endif
virgl_renderer_create_fence(cmd->cmd_hdr.fence_id, cmd->cmd_hdr.type);
}
@ -983,6 +992,11 @@ static void virgl_write_fence(void *opaque, uint32_t fence)
* the guest can end up emitting fences out of order
* so we should check all fenced cmds not just the first one.
*/
#if VIRGL_VERSION_MAJOR >= 1
if (cmd->cmd_hdr.flags & VIRTIO_GPU_FLAG_INFO_RING_IDX) {
continue;
}
#endif
if (cmd->cmd_hdr.fence_id > fence) {
continue;
}
@ -997,6 +1011,29 @@ static void virgl_write_fence(void *opaque, uint32_t fence)
}
}
#if VIRGL_VERSION_MAJOR >= 1
static void virgl_write_context_fence(void *opaque, uint32_t ctx_id,
uint32_t ring_idx, uint64_t fence_id) {
VirtIOGPU *g = opaque;
struct virtio_gpu_ctrl_command *cmd, *tmp;
QTAILQ_FOREACH_SAFE(cmd, &g->fenceq, next, tmp) {
if (cmd->cmd_hdr.flags & VIRTIO_GPU_FLAG_INFO_RING_IDX &&
cmd->cmd_hdr.ctx_id == ctx_id && cmd->cmd_hdr.ring_idx == ring_idx &&
cmd->cmd_hdr.fence_id <= fence_id) {
trace_virtio_gpu_fence_resp(cmd->cmd_hdr.fence_id);
virtio_gpu_ctrl_response_nodata(g, cmd, VIRTIO_GPU_RESP_OK_NODATA);
QTAILQ_REMOVE(&g->fenceq, cmd, next);
g_free(cmd);
g->inflight--;
if (virtio_gpu_stats_enabled(g->parent_obj.conf)) {
trace_virtio_gpu_dec_inflight_fences(g->inflight);
}
}
}
}
#endif
static virgl_renderer_gl_context
virgl_create_context(void *opaque, int scanout_idx,
struct virgl_renderer_gl_ctx_param *params)
@ -1031,11 +1068,18 @@ static int virgl_make_context_current(void *opaque, int scanout_idx,
}
static struct virgl_renderer_callbacks virtio_gpu_3d_cbs = {
#if VIRGL_VERSION_MAJOR >= 1
.version = 3,
#else
.version = 1,
#endif
.write_fence = virgl_write_fence,
.create_gl_context = virgl_create_context,
.destroy_gl_context = virgl_destroy_context,
.make_current = virgl_make_context_current,
#if VIRGL_VERSION_MAJOR >= 1
.write_context_fence = virgl_write_context_fence,
#endif
};
static void virtio_gpu_print_stats(void *opaque)

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

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

15
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->neg.plugin_cb_flags = flags;
}
static inline enum qemu_plugin_cb_flags qemu_plugin_get_cb_flags(void)
{
assert(current_cpu);
return current_cpu->neg.plugin_cb_flags;
}
#else /* !CONFIG_PLUGIN */
static inline void qemu_plugin_add_opts(void)

176
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
@ -254,9 +261,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 +875,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 +898,51 @@ 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, if called inside a
* callback that can be registered with a qemu_plugin_cb_flags argument. This
* function can also be used in any callback context that does not use a flags
* argument, such as in a callback registered with
* qemu_plugin_register_vcpu_init_cb(), except for callbacks registered with
* qemu_plugin_register_atexit_cb() and qemu_plugin_register_flush_cb().
*
* 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 read access is
* explicitly requested via the QEMU_PLUGIN_CB_RW_REGS flag, if called inside a
* callback that can be registered with a qemu_plugin_cb_flags argument. This
* function can also be used in any callback context that does not use a flags
* argument, such as in a callback registered with
* qemu_plugin_register_vcpu_init_cb(), except for callbacks registered with
* qemu_plugin_register_atexit_cb() and qemu_plugin_register_flush_cb().
*
* 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 +966,118 @@ 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 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
int qemu_plugin_read_register(struct qemu_plugin_register *handle,
GByteArray *buf);
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
bool qemu_plugin_translate_vaddr(uint64_t vaddr, uint64_t *hwaddr);
/**
* qemu_plugin_scoreboard_new() - alloc a new scoreboard

12
include/semihosting/uaccess.h

@ -15,9 +15,9 @@
#endif
#include "exec/cpu-common.h"
#include "exec/cpu-defs.h"
#include "exec/tswap.h"
#include "exec/page-protection.h"
#include "exec/vaddr.h"
/**
* get_user_u64:
@ -89,8 +89,8 @@
*
* The returned pointer should be freed using uaccess_unlock_user().
*/
void *uaccess_lock_user(CPUArchState *env, target_ulong addr,
target_ulong len, bool copy);
void *uaccess_lock_user(CPUArchState *env, vaddr addr,
size_t len, bool copy);
/**
* lock_user:
*
@ -103,7 +103,7 @@ void *uaccess_lock_user(CPUArchState *env, target_ulong addr,
*
* The returned string should be freed using uaccess_unlock_user().
*/
char *uaccess_lock_user_string(CPUArchState *env, target_ulong addr);
char *uaccess_lock_user_string(CPUArchState *env, vaddr addr);
/**
* uaccess_lock_user_string:
*
@ -112,10 +112,10 @@ char *uaccess_lock_user_string(CPUArchState *env, target_ulong addr);
#define lock_user_string(p) uaccess_lock_user_string(env, p)
void uaccess_unlock_user(CPUArchState *env, void *p,
target_ulong addr, target_ulong len);
vaddr addr, size_t len);
#define unlock_user(s, args, len) uaccess_unlock_user(env, s, args, len)
ssize_t uaccess_strlen_user(CPUArchState *env, target_ulong addr);
ssize_t uaccess_strlen_user(CPUArchState *env, vaddr addr);
#define target_strlen(p) uaccess_strlen_user(env, p)
#endif /* SEMIHOSTING_SOFTMMU_UACCESS_H */

135
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"
@ -433,6 +434,29 @@ 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);
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);
}
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,11 +477,118 @@ 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)
bool qemu_plugin_write_memory_vaddr(uint64_t 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;
}
enum qemu_plugin_hwaddr_operation_result
qemu_plugin_read_memory_hwaddr(hwaddr addr, GByteArray *data, size_t len)
{
#ifdef CONFIG_SOFTMMU
if (len == 0) {
return QEMU_PLUGIN_HWADDR_OPERATION_ERROR;
}
g_assert(current_cpu);
int as_idx = cpu_asidx_from_attrs(current_cpu, MEMTXATTRS_UNSPECIFIED);
AddressSpace *as = cpu_get_address_space(current_cpu, as_idx);
if (as == NULL) {
return QEMU_PLUGIN_HWADDR_OPERATION_INVALID_ADDRESS_SPACE;
}
g_byte_array_set_size(data, len);
MemTxResult res = address_space_rw(as, addr,
MEMTXATTRS_UNSPECIFIED, data->data,
data->len, false);
switch (res) {
case MEMTX_OK:
return QEMU_PLUGIN_HWADDR_OPERATION_OK;
case MEMTX_ERROR:
return QEMU_PLUGIN_HWADDR_OPERATION_DEVICE_ERROR;
case MEMTX_DECODE_ERROR:
return QEMU_PLUGIN_HWADDR_OPERATION_INVALID_ADDRESS;
case MEMTX_ACCESS_ERROR:
return QEMU_PLUGIN_HWADDR_OPERATION_ACCESS_DENIED;
default:
return QEMU_PLUGIN_HWADDR_OPERATION_ERROR;
}
#else
return QEMU_PLUGIN_HWADDR_OPERATION_ERROR;
#endif
}
enum qemu_plugin_hwaddr_operation_result
qemu_plugin_write_memory_hwaddr(hwaddr addr, GByteArray *data)
{
#ifdef CONFIG_SOFTMMU
if (data->len == 0) {
return QEMU_PLUGIN_HWADDR_OPERATION_ERROR;
}
g_assert(current_cpu);
int as_idx = cpu_asidx_from_attrs(current_cpu, MEMTXATTRS_UNSPECIFIED);
AddressSpace *as = cpu_get_address_space(current_cpu, as_idx);
if (as == NULL) {
return QEMU_PLUGIN_HWADDR_OPERATION_INVALID_ADDRESS_SPACE;
}
MemTxResult res = address_space_rw(as, addr,
MEMTXATTRS_UNSPECIFIED, data->data,
data->len, true);
switch (res) {
case MEMTX_OK:
return QEMU_PLUGIN_HWADDR_OPERATION_OK;
case MEMTX_ERROR:
return QEMU_PLUGIN_HWADDR_OPERATION_DEVICE_ERROR;
case MEMTX_DECODE_ERROR:
return QEMU_PLUGIN_HWADDR_OPERATION_INVALID_ADDRESS;
case MEMTX_ACCESS_ERROR:
return QEMU_PLUGIN_HWADDR_OPERATION_ACCESS_DENIED;
default:
return QEMU_PLUGIN_HWADDR_OPERATION_ERROR;
}
#else
return QEMU_PLUGIN_HWADDR_OPERATION_ERROR;
#endif
}
bool qemu_plugin_translate_vaddr(uint64_t vaddr, uint64_t *hwaddr)
{
#ifdef CONFIG_SOFTMMU
g_assert(current_cpu);
uint64_t res = cpu_get_phys_page_debug(current_cpu, vaddr);
if (res == (uint64_t)-1) {
return false;
}
*hwaddr = res | (vaddr & ~TARGET_PAGE_MASK);
return true;
#else
return false;
#endif
}
struct qemu_plugin_scoreboard *qemu_plugin_scoreboard_new(size_t element_size)

33
plugins/core.c

@ -15,6 +15,7 @@
#include "qemu/lockable.h"
#include "qemu/option.h"
#include "qemu/plugin.h"
#include "qemu/qemu-plugin.h"
#include "qemu/queue.h"
#include "qemu/rcu_queue.h"
#include "qemu/rcu.h"
@ -266,7 +267,9 @@ static void qemu_plugin_vcpu_init__async(CPUState *cpu, run_on_cpu_data unused)
plugin_grow_scoreboards__locked(cpu);
qemu_rec_mutex_unlock(&plugin.lock);
qemu_plugin_set_cb_flags(cpu, QEMU_PLUGIN_CB_RW_REGS);
plugin_vcpu_cb__simple(cpu, QEMU_PLUGIN_EV_VCPU_INIT);
qemu_plugin_set_cb_flags(cpu, QEMU_PLUGIN_CB_NO_REGS);
}
void qemu_plugin_vcpu_init_hook(CPUState *cpu)
@ -279,7 +282,9 @@ void qemu_plugin_vcpu_exit_hook(CPUState *cpu)
{
bool success;
qemu_plugin_set_cb_flags(cpu, QEMU_PLUGIN_CB_RW_REGS);
plugin_vcpu_cb__simple(cpu, QEMU_PLUGIN_EV_VCPU_EXIT);
qemu_plugin_set_cb_flags(cpu, QEMU_PLUGIN_CB_NO_REGS);
assert(cpu->cpu_index != UNASSIGNED_CPU_INDEX);
qemu_rec_mutex_lock(&plugin.lock);
@ -367,6 +372,7 @@ void plugin_register_dyn_cb__udata(GArray **arr,
static TCGHelperInfo info[3] = {
[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 *)
@ -396,6 +402,7 @@ void plugin_register_dyn_cond_cb__udata(GArray **arr,
static TCGHelperInfo info[3] = {
[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 *)
@ -434,6 +441,7 @@ void plugin_register_vcpu_mem_cb(GArray **arr,
static TCGHelperInfo info[3] = {
[QEMU_PLUGIN_CB_NO_REGS].flags = TCG_CALL_NO_RWG,
[QEMU_PLUGIN_CB_R_REGS].flags = TCG_CALL_NO_WG,
[QEMU_PLUGIN_CB_RW_REGS].flags = 0,
/*
* Match qemu_plugin_vcpu_mem_cb_t:
* void (*)(uint32_t, qemu_plugin_meminfo_t, uint64_t, void *)
@ -473,7 +481,9 @@ void qemu_plugin_tb_trans_cb(CPUState *cpu, struct qemu_plugin_tb *tb)
QLIST_FOREACH_SAFE_RCU(cb, &plugin.cb_lists[ev], entry, next) {
qemu_plugin_vcpu_tb_trans_cb_t func = cb->f.vcpu_tb_trans;
qemu_plugin_set_cb_flags(cpu, QEMU_PLUGIN_CB_RW_REGS);
func(cb->ctx->id, tb);
qemu_plugin_set_cb_flags(cpu, QEMU_PLUGIN_CB_NO_REGS);
}
}
@ -498,7 +508,9 @@ qemu_plugin_vcpu_syscall(CPUState *cpu, int64_t num, uint64_t a1, uint64_t a2,
QLIST_FOREACH_SAFE_RCU(cb, &plugin.cb_lists[ev], entry, next) {
qemu_plugin_vcpu_syscall_cb_t func = cb->f.vcpu_syscall;
qemu_plugin_set_cb_flags(cpu, QEMU_PLUGIN_CB_RW_REGS);
func(cb->ctx->id, cpu->cpu_index, num, a1, a2, a3, a4, a5, a6, a7, a8);
qemu_plugin_set_cb_flags(cpu, QEMU_PLUGIN_CB_NO_REGS);
}
}
@ -520,7 +532,9 @@ void qemu_plugin_vcpu_syscall_ret(CPUState *cpu, int64_t num, int64_t ret)
QLIST_FOREACH_SAFE_RCU(cb, &plugin.cb_lists[ev], entry, next) {
qemu_plugin_vcpu_syscall_ret_cb_t func = cb->f.vcpu_syscall_ret;
qemu_plugin_set_cb_flags(cpu, QEMU_PLUGIN_CB_RW_REGS);
func(cb->ctx->id, cpu->cpu_index, num, ret);
qemu_plugin_set_cb_flags(cpu, QEMU_PLUGIN_CB_NO_REGS);
}
}
@ -528,14 +542,18 @@ void qemu_plugin_vcpu_idle_cb(CPUState *cpu)
{
/* idle and resume cb may be called before init, ignore in this case */
if (cpu->cpu_index < plugin.num_vcpus) {
qemu_plugin_set_cb_flags(cpu, QEMU_PLUGIN_CB_RW_REGS);
plugin_vcpu_cb__simple(cpu, QEMU_PLUGIN_EV_VCPU_IDLE);
qemu_plugin_set_cb_flags(cpu, QEMU_PLUGIN_CB_NO_REGS);
}
}
void qemu_plugin_vcpu_resume_cb(CPUState *cpu)
{
if (cpu->cpu_index < plugin.num_vcpus) {
qemu_plugin_set_cb_flags(cpu, QEMU_PLUGIN_CB_RW_REGS);
plugin_vcpu_cb__simple(cpu, QEMU_PLUGIN_EV_VCPU_RESUME);
qemu_plugin_set_cb_flags(cpu, QEMU_PLUGIN_CB_NO_REGS);
}
}
@ -615,9 +633,13 @@ void qemu_plugin_vcpu_mem_cb(CPUState *cpu, uint64_t vaddr,
switch (cb->type) {
case PLUGIN_CB_MEM_REGULAR:
if (rw & cb->regular.rw) {
qemu_plugin_set_cb_flags(cpu,
tcg_call_to_qemu_plugin_cb_flags(cb->regular.info->flags));
cb->regular.f.vcpu_mem(cpu->cpu_index,
make_plugin_meminfo(oi, rw),
vaddr, cb->regular.userp);
qemu_plugin_set_cb_flags(cpu, QEMU_PLUGIN_CB_NO_REGS);
}
break;
case PLUGIN_CB_INLINE_ADD_U64:
@ -760,3 +782,14 @@ void plugin_scoreboard_free(struct qemu_plugin_scoreboard *score)
g_array_free(score->data, TRUE);
g_free(score);
}
enum qemu_plugin_cb_flags tcg_call_to_qemu_plugin_cb_flags(int flags)
{
if (flags & TCG_CALL_NO_RWG) {
return QEMU_PLUGIN_CB_NO_REGS;
} else if (flags & TCG_CALL_NO_WG) {
return QEMU_PLUGIN_CB_R_REGS;
} else {
return QEMU_PLUGIN_CB_RW_REGS;
}
}

5
semihosting/meson.build

@ -3,15 +3,12 @@ specific_ss.add(when: 'CONFIG_SEMIHOSTING', if_true: files(
'syscalls.c',
))
specific_ss.add(when: ['CONFIG_SEMIHOSTING', 'CONFIG_SYSTEM_ONLY'], if_true: files(
'uaccess.c',
))
common_ss.add(when: 'CONFIG_SEMIHOSTING', if_false: files('stubs-all.c'))
user_ss.add(when: 'CONFIG_SEMIHOSTING', if_true: files('user.c'))
system_ss.add(when: 'CONFIG_SEMIHOSTING', if_true: files(
'config.c',
'console.c',
'uaccess.c',
), if_false: files(
'stubs-system.c',
))

10
semihosting/uaccess.c

@ -14,8 +14,8 @@
#include "exec/tlb-flags.h"
#include "semihosting/uaccess.h"
void *uaccess_lock_user(CPUArchState *env, target_ulong addr,
target_ulong len, bool copy)
void *uaccess_lock_user(CPUArchState *env, vaddr addr,
size_t len, bool copy)
{
void *p = malloc(len);
if (p && copy) {
@ -27,7 +27,7 @@ void *uaccess_lock_user(CPUArchState *env, target_ulong addr,
return p;
}
ssize_t uaccess_strlen_user(CPUArchState *env, target_ulong addr)
ssize_t uaccess_strlen_user(CPUArchState *env, vaddr addr)
{
int mmu_idx = cpu_mmu_index(env_cpu(env), false);
size_t len = 0;
@ -75,7 +75,7 @@ ssize_t uaccess_strlen_user(CPUArchState *env, target_ulong addr)
}
}
char *uaccess_lock_user_string(CPUArchState *env, target_ulong addr)
char *uaccess_lock_user_string(CPUArchState *env, vaddr addr)
{
ssize_t len = uaccess_strlen_user(env, addr);
if (len < 0) {
@ -85,7 +85,7 @@ char *uaccess_lock_user_string(CPUArchState *env, target_ulong addr)
}
void uaccess_unlock_user(CPUArchState *env, void *p,
target_ulong addr, target_ulong len)
vaddr addr, size_t len)
{
if (len) {
cpu_memory_rw_debug(env_cpu(env), addr, p, len, 1);

1
tests/functional/meson.build

@ -85,6 +85,7 @@ tests_aarch64_system_thorough = [
'aarch64_aspeed_ast2700',
'aarch64_aspeed_ast2700fc',
'aarch64_device_passthrough',
'aarch64_hotplug_pci',
'aarch64_imx8mp_evk',
'aarch64_raspi3',
'aarch64_raspi4',

72
tests/functional/test_aarch64_hotplug_pci.py

@ -0,0 +1,72 @@
#!/usr/bin/env python3
#
# The test hotplugs a PCI device and checks it on a Linux guest.
#
# Copyright (c) 2025 Linaro Ltd.
#
# Author:
# Gustavo Romero <gustavo.romero@linaro.org>
#
# SPDX-License-Identifier: GPL-2.0-or-later
from qemu_test import LinuxKernelTest, Asset, exec_command_and_wait_for_pattern
from qemu_test import BUILD_DIR
class HotplugPCI(LinuxKernelTest):
ASSET_KERNEL = Asset(
('https://ftp.debian.org/debian/dists/stable/main/installer-arm64/'
'20230607+deb12u11/images/netboot/debian-installer/arm64/linux'),
'd92a60392ce1e379ca198a1a820899f8f0d39a62d047c41ab79492f81541a9d9')
ASSET_INITRD = Asset(
('https://ftp.debian.org/debian/dists/stable/main/installer-arm64/'
'20230607+deb12u11/images/netboot/debian-installer/arm64/initrd.gz'),
'9f817f76951f3237bca8216bee35267bfb826815687f4b2fcdd5e6c2a917790c')
def test_hotplug_pci(self):
self.set_machine('virt')
self.vm.add_args('-m', '512M',
'-cpu', 'cortex-a57',
'-append',
'console=ttyAMA0,115200 init=/bin/sh',
'-device',
'pcie-root-port,bus=pcie.0,chassis=1,slot=1,id=pcie.1',
'-bios',
self.build_file('pc-bios', 'edk2-aarch64-code.fd'))
# BusyBox prompt
prompt = "~ #"
self.launch_kernel(self.ASSET_KERNEL.fetch(),
self.ASSET_INITRD.fetch(),
wait_for=prompt)
# Check for initial state: 2 network adapters, lo and enp0s1.
exec_command_and_wait_for_pattern(self,
'ls /sys/class/net | wc -l',
'2')
# Hotplug one network adapter to the root port, i.e. pcie.1 bus.
self.vm.cmd('device_add',
driver='virtio-net-pci',
bus='pcie.1',
addr=0,
id='na')
# Wait for the kernel to recognize the new device.
self.wait_for_console_pattern('virtio-pci')
self.wait_for_console_pattern('virtio_net')
# Check if there is a new network adapter.
exec_command_and_wait_for_pattern(self,
'ls /sys/class/net | wc -l',
'3')
self.vm.cmd('device_del', id='na')
exec_command_and_wait_for_pattern(self,
'ls /sys/class/net | wc -l',
'2')
if __name__ == '__main__':
LinuxKernelTest.main()

7
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

2
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',

251
tests/tcg/plugins/patch.c

@ -0,0 +1,251 @@
/*
* SPDX-License-Identifier: GPL-2.0-or-later
*
* This plugin patches instructions matching a pattern to a different
* instruction as they execute
*
*/
#include "glib.h"
#include "glibconfig.h"
#include <qemu-plugin.h>
#include <string.h>
#include <stdio.h>
QEMU_PLUGIN_EXPORT int qemu_plugin_version = QEMU_PLUGIN_VERSION;
static bool use_hwaddr;
static 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)
{
uintptr_t addr = (uintptr_t) userdata;
g_autoptr(GString) str = g_string_new(NULL);
g_string_printf(str, "patching: @0x%"
PRIxPTR "\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_data->data, read_data->data, patch_data->len) != 0) {
qemu_plugin_outs("Failed to read back written data\n");
}
qemu_plugin_outs("Success!\n");
return;
}
static void patch_vaddr(unsigned int vcpu_index, void *userdata)
{
uintptr_t addr = (uintptr_t) userdata;
uint64_t hwaddr = 0;
if (!qemu_plugin_translate_vaddr(addr, &hwaddr)) {
qemu_plugin_outs("Failed to translate vaddr\n");
return;
}
g_autoptr(GString) str = g_string_new(NULL);
g_string_printf(str, "patching: @0x%"
PRIxPTR " hw: @0x%" PRIx64 "\n",
addr, hwaddr);
qemu_plugin_outs(str->str);
qemu_plugin_outs("Writing memory (vaddr)...\n");
if (!qemu_plugin_write_memory_vaddr(addr, patch_data)) {
qemu_plugin_outs("Failed to write memory\n");
return;
}
qemu_plugin_outs("Reading memory (vaddr)...\n");
g_autoptr(GByteArray) read_data = g_byte_array_new();
if (!qemu_plugin_read_memory_vaddr(addr, read_data, patch_data->len)) {
qemu_plugin_outs("Failed to read memory\n");
return;
}
if (memcmp(patch_data->data, read_data->data, patch_data->len) != 0) {
qemu_plugin_outs("Failed to read back written data\n");
}
qemu_plugin_outs("Success!\n");
return;
}
/*
* Callback on translation of a translation block.
*/
static void vcpu_tb_trans_cb(qemu_plugin_id_t id, struct qemu_plugin_tb *tb)
{
g_autoptr(GByteArray) insn_data = g_byte_array_new();
uintptr_t addr = 0;
for (size_t i = 0; i < qemu_plugin_tb_n_insns(tb); i++) {
struct qemu_plugin_insn *insn = qemu_plugin_tb_get_insn(tb, i);
uint64_t vaddr = qemu_plugin_insn_vaddr(insn);
if (use_hwaddr) {
uint64_t hwaddr = 0;
if (!qemu_plugin_translate_vaddr(vaddr, &hwaddr)) {
qemu_plugin_outs("Failed to translate vaddr\n");
continue;
}
/*
* As we cannot emulate 64 bit systems on 32 bit hosts we
* should never see the top bits set, hence we can safely
* cast to uintptr_t.
*/
g_assert(hwaddr <= UINTPTR_MAX);
addr = (uintptr_t) hwaddr;
} else {
g_assert(vaddr <= UINTPTR_MAX);
addr = (uintptr_t) vaddr;
}
g_byte_array_set_size(insn_data, qemu_plugin_insn_size(insn));
qemu_plugin_insn_data(insn, insn_data->data, insn_data->len);
if (insn_data->len >= target_data->len &&
!memcmp(insn_data->data, target_data->data,
MIN(target_data->len, insn_data->len))) {
if (use_hwaddr) {
qemu_plugin_register_vcpu_tb_exec_cb(tb, patch_hwaddr,
QEMU_PLUGIN_CB_NO_REGS,
(void *) addr);
} else {
qemu_plugin_register_vcpu_tb_exec_cb(tb, patch_vaddr,
QEMU_PLUGIN_CB_NO_REGS,
(void *) addr);
}
}
}
}
static void usage(void)
{
fprintf(stderr, "Usage: <lib>,target=<bytes>,patch=<new_bytes>"
"[,use_hwaddr=true|false]");
}
/*
* Called when the plugin is installed
*/
QEMU_PLUGIN_EXPORT int qemu_plugin_install(qemu_plugin_id_t id,
const qemu_info_t *info, int argc,
char **argv)
{
use_hwaddr = true;
target_data = NULL;
patch_data = NULL;
if (argc > 4) {
usage();
return -1;
}
for (size_t i = 0; i < argc; i++) {
char *opt = argv[i];
g_auto(GStrv) tokens = g_strsplit(opt, "=", 2);
if (g_strcmp0(tokens[0], "use_hwaddr") == 0) {
if (!qemu_plugin_bool_parse(tokens[0], tokens[1], &use_hwaddr)) {
fprintf(stderr,
"Failed to parse boolean argument use_hwaddr\n");
return -1;
}
} else if (g_strcmp0(tokens[0], "target") == 0) {
target_data = str_to_bytes(tokens[1]);
if (!target_data) {
fprintf(stderr,
"Failed to parse target bytes.\n");
return -1;
}
} else if (g_strcmp0(tokens[0], "patch") == 0) {
patch_data = str_to_bytes(tokens[1]);
if (!patch_data) {
fprintf(stderr, "Failed to parse patch bytes.\n");
return -1;
}
} else {
fprintf(stderr, "Unknown argument: %s\n", tokens[0]);
usage();
return -1;
}
}
if (!target_data) {
fprintf(stderr, "target argument is required\n");
usage();
return -1;
}
if (!patch_data) {
fprintf(stderr, "patch argument is required\n");
usage();
return -1;
}
if (target_data->len != patch_data->len) {
fprintf(stderr, "Target and patch data must be the same length\n");
return -1;
}
qemu_plugin_register_vcpu_tb_trans_cb(id, vcpu_tb_trans_cb);
return 0;
}

21
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
@ -35,3 +33,12 @@ memory: CFLAGS+=-DCHECK_UNALIGNED=1
# Running
QEMU_OPTS+=-device isa-debugcon,chardev=output -device isa-debug-exit,iobase=0xf4,iosize=0x4 -kernel
ifeq ($(CONFIG_PLUGIN),y)
run-plugin-patch-target-with-libpatch.so: \
PLUGIN_ARGS=$(COMMA)target=ffc0$(COMMA)patch=9090$(COMMA)use_hwaddr=true
run-plugin-patch-target-with-libpatch.so: \
CHECK_PLUGIN_OUTPUT_COMMAND=$(X64_SYSTEM_SRC)/validate-patch.py $@.out
run-plugin-patch-target-with-libpatch.so: patch-target libpatch.so
EXTRA_RUNS+=run-plugin-patch-target-with-libpatch.so
endif

22
tests/tcg/x86_64/system/patch-target.c

@ -0,0 +1,22 @@
/*
* SPDX-License-Identifier: GPL-2.0-or-later
*
* This test target increments a value 100 times. The patcher converts the
* inc instruction to a nop, so it only increments the value once.
*
*/
#include <minilib.h>
int main(void)
{
ml_printf("Running test...\n");
unsigned int x = 0;
for (int i = 0; i < 100; i++) {
asm volatile (
"inc %[x]"
: [x] "+a" (x)
);
}
ml_printf("Value: %d\n", x);
return 0;
}

39
tests/tcg/x86_64/system/validate-patch.py

@ -0,0 +1,39 @@
#!/usr/bin/env python3
#
# validate-patch.py: check the patch applies
#
# This program takes two inputs:
# - the plugin output
# - the binary output
#
# Copyright (C) 2024
#
# SPDX-License-Identifier: GPL-2.0-or-later
import sys
from argparse import ArgumentParser
def main() -> None:
"""
Process the arguments, injest the program and plugin out and
verify they match up and report if they do not.
"""
parser = ArgumentParser(description="Validate patch")
parser.add_argument('test_output',
help="The output from the test itself")
parser.add_argument('plugin_output',
help="The output from plugin")
args = parser.parse_args()
with open(args.test_output, 'r') as f:
test_data = f.read()
with open(args.plugin_output, 'r') as f:
plugin_data = f.read()
if "Value: 1" in test_data:
sys.exit(0)
else:
sys.exit(1)
if __name__ == "__main__":
main()
Loading…
Cancel
Save