From nobody Thu Apr 2 01:30:50 2026 Delivered-To: importer@patchew.org Authentication-Results: mx.zohomail.com; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=fail(p=none dis=none) header.from=sjtu.edu.cn Return-Path: Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) by mx.zohomail.com with SMTPS id 1774991937712386.3235055258632; Tue, 31 Mar 2026 14:18:57 -0700 (PDT) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1w7gTL-0008RN-FX; Tue, 31 Mar 2026 17:18:03 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1w7d8v-0004tN-26 for qemu-devel@nongnu.org; Tue, 31 Mar 2026 13:44:46 -0400 Received: from smtp232.sjtu.edu.cn ([202.120.2.232]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1w7d8q-00089Y-CB for qemu-devel@nongnu.org; Tue, 31 Mar 2026 13:44:44 -0400 Received: from proxy188.sjtu.edu.cn (smtp188.sjtu.edu.cn [202.120.2.188]) by smtp232.sjtu.edu.cn (Postfix) with ESMTPS id 2BE7A1068BE6E; Wed, 1 Apr 2026 01:38:03 +0800 (CST) Received: from xuklXiaoxin (unknown [202.120.32.222]) by proxy188.sjtu.edu.cn (Postfix) with ESMTPSA id 04A9A37C8CA; Wed, 1 Apr 2026 01:38:03 +0800 (CST) From: XU Kailiang To: qemu-devel Cc: =?UTF-8?q?Alex=20Benn=C3=A9e?= , Pierrick Bouvier , Alexandre Iooss , Mahmoud Mandour , Ziyang Zhang , Yun Wang , Mingyuan Xia , Zhengwei Qi , XU Kailiang 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: quoted-printable Received-SPF: pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) client-ip=209.51.188.17; envelope-from=qemu-devel-bounces+importer=patchew.org@nongnu.org; helo=lists.gnu.org; Received-SPF: pass client-ip=202.120.2.232; envelope-from=xukl2019@sjtu.edu.cn; 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, RCVD_IN_VALIDITY_CERTIFIED_BLOCKED=1, RCVD_IN_VALIDITY_RPBL_BLOCKED=1, 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 List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+importer=patchew.org@nongnu.org Sender: qemu-devel-bounces+importer=patchew.org@nongnu.org X-ZM-MESSAGEID: 1774991944865154101 Content-Type: text/plain; charset="utf-8" Add a minimal linux-user plugin example that uses the syscall filter callback API to intercept a guest library load and replace it with a local thunk library. The guest-side demo links against libdemo-zlib.so. When the guest dynamic loader issues openat() for that library, the plugin returns a file descriptor for libdemo-zlib-thunk.so instead. The thunk library forwards compressBound(), compress2(), and uncompress() through magic syscalls, which the plugin handles by calling the host zlib implementation directly. At this stage, the loader redirection covers only the openat()-based library load path. The follow-up patch in this series extends the same demo to open() and openat2(). Once the demo library path matches, the plugin asserts that opening the thunk library succeeds. That is intentional here: this example is meant to show the direct hand-off to a known local thunk library, not fallback behavior when that local library is missing or unusable. Document the assumptions and add a small example directory with a Makefile so the behavior is easy to reproduce. Signed-off-by: XU Kailiang Co-authored-by: Ziyang Zhang --- contrib/plugins/meson.build | 12 +- .../syscall_filter_zlib-example/Makefile | 19 ++ .../syscall_filter_zlib-example/README.rst | 43 +++ .../zcompress-demo.c | 94 ++++++ .../zcompress-thunk.c | 35 +++ .../zcompress-thunk.h | 16 ++ contrib/plugins/syscall_filter_zlib.c | 268 ++++++++++++++++++ docs/about/emulation.rst | 41 +++ 8 files changed, 527 insertions(+), 1 deletion(-) create mode 100644 contrib/plugins/syscall_filter_zlib-example/Makefile create mode 100644 contrib/plugins/syscall_filter_zlib-example/README.rst create mode 100644 contrib/plugins/syscall_filter_zlib-example/zcompress-d= emo.c create mode 100644 contrib/plugins/syscall_filter_zlib-example/zcompress-t= hunk.c create mode 100644 contrib/plugins/syscall_filter_zlib-example/zcompress-t= hunk.h create mode 100644 contrib/plugins/syscall_filter_zlib.c diff --git a/contrib/plugins/meson.build b/contrib/plugins/meson.build index 099319e7a1..aa3b95eeab 100644 --- a/contrib/plugins/meson.build +++ b/contrib/plugins/meson.build @@ -19,6 +19,12 @@ if host_os !=3D 'windows' contrib_plugins +=3D 'lockstep.c' endif =20 +if host_os !=3D 'windows' and host_os !=3D 'darwin' and + host_machine.endian() =3D=3D 'little' + # The zlib syscall-filter demo assumes little-endian direct-pointer acce= ss. + contrib_plugins +=3D 'syscall_filter_zlib.c' +endif + if 'cpp' in all_languages contrib_plugins +=3D 'cpp.cpp' endif @@ -26,8 +32,12 @@ endif t =3D [] if get_option('plugins') foreach i : contrib_plugins + deps =3D plugins_deps + if i =3D=3D 'syscall_filter_zlib.c' + deps =3D [plugins_deps, zlib] + endif t +=3D shared_module(fs.stem(i), files(i), - dependencies: plugins_deps) + dependencies: deps) endforeach endif if t.length() > 0 diff --git a/contrib/plugins/syscall_filter_zlib-example/Makefile b/contrib= /plugins/syscall_filter_zlib-example/Makefile new file mode 100644 index 0000000000..f94b93e950 --- /dev/null +++ b/contrib/plugins/syscall_filter_zlib-example/Makefile @@ -0,0 +1,19 @@ +# SPDX-License-Identifier: GPL-2.0-or-later + +CC ?=3D cc +CFLAGS ?=3D +LDFLAGS ?=3D +RM ?=3D rm -f + +all: libdemo-zlib-thunk.so zcompress-demo + +libdemo-zlib-thunk.so: zcompress-thunk.c zcompress-thunk.h + $(CC) $(CFLAGS) -fPIC -shared zcompress-thunk.c -Wl,-soname,libdemo-zlib.= so -o $@ $(LDFLAGS) + +zcompress-demo: zcompress-demo.c zcompress-thunk.h libdemo-zlib-thunk.so + $(CC) $(CFLAGS) zcompress-demo.c ./libdemo-zlib-thunk.so -Wl,-rpath,'$$OR= IGIN' -o $@ $(LDFLAGS) + +clean: + $(RM) libdemo-zlib-thunk.so zcompress-demo + +.PHONY: all clean diff --git a/contrib/plugins/syscall_filter_zlib-example/README.rst b/contr= ib/plugins/syscall_filter_zlib-example/README.rst new file mode 100644 index 0000000000..4920187a2b --- /dev/null +++ b/contrib/plugins/syscall_filter_zlib-example/README.rst @@ -0,0 +1,43 @@ +.. SPDX-License-Identifier: GPL-2.0-or-later + +zlib compression syscall-filter example +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D + +This directory contains the guest-side pieces used by +``contrib/plugins/syscall_filter_zlib.c``: + +* ``zcompress-demo.c`` is linked against ``libdemo-zlib.so`` and calls the + compression helpers directly. +* The plugin intercepts the loader's ``openat()`` call and returns a file + descriptor for ``./libdemo-zlib-thunk.so`` instead. +* ``zcompress-thunk.c`` exposes a tiny compression API as thin wrappers ar= ound + magic syscalls. +* The plugin filters those magic syscalls and executes the host zlib + ``compressBound()``, ``compress2()``, and ``uncompress()`` implementatio= ns + directly on guest buffers. +* The example currently supports ``x86_64`` linux-user only. Extending the + syscall-number table for more targets is straightforward, but is outside= the + scope of this patch. +* To keep the demo small, the plugin assumes ``guest_base =3D=3D 0`` on a + little-endian 64-bit host. For this x86_64 linux-user demo, that means g= uest + virtual addresses are directly usable as host pointers. In practice that + means running ``qemu-x86_64`` on a little-endian 64-bit host without for= cing + a nonzero guest base. + +Build the guest-side demo with:: + + make + +Then run it from this directory with QEMU linux-user and the plugin:: + + QEMU_BUILD=3D/path/to/qemu/build + $QEMU_BUILD/qemu-x86_64 \ + -plugin $QEMU_BUILD/contrib/plugins/libsyscall_filter_zlib.so \ + -d plugin \ + ./zcompress-demo + +The build links ``zcompress-demo`` against ``libdemo-zlib-thunk.so`` while +giving that shared object the soname ``libdemo-zlib.so``. Without the plug= in, +the program fails at startup because no ``libdemo-zlib.so`` file exists in= the +runtime search path. With the plugin, the guest sees a working library load +even though the loader actually receives ``libdemo-zlib-thunk.so``. diff --git a/contrib/plugins/syscall_filter_zlib-example/zcompress-demo.c b= /contrib/plugins/syscall_filter_zlib-example/zcompress-demo.c new file mode 100644 index 0000000000..a199b8d290 --- /dev/null +++ b/contrib/plugins/syscall_filter_zlib-example/zcompress-demo.c @@ -0,0 +1,94 @@ +/* + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include +#include +#include +#include + +#include "zcompress-thunk.h" + +#define INPUT_SIZE (8 * 1024 * 1024) +#define Z_BEST_COMPRESSION 9 + +static void fill_input(unsigned char *data, size_t len) +{ + static const unsigned char pattern[] =3D + "QEMU syscall filter zlib compression demo payload\n"; + size_t i; + + for (i =3D 0; i < len; i++) { + data[i] =3D pattern[i % (sizeof(pattern) - 1)]; + if ((i % 4096) =3D=3D 0) { + data[i] =3D (unsigned char)(i >> 12); + } + } +} + +int main(void) +{ + unsigned char *input =3D NULL; + unsigned char *compressed =3D NULL; + unsigned char *output =3D NULL; + size_t compressed_len; + size_t output_len; + size_t compressed_bound; + int ret =3D EXIT_FAILURE; + + input =3D malloc(INPUT_SIZE); + if (input =3D=3D NULL) { + perror("malloc"); + goto cleanup; + } + + fill_input(input, INPUT_SIZE); + + compressed_bound =3D zcompress_compress_bound(INPUT_SIZE); + if (compressed_bound =3D=3D 0) { + fprintf(stderr, "zcompress_compress_bound failed\n"); + goto cleanup; + } + + compressed =3D malloc(compressed_bound); + output =3D malloc(INPUT_SIZE); + if (compressed =3D=3D NULL || output =3D=3D NULL) { + perror("malloc"); + goto cleanup; + } + + compressed_len =3D compressed_bound; + if (zcompress_compress(input, INPUT_SIZE, compressed, &compressed_len, + Z_BEST_COMPRESSION) !=3D 0) { + fprintf(stderr, "zcompress_compress failed\n"); + goto cleanup; + } + + output_len =3D INPUT_SIZE; + if (zcompress_uncompress(compressed, compressed_len, output, + &output_len) !=3D 0) { + fprintf(stderr, "zcompress_uncompress failed\n"); + goto cleanup; + } + + if (output_len !=3D INPUT_SIZE || memcmp(input, output, INPUT_SIZE) != =3D 0) { + fprintf(stderr, "round-trip mismatch\n"); + goto cleanup; + } + + if (compressed_len >=3D INPUT_SIZE) { + fprintf(stderr, "compressed output was not smaller than input\n"); + goto cleanup; + } + + printf("zlib demo compressed %u bytes to %zu bytes\n", + INPUT_SIZE, compressed_len); + puts("zlib demo round-tripped successfully"); + ret =3D EXIT_SUCCESS; + +cleanup: + free(output); + free(compressed); + free(input); + return ret; +} diff --git a/contrib/plugins/syscall_filter_zlib-example/zcompress-thunk.c = b/contrib/plugins/syscall_filter_zlib-example/zcompress-thunk.c new file mode 100644 index 0000000000..1e498a728e --- /dev/null +++ b/contrib/plugins/syscall_filter_zlib-example/zcompress-thunk.c @@ -0,0 +1,35 @@ +/* + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include +#include + +#include "zcompress-thunk.h" + +#define ZLIB_COMPRESS_MAGIC_SYSCALL 4096 +#define ZLIB_COMPRESS_OP_BOUND 1 +#define ZLIB_COMPRESS_OP_COMPRESS2 2 +#define ZLIB_COMPRESS_OP_UNCOMPRESS 3 + +size_t zcompress_compress_bound(size_t source_len) +{ + return (size_t)syscall(ZLIB_COMPRESS_MAGIC_SYSCALL, + ZLIB_COMPRESS_OP_BOUND, source_len); +} + +int zcompress_compress(const void *source, size_t source_len, + void *dest, size_t *dest_len, int level) +{ + return (int)syscall(ZLIB_COMPRESS_MAGIC_SYSCALL, + ZLIB_COMPRESS_OP_COMPRESS2, + source, source_len, dest, dest_len, level); +} + +int zcompress_uncompress(const void *source, size_t source_len, + void *dest, size_t *dest_len) +{ + return (int)syscall(ZLIB_COMPRESS_MAGIC_SYSCALL, + ZLIB_COMPRESS_OP_UNCOMPRESS, + source, source_len, dest, dest_len); +} diff --git a/contrib/plugins/syscall_filter_zlib-example/zcompress-thunk.h = b/contrib/plugins/syscall_filter_zlib-example/zcompress-thunk.h new file mode 100644 index 0000000000..f0092ff681 --- /dev/null +++ b/contrib/plugins/syscall_filter_zlib-example/zcompress-thunk.h @@ -0,0 +1,16 @@ +/* + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef CONTRIB_PLUGINS_SYSCALL_FILTER_ZLIB_EXAMPLE_ZCOMPRESS_THUNK_H +#define CONTRIB_PLUGINS_SYSCALL_FILTER_ZLIB_EXAMPLE_ZCOMPRESS_THUNK_H + +#include + +size_t zcompress_compress_bound(size_t source_len); +int zcompress_compress(const void *source, size_t source_len, + void *dest, size_t *dest_len, int level); +int zcompress_uncompress(const void *source, size_t source_len, + void *dest, size_t *dest_len); + +#endif diff --git a/contrib/plugins/syscall_filter_zlib.c b/contrib/plugins/syscal= l_filter_zlib.c new file mode 100644 index 0000000000..e8f430cbaf --- /dev/null +++ b/contrib/plugins/syscall_filter_zlib.c @@ -0,0 +1,268 @@ +/* + * SPDX-License-Identifier: GPL-2.0-or-later + * + * Minimal linux-user plugin that demonstrates syscall filtering for + * local library interception with a host zlib compression example. + * + * When the guest dynamic loader attempts to open "./libdemo-zlib.so", this + * plugin intercepts openat() and instead returns a file descriptor for + * "libdemo-zlib-thunk.so" in the same directory. The thunk library then + * forwards compression requests through magic syscalls, which are handled= by + * this plugin and executed by the host's zlib implementation. + * + * This demo intentionally assumes a linux-user run with guest_base =3D=3D= 0 on a + * little-endian 64-bit host, so guest virtual addresses are directly usab= le + * as host pointers. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +QEMU_PLUGIN_EXPORT int qemu_plugin_version =3D QEMU_PLUGIN_VERSION; + +#define MAGIC_SYSCALL 4096 +#define ZLIB_COMPRESS_OP_BOUND 1 +#define ZLIB_COMPRESS_OP_COMPRESS2 2 +#define ZLIB_COMPRESS_OP_UNCOMPRESS 3 +#define ZLIB_COMPRESS_LIBRARY "libdemo-zlib.so" +#define ZLIB_COMPRESS_THUNK_LIBRARY "libdemo-zlib-thunk.so" +#define ZLIB_COMPRESS_MAX_INPUT (64 * 1024 * 1024) +#define ZLIB_COMPRESS_MAX_BUFFER (128 * 1024 * 1024) +#define GUEST_STRING_CHUNK 64 +#define GUEST_STRING_LIMIT (1 << 20) +#define X86_64_OPENAT_NR 257 + +static char *read_guest_cstring(uint64_t addr) +{ + g_autoptr(GByteArray) data =3D g_byte_array_sized_new(GUEST_STRING_CHU= NK); + g_autoptr(GString) str =3D g_string_sized_new(GUEST_STRING_CHUNK); + size_t offset; + + for (offset =3D 0; + offset < GUEST_STRING_LIMIT; + offset +=3D GUEST_STRING_CHUNK) { + g_byte_array_set_size(data, GUEST_STRING_CHUNK); + if (!qemu_plugin_read_memory_vaddr(addr + offset, data, + GUEST_STRING_CHUNK)) { + return NULL; + } + + for (guint i =3D 0; i < data->len; i++) { + if (data->data[i] =3D=3D '\0') { + return g_string_free(g_steal_pointer(&str), FALSE); + } + g_string_append_c(str, data->data[i]); + } + } + + return NULL; +} + +static bool guest_path_matches_zlib_compress(const char *path) +{ + g_autofree char *basename =3D g_path_get_basename(path); + + return strcmp(basename, ZLIB_COMPRESS_LIBRARY) =3D=3D 0; +} + +static char *build_thunk_path(const char *path) +{ + g_autofree char *dirname =3D g_path_get_dirname(path); + + if (strcmp(dirname, ".") =3D=3D 0) { + return g_strdup(ZLIB_COMPRESS_THUNK_LIBRARY); + } + + return g_build_filename(dirname, ZLIB_COMPRESS_THUNK_LIBRARY, NULL); +} + +static bool handle_library_open(int64_t num, uint64_t a1, uint64_t a2, + uint64_t a3, uint64_t a4, uint64_t *sysret) +{ + g_autofree char *path =3D NULL; + g_autofree char *thunk_path =3D NULL; + g_autofree char *out =3D NULL; + int fd; + + if (num !=3D X86_64_OPENAT_NR) { + return false; + } + + path =3D read_guest_cstring(a2); + if (path =3D=3D NULL || !guest_path_matches_zlib_compress(path)) { + return false; + } + thunk_path =3D build_thunk_path(path); + if (access(thunk_path, F_OK) !=3D 0) { + return false; + } + + fd =3D openat((int)a1, thunk_path, (int)a3, (mode_t)a4); + g_assert(fd >=3D 0); + + *sysret =3D fd; + out =3D g_strdup_printf("syscall_filter_zlib: redirected %s -> %s (fd= =3D%d)\n", + path, thunk_path, fd); + qemu_plugin_outs(out); + return true; +} + +static bool handle_compress_bound(int64_t num, uint64_t a1, uint64_t a2, + uint64_t *sysret) +{ + if (num !=3D MAGIC_SYSCALL || a1 !=3D ZLIB_COMPRESS_OP_BOUND) { + return false; + } + + if (a2 > ZLIB_COMPRESS_MAX_INPUT) { + *sysret =3D 0; + return true; + } + + *sysret =3D compressBound((uLong)a2); + return true; +} + +static bool handle_compress2(int64_t num, uint64_t a1, uint64_t a2, + uint64_t a3, uint64_t a4, uint64_t a5, + uint64_t a6, uint64_t *sysret) +{ + g_autofree char *out =3D NULL; + const Bytef *source; + Bytef *dest; + uLongf *dest_lenp; + uLongf guest_dest_len; + int status; + int level =3D (int)a6; + + if (num !=3D MAGIC_SYSCALL || a1 !=3D ZLIB_COMPRESS_OP_COMPRESS2) { + return false; + } + + g_assert(a2 !=3D 0 && a4 !=3D 0 && a5 !=3D 0); + g_assert(level =3D=3D Z_DEFAULT_COMPRESSION || + (level >=3D Z_NO_COMPRESSION && level <=3D Z_BEST_COMPRESSION= )); + + dest_lenp =3D (uLongf *)(uintptr_t)a5; + guest_dest_len =3D *dest_lenp; + + g_assert(a3 <=3D ZLIB_COMPRESS_MAX_INPUT); + g_assert(guest_dest_len <=3D ZLIB_COMPRESS_MAX_BUFFER); + + source =3D (const Bytef *)(uintptr_t)a2; + dest =3D (Bytef *)(uintptr_t)a4; + *dest_lenp =3D guest_dest_len; + status =3D compress2(dest, dest_lenp, source, (uLong)a3, level); + + if (status !=3D Z_OK) { + *sysret =3D (uint64_t)(uint32_t)status; + return true; + } + + *sysret =3D Z_OK; + out =3D g_strdup_printf( + "syscall_filter_zlib: compressed %" PRIu64 + " guest bytes to %lu host bytes\n", + a3, (unsigned long)*dest_lenp); + qemu_plugin_outs(out); + return true; +} + +static bool handle_uncompress(int64_t num, uint64_t a1, uint64_t a2, + uint64_t a3, uint64_t a4, uint64_t a5, + uint64_t *sysret) +{ + g_autofree char *out =3D NULL; + const Bytef *source; + Bytef *dest; + uLongf *dest_lenp; + uLongf guest_dest_len; + int status; + + if (num !=3D MAGIC_SYSCALL || a1 !=3D ZLIB_COMPRESS_OP_UNCOMPRESS) { + return false; + } + + g_assert(a2 !=3D 0 && a4 !=3D 0 && a5 !=3D 0); + + dest_lenp =3D (uLongf *)(uintptr_t)a5; + guest_dest_len =3D *dest_lenp; + + g_assert(a3 <=3D ZLIB_COMPRESS_MAX_BUFFER); + g_assert(guest_dest_len <=3D ZLIB_COMPRESS_MAX_INPUT); + + source =3D (const Bytef *)(uintptr_t)a2; + dest =3D (Bytef *)(uintptr_t)a4; + *dest_lenp =3D guest_dest_len; + status =3D uncompress(dest, dest_lenp, source, (uLong)a3); + + if (status !=3D Z_OK) { + *sysret =3D (uint64_t)(uint32_t)status; + return true; + } + + *sysret =3D Z_OK; + out =3D g_strdup_printf( + "syscall_filter_zlib: uncompressed %" PRIu64 + " guest bytes to %lu host bytes\n", + a3, (unsigned long)*dest_lenp); + qemu_plugin_outs(out); + return true; +} + +static bool vcpu_syscall_filter(qemu_plugin_id_t id, unsigned int vcpu_ind= ex, + int64_t num, uint64_t a1, uint64_t a2, + uint64_t a3, uint64_t a4, uint64_t a5, + uint64_t a6, uint64_t a7, uint64_t a8, + uint64_t *sysret) +{ + if (handle_library_open(num, a1, a2, a3, a4, sysret)) { + return true; + } + + if (handle_compress_bound(num, a1, a2, sysret)) { + return true; + } + + if (handle_compress2(num, a1, a2, a3, a4, a5, a6, sysret)) { + return true; + } + + if (handle_uncompress(num, a1, a2, a3, a4, a5, sysret)) { + return true; + } + + return false; +} + +QEMU_PLUGIN_EXPORT int qemu_plugin_install(qemu_plugin_id_t id, + const qemu_info_t *info, + int argc, char **argv) +{ + if (argc !=3D 0) { + fprintf(stderr, + "syscall_filter_zlib: this example plugin does not take ar= guments\n"); + return -1; + } + + if (strcmp(info->target_name, "x86_64") !=3D 0) { + fprintf(stderr, + "syscall_filter_zlib: unsupported linux-user target '%s' " + "(supported: x86_64)\n", + info->target_name); + return -1; + } + + qemu_plugin_register_vcpu_syscall_filter_cb(id, vcpu_syscall_filter); + return 0; +} diff --git a/docs/about/emulation.rst b/docs/about/emulation.rst index 469f31bab6..4afec85ff6 100644 --- a/docs/about/emulation.rst +++ b/docs/about/emulation.rst @@ -235,6 +235,47 @@ basic plugins that are used to test and exercise the A= PI during the ``make check-tcg`` target in ``tests/tcg/plugins`` that are never the less useful for basic analysis. =20 +Local Library Interception +.......................... + +``contrib/plugins/syscall_filter_zlib.c`` + +This linux-user example shows how to use the syscall filter callback API to +intercept a library load and replace it with a guest thunk library. The gu= est +side example lives in ``contrib/plugins/syscall_filter_zlib-example``. + +The plugin does two things: + +* It filters the guest ``openat()`` that the dynamic loader issues for + ``./libdemo-zlib.so`` and instead returns a file descriptor for + ``libdemo-zlib-thunk.so``. +* It filters magic syscalls from the thunk library and runs the host's zlib + ``compressBound()``, ``compress2()``, and ``uncompress()`` implementatio= ns + directly on guest buffers. + +This makes the motivation concrete without introducing callback semantics:= the +example looks like a plausible local compression offload, and the guest-vi= sible +API stays small and easy to reproduce. +The example currently supports ``x86_64`` linux-user only. To keep the exa= mple +minimal, it currently assumes ``guest_base =3D=3D 0`` on a little-endian 6= 4-bit +host. For this x86_64 linux-user demo, that means guest virtual addresses = are +directly usable as host pointers. + +To reproduce the example on ``qemu-x86_64``:: + + $ ninja -C build contrib-plugins + $ cd contrib/plugins/syscall_filter_zlib-example + $ make + $ QEMU_BUILD=3D/path/to/qemu/build + $ $QEMU_BUILD/qemu-x86_64 \ + -plugin $QEMU_BUILD/contrib/plugins/libsyscall_filter_zlib.so \ + -d plugin \ + ./zcompress-demo + +Without the plugin, the guest program fails at startup because +``libdemo-zlib.so`` is missing. With the plugin, QEMU's plugin log shows t= he +loader redirection plus native compression and decompression handling. + Empty ..... =20 --=20 2.53.0