On Wed, 8 Jul 2026 18:59:01 -0700, Pierrick Bouvier wrote:
On 7/8/26 6:47 PM, Ziyang Zhang wrote:


On Wed, 8 Jul 2026 18:35:09 -0700, Pierrick Bouvier wrote:
On 7/8/2026 6:19 PM, Ziyang Zhang wrote:
Document the dlcall plugin under Example Plugins: what it does, the trusted- guests and guest_base == 0 constraints, how to load it, and a pointer to Lorelei, one end-to-end userspace implementation, for the toolchain and a
runnable example.

Co-authored-by: Kailiang Xu <[email protected]>
Co-authored-by: Mingyuan Xia <[email protected]>
Signed-off-by: Ziyang Zhang <[email protected]>
---
   docs/about/emulation.rst | 202 ++++++++++++++++++++++++++++++++++ +++++
   1 file changed, 202 insertions(+)

diff --git a/docs/about/emulation.rst b/docs/about/emulation.rst
index 3b4c365933..b8fdc88a45 100644
--- a/docs/about/emulation.rst
+++ b/docs/about/emulation.rst
@@ -1046,6 +1046,208 @@ Count traps
   This plugin counts the number of interrupts (asynchronous events), exceptions
   (synchronous events) and host calls (e.g. semihosting) per cpu.
+Dynamic Linking Call
+....................
+
+``contrib/plugins/dlcall.c``
+
+This plugin provides a dynamic linking function call interception mechanism +for linux-user guests: the guest hands a call off to the host, where the plugin +runs native code in its place instead of the guest emulating it. Interception +alone enables several uses, for instance tracing or auditing guest calls. +One use is acceleration by leveraging the host's native shared libraries. For +example, a thunk layer can run the stock zlib ``minizip`` utility under +emulation while forwarding its ``deflate`` calls to the host's native zlib +library (libz). This avoids emulating those selected library calls instruction
+by instruction.
+
+The guest issues a reserved "magic" system call (4096 by default, configurable +with ``syscall_num=N``) whose first argument selects a pass-through operation: +dlopen/dlclose a host library, dlsym a symbol, and invoke a resolved host +function. The plugin performs the operation on the host and consumes the
+syscall, so the real kernel never sees it.
+
+.. warning::
+
+   Trusted guests only. The guest can load arbitrary host libraries and run +   arbitrary code in the QEMU host process. The plugin is not a sandbox and +   provides no isolation. It also requires ``guest_base == 0`` (qemu-user's +   default), as guest pointers are dereferenced as host addresses with no
+   translation.
+
+The plugin intentionally keeps the QEMU side lightweight and knows nothing +about any particular library or its calling convention. Turning a real library +into working thunks, including argument marshalling, callbacks and variadic +functions, is done entirely in userspace, and any toolchain can implement the
+interface.
+
+Loading the plugin is all that is required from QEMU's side:
+
+.. code-block:: shell
+
+   qemu-x86_64 -plugin contrib/plugins/libdlcall.so <guest- program> ...
+
+`Lorelei <https://github.com/rover2024/lorelei>`_ is one end-to-end userspace
+implementation of this: it provides the guest and host runtimes and an
+automated toolchain that generates the thunks from a library's headers, so guest +library calls run on the host's native libraries. It supports an x86_64 guest
+running on an x86_64, aarch64 or riscv64 host.
+
+A minimal end-to-end example uses a one-function library, ``libhello.so``, built +two ways: the guest build tags its output ``from guest`` and the host build tags +it ``from host``. An unmodified guest program ``main`` calls ``hello("world", +7)``, and the thunk makes that same binary reach the host build in place of its
+own. The sources live under ``src/``:
+
+.. code-block:: c
+
+   /* src/hello.h */
+   #ifdef __cplusplus
+   extern "C" {
+   #endif
+   void hello(const char *name, int lucky);
+   #ifdef __cplusplus
+   }
+   #endif
+
+.. code-block:: c
+
+   /* src/hello_guest.c */
+   #include "hello.h"
+   #include <stdio.h>
+
+   void hello(const char *name, int lucky)
+   {
+       printf("hello from guest: %s, lucky %d\n", name, lucky);
+       fflush(stdout);
+   }
+
+.. code-block:: c
+
+   /* src/hello_host.c */
+   #include "hello.h"
+   #include <stdio.h>
+
+   void hello(const char *name, int lucky)
+   {
+       printf("hello from host: %s, lucky %d\n", name, lucky);
+       fflush(stdout);
+   }
+
+.. code-block:: c
+
+   /* src/main.c */
+   #include "hello.h"
+
+   int main(void)
+   {
+       hello("world", 7);
+       return 0;
+   }
+
+Lorelei ships a prebuilt toolchain (a "devkit") in its releases. Download the
+one for your host and unpack it:
+
+.. code-block:: shell
+
+   # <arch> is your host architecture: x86_64, aarch64 or riscv64
+   wget https://github.com/rover2024/lorelei/releases/download/ v<version>/lorelei-devkit-<arch>-<version>.tar.xz
+   tar -xf lorelei-devkit-<arch>-<version>.tar.xz
+   DEVKIT=lorelei-devkit-<arch>
+
+Build the guest ``libhello.so`` (x86_64) and the host ``libhello.so`` (this +host's own architecture), then the guest program. ``main`` links the guest
+library with an rpath, so by default it loads that one:
+
+.. code-block:: shell
+
+   mkdir -p build/guest build/host
+   $DEVKIT/bin/x86_64-linux-gnu-clang -shared -fPIC -o build/guest/ libhello.so src/hello_guest.c
+   cc -shared -fPIC -o build/host/libhello.so src/hello_host.c
+   $DEVKIT/bin/x86_64-linux-gnu-clang src/main.c -Isrc -Lbuild/ guest -lhello \
+       -Wl,-rpath,'$ORIGIN' -o build/guest/main
+

Why is rpath needed? We don't want necessarily to find the library
relative to where the binary is located.

In practice, we'll have:
- the binary
- folder A containing host library
- folder B containing thunks

A and B are not related to binary location.
This example ships the guest libhello.so which is at the same directory
as the main executable, so I add the rpath to make QEMU run the original
program without specifying anything.

+Run it under qemu. It loads its own guest library:
+
+.. code-block:: shell
+
+   qemu-x86_64 build/guest/main
+
+which prints::
+
+   hello from guest: world, lucky 7Would your suggestion be to not include rpath when compiling main, and
then set LD_LIBRARY_PATH to the library path when running the original
program?

qemu-x86_64 -E LD_LIBRARY_PATH=build/guest build/guest/main

Like this?

Yes, exactly.

By doing this, the end goal is to see that we can dynamically switch from the original host library to the thunks simply by switching this variable.

Regards,
Pierrick

Thank you for the explanation. I will send out PATCH v9, and then I will
update Lorelei.

Reply via email to