On 7/11/2026 2:45 AM, Ziyang Zhang wrote: > Hi, > > This patch adds a single plugin, contrib/plugins/dlcall.c (~250 lines, > no changes to QEMU core), that lets a linux-user guest call functions in the > host's native shared libraries instead of emulating them. > > It is the natural next step on top of the vCPU syscall-filter callback that I > contributed and that was merged earlier: > > > https://lore.kernel.org/qemu-devel/[email protected]/ > > Why bother? Because it turns slow, instruction-by-instruction emulation of a > library into a native host call. Some results, all on completely unmodified > guest binaries: > > * minizip (the stock zlib utility) compresses several times faster, because > the actual deflate runs natively on the host instead of being translated. > * Real OpenGL/Vulkan games run under qemu-user: SuperTuxKart and Hollow > Knight are playable, with their graphics calls going straight to the host > GPU. > > How it works > ============ > > The guest makes a system call with a reserved number (4096 by default) that no > real Linux ABI uses. Its first argument selects a pass-through operation, and > the > rest carry operands: > > syscall(4096, op, arg1, arg2, ...) > | | \............ operands (pointers / values) > | \................. which pass-through operation > \....................... the reserved "magic" number > > The plugin registers a vCPU syscall filter: before QEMU forwards a syscall to > the host kernel, the filter runs, sees 4096, performs the operation on the > host, writes the result back, and tells QEMU the syscall is consumed, so the > real kernel never sees it. > > The whole interface is just a handful of primitives: > > * query a host attribute > * dlopen / dlclose a host shared library > * dlsym a symbol, and read the last dlerror > * invoke a resolved host function with a void(void *, void *) signature > > That is all the plugin does. It knows nothing about zlib, X11 or OpenGL, or > about any library's calling convention. > > The same machinery also runs in reverse: when a host function needs to call > back into the guest (a qsort comparator, an allocator, a GUI or game > callback), > control re-enters the guest to run the callback and then resumes the suspended > host call. This reentry is what lets stateful, callback-driven APIs work, not > just leaf functions. > > Why the plugin belongs in QEMU, and the rest does not > ===================================================== > > Only the plugin lives in the tree. Everything else is ordinary userspace: > > --- userspace (out of tree, not tied to any DBT) ------------- > guest: unmodified program -> guest runtime + thunk libs > -------------------------------------------------------------- > | syscall(4096, op, args) (only crossing point) > v > === inside QEMU: THIS PATCH, ~250 lines ====================== > dlcall plugin: dlopen / dlsym / invoke a host fn > ============================================================== > | > v > --- userspace (out of tree) ---------------------------------- > host: host runtime + thunk libs -> real libz / libGL ... > -------------------------------------------------------------- > > The split is deliberate, and it is why only this one file is proposed for the > tree: > > * This plugin defines the most general interaction interface for native > pass-through: the magic-syscall ABI between an emulated guest and its > emulator. That contract is what every pass-through implementation builds > on, so it belongs in a stable, shared place. > * It is also the only piece that is inherently QEMU-specific: it plugs into > QEMU's syscall-filter hook and runs inside the QEMU process. The argument > marshalling, calling conventions, callbacks/reentry and per-library > coverage are not tied to any particular DBT and behave as ordinary > userspace, so they should stay out of tree rather than couple QEMU to > them. > > Background: we presented this approach at KVM Forum 2025, "Lorelei: Enable > QEMU > to Leverage Native Shared Libraries": > > https://www.youtube.com/watch?v=_jioQFm7wyU > > The userspace side > ================== > > A fair point on v3 was that, on its own, the plugin is only half of an > interface: useful only if the other half (the guest/host runtimes and the > per-library thunks) is public and specified, rather than a private demo. That > half is now available as a standalone, documented, CI-tested project, Lorelei: > > https://github.com/rover2024/lorelei > > Lorelei provides the guest and host runtimes and a Thunk Library Compiler > (TLC, built on Clang LibTooling) that reads a library's headers and generates > the guest and host thunks automatically, including the awkward cases of > function-pointer callbacks and variadic functions. It has CI for x86_64, arm64 > and riscv64 hosts, and the devkit is exercised end to end on Ubuntu, Debian, > Fedora and Arch. Lorelei and its thunk libraries are MIT-licensed. > > Adding a thunk is deliberately little work, and needs nothing but a release. > Lorelei's releases ship a prebuilt toolchain (a "devkit"); after unpacking > one, > a single command reads a library and its headers and generates both the guest > and host thunks, with no per-function code, no manifest and no build system to > drive: > > devkit/bin/LoreMakeThunk.py --name z --lib libz.so.1 --header zlib.h -o > thunks > > Two runnable examples come with Lorelei, each with a Makefile that does > exactly > this and runs the example under the plugin in one step: > > * hello, a minimal one-function library: > https://github.com/rover2024/lorelei/tree/main/examples/hello > * demo, variadic functions and a callback that reenters the guest: > https://github.com/rover2024/lorelei/tree/main/examples/demo > > If you do not have qemu or an x86_64 rootfs on hand, the project also ships a > small Docker setup that builds qemu and runs the examples for you, so a single > command reproduces the whole flow. > > Because the plugin is not upstream yet, Lorelei currently builds and tests > against the QEMU fork that carries it. Its releases ship prebuilt guest and > host thunk trees for x86_64, aarch64 and riscv64 hosts, so trying it out does > not require building from source: > > https://github.com/rover2024/lorelei/releases > > The plugin stays deliberately minimal and prescribes nothing about how > thunking is done. Lorelei is one reference implementation of the userspace > side. Any toolchain, or another instrumentation framework, can implement the > same dlcall interface. > > A from-scratch walkthrough of the bare mechanism, with the minizip and > OpenGL/X11 examples above, is also available here: > > https://github.com/rover2024/qemu-passthrough-test > > It is fully opt-in (loaded with -plugin) and targets linux-user, where the > guest and host already share a trust domain. The test cases use x86_64 guests > and run on x86_64, arm64 and riscv64 Linux hosts. > > Feedback on the plugin and on the pass-through approach is welcome. > > Changes since v11: > > Documentation only, no change to the plugin. Following Pierrick Bouvier's > v11 review of the docs/about/emulation.rst walkthrough: > > * Both qemu command lines now pass -L /usr/x86_64-linux-gnu/, so the guest > program's loader and libc come from the host's own x86_64 runtime, > matching the default people will use rather than a bundled sysroot. > * Minor cleanups to the devkit download snippet: named the version > variable, dropped a glob from the tar, and noted the host arch can be > x86_64, aarch64 or riscv64. > > Changes since v10: > > Documentation only, no change to the plugin. Updated the > docs/about/emulation.rst walkthrough to the current Lorelei userspace: > > * The example now runs the x86_64 guest on an aarch64 host, showing the > cross-architecture case rather than x86_64-on-x86_64. > * The thunk run no longer sets a LORELEI_THUNK_PATH variable: a guest thunk > now records where its host thunk is, so discovery needs only > LD_LIBRARY_PATH and the guest thunk directory is a flat thunks/<arch>. > * The devkit download resolves the release asset for the host arch, whose > name carries the version. > * The hello library prints a plain sentence rather than a terse debug line. > > Changes since v9: > > Documentation only, no change to the plugin. Polished the > docs/about/emulation.rst walkthrough for clarity: tightened the > LD_LIBRARY_PATH explanation, named the directory-tree listing, and trimmed a > couple of redundant phrasings. > > Changes since v8: > > Following Pierrick Bouvier's v8 request, the guest program in the > docs/about/emulation.rst example no longer links with an rpath. It carries > no rpath at all, so which libhello.so it loads is chosen entirely by > LD_LIBRARY_PATH at run time: the baseline run puts the guest build there, > and the thunk run puts the generated guest thunk there in its place. The > plugin code is unchanged. > > Changes since v7: > > Reworked the docs/about/emulation.rst example into a complete, > self-contained > walkthrough, following Pierrick Bouvier's v7 review: > > * Included the full source files and the exact compiler commands for the two > libraries and the guest program, runnable end to end. > * The example now shows the thunk as a drop-in: the same unmodified guest > program prints "from guest" with its own library, and "from host" once the > generated guest thunk is placed ahead of it on LD_LIBRARY_PATH. > The plugin code is unchanged. > > Changes since v6: > > Addressing Pierrick Bouvier's v6 request that trying the userspace side need > only the release, with no second repository to clone: > > * The thunk generator now ships inside the devkit that Lorelei's releases > provide, as bin/LoreMakeThunk.py. After unpacking a devkit, a single > command reads a library and its headers and generates both the guest and > host thunks, producing all the boilerplate itself. It takes plain > command-line arguments, with no manifest or config file and no > cmake/make/git behind it, so the whole flow is: download a devkit, run one > command, run the guest under the plugin. > * docs/about/emulation.rst: rewrote the hello walkthrough to that flow > (download a devkit, one LoreMakeThunk command, run under the plugin) and > pointed the example links at the lorelei repository, where the examples > now > live. The plugin code is unchanged. > > Changes since v5: > > Following a discussion with Pierrick Bouvier on making the userspace side > easy to try, this version focuses on that. > > * docs/about/emulation.rst: expanded the example into a runnable hello > walkthrough (the wrapped one-function library and its thunk manifest) and > added links to the two example directories, hello and demo. Each example > comes with a Makefile that builds the thunk and runs it under the plugin > in > one command, plus a small Docker setup for hosts without qemu or an x86_64 > rootfs. Also reframed the section as a dynamic linking function call > interception mechanism (tracing/auditing as further uses, acceleration as > one use) and renamed it to "Dynamic Linking Call". > * The plugin header now names it "dlcall (Dynamic Linking Call)" and refers > to Lorelei as one end-to-end userspace implementation. The plugin code is > otherwise unchanged. > > Changes since v4: > > * Added a documentation patch for docs/about/emulation.rst (Pierrick > Bouvier): what the plugin does, its trusted-guests and guest_base == 0 > constraints, how to load it, and a minimal example, with a pointer to > Lorelei. > * The plugin's header comment now points to Lorelei as one userspace > implementation. The plugin code is otherwise unchanged. > > Changes since v3: > > * Lorelei, the userspace toolchain that implements this interface, is now a > public, documented, CI-tested project, with a Thunk Library Compiler that > generates the guest/host thunks automatically. This addresses the v3 > feedback that the interface needs a public implementation behind it. The > plugin code itself is unchanged. > > Changes since v2: > > * Dropped the RFC tag. The approach was positively received on v2. > * Rebased on master and adjusted the syscall-filter callback to its updated > signature (int64_t sysret, added userdata, dropped the plugin id > argument). > > Changes since v1: > > * Renamed the plugin from "passthrough" to "dlcall" (Pierrick Bouvier). > The old name was too generic. The name "dlcall" reflects what the plugin > actually > does (dlopen/dlsym a host symbol and call it) and avoids confusion with > QEMU's existing plugin hostcall concept (QEMU_PLUGIN_*_HOSTCALL). > * Made the magic syscall number configurable at load time via the > "syscall_num=N" argument, defaulting to 4096 and rejecting values low > enough to clash with a real syscall (Pierrick Bouvier). > > v1: > https://lore.kernel.org/qemu-devel/[email protected]/ > v2: > https://lore.kernel.org/qemu-devel/[email protected]/ > v3: > https://lore.kernel.org/qemu-devel/[email protected]/ > v4: > https://lore.kernel.org/qemu-devel/[email protected]/ > v5: > https://lore.kernel.org/qemu-devel/[email protected]/ > v6: > https://lore.kernel.org/qemu-devel/[email protected]/ > v7: > https://lore.kernel.org/qemu-devel/[email protected]/ > v8: > https://lore.kernel.org/qemu-devel/[email protected]/ > v9: > https://lore.kernel.org/qemu-devel/[email protected]/ > v10: > https://lore.kernel.org/qemu-devel/[email protected]/ > v11: > https://lore.kernel.org/qemu-devel/[email protected]/ > > Thanks, > Ziyang Zhang > > Ziyang Zhang (2): > contrib/plugins: add a minimal dlcall plugin > docs/about/emulation: document the dlcall plugin > > contrib/plugins/dlcall.c | 248 ++++++++++++++++++++++++++++++++++++ > contrib/plugins/meson.build | 5 + > docs/about/emulation.rst | 158 +++++++++++++++++++++++ > 3 files changed, 411 insertions(+) > create mode 100644 contrib/plugins/dlcall.c >
This was merged into master (c149f7b367fe85edd1c438ecfd876f0c80a6ab11). Thank you for your contribution! Regards, Pierrick
