Patch Detail
get:
Show a patch.
patch:
Update a patch.
put:
Update a patch.
GET /api/patches/2218279/?format=api
{ "id": 2218279, "url": "http://patchwork.ozlabs.org/api/patches/2218279/?format=api", "web_url": "http://patchwork.ozlabs.org/project/qemu-devel/patch/20260331173656.35305-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": "<20260331173656.35305-2-xukl2019@sjtu.edu.cn>", "list_archive_url": null, "date": "2026-03-31T17:36:24", "name": "[v1,1/3] contrib/plugins: add a zlib compression filter example", "commit_ref": null, "pull_url": null, "state": "new", "archived": false, "hash": "d6f051894bdcecc3b49b80e680bcb89ce2cdac61", "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-2-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/2218279/comments/", "check": "pending", "checks": "http://patchwork.ozlabs.org/api/patches/2218279/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 4flgwL0Vbpz1yFv\n\tfor <incoming@patchwork.ozlabs.org>; Wed, 01 Apr 2026 08:18:58 +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-0008RN-FX; 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 1w7d8v-0004tN-26\n for qemu-devel@nongnu.org; Tue, 31 Mar 2026 13:44:46 -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 1w7d8q-00089Y-CB\n for qemu-devel@nongnu.org; Tue, 31 Mar 2026 13:44:44 -0400", "from proxy188.sjtu.edu.cn (smtp188.sjtu.edu.cn [202.120.2.188])\n by smtp232.sjtu.edu.cn (Postfix) with ESMTPS id 2BE7A1068BE6E;\n Wed, 1 Apr 2026 01:38:03 +0800 (CST)", "from xuklXiaoxin (unknown [202.120.32.222])\n by proxy188.sjtu.edu.cn (Postfix) with ESMTPSA id 04A9A37C8CA;\n Wed, 1 Apr 2026 01:38:03 +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 1/3] contrib/plugins: add a zlib compression filter example", "Date": "Wed, 1 Apr 2026 01:36:24 +0800", "Message-ID": "<20260331173656.35305-2-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 minimal linux-user plugin example that uses the syscall filter\ncallback API to intercept a guest library load and replace it with a\nlocal thunk library.\n\nThe guest-side demo links against libdemo-zlib.so. When the guest\ndynamic loader issues openat() for that library, the plugin returns a\nfile descriptor for libdemo-zlib-thunk.so instead. The thunk library\nforwards compressBound(), compress2(), and uncompress() through magic\nsyscalls, which the plugin handles by calling the host zlib\nimplementation directly.\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\ndemo 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 example is meant\nto show the direct hand-off to a known local thunk library, not fallback\nbehavior when that local library is missing or unusable.\n\nDocument the assumptions and add a small example directory with a\nMakefile so the behavior is easy to reproduce.\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 .../syscall_filter_zlib-example/Makefile | 19 ++\n .../syscall_filter_zlib-example/README.rst | 43 +++\n .../zcompress-demo.c | 94 ++++++\n .../zcompress-thunk.c | 35 +++\n .../zcompress-thunk.h | 16 ++\n contrib/plugins/syscall_filter_zlib.c | 268 ++++++++++++++++++\n docs/about/emulation.rst | 41 +++\n 8 files changed, 527 insertions(+), 1 deletion(-)\n create mode 100644 contrib/plugins/syscall_filter_zlib-example/Makefile\n create mode 100644 contrib/plugins/syscall_filter_zlib-example/README.rst\n create mode 100644 contrib/plugins/syscall_filter_zlib-example/zcompress-demo.c\n create mode 100644 contrib/plugins/syscall_filter_zlib-example/zcompress-thunk.c\n create mode 100644 contrib/plugins/syscall_filter_zlib-example/zcompress-thunk.h\n create mode 100644 contrib/plugins/syscall_filter_zlib.c", "diff": "diff --git a/contrib/plugins/meson.build b/contrib/plugins/meson.build\nindex 099319e7a1..aa3b95eeab 100644\n--- a/contrib/plugins/meson.build\n+++ b/contrib/plugins/meson.build\n@@ -19,6 +19,12 @@ 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+endif\n+\n if 'cpp' in all_languages\n contrib_plugins += 'cpp.cpp'\n endif\n@@ -26,8 +32,12 @@ endif\n t = []\n if get_option('plugins')\n foreach i : contrib_plugins\n+ deps = plugins_deps\n+ if i == 'syscall_filter_zlib.c'\n+ deps = [plugins_deps, zlib]\n+ endif\n t += shared_module(fs.stem(i), files(i),\n- dependencies: plugins_deps)\n+ dependencies: deps)\n endforeach\n endif\n if t.length() > 0\ndiff --git a/contrib/plugins/syscall_filter_zlib-example/Makefile b/contrib/plugins/syscall_filter_zlib-example/Makefile\nnew file mode 100644\nindex 0000000000..f94b93e950\n--- /dev/null\n+++ b/contrib/plugins/syscall_filter_zlib-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-zlib-thunk.so zcompress-demo\n+\n+libdemo-zlib-thunk.so: zcompress-thunk.c zcompress-thunk.h\n+\t$(CC) $(CFLAGS) -fPIC -shared zcompress-thunk.c -Wl,-soname,libdemo-zlib.so -o $@ $(LDFLAGS)\n+\n+zcompress-demo: zcompress-demo.c zcompress-thunk.h libdemo-zlib-thunk.so\n+\t$(CC) $(CFLAGS) zcompress-demo.c ./libdemo-zlib-thunk.so -Wl,-rpath,'$$ORIGIN' -o $@ $(LDFLAGS)\n+\n+clean:\n+\t$(RM) libdemo-zlib-thunk.so zcompress-demo\n+\n+.PHONY: all clean\ndiff --git a/contrib/plugins/syscall_filter_zlib-example/README.rst b/contrib/plugins/syscall_filter_zlib-example/README.rst\nnew file mode 100644\nindex 0000000000..4920187a2b\n--- /dev/null\n+++ b/contrib/plugins/syscall_filter_zlib-example/README.rst\n@@ -0,0 +1,43 @@\n+.. SPDX-License-Identifier: GPL-2.0-or-later\n+\n+zlib compression syscall-filter example\n+=======================================\n+\n+This directory contains the guest-side pieces used by\n+``contrib/plugins/syscall_filter_zlib.c``:\n+\n+* ``zcompress-demo.c`` is linked against ``libdemo-zlib.so`` and calls the\n+ compression helpers directly.\n+* The plugin intercepts the loader's ``openat()`` call and returns a file\n+ descriptor for ``./libdemo-zlib-thunk.so`` instead.\n+* ``zcompress-thunk.c`` exposes a tiny compression API as thin wrappers around\n+ magic syscalls.\n+* The plugin filters those magic syscalls and executes the host zlib\n+ ``compressBound()``, ``compress2()``, and ``uncompress()`` implementations\n+ directly on guest buffers.\n+* The example currently supports ``x86_64`` linux-user only. Extending the\n+ syscall-number table for more targets is straightforward, but is outside the\n+ scope of this patch.\n+* To keep the demo small, the plugin 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 demo 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_zlib.so \\\n+ -d plugin \\\n+ ./zcompress-demo\n+\n+The build links ``zcompress-demo`` against ``libdemo-zlib-thunk.so`` while\n+giving that shared object the soname ``libdemo-zlib.so``. Without the plugin,\n+the program fails at startup because no ``libdemo-zlib.so`` file exists in the\n+runtime search path. With the plugin, the guest sees a working library load\n+even though the loader actually receives ``libdemo-zlib-thunk.so``.\ndiff --git a/contrib/plugins/syscall_filter_zlib-example/zcompress-demo.c b/contrib/plugins/syscall_filter_zlib-example/zcompress-demo.c\nnew file mode 100644\nindex 0000000000..a199b8d290\n--- /dev/null\n+++ b/contrib/plugins/syscall_filter_zlib-example/zcompress-demo.c\n@@ -0,0 +1,94 @@\n+/*\n+ * SPDX-License-Identifier: GPL-2.0-or-later\n+ */\n+\n+#include <stdint.h>\n+#include <stdio.h>\n+#include <stdlib.h>\n+#include <string.h>\n+\n+#include \"zcompress-thunk.h\"\n+\n+#define INPUT_SIZE (8 * 1024 * 1024)\n+#define Z_BEST_COMPRESSION 9\n+\n+static void fill_input(unsigned char *data, size_t len)\n+{\n+ static const unsigned char pattern[] =\n+ \"QEMU syscall filter zlib compression 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+ unsigned char *input = NULL;\n+ unsigned char *compressed = NULL;\n+ unsigned char *output = NULL;\n+ size_t compressed_len;\n+ size_t output_len;\n+ size_t 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 = zcompress_compress_bound(INPUT_SIZE);\n+ if (compressed_bound == 0) {\n+ fprintf(stderr, \"zcompress_compress_bound 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 (zcompress_compress(input, INPUT_SIZE, compressed, &compressed_len,\n+ Z_BEST_COMPRESSION) != 0) {\n+ fprintf(stderr, \"zcompress_compress failed\\n\");\n+ goto cleanup;\n+ }\n+\n+ output_len = INPUT_SIZE;\n+ if (zcompress_uncompress(compressed, compressed_len, output,\n+ &output_len) != 0) {\n+ fprintf(stderr, \"zcompress_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(\"zlib demo compressed %u bytes to %zu bytes\\n\",\n+ INPUT_SIZE, compressed_len);\n+ puts(\"zlib 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/syscall_filter_zlib-example/zcompress-thunk.c b/contrib/plugins/syscall_filter_zlib-example/zcompress-thunk.c\nnew file mode 100644\nindex 0000000000..1e498a728e\n--- /dev/null\n+++ b/contrib/plugins/syscall_filter_zlib-example/zcompress-thunk.c\n@@ -0,0 +1,35 @@\n+/*\n+ * SPDX-License-Identifier: GPL-2.0-or-later\n+ */\n+\n+#include <stdint.h>\n+#include <unistd.h>\n+\n+#include \"zcompress-thunk.h\"\n+\n+#define ZLIB_COMPRESS_MAGIC_SYSCALL 4096\n+#define ZLIB_COMPRESS_OP_BOUND 1\n+#define ZLIB_COMPRESS_OP_COMPRESS2 2\n+#define ZLIB_COMPRESS_OP_UNCOMPRESS 3\n+\n+size_t zcompress_compress_bound(size_t source_len)\n+{\n+ return (size_t)syscall(ZLIB_COMPRESS_MAGIC_SYSCALL,\n+ ZLIB_COMPRESS_OP_BOUND, source_len);\n+}\n+\n+int zcompress_compress(const void *source, size_t source_len,\n+ void *dest, size_t *dest_len, int level)\n+{\n+ return (int)syscall(ZLIB_COMPRESS_MAGIC_SYSCALL,\n+ ZLIB_COMPRESS_OP_COMPRESS2,\n+ source, source_len, dest, dest_len, level);\n+}\n+\n+int zcompress_uncompress(const void *source, size_t source_len,\n+ void *dest, size_t *dest_len)\n+{\n+ return (int)syscall(ZLIB_COMPRESS_MAGIC_SYSCALL,\n+ ZLIB_COMPRESS_OP_UNCOMPRESS,\n+ source, source_len, dest, dest_len);\n+}\ndiff --git a/contrib/plugins/syscall_filter_zlib-example/zcompress-thunk.h b/contrib/plugins/syscall_filter_zlib-example/zcompress-thunk.h\nnew file mode 100644\nindex 0000000000..f0092ff681\n--- /dev/null\n+++ b/contrib/plugins/syscall_filter_zlib-example/zcompress-thunk.h\n@@ -0,0 +1,16 @@\n+/*\n+ * SPDX-License-Identifier: GPL-2.0-or-later\n+ */\n+\n+#ifndef CONTRIB_PLUGINS_SYSCALL_FILTER_ZLIB_EXAMPLE_ZCOMPRESS_THUNK_H\n+#define CONTRIB_PLUGINS_SYSCALL_FILTER_ZLIB_EXAMPLE_ZCOMPRESS_THUNK_H\n+\n+#include <stddef.h>\n+\n+size_t zcompress_compress_bound(size_t source_len);\n+int zcompress_compress(const void *source, size_t source_len,\n+ void *dest, size_t *dest_len, int level);\n+int zcompress_uncompress(const void *source, size_t source_len,\n+ void *dest, size_t *dest_len);\n+\n+#endif\ndiff --git a/contrib/plugins/syscall_filter_zlib.c b/contrib/plugins/syscall_filter_zlib.c\nnew file mode 100644\nindex 0000000000..e8f430cbaf\n--- /dev/null\n+++ b/contrib/plugins/syscall_filter_zlib.c\n@@ -0,0 +1,268 @@\n+/*\n+ * SPDX-License-Identifier: GPL-2.0-or-later\n+ *\n+ * Minimal linux-user plugin that demonstrates syscall filtering for\n+ * local library interception with a host zlib compression example.\n+ *\n+ * When the guest dynamic loader attempts to open \"./libdemo-zlib.so\", this\n+ * plugin intercepts openat() and instead returns a file descriptor for\n+ * \"libdemo-zlib-thunk.so\" in the same directory. The thunk library then\n+ * forwards compression requests through magic syscalls, which are handled by\n+ * this plugin and executed by the host's zlib implementation.\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 <string.h>\n+#include <unistd.h>\n+#include <zlib.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 ZLIB_COMPRESS_OP_BOUND 1\n+#define ZLIB_COMPRESS_OP_COMPRESS2 2\n+#define ZLIB_COMPRESS_OP_UNCOMPRESS 3\n+#define ZLIB_COMPRESS_LIBRARY \"libdemo-zlib.so\"\n+#define ZLIB_COMPRESS_THUNK_LIBRARY \"libdemo-zlib-thunk.so\"\n+#define ZLIB_COMPRESS_MAX_INPUT (64 * 1024 * 1024)\n+#define ZLIB_COMPRESS_MAX_BUFFER (128 * 1024 * 1024)\n+#define GUEST_STRING_CHUNK 64\n+#define GUEST_STRING_LIMIT (1 << 20)\n+#define X86_64_OPENAT_NR 257\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_zlib_compress(const char *path)\n+{\n+ g_autofree char *basename = g_path_get_basename(path);\n+\n+ return strcmp(basename, ZLIB_COMPRESS_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(ZLIB_COMPRESS_THUNK_LIBRARY);\n+ }\n+\n+ return g_build_filename(dirname, ZLIB_COMPRESS_THUNK_LIBRARY, NULL);\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_zlib_compress(path)) {\n+ return false;\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(\"syscall_filter_zlib: 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_compress_bound(int64_t num, uint64_t a1, uint64_t a2,\n+ uint64_t *sysret)\n+{\n+ if (num != MAGIC_SYSCALL || a1 != ZLIB_COMPRESS_OP_BOUND) {\n+ return false;\n+ }\n+\n+ if (a2 > ZLIB_COMPRESS_MAX_INPUT) {\n+ *sysret = 0;\n+ return true;\n+ }\n+\n+ *sysret = compressBound((uLong)a2);\n+ return true;\n+}\n+\n+static bool handle_compress2(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 *sysret)\n+{\n+ g_autofree char *out = NULL;\n+ const Bytef *source;\n+ Bytef *dest;\n+ uLongf *dest_lenp;\n+ uLongf guest_dest_len;\n+ int status;\n+ int level = (int)a6;\n+\n+ if (num != MAGIC_SYSCALL || a1 != ZLIB_COMPRESS_OP_COMPRESS2) {\n+ return false;\n+ }\n+\n+ g_assert(a2 != 0 && a4 != 0 && a5 != 0);\n+ g_assert(level == Z_DEFAULT_COMPRESSION ||\n+ (level >= Z_NO_COMPRESSION && level <= Z_BEST_COMPRESSION));\n+\n+ dest_lenp = (uLongf *)(uintptr_t)a5;\n+ guest_dest_len = *dest_lenp;\n+\n+ g_assert(a3 <= ZLIB_COMPRESS_MAX_INPUT);\n+ g_assert(guest_dest_len <= ZLIB_COMPRESS_MAX_BUFFER);\n+\n+ source = (const Bytef *)(uintptr_t)a2;\n+ dest = (Bytef *)(uintptr_t)a4;\n+ *dest_lenp = guest_dest_len;\n+ status = compress2(dest, dest_lenp, source, (uLong)a3, level);\n+\n+ if (status != Z_OK) {\n+ *sysret = (uint64_t)(uint32_t)status;\n+ return true;\n+ }\n+\n+ *sysret = Z_OK;\n+ out = g_strdup_printf(\n+ \"syscall_filter_zlib: compressed %\" PRIu64\n+ \" guest bytes to %lu host bytes\\n\",\n+ a3, (unsigned long)*dest_lenp);\n+ qemu_plugin_outs(out);\n+ return true;\n+}\n+\n+static bool handle_uncompress(int64_t num, uint64_t a1, uint64_t a2,\n+ uint64_t a3, uint64_t a4, uint64_t a5,\n+ uint64_t *sysret)\n+{\n+ g_autofree char *out = NULL;\n+ const Bytef *source;\n+ Bytef *dest;\n+ uLongf *dest_lenp;\n+ uLongf guest_dest_len;\n+ int status;\n+\n+ if (num != MAGIC_SYSCALL || a1 != ZLIB_COMPRESS_OP_UNCOMPRESS) {\n+ return false;\n+ }\n+\n+ g_assert(a2 != 0 && a4 != 0 && a5 != 0);\n+\n+ dest_lenp = (uLongf *)(uintptr_t)a5;\n+ guest_dest_len = *dest_lenp;\n+\n+ g_assert(a3 <= ZLIB_COMPRESS_MAX_BUFFER);\n+ g_assert(guest_dest_len <= ZLIB_COMPRESS_MAX_INPUT);\n+\n+ source = (const Bytef *)(uintptr_t)a2;\n+ dest = (Bytef *)(uintptr_t)a4;\n+ *dest_lenp = guest_dest_len;\n+ status = uncompress(dest, dest_lenp, source, (uLong)a3);\n+\n+ if (status != Z_OK) {\n+ *sysret = (uint64_t)(uint32_t)status;\n+ return true;\n+ }\n+\n+ *sysret = Z_OK;\n+ out = g_strdup_printf(\n+ \"syscall_filter_zlib: uncompressed %\" PRIu64\n+ \" guest bytes to %lu host bytes\\n\",\n+ a3, (unsigned long)*dest_lenp);\n+ qemu_plugin_outs(out);\n+ return true;\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+ if (handle_library_open(num, a1, a2, a3, a4, sysret)) {\n+ return true;\n+ }\n+\n+ if (handle_compress_bound(num, a1, a2, sysret)) {\n+ return true;\n+ }\n+\n+ if (handle_compress2(num, a1, a2, a3, a4, a5, a6, sysret)) {\n+ return true;\n+ }\n+\n+ if (handle_uncompress(num, a1, a2, a3, a4, a5, sysret)) {\n+ return true;\n+ }\n+\n+ return false;\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_zlib: this example 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_zlib: unsupported linux-user target '%s' \"\n+ \"(supported: x86_64)\\n\",\n+ info->target_name);\n+ return -1;\n+ }\n+\n+ qemu_plugin_register_vcpu_syscall_filter_cb(id, vcpu_syscall_filter);\n+ return 0;\n+}\ndiff --git a/docs/about/emulation.rst b/docs/about/emulation.rst\nindex 469f31bab6..4afec85ff6 100644\n--- a/docs/about/emulation.rst\n+++ b/docs/about/emulation.rst\n@@ -235,6 +235,47 @@ basic plugins that are used to test and exercise the API during the\n ``make check-tcg`` target in ``tests/tcg/plugins`` that are never the\n less useful for basic analysis.\n \n+Local Library Interception\n+..........................\n+\n+``contrib/plugins/syscall_filter_zlib.c``\n+\n+This linux-user example shows how to use the syscall filter callback API to\n+intercept a library load and replace it with a guest thunk library. The guest\n+side example lives in ``contrib/plugins/syscall_filter_zlib-example``.\n+\n+The plugin does two things:\n+\n+* It filters the guest ``openat()`` that the dynamic loader issues for\n+ ``./libdemo-zlib.so`` and instead returns a file descriptor for\n+ ``libdemo-zlib-thunk.so``.\n+* It filters magic syscalls from the thunk library and runs the host's zlib\n+ ``compressBound()``, ``compress2()``, and ``uncompress()`` implementations\n+ directly on guest buffers.\n+\n+This makes the motivation concrete without introducing callback semantics: the\n+example looks like a plausible local compression offload, and the guest-visible\n+API stays small and easy to reproduce.\n+The example currently supports ``x86_64`` linux-user only. To keep the example\n+minimal, it currently assumes ``guest_base == 0`` on a little-endian 64-bit\n+host. For this x86_64 linux-user demo, that means guest virtual addresses are\n+directly usable as host pointers.\n+\n+To reproduce the example on ``qemu-x86_64``::\n+\n+ $ ninja -C build contrib-plugins\n+ $ cd contrib/plugins/syscall_filter_zlib-example\n+ $ make\n+ $ QEMU_BUILD=/path/to/qemu/build\n+ $ $QEMU_BUILD/qemu-x86_64 \\\n+ -plugin $QEMU_BUILD/contrib/plugins/libsyscall_filter_zlib.so \\\n+ -d plugin \\\n+ ./zcompress-demo\n+\n+Without the plugin, the guest program fails at startup because\n+``libdemo-zlib.so`` is missing. With the plugin, QEMU's plugin log shows the\n+loader redirection plus native compression and decompression handling.\n+\n Empty\n .....\n \n", "prefixes": [ "v1", "1/3" ] }