Patch Detail
get:
Show a patch.
patch:
Update a patch.
put:
Update a patch.
GET /api/patches/2218277/?format=api
{ "id": 2218277, "url": "http://patchwork.ozlabs.org/api/patches/2218277/?format=api", "web_url": "http://patchwork.ozlabs.org/project/qemu-devel/patch/20260331173656.35305-3-xukl2019@sjtu.edu.cn/", "project": { "id": 14, "url": "http://patchwork.ozlabs.org/api/projects/14/?format=api", "name": "QEMU Development", "link_name": "qemu-devel", "list_id": "qemu-devel.nongnu.org", "list_email": "qemu-devel@nongnu.org", "web_url": "", "scm_url": "", "webscm_url": "", "list_archive_url": "", "list_archive_url_format": "", "commit_url_format": "" }, "msgid": "<20260331173656.35305-3-xukl2019@sjtu.edu.cn>", "list_archive_url": null, "date": "2026-03-31T17:36:25", "name": "[v1,2/3] contrib/plugins: add a callback-capable qsort prototype", "commit_ref": null, "pull_url": null, "state": "new", "archived": false, "hash": "9ef0e17f82279cd30567a5e51349847d1c7dd41d", "submitter": { "id": 93022, "url": "http://patchwork.ozlabs.org/api/people/93022/?format=api", "name": "XU Kailiang", "email": "xukl2019@sjtu.edu.cn" }, "delegate": null, "mbox": "http://patchwork.ozlabs.org/project/qemu-devel/patch/20260331173656.35305-3-xukl2019@sjtu.edu.cn/mbox/", "series": [ { "id": 498254, "url": "http://patchwork.ozlabs.org/api/series/498254/?format=api", "web_url": "http://patchwork.ozlabs.org/project/qemu-devel/list/?series=498254", "date": "2026-03-31T17:36:25", "name": "contrib/plugins: add syscall-filter local-library demos", "version": 1, "mbox": "http://patchwork.ozlabs.org/series/498254/mbox/" } ], "comments": "http://patchwork.ozlabs.org/api/patches/2218277/comments/", "check": "pending", "checks": "http://patchwork.ozlabs.org/api/patches/2218277/checks/", "tags": {}, "related": [], "headers": { "Return-Path": "<qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org>", "X-Original-To": "incoming@patchwork.ozlabs.org", "Delivered-To": "patchwork-incoming@legolas.ozlabs.org", "Authentication-Results": "legolas.ozlabs.org;\n spf=pass (sender SPF authorized) smtp.mailfrom=nongnu.org\n (client-ip=209.51.188.17; helo=lists.gnu.org;\n envelope-from=qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org;\n receiver=patchwork.ozlabs.org)", "Received": [ "from lists.gnu.org (lists.gnu.org [209.51.188.17])\n\t(using TLSv1.2 with cipher ECDHE-ECDSA-AES256-GCM-SHA384 (256/256 bits))\n\t(No client certificate requested)\n\tby legolas.ozlabs.org (Postfix) with ESMTPS id 4flgwF0WBqz1yFv\n\tfor <incoming@patchwork.ozlabs.org>; Wed, 01 Apr 2026 08:18:51 +1100 (AEDT)", "from localhost ([::1] helo=lists1p.gnu.org)\n\tby lists.gnu.org with esmtp (Exim 4.90_1)\n\t(envelope-from <qemu-devel-bounces@nongnu.org>)\n\tid 1w7gTL-0008SX-SJ; Tue, 31 Mar 2026 17:18:03 -0400", "from eggs.gnu.org ([2001:470:142:3::10])\n by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256)\n (Exim 4.90_1) (envelope-from <xukl2019@sjtu.edu.cn>)\n id 1w7dE7-0007Pp-1a\n for qemu-devel@nongnu.org; Tue, 31 Mar 2026 13:50:07 -0400", "from smtp232.sjtu.edu.cn ([202.120.2.232])\n by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256)\n (Exim 4.90_1) (envelope-from <xukl2019@sjtu.edu.cn>)\n id 1w7dE3-0000M9-4f\n for qemu-devel@nongnu.org; Tue, 31 Mar 2026 13:50:06 -0400", "from proxy188.sjtu.edu.cn (smtp188.sjtu.edu.cn [202.120.2.188])\n by smtp232.sjtu.edu.cn (Postfix) with ESMTPS id 0D1FB10097F65;\n Wed, 1 Apr 2026 01:38:09 +0800 (CST)", "from xuklXiaoxin (unknown [202.120.32.222])\n by proxy188.sjtu.edu.cn (Postfix) with ESMTPSA id DAF7037C8CA;\n Wed, 1 Apr 2026 01:38:08 +0800 (CST)" ], "From": "XU Kailiang <xukl2019@sjtu.edu.cn>", "To": "qemu-devel <qemu-devel@nongnu.org>", "Cc": "=?utf-8?q?Alex_Benn=C3=A9e?= <alex.bennee@linaro.org>,\n Pierrick Bouvier <pierrick.bouvier@linaro.org>,\n Alexandre Iooss <erdnaxe@crans.org>, Mahmoud Mandour <ma.mandourr@gmail.com>,\n Ziyang Zhang <functioner@sjtu.edu.cn>, Yun Wang <yunwang94@sjtu.edu.cn>,\n Mingyuan Xia <xiamy@ultrarisc.com>, Zhengwei Qi <qizhwei@sjtu.edu.cn>,\n XU Kailiang <xukl2019@sjtu.edu.cn>", "Subject": "[PATCH v1 2/3] contrib/plugins: add a callback-capable qsort\n 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": "8bit", "Received-SPF": "pass client-ip=202.120.2.232;\n envelope-from=xukl2019@sjtu.edu.cn;\n 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,\n RCVD_IN_VALIDITY_CERTIFIED_BLOCKED=1, RCVD_IN_VALIDITY_RPBL_BLOCKED=1,\n 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 <qemu-devel.nongnu.org>", "List-Unsubscribe": "<https://lists.nongnu.org/mailman/options/qemu-devel>,\n <mailto:qemu-devel-request@nongnu.org?subject=unsubscribe>", "List-Archive": "<https://lists.nongnu.org/archive/html/qemu-devel>", "List-Post": "<mailto:qemu-devel@nongnu.org>", "List-Help": "<mailto:qemu-devel-request@nongnu.org?subject=help>", "List-Subscribe": "<https://lists.nongnu.org/mailman/listinfo/qemu-devel>,\n <mailto:qemu-devel-request@nongnu.org?subject=subscribe>", "Errors-To": "qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org", "Sender": "qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org" }, "content": "Add a second linux-user example that demonstrates a more advanced use of\nthe syscall filter API: redirecting a thunk library for qsort() and\nbridging comparator callbacks back into guest translated code.\n\nThe guest-side prototype links against libdemo-callback-qsort.so. The\nplugin redirects the loader's openat() to\nlibdemo-callback-qsort-thunk.so, runs host qsort() inside a ucontext\nworker, and on each comparator invocation rewrites guest register state\nand PC so execution continues in the guest comparator. The guest return\ntrampoline then resumes the suspended host call with a second magic\nsyscall.\n\nAt this stage, the loader redirection covers only the openat()-based\nlibrary load path. The follow-up patch in this series extends the same\nprototype to open() and openat2().\n\nOnce the demo library path matches, the plugin asserts that opening the\nthunk library succeeds. That is intentional here: this prototype is\nmeant to show the callback bridge once a known local thunk library is in\nplace, not fallback behavior when that local library is missing or\nunusable.\n\nKeep the scope explicit: this is an x86_64-only prototype intended to\nshow what the interface can support, not a general callback ABI.\n\nSigned-off-by: XU Kailiang <xukl2019@sjtu.edu.cn>\nCo-authored-by: Ziyang Zhang <functioner@sjtu.edu.cn>\n---\n contrib/plugins/meson.build | 12 +-\n .../Makefile | 19 +\n .../README.rst | 46 ++\n .../callback-demo.c | 75 +++\n .../callback-qsort.h | 14 +\n .../callback-thunk.S | 38 ++\n .../plugins/syscall_filter_callback_qsort.c | 431 ++++++++++++++++++\n 7 files changed, 631 insertions(+), 4 deletions(-)\n create mode 100644 contrib/plugins/syscall_filter_callback_qsort-example/Makefile\n create mode 100644 contrib/plugins/syscall_filter_callback_qsort-example/README.rst\n create mode 100644 contrib/plugins/syscall_filter_callback_qsort-example/callback-demo.c\n create mode 100644 contrib/plugins/syscall_filter_callback_qsort-example/callback-qsort.h\n create mode 100644 contrib/plugins/syscall_filter_callback_qsort-example/callback-thunk.S\n create mode 100644 contrib/plugins/syscall_filter_callback_qsort.c", "diff": "diff --git a/contrib/plugins/meson.build b/contrib/plugins/meson.build\nindex aa3b95eeab..cc82255666 100644\n--- a/contrib/plugins/meson.build\n+++ b/contrib/plugins/meson.build\n@@ -19,10 +19,14 @@ if host_os != 'windows'\n contrib_plugins += 'lockstep.c'\n endif\n \n-if host_os != 'windows' and host_os != 'darwin' and\n- host_machine.endian() == 'little'\n- # The zlib syscall-filter demo assumes little-endian direct-pointer access.\n- contrib_plugins += 'syscall_filter_zlib.c'\n+if host_os != 'windows' and host_os != 'darwin'\n+ # The syscall-filter demos assume little-endian direct-pointer access.\n+ if host_machine.endian() == 'little'\n+ contrib_plugins += 'syscall_filter_zlib.c'\n+ if supported_backends.contains('ucontext')\n+ contrib_plugins += 'syscall_filter_callback_qsort.c'\n+ endif\n+ endif\n endif\n \n if 'cpp' in all_languages\ndiff --git a/contrib/plugins/syscall_filter_callback_qsort-example/Makefile b/contrib/plugins/syscall_filter_callback_qsort-example/Makefile\nnew file mode 100644\nindex 0000000000..0144e46790\n--- /dev/null\n+++ b/contrib/plugins/syscall_filter_callback_qsort-example/Makefile\n@@ -0,0 +1,19 @@\n+# SPDX-License-Identifier: GPL-2.0-or-later\n+\n+CC ?= cc\n+CFLAGS ?=\n+LDFLAGS ?=\n+RM ?= rm -f\n+\n+all: libdemo-callback-qsort-thunk.so callback-demo\n+\n+libdemo-callback-qsort-thunk.so: callback-thunk.S\n+\t$(CC) $(CFLAGS) -fPIC -shared callback-thunk.S -Wl,-soname,libdemo-callback-qsort.so -o $@ $(LDFLAGS)\n+\n+callback-demo: callback-demo.c callback-qsort.h libdemo-callback-qsort-thunk.so\n+\t$(CC) $(CFLAGS) callback-demo.c ./libdemo-callback-qsort-thunk.so -Wl,-rpath,'$$ORIGIN' -o $@ $(LDFLAGS)\n+\n+clean:\n+\t$(RM) libdemo-callback-qsort-thunk.so callback-demo\n+\n+.PHONY: all clean\ndiff --git a/contrib/plugins/syscall_filter_callback_qsort-example/README.rst b/contrib/plugins/syscall_filter_callback_qsort-example/README.rst\nnew file mode 100644\nindex 0000000000..e2bba7e2b6\n--- /dev/null\n+++ b/contrib/plugins/syscall_filter_callback_qsort-example/README.rst\n@@ -0,0 +1,46 @@\n+.. SPDX-License-Identifier: GPL-2.0-or-later\n+\n+Callback qsort bridge prototype\n+===============================\n+\n+This directory contains a prototype for callback-capable local library\n+interception on ``qemu-x86_64``.\n+\n+* ``callback-demo.c`` is linked against ``libdemo-callback-qsort.so`` and calls\n+ ``callback_qsort()`` directly.\n+* The plugin intercepts the loader's ``openat()`` and returns a file\n+ descriptor for ``./libdemo-callback-qsort-thunk.so`` instead.\n+* ``callback-thunk.S`` issues a ``START`` magic syscall for ``qsort()`` and a\n+ ``RESUME`` magic syscall from a guest return trampoline.\n+* ``contrib/plugins/syscall_filter_callback_qsort.c`` runs host ``qsort()``\n+ inside a ``ucontext`` worker, yields on every comparator invocation, and\n+ redirects control flow into the guest comparator with\n+ ``qemu_plugin_set_pc()``.\n+* This callback prototype is intentionally ``x86_64``-only. Supporting\n+ additional targets would require target-specific register and ABI handling,\n+ which is beyond the scope of this example.\n+* The plugin is built only on hosts where ``ucontext`` is available.\n+* To keep the prototype minimal, it assumes ``guest_base == 0`` on a\n+ little-endian 64-bit host. For this x86_64 linux-user demo, that means guest\n+ virtual addresses are directly usable as host pointers. In practice that\n+ means running ``qemu-x86_64`` on a little-endian 64-bit host without forcing\n+ a nonzero guest base.\n+\n+Build the guest-side prototype with::\n+\n+ make\n+\n+Then run it from this directory with QEMU linux-user and the plugin::\n+\n+ QEMU_BUILD=/path/to/qemu/build\n+ $QEMU_BUILD/qemu-x86_64 \\\n+ -plugin $QEMU_BUILD/contrib/plugins/libsyscall_filter_callback_qsort.so \\\n+ -d plugin \\\n+ ./callback-demo\n+\n+The build links ``callback-demo`` against ``libdemo-callback-qsort-thunk.so``\n+while giving that shared object the soname ``libdemo-callback-qsort.so``.\n+Without the plugin, the program fails at startup because no\n+``libdemo-callback-qsort.so`` file exists in the runtime search path. With the\n+plugin, the guest sees a working library load even though the loader actually\n+receives ``libdemo-callback-qsort-thunk.so``.\ndiff --git a/contrib/plugins/syscall_filter_callback_qsort-example/callback-demo.c b/contrib/plugins/syscall_filter_callback_qsort-example/callback-demo.c\nnew file mode 100644\nindex 0000000000..5e89d9f022\n--- /dev/null\n+++ b/contrib/plugins/syscall_filter_callback_qsort-example/callback-demo.c\n@@ -0,0 +1,75 @@\n+/*\n+ * SPDX-License-Identifier: GPL-2.0-or-later\n+ */\n+\n+#include <inttypes.h>\n+#include <stdint.h>\n+#include <stdio.h>\n+#include <stdlib.h>\n+\n+#include \"callback-qsort.h\"\n+\n+#define VALUE_COUNT 128\n+\n+static int compare_count;\n+\n+static int guest_compare_int32(const void *lhs, const void *rhs)\n+{\n+ const int32_t *a = lhs;\n+ const int32_t *b = rhs;\n+\n+ compare_count++;\n+ if (*a < *b) {\n+ return -1;\n+ }\n+ if (*a > *b) {\n+ return 1;\n+ }\n+ return 0;\n+}\n+\n+static int is_sorted(const int32_t *values, size_t count)\n+{\n+ size_t i;\n+\n+ for (i = 1; i < count; i++) {\n+ if (values[i - 1] > values[i]) {\n+ return 0;\n+ }\n+ }\n+\n+ return 1;\n+}\n+\n+int main(void)\n+{\n+ int32_t values[VALUE_COUNT];\n+ size_t i;\n+ int ret;\n+\n+ for (i = 0; i < VALUE_COUNT; i++) {\n+ values[i] = (int32_t)(VALUE_COUNT - i);\n+ }\n+\n+ ret = callback_qsort(values, VALUE_COUNT, sizeof(values[0]),\n+ guest_compare_int32);\n+ if (ret != 0) {\n+ fprintf(stderr, \"callback_qsort failed: %d\\n\", ret);\n+ return EXIT_FAILURE;\n+ }\n+\n+ if (!is_sorted(values, VALUE_COUNT)) {\n+ fprintf(stderr, \"values are not sorted\\n\");\n+ return EXIT_FAILURE;\n+ }\n+\n+ if (compare_count == 0) {\n+ fprintf(stderr, \"guest comparator never ran\\n\");\n+ return EXIT_FAILURE;\n+ }\n+\n+ printf(\"callback demo sorted %u values with %d guest comparator calls\\n\",\n+ VALUE_COUNT, compare_count);\n+ puts(\"callback demo succeeded\");\n+ return EXIT_SUCCESS;\n+}\ndiff --git a/contrib/plugins/syscall_filter_callback_qsort-example/callback-qsort.h b/contrib/plugins/syscall_filter_callback_qsort-example/callback-qsort.h\nnew file mode 100644\nindex 0000000000..4a1021841d\n--- /dev/null\n+++ b/contrib/plugins/syscall_filter_callback_qsort-example/callback-qsort.h\n@@ -0,0 +1,14 @@\n+/*\n+ * SPDX-License-Identifier: GPL-2.0-or-later\n+ */\n+\n+#ifndef CONTRIB_PLUGINS_SYSCALL_FILTER_CALLBACK_QSORT_EXAMPLE_CALLBACK_QSORT_H\n+#define CONTRIB_PLUGINS_SYSCALL_FILTER_CALLBACK_QSORT_EXAMPLE_CALLBACK_QSORT_H\n+\n+#include <stddef.h>\n+typedef int (*callback_qsort_cmp_fn)(const void *lhs, const void *rhs);\n+\n+int callback_qsort(void *base, size_t nmemb, size_t size,\n+ callback_qsort_cmp_fn cmp);\n+\n+#endif\ndiff --git a/contrib/plugins/syscall_filter_callback_qsort-example/callback-thunk.S b/contrib/plugins/syscall_filter_callback_qsort-example/callback-thunk.S\nnew file mode 100644\nindex 0000000000..a8d9607d5c\n--- /dev/null\n+++ b/contrib/plugins/syscall_filter_callback_qsort-example/callback-thunk.S\n@@ -0,0 +1,38 @@\n+/*\n+ * SPDX-License-Identifier: GPL-2.0-or-later\n+ */\n+\n+#define MAGIC_SYSCALL 4096\n+#define CALLBACK_BRIDGE_OP_START 1\n+#define CALLBACK_BRIDGE_OP_RESUME 2\n+\n+ .text\n+ .hidden callback_qsort_resume_trampoline\n+ .globl callback_qsort\n+ .type callback_qsort, @function\n+callback_qsort:\n+ /*\n+ * callback_qsort(base, nmemb, elem_size, cmp_fn) arrives in\n+ * %rdi, %rsi, %rdx, and %rcx. Shuffle those into the x86_64 Linux\n+ * syscall ABI so START sees: op, base, nmemb, elem_size, cmp_fn,\n+ * trampoline.\n+ */\n+ mov %rcx, %r8\n+ mov %rdx, %r10\n+ mov %rsi, %rdx\n+ lea callback_qsort_resume_trampoline(%rip), %r9\n+ mov %rdi, %rsi\n+ mov $CALLBACK_BRIDGE_OP_START, %rdi\n+ mov $MAGIC_SYSCALL, %rax\n+ syscall\n+ ret\n+\n+ .globl callback_qsort_resume_trampoline\n+ .type callback_qsort_resume_trampoline, @function\n+callback_qsort_resume_trampoline:\n+ /* Sign-extend the guest comparator result into the RESUME argument. */\n+ movslq %eax, %rsi\n+ mov $CALLBACK_BRIDGE_OP_RESUME, %rdi\n+ mov $MAGIC_SYSCALL, %rax\n+ syscall\n+ ret\ndiff --git a/contrib/plugins/syscall_filter_callback_qsort.c b/contrib/plugins/syscall_filter_callback_qsort.c\nnew file mode 100644\nindex 0000000000..45a83cb5b1\n--- /dev/null\n+++ b/contrib/plugins/syscall_filter_callback_qsort.c\n@@ -0,0 +1,431 @@\n+/*\n+ * SPDX-License-Identifier: GPL-2.0-or-later\n+ *\n+ * x86_64-only prototype that demonstrates a callback-capable syscall filter\n+ * plugin by redirecting a qsort() thunk library and bridging comparator calls\n+ * back into guest translated code with ucontext. The loader redirection\n+ * handles openat().\n+ *\n+ * This demo intentionally assumes a linux-user run with guest_base == 0 on a\n+ * little-endian 64-bit host, so guest virtual addresses are directly usable\n+ * as host pointers.\n+ */\n+\n+#include <fcntl.h>\n+#include <glib.h>\n+#include <inttypes.h>\n+#include <stdbool.h>\n+#include <stddef.h>\n+#include <stdint.h>\n+#include <stdio.h>\n+#include <stdlib.h>\n+#include <string.h>\n+#include <ucontext.h>\n+#include <unistd.h>\n+\n+#include <qemu-plugin.h>\n+\n+QEMU_PLUGIN_EXPORT int qemu_plugin_version = QEMU_PLUGIN_VERSION;\n+\n+#define MAGIC_SYSCALL 4096\n+#define CALLBACK_QSORT_OP_START 1\n+#define CALLBACK_QSORT_OP_RESUME 2\n+#define CALLBACK_QSORT_LIBRARY \"libdemo-callback-qsort.so\"\n+#define CALLBACK_QSORT_THUNK_LIBRARY \"libdemo-callback-qsort-thunk.so\"\n+#define GUEST_STRING_CHUNK 64\n+#define GUEST_STRING_LIMIT (1 << 20)\n+#define CALLBACK_QSORT_MAX_ELEMS (1 << 20)\n+#define CALLBACK_QSORT_STACK_SIZE (1 << 20)\n+#define X86_64_OPENAT_NR 257\n+\n+typedef enum CallbackQsortPhase {\n+ CALLBACK_QSORT_PHASE_IDLE,\n+ CALLBACK_QSORT_PHASE_NEED_CALLBACK,\n+ CALLBACK_QSORT_PHASE_FINISHED,\n+} CallbackQsortPhase;\n+\n+typedef struct CallbackQsortState {\n+ uint64_t sort_base;\n+ uint64_t guest_cmp_fn;\n+ uint64_t guest_trampoline;\n+ size_t nmemb;\n+ size_t elem_size;\n+ void *worker_stack;\n+ uint64_t pending_guest_a;\n+ uint64_t pending_guest_b;\n+ int cmp_result;\n+ unsigned int guest_callback_count;\n+ CallbackQsortPhase phase;\n+ ucontext_t plugin_ctx;\n+ ucontext_t worker_ctx;\n+} CallbackQsortState;\n+\n+typedef struct X86Registers {\n+ struct qemu_plugin_register *rsp;\n+ struct qemu_plugin_register *rdi;\n+ struct qemu_plugin_register *rsi;\n+} X86Registers;\n+\n+typedef struct VcpuState {\n+ X86Registers regs;\n+ GByteArray *buf;\n+ CallbackQsortState *active_call;\n+} VcpuState;\n+\n+static GPtrArray *vcpu_states;\n+static __thread CallbackQsortState *tls_active_call;\n+\n+static VcpuState *get_vcpu_state(unsigned int vcpu_index)\n+{\n+ while (vcpu_states->len <= vcpu_index) {\n+ g_ptr_array_add(vcpu_states, NULL);\n+ }\n+\n+ if (g_ptr_array_index(vcpu_states, vcpu_index) == NULL) {\n+ VcpuState *state = g_new0(VcpuState, 1);\n+ state->buf = g_byte_array_sized_new(16);\n+ g_ptr_array_index(vcpu_states, vcpu_index) = state;\n+ }\n+\n+ return g_ptr_array_index(vcpu_states, vcpu_index);\n+}\n+\n+static struct qemu_plugin_register *find_register(const char *name)\n+{\n+ g_autoptr(GArray) regs = qemu_plugin_get_registers();\n+\n+ for (guint i = 0; i < regs->len; i++) {\n+ qemu_plugin_reg_descriptor *reg =\n+ &g_array_index(regs, qemu_plugin_reg_descriptor, i);\n+\n+ if (strcmp(reg->name, name) == 0) {\n+ return reg->handle;\n+ }\n+ }\n+\n+ return NULL;\n+}\n+\n+static uint64_t read_reg64(VcpuState *vcpu, struct qemu_plugin_register *reg)\n+{\n+ bool success;\n+\n+ g_byte_array_set_size(vcpu->buf, 0);\n+ success = qemu_plugin_read_register(reg, vcpu->buf);\n+ g_assert(success);\n+ g_assert(vcpu->buf->len == 8);\n+ return *(uint64_t *)vcpu->buf->data;\n+}\n+\n+static void write_reg64(VcpuState *vcpu, struct qemu_plugin_register *reg,\n+ uint64_t value)\n+{\n+ bool success;\n+\n+ g_byte_array_set_size(vcpu->buf, 8);\n+ memcpy(vcpu->buf->data, &value, sizeof(value));\n+ success = qemu_plugin_write_register(reg, vcpu->buf);\n+ g_assert(success);\n+}\n+\n+static bool write_guest_u64(uint64_t addr, uint64_t value)\n+{\n+ GByteArray data = {\n+ .data = (guint8 *)&value,\n+ .len = sizeof(value),\n+ };\n+\n+ return qemu_plugin_write_memory_vaddr(addr, &data);\n+}\n+\n+static char *read_guest_cstring(uint64_t addr)\n+{\n+ g_autoptr(GByteArray) data = g_byte_array_sized_new(GUEST_STRING_CHUNK);\n+ g_autoptr(GString) str = g_string_sized_new(GUEST_STRING_CHUNK);\n+ size_t offset;\n+\n+ for (offset = 0;\n+ offset < GUEST_STRING_LIMIT;\n+ offset += GUEST_STRING_CHUNK) {\n+ g_byte_array_set_size(data, GUEST_STRING_CHUNK);\n+ if (!qemu_plugin_read_memory_vaddr(addr + offset, data,\n+ GUEST_STRING_CHUNK)) {\n+ return NULL;\n+ }\n+\n+ for (guint i = 0; i < data->len; i++) {\n+ if (data->data[i] == '\\0') {\n+ return g_string_free(g_steal_pointer(&str), FALSE);\n+ }\n+ g_string_append_c(str, data->data[i]);\n+ }\n+ }\n+\n+ return NULL;\n+}\n+\n+static bool guest_path_matches_bridge(const char *path)\n+{\n+ g_autofree char *basename = g_path_get_basename(path);\n+\n+ return strcmp(basename, CALLBACK_QSORT_LIBRARY) == 0;\n+}\n+\n+static char *build_thunk_path(const char *path)\n+{\n+ g_autofree char *dirname = g_path_get_dirname(path);\n+\n+ if (strcmp(dirname, \".\") == 0) {\n+ return g_strdup(CALLBACK_QSORT_THUNK_LIBRARY);\n+ }\n+\n+ return g_build_filename(dirname, CALLBACK_QSORT_THUNK_LIBRARY, NULL);\n+}\n+\n+static int host_compare(const void *lhs, const void *rhs)\n+{\n+ CallbackQsortState *state = tls_active_call;\n+ state->pending_guest_a = (uint64_t)(uintptr_t)lhs;\n+ state->pending_guest_b = (uint64_t)(uintptr_t)rhs;\n+\n+ state->phase = CALLBACK_QSORT_PHASE_NEED_CALLBACK;\n+ swapcontext(&state->worker_ctx, &state->plugin_ctx);\n+ return state->cmp_result;\n+}\n+\n+static void callback_qsort_worker(void)\n+{\n+ CallbackQsortState *state = tls_active_call;\n+\n+ qsort((void *)(uintptr_t)state->sort_base, state->nmemb, state->elem_size,\n+ host_compare);\n+ state->phase = CALLBACK_QSORT_PHASE_FINISHED;\n+ swapcontext(&state->worker_ctx, &state->plugin_ctx);\n+ g_assert_not_reached();\n+}\n+\n+static void free_callback_state(CallbackQsortState *state)\n+{\n+ if (state == NULL) {\n+ return;\n+ }\n+\n+ g_free(state->worker_stack);\n+ g_free(state);\n+}\n+\n+static bool finalize_if_finished(unsigned int vcpu_index, VcpuState *vcpu,\n+ uint64_t *sysret)\n+{\n+ CallbackQsortState *state = vcpu->active_call;\n+ g_autofree char *out = NULL;\n+\n+ if (state->phase != CALLBACK_QSORT_PHASE_FINISHED) {\n+ return false;\n+ }\n+\n+ *sysret = 0;\n+\n+ out = g_strdup_printf(\n+ \"syscall_filter_callback_qsort: vcpu %u completed qsort \"\n+ \"with %u guest callbacks\\n\",\n+ vcpu_index, state->guest_callback_count);\n+ qemu_plugin_outs(out);\n+\n+ free_callback_state(state);\n+ vcpu->active_call = NULL;\n+ tls_active_call = NULL;\n+ return true;\n+}\n+\n+static void jump_to_guest_callback(VcpuState *vcpu, CallbackQsortState *state)\n+{\n+ uint64_t current_rsp = read_reg64(vcpu, vcpu->regs.rsp);\n+ uint64_t callback_rsp = current_rsp - sizeof(uint64_t);\n+ uint64_t guest_return;\n+\n+ g_assert((current_rsp & 0xf) == 0 || (current_rsp & 0xf) == 8);\n+\n+ if ((callback_rsp & 0xf) != 8) {\n+ /*\n+ * Rebuild the stack so the guest comparator sees a normal SysV\n+ * function entry: %rsp points at its return address and\n+ * %rsp % 16 == 8.\n+ */\n+ callback_rsp -= sizeof(uint64_t);\n+ read_guest_buffer(current_rsp, &guest_return, sizeof(guest_return));\n+ g_assert(write_guest_u64(current_rsp - sizeof(uint64_t),\n+ guest_return));\n+ }\n+\n+ g_assert(write_guest_u64(callback_rsp, state->guest_trampoline));\n+\n+ write_reg64(vcpu, vcpu->regs.rsp, callback_rsp);\n+ write_reg64(vcpu, vcpu->regs.rdi, state->pending_guest_a);\n+ write_reg64(vcpu, vcpu->regs.rsi, state->pending_guest_b);\n+ state->guest_callback_count++;\n+ qemu_plugin_set_pc(state->guest_cmp_fn);\n+}\n+\n+static bool handle_library_open(int64_t num, uint64_t a1, uint64_t a2,\n+ uint64_t a3, uint64_t a4, uint64_t *sysret)\n+{\n+ g_autofree char *path = NULL;\n+ g_autofree char *thunk_path = NULL;\n+ g_autofree char *out = NULL;\n+ int fd;\n+\n+ if (num != X86_64_OPENAT_NR) {\n+ return false;\n+ }\n+\n+ path = read_guest_cstring(a2);\n+ if (path == NULL || !guest_path_matches_bridge(path)) {\n+ return false;\n+ }\n+\n+ thunk_path = build_thunk_path(path);\n+ if (access(thunk_path, F_OK) != 0) {\n+ return false;\n+ }\n+\n+ fd = openat((int)a1, thunk_path, (int)a3, (mode_t)a4);\n+ g_assert(fd >= 0);\n+\n+ *sysret = fd;\n+ out = g_strdup_printf(\n+ \"syscall_filter_callback_qsort: redirected %s -> %s (fd=%d)\\n\",\n+ path, thunk_path, fd);\n+ qemu_plugin_outs(out);\n+ return true;\n+}\n+\n+static bool handle_callback_start(unsigned int vcpu_index, VcpuState *vcpu,\n+ uint64_t sort_base, uint64_t nmemb,\n+ uint64_t elem_size, uint64_t cmp_fn,\n+ uint64_t trampoline, uint64_t *sysret)\n+{\n+ CallbackQsortState *state;\n+\n+ g_assert(vcpu->active_call == NULL);\n+ g_assert(elem_size > 0);\n+ g_assert(nmemb > 0);\n+ g_assert(nmemb <= CALLBACK_QSORT_MAX_ELEMS);\n+ g_assert(cmp_fn != 0 && trampoline != 0);\n+\n+ state = g_new0(CallbackQsortState, 1);\n+ state->sort_base = sort_base;\n+ state->guest_cmp_fn = cmp_fn;\n+ state->guest_trampoline = trampoline;\n+ state->nmemb = nmemb;\n+ state->elem_size = elem_size;\n+ state->worker_stack = g_malloc(CALLBACK_QSORT_STACK_SIZE);\n+ state->phase = CALLBACK_QSORT_PHASE_IDLE;\n+\n+ getcontext(&state->worker_ctx);\n+ state->worker_ctx.uc_link = NULL;\n+ state->worker_ctx.uc_stack.ss_sp = state->worker_stack;\n+ state->worker_ctx.uc_stack.ss_size = CALLBACK_QSORT_STACK_SIZE;\n+ makecontext(&state->worker_ctx, callback_qsort_worker, 0);\n+\n+ vcpu->active_call = state;\n+ tls_active_call = state;\n+ swapcontext(&state->plugin_ctx, &state->worker_ctx);\n+\n+ if (finalize_if_finished(vcpu_index, vcpu, sysret)) {\n+ return true;\n+ }\n+\n+ if (state->phase == CALLBACK_QSORT_PHASE_NEED_CALLBACK) {\n+ jump_to_guest_callback(vcpu, state);\n+ return true;\n+ }\n+\n+ g_assert_not_reached();\n+}\n+\n+static bool handle_callback_resume(unsigned int vcpu_index, VcpuState *vcpu,\n+ int64_t cmp_result, uint64_t *sysret)\n+{\n+ CallbackQsortState *state = vcpu->active_call;\n+\n+ g_assert(state != NULL);\n+\n+ state->cmp_result = (int)cmp_result;\n+ tls_active_call = state;\n+ swapcontext(&state->plugin_ctx, &state->worker_ctx);\n+\n+ if (finalize_if_finished(vcpu_index, vcpu, sysret)) {\n+ return true;\n+ }\n+\n+ if (state->phase == CALLBACK_QSORT_PHASE_NEED_CALLBACK) {\n+ jump_to_guest_callback(vcpu, state);\n+ return true;\n+ }\n+\n+ g_assert_not_reached();\n+}\n+\n+static bool vcpu_syscall_filter(qemu_plugin_id_t id, unsigned int vcpu_index,\n+ int64_t num, uint64_t a1, uint64_t a2,\n+ uint64_t a3, uint64_t a4, uint64_t a5,\n+ uint64_t a6, uint64_t a7, uint64_t a8,\n+ uint64_t *sysret)\n+{\n+ VcpuState *vcpu = get_vcpu_state(vcpu_index);\n+\n+ if (handle_library_open(num, a1, a2, a3, a4, sysret)) {\n+ return true;\n+ }\n+\n+ if (num != MAGIC_SYSCALL) {\n+ return false;\n+ }\n+\n+ switch (a1) {\n+ case CALLBACK_QSORT_OP_START:\n+ return handle_callback_start(vcpu_index, vcpu, a2, a3, a4, a5, a6,\n+ sysret);\n+ case CALLBACK_QSORT_OP_RESUME:\n+ return handle_callback_resume(vcpu_index, vcpu, (int64_t)a2, sysret);\n+ default:\n+ return false;\n+ }\n+}\n+\n+static void vcpu_init_cb(qemu_plugin_id_t id, unsigned int vcpu_index)\n+{\n+ VcpuState *vcpu = get_vcpu_state(vcpu_index);\n+\n+ vcpu->regs.rsp = find_register(\"rsp\");\n+ vcpu->regs.rdi = find_register(\"rdi\");\n+ vcpu->regs.rsi = find_register(\"rsi\");\n+ g_assert(vcpu->regs.rsp != NULL);\n+ g_assert(vcpu->regs.rdi != NULL);\n+ g_assert(vcpu->regs.rsi != NULL);\n+}\n+\n+QEMU_PLUGIN_EXPORT int qemu_plugin_install(qemu_plugin_id_t id,\n+ const qemu_info_t *info,\n+ int argc, char **argv)\n+{\n+ if (argc != 0) {\n+ fprintf(stderr,\n+ \"syscall_filter_callback_qsort: this prototype plugin does not take arguments\\n\");\n+ return -1;\n+ }\n+\n+ if (strcmp(info->target_name, \"x86_64\") != 0) {\n+ fprintf(stderr,\n+ \"syscall_filter_callback_qsort: unsupported linux-user target '%s' \"\n+ \"(this prototype currently supports only x86_64)\\n\",\n+ info->target_name);\n+ return -1;\n+ }\n+\n+ vcpu_states = g_ptr_array_new();\n+ qemu_plugin_register_vcpu_init_cb(id, vcpu_init_cb);\n+ qemu_plugin_register_vcpu_syscall_filter_cb(id, vcpu_syscall_filter);\n+ return 0;\n+}\n", "prefixes": [ "v1", "2/3" ] }