get:
Show a patch.

patch:
Update a patch.

put:
Update a patch.

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

{
    "id": 2220193,
    "url": "http://patchwork.ozlabs.org/api/patches/2220193/?format=api",
    "web_url": "http://patchwork.ozlabs.org/project/qemu-devel/patch/20260406153331.8846-2-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": "<20260406153331.8846-2-xukl2019@sjtu.edu.cn>",
    "list_archive_url": null,
    "date": "2026-04-06T15:32:52",
    "name": "[v2,1/1] contrib/plugins: add syscall-filter passthrough zlib demo",
    "commit_ref": null,
    "pull_url": null,
    "state": "new",
    "archived": false,
    "hash": "04dd55e3e6ae80025854f8ba0b768ca6e2418eac",
    "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/20260406153331.8846-2-xukl2019@sjtu.edu.cn/mbox/",
    "series": [
        {
            "id": 498875,
            "url": "http://patchwork.ozlabs.org/api/series/498875/?format=api",
            "web_url": "http://patchwork.ozlabs.org/project/qemu-devel/list/?series=498875",
            "date": "2026-04-06T15:32:52",
            "name": "contrib/plugins: add syscall-filter passthrough zlib demo",
            "version": 2,
            "mbox": "http://patchwork.ozlabs.org/series/498875/mbox/"
        }
    ],
    "comments": "http://patchwork.ozlabs.org/api/patches/2220193/comments/",
    "check": "pending",
    "checks": "http://patchwork.ozlabs.org/api/patches/2220193/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 4fqD194YxZz1yFt\n\tfor <incoming@patchwork.ozlabs.org>; Tue, 07 Apr 2026 01:35:25 +1000 (AEST)",
            "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 1w9lyn-0007Ci-Fg; Mon, 06 Apr 2026 11:35:09 -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 1w9lyl-0007CI-Db\n for qemu-devel@nongnu.org; Mon, 06 Apr 2026 11:35: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 1w9lyh-0008C9-PW\n for qemu-devel@nongnu.org; Mon, 06 Apr 2026 11:35:07 -0400",
            "from proxy188.sjtu.edu.cn (smtp188.sjtu.edu.cn [202.120.2.188])\n by smtp232.sjtu.edu.cn (Postfix) with ESMTPS id AB1E110272B30;\n Mon,  6 Apr 2026 23:34:45 +0800 (CST)",
            "from xuklXiaoxin (unknown [202.120.32.222])\n by proxy188.sjtu.edu.cn (Postfix) with ESMTPSA id 78EC537C8CA;\n Mon,  6 Apr 2026 23:34:45 +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 v2 1/1] contrib/plugins: add syscall-filter passthrough zlib\n demo",
        "Date": "Mon,  6 Apr 2026 23:32:52 +0800",
        "Message-ID": "<20260406153331.8846-2-xukl2019@sjtu.edu.cn>",
        "X-Mailer": "git-send-email 2.53.0",
        "In-Reply-To": "<20260406153331.8846-1-xukl2019@sjtu.edu.cn>",
        "References": "<20260331173656.35305-1-xukl2019@sjtu.edu.cn>\n <20260406153331.8846-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": "-18",
        "X-Spam_score": "-1.9",
        "X-Spam_bar": "-",
        "X-Spam_report": "(-1.9 / 5.0 requ) BAYES_00=-1.9,\n RCVD_IN_VALIDITY_RPBL_BLOCKED=0.001, RCVD_IN_VALIDITY_SAFE_BLOCKED=0.001,\n SPF_HELO_NONE=0.001, SPF_PASS=-0.001 autolearn=ham autolearn_force=no",
        "X-Spam_action": "no action",
        "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 linux-user syscall-filter plugin that handles a small passthrough\nprotocol over a dedicated magic syscall number. The plugin supports three\noperations: load HTL module, resolve HTL symbol, and invoke HTL entry with\na flat void * argument array plus return pointer.\n\nAlso add a zlib example under contrib/plugins/passthrough-examples/:\n- guest thunk library (GTL): libz.so\n- host thunk library (HTL): libz_HTL.so\n- guest demo program: zlib-demo\n\nThe GTL constructor loads/resolves HTL entries through the plugin, wrapper\nfunctions forward calls via the magic syscall, and the destructor closes the\nHTL handle.\n\nBuild split:\n- Meson builds host-side artifacts (plugin + HTL).\n- Makefile builds guest-side artifacts (GTL + demo), with GUEST_CC\n  overridable for cross guest builds.\n\nDocumentation is added to docs/about/emulation.rst and the example README\nwith reproducible build/run steps and current scope limitations (64-bit\nlittle-endian guests, guest_base == 0 assumption for the demo).\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                   |  11 ++\n contrib/plugins/passthrough-examples/Makefile |  23 +++\n .../plugins/passthrough-examples/README.rst   |  60 +++++++\n .../plugins/passthrough-examples/meson.build  |  19 ++\n .../passthrough-examples/passthrough-gtl.c    |  41 +++++\n .../passthrough-examples/passthrough-gtl.h    |  17 ++\n .../plugins/passthrough-examples/zlib-demo.c  |  90 ++++++++++\n .../plugins/passthrough-examples/zlib-gtl.c   |  72 ++++++++\n .../plugins/passthrough-examples/zlib-htl.c   |  37 ++++\n contrib/plugins/passthrough-protocol.h        |  14 ++\n contrib/plugins/passthrough.c                 | 162 ++++++++++++++++++\n docs/about/emulation.rst                      |  33 ++++\n 12 files changed, 579 insertions(+)\n create mode 100644 contrib/plugins/passthrough-examples/Makefile\n create mode 100644 contrib/plugins/passthrough-examples/README.rst\n create mode 100644 contrib/plugins/passthrough-examples/meson.build\n create mode 100644 contrib/plugins/passthrough-examples/passthrough-gtl.c\n create mode 100644 contrib/plugins/passthrough-examples/passthrough-gtl.h\n create mode 100644 contrib/plugins/passthrough-examples/zlib-demo.c\n create mode 100644 contrib/plugins/passthrough-examples/zlib-gtl.c\n create mode 100644 contrib/plugins/passthrough-examples/zlib-htl.c\n create mode 100644 contrib/plugins/passthrough-protocol.h\n create mode 100644 contrib/plugins/passthrough.c",
    "diff": "diff --git a/contrib/plugins/meson.build b/contrib/plugins/meson.build\nindex 099319e7a1..70ab219c30 100644\n--- a/contrib/plugins/meson.build\n+++ b/contrib/plugins/meson.build\n@@ -14,9 +14,18 @@ contrib_plugins = [\n 'uftrace.c',\n ]\n \n+passthrough_host_supported = (\n+  host_os != 'darwin' and\n+  host_machine.endian() == 'little' and\n+  ['x86_64', 'aarch64', 'riscv64'].contains(host_machine.cpu_family())\n+)\n+\n if host_os != 'windows'\n   # lockstep uses socket.h\n   contrib_plugins += 'lockstep.c'\n+  if passthrough_host_supported\n+    contrib_plugins += 'passthrough.c'\n+  endif\n endif\n \n if 'cpp' in all_languages\n@@ -36,4 +45,6 @@ else\n   run_target('contrib-plugins', command: [python, '-c', ''])\n endif\n \n+subdir('passthrough-examples')\n+\n plugin_modules += t\ndiff --git a/contrib/plugins/passthrough-examples/Makefile b/contrib/plugins/passthrough-examples/Makefile\nnew file mode 100644\nindex 0000000000..bf97d359ae\n--- /dev/null\n+++ b/contrib/plugins/passthrough-examples/Makefile\n@@ -0,0 +1,23 @@\n+# SPDX-License-Identifier: GPL-2.0-or-later\n+\n+GUEST_CC ?= cc\n+GUEST_CPPFLAGS ?=\n+GUEST_CFLAGS ?=\n+GUEST_LDFLAGS ?=\n+RM ?= rm -f\n+\n+all: libz.so zlib-demo\n+\n+libz.so: zlib-gtl.c passthrough-gtl.c passthrough-gtl.h ../passthrough-protocol.h\n+\t$(GUEST_CC) $(GUEST_CPPFLAGS) $(GUEST_CFLAGS) -fPIC -shared \\\n+\t\tzlib-gtl.c passthrough-gtl.c -Wl,-soname,libz.so \\\n+\t\t-o $@ $(GUEST_LDFLAGS)\n+\n+zlib-demo: zlib-demo.c libz.so\n+\t$(GUEST_CC) $(GUEST_CPPFLAGS) $(GUEST_CFLAGS) zlib-demo.c -L. -lz \\\n+\t\t-o $@ $(GUEST_LDFLAGS)\n+\n+clean:\n+\t$(RM) libz.so zlib-demo\n+\n+.PHONY: all clean\ndiff --git a/contrib/plugins/passthrough-examples/README.rst b/contrib/plugins/passthrough-examples/README.rst\nnew file mode 100644\nindex 0000000000..5551b63256\n--- /dev/null\n+++ b/contrib/plugins/passthrough-examples/README.rst\n@@ -0,0 +1,60 @@\n+.. SPDX-License-Identifier: GPL-2.0-or-later\n+\n+Passthrough syscall-filter example\n+==================================\n+\n+This directory contains a zlib demo for ``contrib/plugins/passthrough.c``.\n+\n+* ``libz.so`` is the guest thunk library (GTL). It exports zlib symbols to the\n+  guest program.\n+* ``libz_HTL.so`` is the host thunk library (HTL). It links to native zlib.\n+* ``zlib-demo.c`` calls ``compressBound()``, ``compress2()``, and\n+  ``uncompress()`` through normal zlib APIs.\n+\n+The GTL constructor initializes passthrough in three steps:\n+\n+1. Send a magic syscall to request loading the HTL for ``libz.so``.\n+2. Send magic syscalls to resolve HTL entry addresses\n+   (``compressBound_HTL``, ``compress2_HTL``, ``uncompress_HTL``).\n+3. Cache those function pointers in GTL globals.\n+\n+The GTL destructor sends a magic syscall to close the loaded HTL handle.\n+\n+Each GTL wrapper then sends a magic syscall with:\n+\n+* the cached HTL function pointer,\n+* a flat ``void *args[]`` payload holding addresses of wrapper-local argument\n+  variables,\n+* and a ``void *ret_ptr`` return-storage address.\n+\n+This example assumes 64-bit little-endian linux-user guests. To keep it small,\n+it also assumes ``guest_base == 0`` on a little-endian 64-bit host.\n+\n+Direct execution of host callbacks back into guest translated code is not\n+covered here. That requires additional target- and ABI-specific handling\n+beyond this demo.\n+\n+Build:\n+\n+.. code-block:: sh\n+\n+  # 1) Build host-side passthrough pieces in QEMU's Meson tree.\n+  #    This produces libpassthrough.so and libz_HTL.so.\n+  ninja -C /path/to/qemu/build contrib-passthrough-examples\n+\n+  # 2) Build guest-side GTL + demo in this directory.\n+  #    By default GUEST_CC=cc; override it for cross guest builds.\n+  make\n+  # make GUEST_CC=aarch64-linux-gnu-gcc\n+\n+Run:\n+\n+.. code-block:: sh\n+\n+  QEMU_BUILD=/path/to/qemu/build\n+  HTL_DIR=$QEMU_BUILD/contrib/plugins/passthrough-examples\n+  $QEMU_BUILD/qemu-x86_64 \\\n+    -E LD_LIBRARY_PATH=$PWD \\\n+    -plugin $QEMU_BUILD/contrib/plugins/libpassthrough.so,htl_dir=$HTL_DIR \\\n+    -d plugin \\\n+    ./zlib-demo\ndiff --git a/contrib/plugins/passthrough-examples/meson.build b/contrib/plugins/passthrough-examples/meson.build\nnew file mode 100644\nindex 0000000000..75f0420e1e\n--- /dev/null\n+++ b/contrib/plugins/passthrough-examples/meson.build\n@@ -0,0 +1,19 @@\n+# SPDX-License-Identifier: GPL-2.0-or-later\n+\n+passthrough_examples = []\n+\n+if passthrough_host_supported and get_option('plugins')\n+  libz_htl = shared_library(\n+    'z_HTL',\n+    files('zlib-htl.c'),\n+    install: false,\n+    dependencies: [zlib],\n+  )\n+  passthrough_examples += [libz_htl]\n+endif\n+\n+if passthrough_examples.length() > 0\n+  alias_target('contrib-passthrough-examples', passthrough_examples)\n+else\n+  run_target('contrib-passthrough-examples', command: [python, '-c', ''])\n+endif\ndiff --git a/contrib/plugins/passthrough-examples/passthrough-gtl.c b/contrib/plugins/passthrough-examples/passthrough-gtl.c\nnew file mode 100644\nindex 0000000000..96629b411e\n--- /dev/null\n+++ b/contrib/plugins/passthrough-examples/passthrough-gtl.c\n@@ -0,0 +1,41 @@\n+/*\n+ * SPDX-License-Identifier: GPL-2.0-or-later\n+ */\n+\n+#include <stdint.h>\n+#include <sys/syscall.h>\n+#include <unistd.h>\n+\n+#include \"../passthrough-protocol.h\"\n+#include \"passthrough-gtl.h\"\n+\n+static long passthrough_syscall(uint64_t op, uint64_t a2, uint64_t a3,\n+                                uint64_t a4)\n+{\n+    return syscall(PASSTHROUGH_MAGIC_SYSCALL, op, a2, a3, a4);\n+}\n+\n+uint64_t passthrough_load_htl(const char *library)\n+{\n+    return (uint64_t)passthrough_syscall(PASSTHROUGH_OP_LOAD_HTL,\n+                                         (uint64_t)(uintptr_t)library, 0, 0);\n+}\n+\n+uint64_t passthrough_dlsym(uint64_t handle, const char *symbol)\n+{\n+    return (uint64_t)passthrough_syscall(PASSTHROUGH_OP_DLSYM, handle,\n+                                         (uint64_t)(uintptr_t)symbol, 0);\n+}\n+\n+void passthrough_invoke(PassthroughThunkEntry entry, void **args, void *ret)\n+{\n+    passthrough_syscall(PASSTHROUGH_OP_INVOKE,\n+                        (uint64_t)(uintptr_t)entry,\n+                        (uint64_t)(uintptr_t)args,\n+                        (uint64_t)(uintptr_t)ret);\n+}\n+\n+void passthrough_close_htl(uint64_t handle)\n+{\n+    passthrough_syscall(PASSTHROUGH_OP_CLOSE_HTL, handle, 0, 0);\n+}\ndiff --git a/contrib/plugins/passthrough-examples/passthrough-gtl.h b/contrib/plugins/passthrough-examples/passthrough-gtl.h\nnew file mode 100644\nindex 0000000000..2ce927cc6e\n--- /dev/null\n+++ b/contrib/plugins/passthrough-examples/passthrough-gtl.h\n@@ -0,0 +1,17 @@\n+/*\n+ * SPDX-License-Identifier: GPL-2.0-or-later\n+ */\n+\n+#ifndef PASSTHROUGH_GTL_H\n+#define PASSTHROUGH_GTL_H\n+\n+#include <stdint.h>\n+\n+typedef void (*PassthroughThunkEntry)(void **args, void *ret);\n+\n+uint64_t passthrough_load_htl(const char *library);\n+uint64_t passthrough_dlsym(uint64_t handle, const char *symbol);\n+void passthrough_invoke(PassthroughThunkEntry entry, void **args, void *ret);\n+void passthrough_close_htl(uint64_t handle);\n+\n+#endif /* PASSTHROUGH_GTL_H */\ndiff --git a/contrib/plugins/passthrough-examples/zlib-demo.c b/contrib/plugins/passthrough-examples/zlib-demo.c\nnew file mode 100644\nindex 0000000000..0782f9bc36\n--- /dev/null\n+++ b/contrib/plugins/passthrough-examples/zlib-demo.c\n@@ -0,0 +1,90 @@\n+/*\n+ * SPDX-License-Identifier: GPL-2.0-or-later\n+ */\n+\n+#include <stdio.h>\n+#include <stdlib.h>\n+#include <string.h>\n+#include <zlib.h>\n+\n+#define INPUT_SIZE (8 * 1024 * 1024)\n+\n+static void fill_input(Bytef *data, size_t len)\n+{\n+    static const unsigned char pattern[] =\n+        \"QEMU passthrough syscall-filter demo payload\\n\";\n+    size_t i;\n+\n+    for (i = 0; i < len; i++) {\n+        data[i] = pattern[i % (sizeof(pattern) - 1)];\n+        if ((i % 4096) == 0) {\n+            data[i] = (unsigned char)(i >> 12);\n+        }\n+    }\n+}\n+\n+int main(void)\n+{\n+    Bytef *input = NULL;\n+    Bytef *compressed = NULL;\n+    Bytef *output = NULL;\n+    uLongf compressed_len;\n+    uLongf output_len;\n+    uLongf compressed_bound;\n+    int ret = EXIT_FAILURE;\n+\n+    input = malloc(INPUT_SIZE);\n+    if (input == NULL) {\n+        perror(\"malloc\");\n+        goto cleanup;\n+    }\n+\n+    fill_input(input, INPUT_SIZE);\n+\n+    compressed_bound = compressBound(INPUT_SIZE);\n+    if (compressed_bound == 0) {\n+        fprintf(stderr, \"compressBound failed\\n\");\n+        goto cleanup;\n+    }\n+\n+    compressed = malloc(compressed_bound);\n+    output = malloc(INPUT_SIZE);\n+    if (compressed == NULL || output == NULL) {\n+        perror(\"malloc\");\n+        goto cleanup;\n+    }\n+\n+    compressed_len = compressed_bound;\n+    if (compress2(compressed, &compressed_len, input, INPUT_SIZE,\n+                  Z_BEST_COMPRESSION) != Z_OK) {\n+        fprintf(stderr, \"compress2 failed\\n\");\n+        goto cleanup;\n+    }\n+\n+    output_len = INPUT_SIZE;\n+    if (uncompress(output, &output_len, compressed, compressed_len) != Z_OK) {\n+        fprintf(stderr, \"uncompress failed\\n\");\n+        goto cleanup;\n+    }\n+\n+    if (output_len != INPUT_SIZE || memcmp(input, output, INPUT_SIZE) != 0) {\n+        fprintf(stderr, \"round-trip mismatch\\n\");\n+        goto cleanup;\n+    }\n+\n+    if (compressed_len >= INPUT_SIZE) {\n+        fprintf(stderr, \"compressed output was not smaller than input\\n\");\n+        goto cleanup;\n+    }\n+\n+    printf(\"passthrough demo compressed %u bytes to %lu bytes\\n\",\n+           INPUT_SIZE, (unsigned long)compressed_len);\n+    puts(\"passthrough demo round-tripped successfully\");\n+    ret = EXIT_SUCCESS;\n+\n+cleanup:\n+    free(output);\n+    free(compressed);\n+    free(input);\n+    return ret;\n+}\ndiff --git a/contrib/plugins/passthrough-examples/zlib-gtl.c b/contrib/plugins/passthrough-examples/zlib-gtl.c\nnew file mode 100644\nindex 0000000000..87b55e0c77\n--- /dev/null\n+++ b/contrib/plugins/passthrough-examples/zlib-gtl.c\n@@ -0,0 +1,72 @@\n+/*\n+ * SPDX-License-Identifier: GPL-2.0-or-later\n+ */\n+\n+#include <zlib.h>\n+\n+#include \"passthrough-gtl.h\"\n+\n+enum ZlibThunkIndex {\n+    ZLIB_THUNK_COMPRESS_BOUND,\n+    ZLIB_THUNK_COMPRESS2,\n+    ZLIB_THUNK_UNCOMPRESS,\n+    ZLIB_THUNK_COUNT,\n+};\n+\n+static const char gtl_library_name[] = \"libz.so\";\n+static uint64_t gtl_htl_handle;\n+static PassthroughThunkEntry gtl_entries[ZLIB_THUNK_COUNT];\n+\n+__attribute__((constructor))\n+static void passthrough_gtl_init(void)\n+{\n+    gtl_htl_handle = passthrough_load_htl(gtl_library_name);\n+    gtl_entries[ZLIB_THUNK_COMPRESS_BOUND] =\n+        (PassthroughThunkEntry)(uintptr_t)\n+        passthrough_dlsym(gtl_htl_handle, \"compressBound_HTL\");\n+    gtl_entries[ZLIB_THUNK_COMPRESS2] = (PassthroughThunkEntry)(uintptr_t)\n+        passthrough_dlsym(gtl_htl_handle, \"compress2_HTL\");\n+    gtl_entries[ZLIB_THUNK_UNCOMPRESS] = (PassthroughThunkEntry)(uintptr_t)\n+        passthrough_dlsym(gtl_htl_handle, \"uncompress_HTL\");\n+}\n+\n+__attribute__((destructor))\n+static void passthrough_gtl_fini(void)\n+{\n+    if (gtl_htl_handle != 0) {\n+        passthrough_close_htl(gtl_htl_handle);\n+        gtl_htl_handle = 0;\n+    }\n+    gtl_entries[ZLIB_THUNK_COMPRESS_BOUND] = NULL;\n+    gtl_entries[ZLIB_THUNK_COMPRESS2] = NULL;\n+    gtl_entries[ZLIB_THUNK_UNCOMPRESS] = NULL;\n+}\n+\n+uLong compressBound(uLong sourceLen)\n+{\n+    void *args[] = { &sourceLen };\n+    uLong ret = 0;\n+\n+    passthrough_invoke(gtl_entries[ZLIB_THUNK_COMPRESS_BOUND], args, &ret);\n+    return ret;\n+}\n+\n+int compress2(Bytef *dest, uLongf *destLen, const Bytef *source,\n+              uLong sourceLen, int level)\n+{\n+    void *args[] = { &dest, &destLen, &source, &sourceLen, &level };\n+    int ret = Z_ERRNO;\n+\n+    passthrough_invoke(gtl_entries[ZLIB_THUNK_COMPRESS2], args, &ret);\n+    return ret;\n+}\n+\n+int uncompress(Bytef *dest, uLongf *destLen, const Bytef *source,\n+               uLong sourceLen)\n+{\n+    void *args[] = { &dest, &destLen, &source, &sourceLen };\n+    int ret = Z_ERRNO;\n+\n+    passthrough_invoke(gtl_entries[ZLIB_THUNK_UNCOMPRESS], args, &ret);\n+    return ret;\n+}\ndiff --git a/contrib/plugins/passthrough-examples/zlib-htl.c b/contrib/plugins/passthrough-examples/zlib-htl.c\nnew file mode 100644\nindex 0000000000..99f9fac890\n--- /dev/null\n+++ b/contrib/plugins/passthrough-examples/zlib-htl.c\n@@ -0,0 +1,37 @@\n+/*\n+ * SPDX-License-Identifier: GPL-2.0-or-later\n+ */\n+\n+#include <zlib.h>\n+\n+void compressBound_HTL(void **args, void *ret);\n+void compress2_HTL(void **args, void *ret);\n+void uncompress_HTL(void **args, void *ret);\n+\n+void compressBound_HTL(void **args, void *ret)\n+{\n+    uLong sourceLen = *(uLong *)args[0];\n+\n+    *(uLong *)ret = compressBound(sourceLen);\n+}\n+\n+void compress2_HTL(void **args, void *ret)\n+{\n+    Bytef *dest = *(Bytef **)args[0];\n+    uLongf *destLen = *(uLongf **)args[1];\n+    const Bytef *source = *(const Bytef **)args[2];\n+    uLong sourceLen = *(uLong *)args[3];\n+    int level = *(int *)args[4];\n+\n+    *(int *)ret = compress2(dest, destLen, source, sourceLen, level);\n+}\n+\n+void uncompress_HTL(void **args, void *ret)\n+{\n+    Bytef *dest = *(Bytef **)args[0];\n+    uLongf *destLen = *(uLongf **)args[1];\n+    const Bytef *source = *(const Bytef **)args[2];\n+    uLong sourceLen = *(uLong *)args[3];\n+\n+    *(int *)ret = uncompress(dest, destLen, source, sourceLen);\n+}\ndiff --git a/contrib/plugins/passthrough-protocol.h b/contrib/plugins/passthrough-protocol.h\nnew file mode 100644\nindex 0000000000..e30cc6a186\n--- /dev/null\n+++ b/contrib/plugins/passthrough-protocol.h\n@@ -0,0 +1,14 @@\n+/*\n+ * SPDX-License-Identifier: GPL-2.0-or-later\n+ */\n+\n+#ifndef QEMU_PASSTHROUGH_PROTOCOL_H\n+#define QEMU_PASSTHROUGH_PROTOCOL_H\n+\n+#define PASSTHROUGH_MAGIC_SYSCALL 4096\n+#define PASSTHROUGH_OP_LOAD_HTL 1\n+#define PASSTHROUGH_OP_DLSYM 2\n+#define PASSTHROUGH_OP_INVOKE 3\n+#define PASSTHROUGH_OP_CLOSE_HTL 4\n+\n+#endif /* QEMU_PASSTHROUGH_PROTOCOL_H */\ndiff --git a/contrib/plugins/passthrough.c b/contrib/plugins/passthrough.c\nnew file mode 100644\nindex 0000000000..feb7491b95\n--- /dev/null\n+++ b/contrib/plugins/passthrough.c\n@@ -0,0 +1,162 @@\n+/*\n+ * SPDX-License-Identifier: GPL-2.0-or-later\n+ *\n+ * Minimal linux-user plugin that demonstrates local-library passthrough with\n+ * the syscall filter API.\n+ *\n+ * The guest thunk library (GTL) can:\n+ * 1) ask this plugin to load a host thunk library (HTL),\n+ * 2) resolve HTL entry points,\n+ * 3) invoke resolved HTL entry points with a void *args[] payload.\n+ *\n+ * This demo intentionally assumes 64-bit little-endian linux-user guests with\n+ * guest_base == 0 on a little-endian 64-bit host, so guest virtual addresses\n+ * are directly usable as host pointers.\n+ */\n+\n+#include <glib.h>\n+#include <gmodule.h>\n+#include <stdbool.h>\n+#include <stdint.h>\n+#include <stdio.h>\n+#include <string.h>\n+\n+#include <qemu-plugin.h>\n+\n+#include \"passthrough-protocol.h\"\n+\n+QEMU_PLUGIN_EXPORT int qemu_plugin_version = QEMU_PLUGIN_VERSION;\n+\n+typedef void (*PassthroughThunkEntry)(void **args, void *ret);\n+\n+static char *htl_dir;\n+\n+static char *build_htl_path(const char *guest_library)\n+{\n+    g_autofree char *basename = g_path_get_basename(guest_library);\n+    g_autofree char *htl_basename = NULL;\n+    const char *so = strstr(basename, \".so\");\n+\n+    g_assert(so != NULL);\n+    htl_basename = g_strdup_printf(\"%.*s_HTL%s\",\n+                                   (int)(so - basename), basename, so);\n+    return g_build_filename(htl_dir, htl_basename, NULL);\n+}\n+\n+static bool handle_load_htl(uint64_t library_ptr, uint64_t *sysret)\n+{\n+    g_autofree char *htl_path = NULL;\n+    const char *guest_library = (const char *)(uintptr_t)library_ptr;\n+    GModule *module;\n+\n+    g_assert(guest_library != NULL);\n+    htl_path = build_htl_path(guest_library);\n+    module = g_module_open(htl_path, G_MODULE_BIND_LOCAL);\n+    g_assert(module != NULL);\n+\n+    *sysret = (uint64_t)(uintptr_t)module;\n+    return true;\n+}\n+\n+static bool handle_dlsym(uint64_t handle, uint64_t symbol_ptr, uint64_t *sysret)\n+{\n+    const char *symbol = (const char *)(uintptr_t)symbol_ptr;\n+    GModule *module = (GModule *)(uintptr_t)handle;\n+    gpointer func = NULL;\n+\n+    g_assert(module != NULL);\n+    g_assert(symbol != NULL);\n+    g_assert(g_module_symbol(module, symbol, &func));\n+    g_assert(func != NULL);\n+\n+    *sysret = (uint64_t)(uintptr_t)func;\n+    return true;\n+}\n+\n+static bool handle_invoke(uint64_t func_ptr, uint64_t args_ptr,\n+                          uint64_t ret_ptr, uint64_t *sysret)\n+{\n+    PassthroughThunkEntry entry = (PassthroughThunkEntry)(uintptr_t)func_ptr;\n+\n+    g_assert(entry != NULL);\n+    entry((void **)(uintptr_t)args_ptr, (void *)(uintptr_t)ret_ptr);\n+    *sysret = 0;\n+    return true;\n+}\n+\n+static bool handle_close_htl(uint64_t handle, uint64_t *sysret)\n+{\n+    GModule *module = (GModule *)(uintptr_t)handle;\n+\n+    g_assert(module != NULL);\n+    g_assert(g_module_close(module));\n+    *sysret = 0;\n+    return true;\n+}\n+\n+static bool passthrough_syscall_filter(qemu_plugin_id_t id,\n+                                       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+    if (num != PASSTHROUGH_MAGIC_SYSCALL) {\n+        return false;\n+    }\n+\n+    switch (a1) {\n+    case PASSTHROUGH_OP_LOAD_HTL:\n+        return handle_load_htl(a2, sysret);\n+    case PASSTHROUGH_OP_DLSYM:\n+        return handle_dlsym(a2, a3, sysret);\n+    case PASSTHROUGH_OP_INVOKE:\n+        return handle_invoke(a2, a3, a4, sysret);\n+    case PASSTHROUGH_OP_CLOSE_HTL:\n+        return handle_close_htl(a2, sysret);\n+    default:\n+        g_assert_not_reached();\n+    }\n+}\n+\n+static void passthrough_exit(qemu_plugin_id_t id, void *userdata)\n+{\n+    g_free(htl_dir);\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+    int i;\n+\n+    if (info->system_emulation) {\n+        fprintf(stderr,\n+                \"passthrough: this demo supports linux-user only\\n\");\n+        return -1;\n+    }\n+\n+    for (i = 0; i < argc; i++) {\n+        g_auto(GStrv) tokens = g_strsplit(argv[i], \"=\", 2);\n+\n+        if (g_strcmp0(tokens[0], \"htl_dir\") == 0) {\n+            g_assert(tokens[1] != NULL);\n+            g_free(htl_dir);\n+            htl_dir = g_strdup(tokens[1]);\n+            continue;\n+        }\n+\n+        fprintf(stderr, \"passthrough: unsupported argument: %s\\n\", argv[i]);\n+        return -1;\n+    }\n+\n+    if (htl_dir == NULL) {\n+        fprintf(stderr,\n+                \"passthrough: missing required argument htl_dir=PATH\\n\");\n+        return -1;\n+    }\n+\n+    qemu_plugin_register_vcpu_syscall_filter_cb(id, passthrough_syscall_filter);\n+    qemu_plugin_register_atexit_cb(id, passthrough_exit, NULL);\n+    return 0;\n+}\ndiff --git a/docs/about/emulation.rst b/docs/about/emulation.rst\nindex 3b4c365933..0a9000c5cb 100644\n--- a/docs/about/emulation.rst\n+++ b/docs/about/emulation.rst\n@@ -777,6 +777,39 @@ The plugin has a number of arguments, all of them are optional:\n   * - l2assoc=A\n     - L2 cache associativity (default: 16), implies ``l2=on``\n \n+Passthrough\n+...........\n+\n+``contrib/plugins/passthrough.c``\n+\n+This plugin demonstrates native library passthrough on top of the linux-user\n+syscall filter API.\n+\n+This demo is currently limited to 64-bit little-endian linux-user guests:\n+``x86_64``, ``aarch64``, and ``riscv64``. It also requires a\n+64-bit little-endian host in the same architecture set, and assumes\n+``guest_base == 0`` so guest virtual addresses are directly usable as host\n+pointers.\n+\n+Direct execution of host callbacks back into guest translated code is not\n+covered here. That requires additional target- and ABI-specific handling\n+beyond this demo.\n+\n+Use it with a linux-user emulator and pass the HTL directory through\n+``htl_dir``. For example::\n+\n+Here HTL means host thunk library, i.e. the host-side shared object loaded by\n+the plugin from ``htl_dir``.\n+\n+  $ HTL_DIR=/path/to/examples\n+  $ ./build/qemu-<target> \\\n+      -E LD_LIBRARY_PATH=$HTL_DIR \\\n+      -plugin ./build/contrib/plugins/libpassthrough.so,htl_dir=$HTL_DIR \\\n+      <guest-program>\n+\n+For complete runnable examples (zlib), see\n+``contrib/plugins/passthrough-examples/README.rst``.\n+\n Stop on Trigger\n ...............\n \n",
    "prefixes": [
        "v2",
        "1/1"
    ]
}