From nobody Wed Apr 1 23:52:03 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 From nobody Wed Apr 1 23:52:03 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 1774991929226370.65244372743837; Tue, 31 Mar 2026 14:18:49 -0700 (PDT) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1w7gTL-0008SX-SJ; 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 1w7dE7-0007Pp-1a for qemu-devel@nongnu.org; Tue, 31 Mar 2026 13:50:07 -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 1w7dE3-0000M9-4f for qemu-devel@nongnu.org; Tue, 31 Mar 2026 13:50:06 -0400 Received: from proxy188.sjtu.edu.cn (smtp188.sjtu.edu.cn [202.120.2.188]) by smtp232.sjtu.edu.cn (Postfix) with ESMTPS id 0D1FB10097F65; Wed, 1 Apr 2026 01:38:09 +0800 (CST) Received: from xuklXiaoxin (unknown [202.120.32.222]) by proxy188.sjtu.edu.cn (Postfix) with ESMTPSA id DAF7037C8CA; Wed, 1 Apr 2026 01:38:08 +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 2/3] contrib/plugins: add a callback-capable qsort prototype Date: Wed, 1 Apr 2026 01:36:25 +0800 Message-ID: <20260331173656.35305-3-xukl2019@sjtu.edu.cn> X-Mailer: git-send-email 2.53.0 In-Reply-To: <20260331173656.35305-1-xukl2019@sjtu.edu.cn> References: <20260331173656.35305-1-xukl2019@sjtu.edu.cn> MIME-Version: 1.0 Content-Transfer-Encoding: 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: 1774991935521158500 Content-Type: text/plain; charset="utf-8" Add a second linux-user example that demonstrates a more advanced use of the syscall filter API: redirecting a thunk library for qsort() and bridging comparator callbacks back into guest translated code. The guest-side prototype links against libdemo-callback-qsort.so. The plugin redirects the loader's openat() to libdemo-callback-qsort-thunk.so, runs host qsort() inside a ucontext worker, and on each comparator invocation rewrites guest register state and PC so execution continues in the guest comparator. The guest return trampoline then resumes the suspended host call with a second magic syscall. At this stage, the loader redirection covers only the openat()-based library load path. The follow-up patch in this series extends the same prototype to open() and openat2(). Once the demo library path matches, the plugin asserts that opening the thunk library succeeds. That is intentional here: this prototype is meant to show the callback bridge once a known local thunk library is in place, not fallback behavior when that local library is missing or unusable. Keep the scope explicit: this is an x86_64-only prototype intended to show what the interface can support, not a general callback ABI. Signed-off-by: XU Kailiang Co-authored-by: Ziyang Zhang --- contrib/plugins/meson.build | 12 +- .../Makefile | 19 + .../README.rst | 46 ++ .../callback-demo.c | 75 +++ .../callback-qsort.h | 14 + .../callback-thunk.S | 38 ++ .../plugins/syscall_filter_callback_qsort.c | 431 ++++++++++++++++++ 7 files changed, 631 insertions(+), 4 deletions(-) create mode 100644 contrib/plugins/syscall_filter_callback_qsort-example/M= akefile create mode 100644 contrib/plugins/syscall_filter_callback_qsort-example/R= EADME.rst create mode 100644 contrib/plugins/syscall_filter_callback_qsort-example/c= allback-demo.c create mode 100644 contrib/plugins/syscall_filter_callback_qsort-example/c= allback-qsort.h create mode 100644 contrib/plugins/syscall_filter_callback_qsort-example/c= allback-thunk.S create mode 100644 contrib/plugins/syscall_filter_callback_qsort.c diff --git a/contrib/plugins/meson.build b/contrib/plugins/meson.build index aa3b95eeab..cc82255666 100644 --- a/contrib/plugins/meson.build +++ b/contrib/plugins/meson.build @@ -19,10 +19,14 @@ 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' +if host_os !=3D 'windows' and host_os !=3D 'darwin' + # The syscall-filter demos assume little-endian direct-pointer access. + if host_machine.endian() =3D=3D 'little' + contrib_plugins +=3D 'syscall_filter_zlib.c' + if supported_backends.contains('ucontext') + contrib_plugins +=3D 'syscall_filter_callback_qsort.c' + endif + endif endif =20 if 'cpp' in all_languages diff --git a/contrib/plugins/syscall_filter_callback_qsort-example/Makefile= b/contrib/plugins/syscall_filter_callback_qsort-example/Makefile new file mode 100644 index 0000000000..0144e46790 --- /dev/null +++ b/contrib/plugins/syscall_filter_callback_qsort-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-callback-qsort-thunk.so callback-demo + +libdemo-callback-qsort-thunk.so: callback-thunk.S + $(CC) $(CFLAGS) -fPIC -shared callback-thunk.S -Wl,-soname,libdemo-callba= ck-qsort.so -o $@ $(LDFLAGS) + +callback-demo: callback-demo.c callback-qsort.h libdemo-callback-qsort-thu= nk.so + $(CC) $(CFLAGS) callback-demo.c ./libdemo-callback-qsort-thunk.so -Wl,-rp= ath,'$$ORIGIN' -o $@ $(LDFLAGS) + +clean: + $(RM) libdemo-callback-qsort-thunk.so callback-demo + +.PHONY: all clean diff --git a/contrib/plugins/syscall_filter_callback_qsort-example/README.r= st b/contrib/plugins/syscall_filter_callback_qsort-example/README.rst new file mode 100644 index 0000000000..e2bba7e2b6 --- /dev/null +++ b/contrib/plugins/syscall_filter_callback_qsort-example/README.rst @@ -0,0 +1,46 @@ +.. SPDX-License-Identifier: GPL-2.0-or-later + +Callback qsort bridge prototype +=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 a prototype for callback-capable local library +interception on ``qemu-x86_64``. + +* ``callback-demo.c`` is linked against ``libdemo-callback-qsort.so`` and = calls + ``callback_qsort()`` directly. +* The plugin intercepts the loader's ``openat()`` and returns a file + descriptor for ``./libdemo-callback-qsort-thunk.so`` instead. +* ``callback-thunk.S`` issues a ``START`` magic syscall for ``qsort()`` an= d a + ``RESUME`` magic syscall from a guest return trampoline. +* ``contrib/plugins/syscall_filter_callback_qsort.c`` runs host ``qsort()`` + inside a ``ucontext`` worker, yields on every comparator invocation, and + redirects control flow into the guest comparator with + ``qemu_plugin_set_pc()``. +* This callback prototype is intentionally ``x86_64``-only. Supporting + additional targets would require target-specific register and ABI handli= ng, + which is beyond the scope of this example. +* The plugin is built only on hosts where ``ucontext`` is available. +* To keep the prototype minimal, it 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 prototype 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_callback_qsort.s= o \ + -d plugin \ + ./callback-demo + +The build links ``callback-demo`` against ``libdemo-callback-qsort-thunk.s= o`` +while giving that shared object the soname ``libdemo-callback-qsort.so``. +Without the plugin, the program fails at startup because no +``libdemo-callback-qsort.so`` file exists in the runtime search path. With= the +plugin, the guest sees a working library load even though the loader actua= lly +receives ``libdemo-callback-qsort-thunk.so``. diff --git a/contrib/plugins/syscall_filter_callback_qsort-example/callback= -demo.c b/contrib/plugins/syscall_filter_callback_qsort-example/callback-de= mo.c new file mode 100644 index 0000000000..5e89d9f022 --- /dev/null +++ b/contrib/plugins/syscall_filter_callback_qsort-example/callback-demo.c @@ -0,0 +1,75 @@ +/* + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include +#include +#include +#include + +#include "callback-qsort.h" + +#define VALUE_COUNT 128 + +static int compare_count; + +static int guest_compare_int32(const void *lhs, const void *rhs) +{ + const int32_t *a =3D lhs; + const int32_t *b =3D rhs; + + compare_count++; + if (*a < *b) { + return -1; + } + if (*a > *b) { + return 1; + } + return 0; +} + +static int is_sorted(const int32_t *values, size_t count) +{ + size_t i; + + for (i =3D 1; i < count; i++) { + if (values[i - 1] > values[i]) { + return 0; + } + } + + return 1; +} + +int main(void) +{ + int32_t values[VALUE_COUNT]; + size_t i; + int ret; + + for (i =3D 0; i < VALUE_COUNT; i++) { + values[i] =3D (int32_t)(VALUE_COUNT - i); + } + + ret =3D callback_qsort(values, VALUE_COUNT, sizeof(values[0]), + guest_compare_int32); + if (ret !=3D 0) { + fprintf(stderr, "callback_qsort failed: %d\n", ret); + return EXIT_FAILURE; + } + + if (!is_sorted(values, VALUE_COUNT)) { + fprintf(stderr, "values are not sorted\n"); + return EXIT_FAILURE; + } + + if (compare_count =3D=3D 0) { + fprintf(stderr, "guest comparator never ran\n"); + return EXIT_FAILURE; + } + + printf("callback demo sorted %u values with %d guest comparator calls\= n", + VALUE_COUNT, compare_count); + puts("callback demo succeeded"); + return EXIT_SUCCESS; +} diff --git a/contrib/plugins/syscall_filter_callback_qsort-example/callback= -qsort.h b/contrib/plugins/syscall_filter_callback_qsort-example/callback-q= sort.h new file mode 100644 index 0000000000..4a1021841d --- /dev/null +++ b/contrib/plugins/syscall_filter_callback_qsort-example/callback-qsort.h @@ -0,0 +1,14 @@ +/* + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef CONTRIB_PLUGINS_SYSCALL_FILTER_CALLBACK_QSORT_EXAMPLE_CALLBACK_QSO= RT_H +#define CONTRIB_PLUGINS_SYSCALL_FILTER_CALLBACK_QSORT_EXAMPLE_CALLBACK_QSO= RT_H + +#include +typedef int (*callback_qsort_cmp_fn)(const void *lhs, const void *rhs); + +int callback_qsort(void *base, size_t nmemb, size_t size, + callback_qsort_cmp_fn cmp); + +#endif diff --git a/contrib/plugins/syscall_filter_callback_qsort-example/callback= -thunk.S b/contrib/plugins/syscall_filter_callback_qsort-example/callback-t= hunk.S new file mode 100644 index 0000000000..a8d9607d5c --- /dev/null +++ b/contrib/plugins/syscall_filter_callback_qsort-example/callback-thunk.S @@ -0,0 +1,38 @@ +/* + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#define MAGIC_SYSCALL 4096 +#define CALLBACK_BRIDGE_OP_START 1 +#define CALLBACK_BRIDGE_OP_RESUME 2 + + .text + .hidden callback_qsort_resume_trampoline + .globl callback_qsort + .type callback_qsort, @function +callback_qsort: + /* + * callback_qsort(base, nmemb, elem_size, cmp_fn) arrives in + * %rdi, %rsi, %rdx, and %rcx. Shuffle those into the x86_64 Linux + * syscall ABI so START sees: op, base, nmemb, elem_size, cmp_fn, + * trampoline. + */ + mov %rcx, %r8 + mov %rdx, %r10 + mov %rsi, %rdx + lea callback_qsort_resume_trampoline(%rip), %r9 + mov %rdi, %rsi + mov $CALLBACK_BRIDGE_OP_START, %rdi + mov $MAGIC_SYSCALL, %rax + syscall + ret + + .globl callback_qsort_resume_trampoline + .type callback_qsort_resume_trampoline, @function +callback_qsort_resume_trampoline: + /* Sign-extend the guest comparator result into the RESUME argument. */ + movslq %eax, %rsi + mov $CALLBACK_BRIDGE_OP_RESUME, %rdi + mov $MAGIC_SYSCALL, %rax + syscall + ret diff --git a/contrib/plugins/syscall_filter_callback_qsort.c b/contrib/plug= ins/syscall_filter_callback_qsort.c new file mode 100644 index 0000000000..45a83cb5b1 --- /dev/null +++ b/contrib/plugins/syscall_filter_callback_qsort.c @@ -0,0 +1,431 @@ +/* + * SPDX-License-Identifier: GPL-2.0-or-later + * + * x86_64-only prototype that demonstrates a callback-capable syscall filt= er + * plugin by redirecting a qsort() thunk library and bridging comparator c= alls + * back into guest translated code with ucontext. The loader redirection + * handles openat(). + * + * 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 + +#include + +QEMU_PLUGIN_EXPORT int qemu_plugin_version =3D QEMU_PLUGIN_VERSION; + +#define MAGIC_SYSCALL 4096 +#define CALLBACK_QSORT_OP_START 1 +#define CALLBACK_QSORT_OP_RESUME 2 +#define CALLBACK_QSORT_LIBRARY "libdemo-callback-qsort.so" +#define CALLBACK_QSORT_THUNK_LIBRARY "libdemo-callback-qsort-thunk.so" +#define GUEST_STRING_CHUNK 64 +#define GUEST_STRING_LIMIT (1 << 20) +#define CALLBACK_QSORT_MAX_ELEMS (1 << 20) +#define CALLBACK_QSORT_STACK_SIZE (1 << 20) +#define X86_64_OPENAT_NR 257 + +typedef enum CallbackQsortPhase { + CALLBACK_QSORT_PHASE_IDLE, + CALLBACK_QSORT_PHASE_NEED_CALLBACK, + CALLBACK_QSORT_PHASE_FINISHED, +} CallbackQsortPhase; + +typedef struct CallbackQsortState { + uint64_t sort_base; + uint64_t guest_cmp_fn; + uint64_t guest_trampoline; + size_t nmemb; + size_t elem_size; + void *worker_stack; + uint64_t pending_guest_a; + uint64_t pending_guest_b; + int cmp_result; + unsigned int guest_callback_count; + CallbackQsortPhase phase; + ucontext_t plugin_ctx; + ucontext_t worker_ctx; +} CallbackQsortState; + +typedef struct X86Registers { + struct qemu_plugin_register *rsp; + struct qemu_plugin_register *rdi; + struct qemu_plugin_register *rsi; +} X86Registers; + +typedef struct VcpuState { + X86Registers regs; + GByteArray *buf; + CallbackQsortState *active_call; +} VcpuState; + +static GPtrArray *vcpu_states; +static __thread CallbackQsortState *tls_active_call; + +static VcpuState *get_vcpu_state(unsigned int vcpu_index) +{ + while (vcpu_states->len <=3D vcpu_index) { + g_ptr_array_add(vcpu_states, NULL); + } + + if (g_ptr_array_index(vcpu_states, vcpu_index) =3D=3D NULL) { + VcpuState *state =3D g_new0(VcpuState, 1); + state->buf =3D g_byte_array_sized_new(16); + g_ptr_array_index(vcpu_states, vcpu_index) =3D state; + } + + return g_ptr_array_index(vcpu_states, vcpu_index); +} + +static struct qemu_plugin_register *find_register(const char *name) +{ + g_autoptr(GArray) regs =3D qemu_plugin_get_registers(); + + for (guint i =3D 0; i < regs->len; i++) { + qemu_plugin_reg_descriptor *reg =3D + &g_array_index(regs, qemu_plugin_reg_descriptor, i); + + if (strcmp(reg->name, name) =3D=3D 0) { + return reg->handle; + } + } + + return NULL; +} + +static uint64_t read_reg64(VcpuState *vcpu, struct qemu_plugin_register *r= eg) +{ + bool success; + + g_byte_array_set_size(vcpu->buf, 0); + success =3D qemu_plugin_read_register(reg, vcpu->buf); + g_assert(success); + g_assert(vcpu->buf->len =3D=3D 8); + return *(uint64_t *)vcpu->buf->data; +} + +static void write_reg64(VcpuState *vcpu, struct qemu_plugin_register *reg, + uint64_t value) +{ + bool success; + + g_byte_array_set_size(vcpu->buf, 8); + memcpy(vcpu->buf->data, &value, sizeof(value)); + success =3D qemu_plugin_write_register(reg, vcpu->buf); + g_assert(success); +} + +static bool write_guest_u64(uint64_t addr, uint64_t value) +{ + GByteArray data =3D { + .data =3D (guint8 *)&value, + .len =3D sizeof(value), + }; + + return qemu_plugin_write_memory_vaddr(addr, &data); +} + +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_bridge(const char *path) +{ + g_autofree char *basename =3D g_path_get_basename(path); + + return strcmp(basename, CALLBACK_QSORT_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(CALLBACK_QSORT_THUNK_LIBRARY); + } + + return g_build_filename(dirname, CALLBACK_QSORT_THUNK_LIBRARY, NULL); +} + +static int host_compare(const void *lhs, const void *rhs) +{ + CallbackQsortState *state =3D tls_active_call; + state->pending_guest_a =3D (uint64_t)(uintptr_t)lhs; + state->pending_guest_b =3D (uint64_t)(uintptr_t)rhs; + + state->phase =3D CALLBACK_QSORT_PHASE_NEED_CALLBACK; + swapcontext(&state->worker_ctx, &state->plugin_ctx); + return state->cmp_result; +} + +static void callback_qsort_worker(void) +{ + CallbackQsortState *state =3D tls_active_call; + + qsort((void *)(uintptr_t)state->sort_base, state->nmemb, state->elem_s= ize, + host_compare); + state->phase =3D CALLBACK_QSORT_PHASE_FINISHED; + swapcontext(&state->worker_ctx, &state->plugin_ctx); + g_assert_not_reached(); +} + +static void free_callback_state(CallbackQsortState *state) +{ + if (state =3D=3D NULL) { + return; + } + + g_free(state->worker_stack); + g_free(state); +} + +static bool finalize_if_finished(unsigned int vcpu_index, VcpuState *vcpu, + uint64_t *sysret) +{ + CallbackQsortState *state =3D vcpu->active_call; + g_autofree char *out =3D NULL; + + if (state->phase !=3D CALLBACK_QSORT_PHASE_FINISHED) { + return false; + } + + *sysret =3D 0; + + out =3D g_strdup_printf( + "syscall_filter_callback_qsort: vcpu %u completed qsort " + "with %u guest callbacks\n", + vcpu_index, state->guest_callback_count); + qemu_plugin_outs(out); + + free_callback_state(state); + vcpu->active_call =3D NULL; + tls_active_call =3D NULL; + return true; +} + +static void jump_to_guest_callback(VcpuState *vcpu, CallbackQsortState *st= ate) +{ + uint64_t current_rsp =3D read_reg64(vcpu, vcpu->regs.rsp); + uint64_t callback_rsp =3D current_rsp - sizeof(uint64_t); + uint64_t guest_return; + + g_assert((current_rsp & 0xf) =3D=3D 0 || (current_rsp & 0xf) =3D=3D 8); + + if ((callback_rsp & 0xf) !=3D 8) { + /* + * Rebuild the stack so the guest comparator sees a normal SysV + * function entry: %rsp points at its return address and + * %rsp % 16 =3D=3D 8. + */ + callback_rsp -=3D sizeof(uint64_t); + read_guest_buffer(current_rsp, &guest_return, sizeof(guest_return)= ); + g_assert(write_guest_u64(current_rsp - sizeof(uint64_t), + guest_return)); + } + + g_assert(write_guest_u64(callback_rsp, state->guest_trampoline)); + + write_reg64(vcpu, vcpu->regs.rsp, callback_rsp); + write_reg64(vcpu, vcpu->regs.rdi, state->pending_guest_a); + write_reg64(vcpu, vcpu->regs.rsi, state->pending_guest_b); + state->guest_callback_count++; + qemu_plugin_set_pc(state->guest_cmp_fn); +} + +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_bridge(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_callback_qsort: redirected %s -> %s (fd=3D%d)\n", + path, thunk_path, fd); + qemu_plugin_outs(out); + return true; +} + +static bool handle_callback_start(unsigned int vcpu_index, VcpuState *vcpu, + uint64_t sort_base, uint64_t nmemb, + uint64_t elem_size, uint64_t cmp_fn, + uint64_t trampoline, uint64_t *sysret) +{ + CallbackQsortState *state; + + g_assert(vcpu->active_call =3D=3D NULL); + g_assert(elem_size > 0); + g_assert(nmemb > 0); + g_assert(nmemb <=3D CALLBACK_QSORT_MAX_ELEMS); + g_assert(cmp_fn !=3D 0 && trampoline !=3D 0); + + state =3D g_new0(CallbackQsortState, 1); + state->sort_base =3D sort_base; + state->guest_cmp_fn =3D cmp_fn; + state->guest_trampoline =3D trampoline; + state->nmemb =3D nmemb; + state->elem_size =3D elem_size; + state->worker_stack =3D g_malloc(CALLBACK_QSORT_STACK_SIZE); + state->phase =3D CALLBACK_QSORT_PHASE_IDLE; + + getcontext(&state->worker_ctx); + state->worker_ctx.uc_link =3D NULL; + state->worker_ctx.uc_stack.ss_sp =3D state->worker_stack; + state->worker_ctx.uc_stack.ss_size =3D CALLBACK_QSORT_STACK_SIZE; + makecontext(&state->worker_ctx, callback_qsort_worker, 0); + + vcpu->active_call =3D state; + tls_active_call =3D state; + swapcontext(&state->plugin_ctx, &state->worker_ctx); + + if (finalize_if_finished(vcpu_index, vcpu, sysret)) { + return true; + } + + if (state->phase =3D=3D CALLBACK_QSORT_PHASE_NEED_CALLBACK) { + jump_to_guest_callback(vcpu, state); + return true; + } + + g_assert_not_reached(); +} + +static bool handle_callback_resume(unsigned int vcpu_index, VcpuState *vcp= u, + int64_t cmp_result, uint64_t *sysret) +{ + CallbackQsortState *state =3D vcpu->active_call; + + g_assert(state !=3D NULL); + + state->cmp_result =3D (int)cmp_result; + tls_active_call =3D state; + swapcontext(&state->plugin_ctx, &state->worker_ctx); + + if (finalize_if_finished(vcpu_index, vcpu, sysret)) { + return true; + } + + if (state->phase =3D=3D CALLBACK_QSORT_PHASE_NEED_CALLBACK) { + jump_to_guest_callback(vcpu, state); + return true; + } + + g_assert_not_reached(); +} + +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) +{ + VcpuState *vcpu =3D get_vcpu_state(vcpu_index); + + if (handle_library_open(num, a1, a2, a3, a4, sysret)) { + return true; + } + + if (num !=3D MAGIC_SYSCALL) { + return false; + } + + switch (a1) { + case CALLBACK_QSORT_OP_START: + return handle_callback_start(vcpu_index, vcpu, a2, a3, a4, a5, a6, + sysret); + case CALLBACK_QSORT_OP_RESUME: + return handle_callback_resume(vcpu_index, vcpu, (int64_t)a2, sysre= t); + default: + return false; + } +} + +static void vcpu_init_cb(qemu_plugin_id_t id, unsigned int vcpu_index) +{ + VcpuState *vcpu =3D get_vcpu_state(vcpu_index); + + vcpu->regs.rsp =3D find_register("rsp"); + vcpu->regs.rdi =3D find_register("rdi"); + vcpu->regs.rsi =3D find_register("rsi"); + g_assert(vcpu->regs.rsp !=3D NULL); + g_assert(vcpu->regs.rdi !=3D NULL); + g_assert(vcpu->regs.rsi !=3D NULL); +} + +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_callback_qsort: this prototype plugin does= not take arguments\n"); + return -1; + } + + if (strcmp(info->target_name, "x86_64") !=3D 0) { + fprintf(stderr, + "syscall_filter_callback_qsort: unsupported linux-user tar= get '%s' " + "(this prototype currently supports only x86_64)\n", + info->target_name); + return -1; + } + + vcpu_states =3D g_ptr_array_new(); + qemu_plugin_register_vcpu_init_cb(id, vcpu_init_cb); + qemu_plugin_register_vcpu_syscall_filter_cb(id, vcpu_syscall_filter); + return 0; +} --=20 2.53.0 From nobody Wed Apr 1 23:52:03 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 1774991929456529.7745057539524; Tue, 31 Mar 2026 14:18:49 -0700 (PDT) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1w7gTL-0008Rl-0V; 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 1w7dDn-0007Hl-Be for qemu-devel@nongnu.org; Tue, 31 Mar 2026 13:49:47 -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 1w7dDh-0000Ki-9D for qemu-devel@nongnu.org; Tue, 31 Mar 2026 13:49:45 -0400 Received: from proxy188.sjtu.edu.cn (smtp188.sjtu.edu.cn [202.120.2.188]) by smtp232.sjtu.edu.cn (Postfix) with ESMTPS id 1ABD21009F96B; Wed, 1 Apr 2026 01:38:12 +0800 (CST) Received: from xuklXiaoxin (unknown [202.120.32.222]) by proxy188.sjtu.edu.cn (Postfix) with ESMTPSA id C9BE237C8CA; Wed, 1 Apr 2026 01:38:11 +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 3/3] contrib/plugins: handle more loader open syscalls in the syscall filter demos Date: Wed, 1 Apr 2026 01:36:26 +0800 Message-ID: <20260331173656.35305-4-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: 1774991935535158500 Content-Type: text/plain; charset="utf-8" Extend both syscall-filter demo plugins so the library redirection logic handles open() and openat2() in addition to openat(). For openat2(), keep the fast path for unrelated library loads by reading the guest pathname first and only decoding struct open_how after the pathname matches the demo library name. Update the example README files and overview text so the reproduced commands and behavior describe all supported loader entry points. Signed-off-by: XU Kailiang Co-authored-by: Ziyang Zhang --- .../README.rst | 5 +- .../plugins/syscall_filter_callback_qsort.c | 61 +++++++++++++++-- .../syscall_filter_zlib-example/README.rst | 5 +- contrib/plugins/syscall_filter_zlib.c | 65 +++++++++++++++++-- docs/about/emulation.rst | 6 +- 5 files changed, 124 insertions(+), 18 deletions(-) diff --git a/contrib/plugins/syscall_filter_callback_qsort-example/README.r= st b/contrib/plugins/syscall_filter_callback_qsort-example/README.rst index e2bba7e2b6..ab758e358b 100644 --- a/contrib/plugins/syscall_filter_callback_qsort-example/README.rst +++ b/contrib/plugins/syscall_filter_callback_qsort-example/README.rst @@ -8,8 +8,9 @@ interception on ``qemu-x86_64``. =20 * ``callback-demo.c`` is linked against ``libdemo-callback-qsort.so`` and = calls ``callback_qsort()`` directly. -* The plugin intercepts the loader's ``openat()`` and returns a file - descriptor for ``./libdemo-callback-qsort-thunk.so`` instead. +* The plugin intercepts the loader's ``open()``, ``openat()``, or + ``openat2()`` and returns a file descriptor for + ``./libdemo-callback-qsort-thunk.so`` instead. * ``callback-thunk.S`` issues a ``START`` magic syscall for ``qsort()`` an= d a ``RESUME`` magic syscall from a guest return trampoline. * ``contrib/plugins/syscall_filter_callback_qsort.c`` runs host ``qsort()`` diff --git a/contrib/plugins/syscall_filter_callback_qsort.c b/contrib/plug= ins/syscall_filter_callback_qsort.c index 45a83cb5b1..8c6c0ddb91 100644 --- a/contrib/plugins/syscall_filter_callback_qsort.c +++ b/contrib/plugins/syscall_filter_callback_qsort.c @@ -4,7 +4,7 @@ * x86_64-only prototype that demonstrates a callback-capable syscall filt= er * plugin by redirecting a qsort() thunk library and bridging comparator c= alls * back into guest translated code with ucontext. The loader redirection - * handles openat(). + * handles open(), openat(), and openat2(). * * 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 @@ -36,7 +36,14 @@ QEMU_PLUGIN_EXPORT int qemu_plugin_version =3D QEMU_PLUG= IN_VERSION; #define GUEST_STRING_LIMIT (1 << 20) #define CALLBACK_QSORT_MAX_ELEMS (1 << 20) #define CALLBACK_QSORT_STACK_SIZE (1 << 20) +#define X86_64_OPEN_NR 2 #define X86_64_OPENAT_NR 257 +#define X86_64_OPENAT2_NR 437 + +typedef struct GuestOpenHow { + uint64_t flags; + uint64_t mode; +} GuestOpenHow; =20 typedef enum CallbackQsortPhase { CALLBACK_QSORT_PHASE_IDLE, @@ -128,6 +135,19 @@ static void write_reg64(VcpuState *vcpu, struct qemu_p= lugin_register *reg, g_assert(success); } =20 +static void read_guest_buffer(uint64_t addr, void *dst, size_t len) +{ + g_autoptr(GByteArray) data =3D g_byte_array_sized_new(len); + + if (len =3D=3D 0) { + return; + } + + g_byte_array_set_size(data, len); + g_assert(qemu_plugin_read_memory_vaddr(addr, data, len)); + memcpy(dst, data->data, len); +} + static bool write_guest_u64(uint64_t addr, uint64_t value) { GByteArray data =3D { @@ -138,6 +158,13 @@ static bool write_guest_u64(uint64_t addr, uint64_t va= lue) return qemu_plugin_write_memory_vaddr(addr, &data); } =20 +static void read_guest_open_how(uint64_t addr, uint64_t guest_size, + GuestOpenHow *how) +{ + g_assert(guest_size >=3D sizeof(*how)); + read_guest_buffer(addr, how, sizeof(*how)); +} + static char *read_guest_cstring(uint64_t addr) { g_autoptr(GByteArray) data =3D g_byte_array_sized_new(GUEST_STRING_CHU= NK); @@ -273,23 +300,49 @@ static bool handle_library_open(int64_t num, uint64_t= a1, uint64_t a2, g_autofree char *path =3D NULL; g_autofree char *thunk_path =3D NULL; g_autofree char *out =3D NULL; + GuestOpenHow how =3D { 0 }; + uint64_t path_addr; + int dirfd; + int flags; + mode_t mode; int fd; =20 - if (num !=3D X86_64_OPENAT_NR) { + if (num =3D=3D X86_64_OPEN_NR) { + dirfd =3D AT_FDCWD; + path_addr =3D a1; + flags =3D (int)a2; + mode =3D (mode_t)a3; + } else if (num =3D=3D X86_64_OPENAT_NR) { + dirfd =3D (int)a1; + path_addr =3D a2; + flags =3D (int)a3; + mode =3D (mode_t)a4; + } else if (num =3D=3D X86_64_OPENAT2_NR) { + dirfd =3D (int)a1; + path_addr =3D a2; + flags =3D 0; + mode =3D 0; + } else { return false; } =20 - path =3D read_guest_cstring(a2); + path =3D read_guest_cstring(path_addr); if (path =3D=3D NULL || !guest_path_matches_bridge(path)) { return false; } =20 + if (num =3D=3D X86_64_OPENAT2_NR) { + read_guest_open_how(a3, a4, &how); + flags =3D (int)how.flags; + mode =3D (mode_t)how.mode; + } + thunk_path =3D build_thunk_path(path); if (access(thunk_path, F_OK) !=3D 0) { return false; } =20 - fd =3D openat((int)a1, thunk_path, (int)a3, (mode_t)a4); + fd =3D openat(dirfd, thunk_path, flags, mode); g_assert(fd >=3D 0); =20 *sysret =3D fd; diff --git a/contrib/plugins/syscall_filter_zlib-example/README.rst b/contr= ib/plugins/syscall_filter_zlib-example/README.rst index 4920187a2b..0a1cd555ca 100644 --- a/contrib/plugins/syscall_filter_zlib-example/README.rst +++ b/contrib/plugins/syscall_filter_zlib-example/README.rst @@ -8,8 +8,9 @@ This directory contains the guest-side pieces used by =20 * ``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. +* The plugin intercepts the loader's ``open()``, ``openat()``, or + ``openat2()`` 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 diff --git a/contrib/plugins/syscall_filter_zlib.c b/contrib/plugins/syscal= l_filter_zlib.c index e8f430cbaf..59e3750c90 100644 --- a/contrib/plugins/syscall_filter_zlib.c +++ b/contrib/plugins/syscall_filter_zlib.c @@ -5,10 +5,10 @@ * 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. + * plugin intercepts open(), openat(), or openat2() 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, whic= h 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 @@ -40,7 +40,14 @@ QEMU_PLUGIN_EXPORT int qemu_plugin_version =3D QEMU_PLUG= IN_VERSION; #define ZLIB_COMPRESS_MAX_BUFFER (128 * 1024 * 1024) #define GUEST_STRING_CHUNK 64 #define GUEST_STRING_LIMIT (1 << 20) +#define X86_64_OPEN_NR 2 #define X86_64_OPENAT_NR 257 +#define X86_64_OPENAT2_NR 437 + +typedef struct GuestOpenHow { + uint64_t flags; + uint64_t mode; +} GuestOpenHow; =20 static char *read_guest_cstring(uint64_t addr) { @@ -68,6 +75,25 @@ static char *read_guest_cstring(uint64_t addr) return NULL; } =20 +static void read_guest_buffer(uint64_t addr, void *dst, size_t len) +{ + g_autoptr(GByteArray) data =3D g_byte_array_sized_new(len); + + if (len =3D=3D 0) { + return; + } + + g_byte_array_set_size(data, len); + g_assert(qemu_plugin_read_memory_vaddr(addr, data, len)); + memcpy(dst, data->data, len); +} + +static void read_guest_open_how(uint64_t addr, uint64_t guest_size, + GuestOpenHow *how) +{ + g_assert(guest_size >=3D sizeof(*how)); + read_guest_buffer(addr, how, sizeof(*how)); +} static bool guest_path_matches_zlib_compress(const char *path) { g_autofree char *basename =3D g_path_get_basename(path); @@ -92,22 +118,47 @@ static bool handle_library_open(int64_t num, uint64_t = a1, uint64_t a2, g_autofree char *path =3D NULL; g_autofree char *thunk_path =3D NULL; g_autofree char *out =3D NULL; + GuestOpenHow how =3D { 0 }; + uint64_t path_addr; + int dirfd; + int flags; + mode_t mode; int fd; =20 - if (num !=3D X86_64_OPENAT_NR) { + if (num =3D=3D X86_64_OPEN_NR) { + dirfd =3D AT_FDCWD; + path_addr =3D a1; + flags =3D (int)a2; + mode =3D (mode_t)a3; + } else if (num =3D=3D X86_64_OPENAT_NR) { + dirfd =3D (int)a1; + path_addr =3D a2; + flags =3D (int)a3; + mode =3D (mode_t)a4; + } else if (num =3D=3D X86_64_OPENAT2_NR) { + dirfd =3D (int)a1; + path_addr =3D a2; + flags =3D 0; + mode =3D 0; + } else { return false; } =20 - path =3D read_guest_cstring(a2); + path =3D read_guest_cstring(path_addr); if (path =3D=3D NULL || !guest_path_matches_zlib_compress(path)) { return false; } + if (num =3D=3D X86_64_OPENAT2_NR) { + read_guest_open_how(a3, a4, &how); + flags =3D (int)how.flags; + mode =3D (mode_t)how.mode; + } thunk_path =3D build_thunk_path(path); if (access(thunk_path, F_OK) !=3D 0) { return false; } =20 - fd =3D openat((int)a1, thunk_path, (int)a3, (mode_t)a4); + fd =3D openat(dirfd, thunk_path, flags, mode); g_assert(fd >=3D 0); =20 *sysret =3D fd; diff --git a/docs/about/emulation.rst b/docs/about/emulation.rst index 4afec85ff6..8630300b37 100644 --- a/docs/about/emulation.rst +++ b/docs/about/emulation.rst @@ -246,9 +246,9 @@ side example lives in ``contrib/plugins/syscall_filter_= zlib-example``. =20 The plugin does two things: =20 -* 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 the guest ``open()``, ``openat()``, or ``openat2()`` that the + dynamic loader issues for ``./libdemo-zlib.so`` and instead returns a fi= le + 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. --=20 2.53.0