Patch Detail
get:
Show a patch.
patch:
Update a patch.
put:
Update a patch.
GET /api/patches/2220193/?format=api
{ "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" ] }