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>
Ziyang Zhang committed
Jul 11, 2026 at 17:45 UTC
eee8e58eed452fb8a7be4fd9c7868d0512abbd6a
1 file changed
+158
docs/about/emulation.rst
+158
@@ -1046,6 +1046,164 @@ Count traps
1046
This plugin counts the number of interrupts (asynchronous events), exceptions
1047
(synchronous events) and host calls (e.g. semihosting) per cpu.
1048
1049
+Dynamic Linking Call
1050
+....................
1051
+
1052
+``contrib/plugins/dlcall.c``
1053
+
1054
+This plugin provides a dynamic linking function call interception mechanism
1055
+for linux-user guests: the guest hands a call off to the host, where the plugin
1056
+runs native code in its place instead of the guest emulating it. Interception
1057
+alone enables several uses, for instance tracing or auditing guest calls.
1058
+One use is acceleration by leveraging the host's native shared libraries. For
1059
+example, a thunk layer can run the stock zlib ``minizip`` utility under
1060
+emulation while forwarding its ``deflate`` calls to the host's native zlib
1061
+library (libz). This avoids emulating those selected library calls instruction
1062
+by instruction.
1063
+
1064
+The guest issues a reserved "magic" system call (4096 by default, configurable
1065
+with ``syscall_num=N``) whose first argument selects a pass-through operation:
1066
+dlopen/dlclose a host library, dlsym a symbol, and invoke a resolved host
1067
+function. The plugin performs the operation on the host and consumes the
1068
+syscall, so the real kernel never sees it.
1069
+
1070
+.. warning::
1071
+
1072
+ Trusted guests only. The guest can load arbitrary host libraries and run
1073
+ arbitrary code in the QEMU host process. The plugin is not a sandbox and
1074
+ provides no isolation. It also requires ``guest_base == 0`` (qemu-user's
1075
+ default), as guest pointers are dereferenced as host addresses with no
1076
+ translation.
1077
+
1078
+The plugin intentionally keeps the QEMU side lightweight and knows nothing
1079
+about any particular library or its calling convention. Turning a real library
1080
+into working thunks, including argument marshalling, callbacks and variadic
1081
+functions, is done entirely in userspace, and any toolchain can implement the
1082
+interface.
1083
+
1084
+Loading the plugin is all that is required from QEMU's side:
1085
+
1086
+.. code-block:: shell
1087
+
1088
+ qemu-x86_64 -plugin contrib/plugins/libdlcall.so <guest-program> ...
1089
+
1090
+`Lorelei <https://github.com/rover2024/lorelei>`_ is one end-to-end userspace
1091
+implementation of this: it provides the guest and host runtimes and an
1092
+automated toolchain that generates the thunks from a library's headers, so guest
1093
+library calls run on the host's native libraries. It supports an x86_64 guest
1094
+running on an x86_64, aarch64 or riscv64 host.
1095
+
1096
+A minimal end-to-end example uses a one-function library, ``libhello.so``, built
1097
+two ways: the guest build tags its output ``(from the guest)`` and the host
1098
+build ``(from the host)``. An unmodified guest program ``main`` calls
1099
+``hello("World", 7)``, and the thunk makes that same binary reach the host build
1100
+in place of its own. The sources live under ``src/``:
1101
+
1102
+.. code-block:: c
1103
+
1104
+ /* src/hello.h */
1105
+ void hello(const char *name, int lucky);
1106
+
1107
+.. code-block:: c
1108
+
1109
+ /* src/hello_guest.c */
1110
+ #include "hello.h"
1111
+ #include <stdio.h>
1112
+
1113
+ void hello(const char *name, int lucky)
1114
+ {
1115
+ printf("Hello, %s! Your lucky number is %d. (from the guest)\n", name, lucky);
1116
+ }
1117
+
1118
+.. code-block:: c
1119
+
1120
+ /* src/hello_host.c */
1121
+ #include "hello.h"
1122
+ #include <stdio.h>
1123
+
1124
+ void hello(const char *name, int lucky)
1125
+ {
1126
+ printf("Hello, %s! Your lucky number is %d. (from the host)\n", name, lucky);
1127
+ }
1128
+
1129
+.. code-block:: c
1130
+
1131
+ /* src/main.c */
1132
+ #include "hello.h"
1133
+
1134
+ int main(void)
1135
+ {
1136
+ hello("World", 7);
1137
+ return 0;
1138
+ }
1139
+
1140
+Lorelei ships a prebuilt toolchain (a "devkit") in its releases. Download the
1141
+one for your host and unpack it:
1142
+
1143
+.. code-block:: shell
1144
+
1145
+ # ARCH is your host's architecture: x86_64, aarch64 or riscv64. This example uses aarch64.
1146
+ # See https://github.com/rover2024/lorelei/releases
1147
+ ARCH=aarch64
1148
+ VERSION=$(curl -fsSL -o /dev/null -w '%{url_effective}' \
1149
+ https://github.com/rover2024/lorelei/releases/latest | sed 's|.*/tag/v||')
1150
+ wget "https://github.com/rover2024/lorelei/releases/download/v$VERSION/lorelei-devkit-$ARCH-$VERSION.tar.xz"
1151
+ tar -xf lorelei-devkit-$ARCH-$VERSION.tar.xz
1152
+ DEVKIT=lorelei-devkit-$ARCH
1153
+
1154
+Build the guest ``libhello.so`` (x86_64) and the host ``libhello.so`` (this
1155
+host's architecture), then the guest program:
1156
+
1157
+.. code-block:: shell
1158
+
1159
+ mkdir -p build/guest build/host
1160
+ $DEVKIT/bin/x86_64-linux-gnu-clang -shared -fPIC src/hello_guest.c -o build/guest/libhello.so
1161
+ cc -shared -fPIC src/hello_host.c -o build/host/libhello.so
1162
+ $DEVKIT/bin/x86_64-linux-gnu-clang src/main.c -Isrc -Lbuild/guest -lhello -o build/guest/main
1163
+
1164
+Run it under qemu:
1165
+
1166
+.. code-block:: shell
1167
+
1168
+ qemu-x86_64 -L /usr/x86_64-linux-gnu/ -E LD_LIBRARY_PATH=build/guest build/guest/main
1169
+
1170
+which prints::
1171
+
1172
+ Hello, World! Your lucky number is 7. (from the guest)
1173
+
1174
+Now generate the thunk from the host ``libhello.so``. This produces a guest-side
1175
+``libhello.so`` that stands in for the guest build, and a host-side thunk library
1176
+that dispatches to the host build:
1177
+
1178
+.. code-block:: shell
1179
+
1180
+ $DEVKIT/bin/LoreMakeThunk.py --name hello --lib build/host/libhello.so \
1181
+ --header hello.h -o thunks -- -Isrc
1182
+
1183
+Run the same ``main`` under the plugin. The call reaches the host build now:
1184
+
1185
+.. code-block:: shell
1186
+
1187
+ LD_LIBRARY_PATH=$DEVKIT/lib:build/host \
1188
+ qemu-x86_64 -plugin contrib/plugins/libdlcall.so \
1189
+ -E LD_LIBRARY_PATH=$DEVKIT/x86_64/lib:thunks/x86_64 \
1190
+ -L /usr/x86_64-linux-gnu/ \
1191
+ build/guest/main
1192
+
1193
+which prints::
1194
+
1195
+ Hello, World! Your lucky number is 7. (from the host)
1196
+
1197
+.. list-table:: Dynamic Linking Call arguments
1198
+ :widths: 20 80
1199
+ :header-rows: 1
1200
+
1201
+ * - Option
1202
+ - Description
1203
+ * - syscall_num=N
1204
+ - The magic syscall number the guest issues (default 4096). Must be high
1205
+ enough not to clash with a real syscall.
1206
+
1207
Other emulation features
1208
------------------------
1209