From nobody Thu Apr 2 01:31:54 2026 Delivered-To: importer@patchew.org Authentication-Results: mx.zohomail.com; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=fail(p=none dis=none) header.from=sjtu.edu.cn Return-Path: Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) by mx.zohomail.com with SMTPS id 1774991929226370.65244372743837; Tue, 31 Mar 2026 14:18:49 -0700 (PDT) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1w7gTL-0008SX-SJ; Tue, 31 Mar 2026 17:18:03 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1w7dE7-0007Pp-1a for qemu-devel@nongnu.org; Tue, 31 Mar 2026 13:50:07 -0400 Received: from smtp232.sjtu.edu.cn ([202.120.2.232]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1w7dE3-0000M9-4f for qemu-devel@nongnu.org; Tue, 31 Mar 2026 13:50:06 -0400 Received: from proxy188.sjtu.edu.cn (smtp188.sjtu.edu.cn [202.120.2.188]) by smtp232.sjtu.edu.cn (Postfix) with ESMTPS id 0D1FB10097F65; Wed, 1 Apr 2026 01:38:09 +0800 (CST) Received: from xuklXiaoxin (unknown [202.120.32.222]) by proxy188.sjtu.edu.cn (Postfix) with ESMTPSA id DAF7037C8CA; Wed, 1 Apr 2026 01:38:08 +0800 (CST) From: XU Kailiang To: qemu-devel Cc: =?UTF-8?q?Alex=20Benn=C3=A9e?= , Pierrick Bouvier , Alexandre Iooss , Mahmoud Mandour , Ziyang Zhang , Yun Wang , Mingyuan Xia , Zhengwei Qi , XU Kailiang Subject: [PATCH v1 2/3] contrib/plugins: add a callback-capable qsort prototype Date: Wed, 1 Apr 2026 01:36:25 +0800 Message-ID: <20260331173656.35305-3-xukl2019@sjtu.edu.cn> X-Mailer: git-send-email 2.53.0 In-Reply-To: <20260331173656.35305-1-xukl2019@sjtu.edu.cn> References: <20260331173656.35305-1-xukl2019@sjtu.edu.cn> MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Received-SPF: pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) client-ip=209.51.188.17; envelope-from=qemu-devel-bounces+importer=patchew.org@nongnu.org; helo=lists.gnu.org; Received-SPF: pass client-ip=202.120.2.232; envelope-from=xukl2019@sjtu.edu.cn; helo=smtp232.sjtu.edu.cn X-Spam_score_int: 1 X-Spam_score: 0.1 X-Spam_bar: / X-Spam_report: (0.1 / 5.0 requ) BAYES_00=-1.9, RCVD_IN_VALIDITY_CERTIFIED_BLOCKED=1, RCVD_IN_VALIDITY_RPBL_BLOCKED=1, SPF_HELO_NONE=0.001, SPF_PASS=-0.001 autolearn=no autolearn_force=no X-Spam_action: no action X-Mailman-Approved-At: Tue, 31 Mar 2026 17:18:00 -0400 X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: qemu development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+importer=patchew.org@nongnu.org Sender: qemu-devel-bounces+importer=patchew.org@nongnu.org X-ZM-MESSAGEID: 1774991935521158500 Content-Type: text/plain; charset="utf-8" Add a second linux-user example that demonstrates a more advanced use of the syscall filter API: redirecting a thunk library for qsort() and bridging comparator callbacks back into guest translated code. The guest-side prototype links against libdemo-callback-qsort.so. The plugin redirects the loader's openat() to libdemo-callback-qsort-thunk.so, runs host qsort() inside a ucontext worker, and on each comparator invocation rewrites guest register state and PC so execution continues in the guest comparator. The guest return trampoline then resumes the suspended host call with a second magic syscall. At this stage, the loader redirection covers only the openat()-based library load path. The follow-up patch in this series extends the same prototype to open() and openat2(). Once the demo library path matches, the plugin asserts that opening the thunk library succeeds. That is intentional here: this prototype is meant to show the callback bridge once a known local thunk library is in place, not fallback behavior when that local library is missing or unusable. Keep the scope explicit: this is an x86_64-only prototype intended to show what the interface can support, not a general callback ABI. Signed-off-by: XU Kailiang Co-authored-by: Ziyang Zhang --- contrib/plugins/meson.build | 12 +- .../Makefile | 19 + .../README.rst | 46 ++ .../callback-demo.c | 75 +++ .../callback-qsort.h | 14 + .../callback-thunk.S | 38 ++ .../plugins/syscall_filter_callback_qsort.c | 431 ++++++++++++++++++ 7 files changed, 631 insertions(+), 4 deletions(-) create mode 100644 contrib/plugins/syscall_filter_callback_qsort-example/M= akefile create mode 100644 contrib/plugins/syscall_filter_callback_qsort-example/R= EADME.rst create mode 100644 contrib/plugins/syscall_filter_callback_qsort-example/c= allback-demo.c create mode 100644 contrib/plugins/syscall_filter_callback_qsort-example/c= allback-qsort.h create mode 100644 contrib/plugins/syscall_filter_callback_qsort-example/c= allback-thunk.S create mode 100644 contrib/plugins/syscall_filter_callback_qsort.c diff --git a/contrib/plugins/meson.build b/contrib/plugins/meson.build index aa3b95eeab..cc82255666 100644 --- a/contrib/plugins/meson.build +++ b/contrib/plugins/meson.build @@ -19,10 +19,14 @@ if host_os !=3D 'windows' contrib_plugins +=3D 'lockstep.c' endif =20 -if host_os !=3D 'windows' and host_os !=3D 'darwin' and - host_machine.endian() =3D=3D 'little' - # The zlib syscall-filter demo assumes little-endian direct-pointer acce= ss. - contrib_plugins +=3D 'syscall_filter_zlib.c' +if host_os !=3D 'windows' and host_os !=3D 'darwin' + # The syscall-filter demos assume little-endian direct-pointer access. + if host_machine.endian() =3D=3D 'little' + contrib_plugins +=3D 'syscall_filter_zlib.c' + if supported_backends.contains('ucontext') + contrib_plugins +=3D 'syscall_filter_callback_qsort.c' + endif + endif endif =20 if 'cpp' in all_languages diff --git a/contrib/plugins/syscall_filter_callback_qsort-example/Makefile= b/contrib/plugins/syscall_filter_callback_qsort-example/Makefile new file mode 100644 index 0000000000..0144e46790 --- /dev/null +++ b/contrib/plugins/syscall_filter_callback_qsort-example/Makefile @@ -0,0 +1,19 @@ +# SPDX-License-Identifier: GPL-2.0-or-later + +CC ?=3D cc +CFLAGS ?=3D +LDFLAGS ?=3D +RM ?=3D rm -f + +all: libdemo-callback-qsort-thunk.so callback-demo + +libdemo-callback-qsort-thunk.so: callback-thunk.S + $(CC) $(CFLAGS) -fPIC -shared callback-thunk.S -Wl,-soname,libdemo-callba= ck-qsort.so -o $@ $(LDFLAGS) + +callback-demo: callback-demo.c callback-qsort.h libdemo-callback-qsort-thu= nk.so + $(CC) $(CFLAGS) callback-demo.c ./libdemo-callback-qsort-thunk.so -Wl,-rp= ath,'$$ORIGIN' -o $@ $(LDFLAGS) + +clean: + $(RM) libdemo-callback-qsort-thunk.so callback-demo + +.PHONY: all clean diff --git a/contrib/plugins/syscall_filter_callback_qsort-example/README.r= st b/contrib/plugins/syscall_filter_callback_qsort-example/README.rst new file mode 100644 index 0000000000..e2bba7e2b6 --- /dev/null +++ b/contrib/plugins/syscall_filter_callback_qsort-example/README.rst @@ -0,0 +1,46 @@ +.. SPDX-License-Identifier: GPL-2.0-or-later + +Callback qsort bridge prototype +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D + +This directory contains a prototype for callback-capable local library +interception on ``qemu-x86_64``. + +* ``callback-demo.c`` is linked against ``libdemo-callback-qsort.so`` and = calls + ``callback_qsort()`` directly. +* The plugin intercepts the loader's ``openat()`` and returns a file + descriptor for ``./libdemo-callback-qsort-thunk.so`` instead. +* ``callback-thunk.S`` issues a ``START`` magic syscall for ``qsort()`` an= d a + ``RESUME`` magic syscall from a guest return trampoline. +* ``contrib/plugins/syscall_filter_callback_qsort.c`` runs host ``qsort()`` + inside a ``ucontext`` worker, yields on every comparator invocation, and + redirects control flow into the guest comparator with + ``qemu_plugin_set_pc()``. +* This callback prototype is intentionally ``x86_64``-only. Supporting + additional targets would require target-specific register and ABI handli= ng, + which is beyond the scope of this example. +* The plugin is built only on hosts where ``ucontext`` is available. +* To keep the prototype minimal, it assumes ``guest_base =3D=3D 0`` on a + little-endian 64-bit host. For this x86_64 linux-user demo, that means g= uest + virtual addresses are directly usable as host pointers. In practice that + means running ``qemu-x86_64`` on a little-endian 64-bit host without for= cing + a nonzero guest base. + +Build the guest-side prototype with:: + + make + +Then run it from this directory with QEMU linux-user and the plugin:: + + QEMU_BUILD=3D/path/to/qemu/build + $QEMU_BUILD/qemu-x86_64 \ + -plugin $QEMU_BUILD/contrib/plugins/libsyscall_filter_callback_qsort.s= o \ + -d plugin \ + ./callback-demo + +The build links ``callback-demo`` against ``libdemo-callback-qsort-thunk.s= o`` +while giving that shared object the soname ``libdemo-callback-qsort.so``. +Without the plugin, the program fails at startup because no +``libdemo-callback-qsort.so`` file exists in the runtime search path. With= the +plugin, the guest sees a working library load even though the loader actua= lly +receives ``libdemo-callback-qsort-thunk.so``. diff --git a/contrib/plugins/syscall_filter_callback_qsort-example/callback= -demo.c b/contrib/plugins/syscall_filter_callback_qsort-example/callback-de= mo.c new file mode 100644 index 0000000000..5e89d9f022 --- /dev/null +++ b/contrib/plugins/syscall_filter_callback_qsort-example/callback-demo.c @@ -0,0 +1,75 @@ +/* + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include +#include +#include +#include + +#include "callback-qsort.h" + +#define VALUE_COUNT 128 + +static int compare_count; + +static int guest_compare_int32(const void *lhs, const void *rhs) +{ + const int32_t *a =3D lhs; + const int32_t *b =3D rhs; + + compare_count++; + if (*a < *b) { + return -1; + } + if (*a > *b) { + return 1; + } + return 0; +} + +static int is_sorted(const int32_t *values, size_t count) +{ + size_t i; + + for (i =3D 1; i < count; i++) { + if (values[i - 1] > values[i]) { + return 0; + } + } + + return 1; +} + +int main(void) +{ + int32_t values[VALUE_COUNT]; + size_t i; + int ret; + + for (i =3D 0; i < VALUE_COUNT; i++) { + values[i] =3D (int32_t)(VALUE_COUNT - i); + } + + ret =3D callback_qsort(values, VALUE_COUNT, sizeof(values[0]), + guest_compare_int32); + if (ret !=3D 0) { + fprintf(stderr, "callback_qsort failed: %d\n", ret); + return EXIT_FAILURE; + } + + if (!is_sorted(values, VALUE_COUNT)) { + fprintf(stderr, "values are not sorted\n"); + return EXIT_FAILURE; + } + + if (compare_count =3D=3D 0) { + fprintf(stderr, "guest comparator never ran\n"); + return EXIT_FAILURE; + } + + printf("callback demo sorted %u values with %d guest comparator calls\= n", + VALUE_COUNT, compare_count); + puts("callback demo succeeded"); + return EXIT_SUCCESS; +} diff --git a/contrib/plugins/syscall_filter_callback_qsort-example/callback= -qsort.h b/contrib/plugins/syscall_filter_callback_qsort-example/callback-q= sort.h new file mode 100644 index 0000000000..4a1021841d --- /dev/null +++ b/contrib/plugins/syscall_filter_callback_qsort-example/callback-qsort.h @@ -0,0 +1,14 @@ +/* + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef CONTRIB_PLUGINS_SYSCALL_FILTER_CALLBACK_QSORT_EXAMPLE_CALLBACK_QSO= RT_H +#define CONTRIB_PLUGINS_SYSCALL_FILTER_CALLBACK_QSORT_EXAMPLE_CALLBACK_QSO= RT_H + +#include +typedef int (*callback_qsort_cmp_fn)(const void *lhs, const void *rhs); + +int callback_qsort(void *base, size_t nmemb, size_t size, + callback_qsort_cmp_fn cmp); + +#endif diff --git a/contrib/plugins/syscall_filter_callback_qsort-example/callback= -thunk.S b/contrib/plugins/syscall_filter_callback_qsort-example/callback-t= hunk.S new file mode 100644 index 0000000000..a8d9607d5c --- /dev/null +++ b/contrib/plugins/syscall_filter_callback_qsort-example/callback-thunk.S @@ -0,0 +1,38 @@ +/* + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#define MAGIC_SYSCALL 4096 +#define CALLBACK_BRIDGE_OP_START 1 +#define CALLBACK_BRIDGE_OP_RESUME 2 + + .text + .hidden callback_qsort_resume_trampoline + .globl callback_qsort + .type callback_qsort, @function +callback_qsort: + /* + * callback_qsort(base, nmemb, elem_size, cmp_fn) arrives in + * %rdi, %rsi, %rdx, and %rcx. Shuffle those into the x86_64 Linux + * syscall ABI so START sees: op, base, nmemb, elem_size, cmp_fn, + * trampoline. + */ + mov %rcx, %r8 + mov %rdx, %r10 + mov %rsi, %rdx + lea callback_qsort_resume_trampoline(%rip), %r9 + mov %rdi, %rsi + mov $CALLBACK_BRIDGE_OP_START, %rdi + mov $MAGIC_SYSCALL, %rax + syscall + ret + + .globl callback_qsort_resume_trampoline + .type callback_qsort_resume_trampoline, @function +callback_qsort_resume_trampoline: + /* Sign-extend the guest comparator result into the RESUME argument. */ + movslq %eax, %rsi + mov $CALLBACK_BRIDGE_OP_RESUME, %rdi + mov $MAGIC_SYSCALL, %rax + syscall + ret diff --git a/contrib/plugins/syscall_filter_callback_qsort.c b/contrib/plug= ins/syscall_filter_callback_qsort.c new file mode 100644 index 0000000000..45a83cb5b1 --- /dev/null +++ b/contrib/plugins/syscall_filter_callback_qsort.c @@ -0,0 +1,431 @@ +/* + * SPDX-License-Identifier: GPL-2.0-or-later + * + * x86_64-only prototype that demonstrates a callback-capable syscall filt= er + * plugin by redirecting a qsort() thunk library and bridging comparator c= alls + * back into guest translated code with ucontext. The loader redirection + * handles openat(). + * + * This demo intentionally assumes a linux-user run with guest_base =3D=3D= 0 on a + * little-endian 64-bit host, so guest virtual addresses are directly usab= le + * as host pointers. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +QEMU_PLUGIN_EXPORT int qemu_plugin_version =3D QEMU_PLUGIN_VERSION; + +#define MAGIC_SYSCALL 4096 +#define CALLBACK_QSORT_OP_START 1 +#define CALLBACK_QSORT_OP_RESUME 2 +#define CALLBACK_QSORT_LIBRARY "libdemo-callback-qsort.so" +#define CALLBACK_QSORT_THUNK_LIBRARY "libdemo-callback-qsort-thunk.so" +#define GUEST_STRING_CHUNK 64 +#define GUEST_STRING_LIMIT (1 << 20) +#define CALLBACK_QSORT_MAX_ELEMS (1 << 20) +#define CALLBACK_QSORT_STACK_SIZE (1 << 20) +#define X86_64_OPENAT_NR 257 + +typedef enum CallbackQsortPhase { + CALLBACK_QSORT_PHASE_IDLE, + CALLBACK_QSORT_PHASE_NEED_CALLBACK, + CALLBACK_QSORT_PHASE_FINISHED, +} CallbackQsortPhase; + +typedef struct CallbackQsortState { + uint64_t sort_base; + uint64_t guest_cmp_fn; + uint64_t guest_trampoline; + size_t nmemb; + size_t elem_size; + void *worker_stack; + uint64_t pending_guest_a; + uint64_t pending_guest_b; + int cmp_result; + unsigned int guest_callback_count; + CallbackQsortPhase phase; + ucontext_t plugin_ctx; + ucontext_t worker_ctx; +} CallbackQsortState; + +typedef struct X86Registers { + struct qemu_plugin_register *rsp; + struct qemu_plugin_register *rdi; + struct qemu_plugin_register *rsi; +} X86Registers; + +typedef struct VcpuState { + X86Registers regs; + GByteArray *buf; + CallbackQsortState *active_call; +} VcpuState; + +static GPtrArray *vcpu_states; +static __thread CallbackQsortState *tls_active_call; + +static VcpuState *get_vcpu_state(unsigned int vcpu_index) +{ + while (vcpu_states->len <=3D vcpu_index) { + g_ptr_array_add(vcpu_states, NULL); + } + + if (g_ptr_array_index(vcpu_states, vcpu_index) =3D=3D NULL) { + VcpuState *state =3D g_new0(VcpuState, 1); + state->buf =3D g_byte_array_sized_new(16); + g_ptr_array_index(vcpu_states, vcpu_index) =3D state; + } + + return g_ptr_array_index(vcpu_states, vcpu_index); +} + +static struct qemu_plugin_register *find_register(const char *name) +{ + g_autoptr(GArray) regs =3D qemu_plugin_get_registers(); + + for (guint i =3D 0; i < regs->len; i++) { + qemu_plugin_reg_descriptor *reg =3D + &g_array_index(regs, qemu_plugin_reg_descriptor, i); + + if (strcmp(reg->name, name) =3D=3D 0) { + return reg->handle; + } + } + + return NULL; +} + +static uint64_t read_reg64(VcpuState *vcpu, struct qemu_plugin_register *r= eg) +{ + bool success; + + g_byte_array_set_size(vcpu->buf, 0); + success =3D qemu_plugin_read_register(reg, vcpu->buf); + g_assert(success); + g_assert(vcpu->buf->len =3D=3D 8); + return *(uint64_t *)vcpu->buf->data; +} + +static void write_reg64(VcpuState *vcpu, struct qemu_plugin_register *reg, + uint64_t value) +{ + bool success; + + g_byte_array_set_size(vcpu->buf, 8); + memcpy(vcpu->buf->data, &value, sizeof(value)); + success =3D qemu_plugin_write_register(reg, vcpu->buf); + g_assert(success); +} + +static bool write_guest_u64(uint64_t addr, uint64_t value) +{ + GByteArray data =3D { + .data =3D (guint8 *)&value, + .len =3D sizeof(value), + }; + + return qemu_plugin_write_memory_vaddr(addr, &data); +} + +static char *read_guest_cstring(uint64_t addr) +{ + g_autoptr(GByteArray) data =3D g_byte_array_sized_new(GUEST_STRING_CHU= NK); + g_autoptr(GString) str =3D g_string_sized_new(GUEST_STRING_CHUNK); + size_t offset; + + for (offset =3D 0; + offset < GUEST_STRING_LIMIT; + offset +=3D GUEST_STRING_CHUNK) { + g_byte_array_set_size(data, GUEST_STRING_CHUNK); + if (!qemu_plugin_read_memory_vaddr(addr + offset, data, + GUEST_STRING_CHUNK)) { + return NULL; + } + + for (guint i =3D 0; i < data->len; i++) { + if (data->data[i] =3D=3D '\0') { + return g_string_free(g_steal_pointer(&str), FALSE); + } + g_string_append_c(str, data->data[i]); + } + } + + return NULL; +} + +static bool guest_path_matches_bridge(const char *path) +{ + g_autofree char *basename =3D g_path_get_basename(path); + + return strcmp(basename, CALLBACK_QSORT_LIBRARY) =3D=3D 0; +} + +static char *build_thunk_path(const char *path) +{ + g_autofree char *dirname =3D g_path_get_dirname(path); + + if (strcmp(dirname, ".") =3D=3D 0) { + return g_strdup(CALLBACK_QSORT_THUNK_LIBRARY); + } + + return g_build_filename(dirname, CALLBACK_QSORT_THUNK_LIBRARY, NULL); +} + +static int host_compare(const void *lhs, const void *rhs) +{ + CallbackQsortState *state =3D tls_active_call; + state->pending_guest_a =3D (uint64_t)(uintptr_t)lhs; + state->pending_guest_b =3D (uint64_t)(uintptr_t)rhs; + + state->phase =3D CALLBACK_QSORT_PHASE_NEED_CALLBACK; + swapcontext(&state->worker_ctx, &state->plugin_ctx); + return state->cmp_result; +} + +static void callback_qsort_worker(void) +{ + CallbackQsortState *state =3D tls_active_call; + + qsort((void *)(uintptr_t)state->sort_base, state->nmemb, state->elem_s= ize, + host_compare); + state->phase =3D CALLBACK_QSORT_PHASE_FINISHED; + swapcontext(&state->worker_ctx, &state->plugin_ctx); + g_assert_not_reached(); +} + +static void free_callback_state(CallbackQsortState *state) +{ + if (state =3D=3D NULL) { + return; + } + + g_free(state->worker_stack); + g_free(state); +} + +static bool finalize_if_finished(unsigned int vcpu_index, VcpuState *vcpu, + uint64_t *sysret) +{ + CallbackQsortState *state =3D vcpu->active_call; + g_autofree char *out =3D NULL; + + if (state->phase !=3D CALLBACK_QSORT_PHASE_FINISHED) { + return false; + } + + *sysret =3D 0; + + out =3D g_strdup_printf( + "syscall_filter_callback_qsort: vcpu %u completed qsort " + "with %u guest callbacks\n", + vcpu_index, state->guest_callback_count); + qemu_plugin_outs(out); + + free_callback_state(state); + vcpu->active_call =3D NULL; + tls_active_call =3D NULL; + return true; +} + +static void jump_to_guest_callback(VcpuState *vcpu, CallbackQsortState *st= ate) +{ + uint64_t current_rsp =3D read_reg64(vcpu, vcpu->regs.rsp); + uint64_t callback_rsp =3D current_rsp - sizeof(uint64_t); + uint64_t guest_return; + + g_assert((current_rsp & 0xf) =3D=3D 0 || (current_rsp & 0xf) =3D=3D 8); + + if ((callback_rsp & 0xf) !=3D 8) { + /* + * Rebuild the stack so the guest comparator sees a normal SysV + * function entry: %rsp points at its return address and + * %rsp % 16 =3D=3D 8. + */ + callback_rsp -=3D sizeof(uint64_t); + read_guest_buffer(current_rsp, &guest_return, sizeof(guest_return)= ); + g_assert(write_guest_u64(current_rsp - sizeof(uint64_t), + guest_return)); + } + + g_assert(write_guest_u64(callback_rsp, state->guest_trampoline)); + + write_reg64(vcpu, vcpu->regs.rsp, callback_rsp); + write_reg64(vcpu, vcpu->regs.rdi, state->pending_guest_a); + write_reg64(vcpu, vcpu->regs.rsi, state->pending_guest_b); + state->guest_callback_count++; + qemu_plugin_set_pc(state->guest_cmp_fn); +} + +static bool handle_library_open(int64_t num, uint64_t a1, uint64_t a2, + uint64_t a3, uint64_t a4, uint64_t *sysret) +{ + g_autofree char *path =3D NULL; + g_autofree char *thunk_path =3D NULL; + g_autofree char *out =3D NULL; + int fd; + + if (num !=3D X86_64_OPENAT_NR) { + return false; + } + + path =3D read_guest_cstring(a2); + if (path =3D=3D NULL || !guest_path_matches_bridge(path)) { + return false; + } + + thunk_path =3D build_thunk_path(path); + if (access(thunk_path, F_OK) !=3D 0) { + return false; + } + + fd =3D openat((int)a1, thunk_path, (int)a3, (mode_t)a4); + g_assert(fd >=3D 0); + + *sysret =3D fd; + out =3D g_strdup_printf( + "syscall_filter_callback_qsort: redirected %s -> %s (fd=3D%d)\n", + path, thunk_path, fd); + qemu_plugin_outs(out); + return true; +} + +static bool handle_callback_start(unsigned int vcpu_index, VcpuState *vcpu, + uint64_t sort_base, uint64_t nmemb, + uint64_t elem_size, uint64_t cmp_fn, + uint64_t trampoline, uint64_t *sysret) +{ + CallbackQsortState *state; + + g_assert(vcpu->active_call =3D=3D NULL); + g_assert(elem_size > 0); + g_assert(nmemb > 0); + g_assert(nmemb <=3D CALLBACK_QSORT_MAX_ELEMS); + g_assert(cmp_fn !=3D 0 && trampoline !=3D 0); + + state =3D g_new0(CallbackQsortState, 1); + state->sort_base =3D sort_base; + state->guest_cmp_fn =3D cmp_fn; + state->guest_trampoline =3D trampoline; + state->nmemb =3D nmemb; + state->elem_size =3D elem_size; + state->worker_stack =3D g_malloc(CALLBACK_QSORT_STACK_SIZE); + state->phase =3D CALLBACK_QSORT_PHASE_IDLE; + + getcontext(&state->worker_ctx); + state->worker_ctx.uc_link =3D NULL; + state->worker_ctx.uc_stack.ss_sp =3D state->worker_stack; + state->worker_ctx.uc_stack.ss_size =3D CALLBACK_QSORT_STACK_SIZE; + makecontext(&state->worker_ctx, callback_qsort_worker, 0); + + vcpu->active_call =3D state; + tls_active_call =3D state; + swapcontext(&state->plugin_ctx, &state->worker_ctx); + + if (finalize_if_finished(vcpu_index, vcpu, sysret)) { + return true; + } + + if (state->phase =3D=3D CALLBACK_QSORT_PHASE_NEED_CALLBACK) { + jump_to_guest_callback(vcpu, state); + return true; + } + + g_assert_not_reached(); +} + +static bool handle_callback_resume(unsigned int vcpu_index, VcpuState *vcp= u, + int64_t cmp_result, uint64_t *sysret) +{ + CallbackQsortState *state =3D vcpu->active_call; + + g_assert(state !=3D NULL); + + state->cmp_result =3D (int)cmp_result; + tls_active_call =3D state; + swapcontext(&state->plugin_ctx, &state->worker_ctx); + + if (finalize_if_finished(vcpu_index, vcpu, sysret)) { + return true; + } + + if (state->phase =3D=3D CALLBACK_QSORT_PHASE_NEED_CALLBACK) { + jump_to_guest_callback(vcpu, state); + return true; + } + + g_assert_not_reached(); +} + +static bool vcpu_syscall_filter(qemu_plugin_id_t id, unsigned int vcpu_ind= ex, + int64_t num, uint64_t a1, uint64_t a2, + uint64_t a3, uint64_t a4, uint64_t a5, + uint64_t a6, uint64_t a7, uint64_t a8, + uint64_t *sysret) +{ + VcpuState *vcpu =3D get_vcpu_state(vcpu_index); + + if (handle_library_open(num, a1, a2, a3, a4, sysret)) { + return true; + } + + if (num !=3D MAGIC_SYSCALL) { + return false; + } + + switch (a1) { + case CALLBACK_QSORT_OP_START: + return handle_callback_start(vcpu_index, vcpu, a2, a3, a4, a5, a6, + sysret); + case CALLBACK_QSORT_OP_RESUME: + return handle_callback_resume(vcpu_index, vcpu, (int64_t)a2, sysre= t); + default: + return false; + } +} + +static void vcpu_init_cb(qemu_plugin_id_t id, unsigned int vcpu_index) +{ + VcpuState *vcpu =3D get_vcpu_state(vcpu_index); + + vcpu->regs.rsp =3D find_register("rsp"); + vcpu->regs.rdi =3D find_register("rdi"); + vcpu->regs.rsi =3D find_register("rsi"); + g_assert(vcpu->regs.rsp !=3D NULL); + g_assert(vcpu->regs.rdi !=3D NULL); + g_assert(vcpu->regs.rsi !=3D NULL); +} + +QEMU_PLUGIN_EXPORT int qemu_plugin_install(qemu_plugin_id_t id, + const qemu_info_t *info, + int argc, char **argv) +{ + if (argc !=3D 0) { + fprintf(stderr, + "syscall_filter_callback_qsort: this prototype plugin does= not take arguments\n"); + return -1; + } + + if (strcmp(info->target_name, "x86_64") !=3D 0) { + fprintf(stderr, + "syscall_filter_callback_qsort: unsupported linux-user tar= get '%s' " + "(this prototype currently supports only x86_64)\n", + info->target_name); + return -1; + } + + vcpu_states =3D g_ptr_array_new(); + qemu_plugin_register_vcpu_init_cb(id, vcpu_init_cb); + qemu_plugin_register_vcpu_syscall_filter_cb(id, vcpu_syscall_filter); + return 0; +} --=20 2.53.0