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?

Reply via email to