get:
Show a patch.

patch:
Update a patch.

put:
Update a patch.

GET /api/patches/2218277/?format=api
HTTP 200 OK
Allow: GET, PUT, PATCH, HEAD, OPTIONS
Content-Type: application/json
Vary: Accept

{
    "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"
    ]
}