docs/about/emulation: document the dlcall plugin

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 <xukl2019@sjtu.edu.cn>
Co-authored-by: Mingyuan Xia <xiamy@ultrarisc.com>
Signed-off-by: Ziyang Zhang <functioner@sjtu.edu.cn>
Reviewed-by: Pierrick Bouvier <pierrick.bouvier@oss.qualcomm.com>
Link: https://lore.kernel.org/qemu-devel/20260711094523.622997-3-functioner@sjtu.edu.cn
Signed-off-by: Pierrick Bouvier <pierrick.bouvier@oss.qualcomm.com>
This commit is contained in:
Ziyang Zhang
2026-07-11 17:45:23 +08:00
committed by Pierrick Bouvier
parent 09b7a409a7
commit eee8e58eed

View File

@@ -1046,6 +1046,164 @@ 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 the guest)`` and the host
build ``(from the 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 */
void hello(const char *name, int lucky);
.. code-block:: c
/* src/hello_guest.c */
#include "hello.h"
#include <stdio.h>
void hello(const char *name, int lucky)
{
printf("Hello, %s! Your lucky number is %d. (from the guest)\n", name, lucky);
}
.. code-block:: c
/* src/hello_host.c */
#include "hello.h"
#include <stdio.h>
void hello(const char *name, int lucky)
{
printf("Hello, %s! Your lucky number is %d. (from the host)\n", name, lucky);
}
.. 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's architecture: x86_64, aarch64 or riscv64. This example uses aarch64.
# See https://github.com/rover2024/lorelei/releases
ARCH=aarch64
VERSION=$(curl -fsSL -o /dev/null -w '%{url_effective}' \
https://github.com/rover2024/lorelei/releases/latest | sed 's|.*/tag/v||')
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 architecture), then the guest program:
.. code-block:: shell
mkdir -p build/guest build/host
$DEVKIT/bin/x86_64-linux-gnu-clang -shared -fPIC src/hello_guest.c -o build/guest/libhello.so
cc -shared -fPIC src/hello_host.c -o build/host/libhello.so
$DEVKIT/bin/x86_64-linux-gnu-clang src/main.c -Isrc -Lbuild/guest -lhello -o build/guest/main
Run it under qemu:
.. code-block:: shell
qemu-x86_64 -L /usr/x86_64-linux-gnu/ -E LD_LIBRARY_PATH=build/guest build/guest/main
which prints::
Hello, World! Your lucky number is 7. (from the guest)
Now generate the thunk from the host ``libhello.so``. This produces a guest-side
``libhello.so`` that stands in for the guest build, and a host-side thunk library
that dispatches to the host build:
.. code-block:: shell
$DEVKIT/bin/LoreMakeThunk.py --name hello --lib build/host/libhello.so \
--header hello.h -o thunks -- -Isrc
Run the same ``main`` under the plugin. The call reaches the host build now:
.. code-block:: shell
LD_LIBRARY_PATH=$DEVKIT/lib:build/host \
qemu-x86_64 -plugin contrib/plugins/libdlcall.so \
-E LD_LIBRARY_PATH=$DEVKIT/x86_64/lib:thunks/x86_64 \
-L /usr/x86_64-linux-gnu/ \
build/guest/main
which prints::
Hello, World! Your lucky number is 7. (from the host)
.. list-table:: Dynamic Linking Call arguments
:widths: 20 80
:header-rows: 1
* - Option
- Description
* - syscall_num=N
- The magic syscall number the guest issues (default 4096). Must be high
enough not to clash with a real syscall.
Other emulation features
------------------------