Merge tag 'pbouvier/pr/plugins-20260713' of https://gitlab.com/p-b-o/qemu into staging
Changes: - [PATCH v1] uftrace: riscv privilege level (Yanfeng Liu <yfliu2008@qq.com>) Link: https://lore.kernel.org/qemu-devel/tencent_E17E8F6494EE130F71527C6BCE481AF33E08@qq.com - [PATCH v12 0/2] contrib/plugins: add dlcall to call host functions (Ziyang Zhang <functioner@sjtu.edu.cn>) Link: https://lore.kernel.org/qemu-devel/20260711094523.622997-1-functioner@sjtu.edu.cn # -----BEGIN PGP SIGNATURE----- # # iQGzBAABCgAdFiEEN8FWlNi6l2Sxlz/btEQ30ZwoYt8FAmpVKSgACgkQtEQ30Zwo # Yt9wYgv8D3Uh6ebumK4SlHAmMm41gF5wjZsfmA5YXstcPlmxt2sZEbVaTnyLyhZd # Q8F34HVJ5vaClPoFiQKz+arCLFvLZ4MAgqMy/pxedIhJVhW2b/dt0Z10HkJc8kDx # baJyv36GmUwnXrTWGZAuLZq7CbQ1xSH4BN6qHAY6mpHogdISABhG6hArz9leHZO/ # NlnxFpfkr29Hd1XLu5u7de35SWRtQbAJYCqtELmR/ZxhKOJU6kr63VR4zr5jVVNX # px6JQxPkeSFypHV1+klbxCQiXkflwBrYpIKRXrAVNZ7HwMXK6iDw4D/L5TzyxdJn # 7yMVZ05+xI0o+yS46A0btGoinrnNyjeZjKh1VV7A4DuZ7mfbIII+fvE3JdAzvhzO # xvrTwwYksiztEK86r8yq7DT9FIx5LLF+B93NgdUtUvvOQP1z1BvvCdePN1KwUrpo # UgO45akAv8QBVD5uNS4Lh/fG3HarYgdrwt87P3vgynEhuGIE+7QZfOmooNwlwu8v # SSngbr/5 # =9JyL # -----END PGP SIGNATURE----- # gpg: Signature made Mon 13 Jul 2026 19:06:32 BST # gpg: using RSA key 37C15694D8BA9764B1973FDBB44437D19C2862DF # gpg: Good signature from "Pierrick Bouvier <pierrick.bouvier@oss.qualcomm.com>" [unknown] # gpg: WARNING: This key is not certified with a trusted signature! # gpg: There is no indication that the signature belongs to the owner. # Primary key fingerprint: 37C1 5694 D8BA 9764 B197 3FDB B444 37D1 9C28 62DF * tag 'pbouvier/pr/plugins-20260713' of https://gitlab.com/p-b-o/qemu: docs/about/emulation: document the dlcall plugin contrib/plugins: add a minimal dlcall plugin uftrace: riscv privilege level Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
This commit is contained in:
248
contrib/plugins/dlcall.c
Normal file
248
contrib/plugins/dlcall.c
Normal file
@@ -0,0 +1,248 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2026, Ziyang Zhang <functioner@sjtu.edu.cn>
|
||||||
|
*
|
||||||
|
* dlcall (Dynamic Linking Call) plugin: lets a linux-user guest invoke host
|
||||||
|
* functions by issuing a magic system call. The guest can ask QEMU to dlopen()
|
||||||
|
* a host shared library, dlsym() a symbol, and call it with guest-supplied
|
||||||
|
* arguments.
|
||||||
|
*
|
||||||
|
* The plugin intentionally keeps the QEMU side lightweight and prescribes
|
||||||
|
* nothing about how a library is thunked. Any toolchain can implement the
|
||||||
|
* userspace side. Lorelei is one end-to-end implementation (guest/host
|
||||||
|
* runtimes plus a thunk compiler that generates thunks from a library's
|
||||||
|
* headers):
|
||||||
|
* https://github.com/rover2024/lorelei
|
||||||
|
*
|
||||||
|
* See docs/about/emulation.rst|Dynamic Linking Call for details and examples.
|
||||||
|
*
|
||||||
|
* WARNING: trusted guests only. The guest can load arbitrary host libraries
|
||||||
|
* and execute arbitrary host code with arbitrary arguments, i.e. full code
|
||||||
|
* execution in the QEMU host process. It is NOT a sandbox and provides no
|
||||||
|
* isolation; only load it for guests you fully trust.
|
||||||
|
*
|
||||||
|
* WARNING: requires guest_base == 0, which is qemu-user's default. Pointer
|
||||||
|
* operands are dereferenced as host addresses directly, and the invoked host
|
||||||
|
* functions dereference guest pointers with no address translation, so guest
|
||||||
|
* and host must share a single address space. A non-zero guest_base (e.g. set
|
||||||
|
* via -B/-R) would make every pointer off by guest_base and hit unrelated
|
||||||
|
* host memory.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <glib.h>
|
||||||
|
#include <dlfcn.h>
|
||||||
|
|
||||||
|
#include <qemu-plugin.h>
|
||||||
|
|
||||||
|
QEMU_PLUGIN_EXPORT int qemu_plugin_version = QEMU_PLUGIN_VERSION;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The magic system call number for dlcall.
|
||||||
|
*
|
||||||
|
* It defaults to DLCALL_SYSCALL_DEFAULT and can be overridden at load time
|
||||||
|
* with the "syscall_num=N" argument. To avoid hijacking a real syscall the
|
||||||
|
* guest might issue, N must be at least DLCALL_SYSCALL_MIN: every Linux ABI
|
||||||
|
* keeps its syscall numbers well below this; numbers from here up are free.
|
||||||
|
*/
|
||||||
|
enum {
|
||||||
|
DLCALL_SYSCALL_DEFAULT = 4096,
|
||||||
|
DLCALL_SYSCALL_MIN = 4096,
|
||||||
|
};
|
||||||
|
|
||||||
|
static int64_t dlcall_syscall_num = DLCALL_SYSCALL_DEFAULT;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* dlcall calling convention.
|
||||||
|
*
|
||||||
|
* The guest issues the magic system call (dlcall_syscall_num). The first
|
||||||
|
* argument (a1) is one of the call IDs below; the remaining arguments (a2, a3,
|
||||||
|
* a4, ...) are that ID's operands. All pointer operands are guest virtual
|
||||||
|
* addresses that the plugin dereferences as host addresses directly (see the
|
||||||
|
* guest_base requirement above). Results are written back through
|
||||||
|
* caller-provided "out" pointers rather than returned in the syscall value.
|
||||||
|
*
|
||||||
|
* The syscall return value (*sysret) only reports dispatch status: 0 on a
|
||||||
|
* recognised ID, -EINVAL for an unknown one. The actual success/failure of an
|
||||||
|
* operation (e.g. a NULL handle from dlopen) is delivered through its out
|
||||||
|
* pointer, exactly like the underlying libdl call.
|
||||||
|
*
|
||||||
|
* Operands per ID:
|
||||||
|
*
|
||||||
|
* DLCALL_ID_GET_HOST_ATTRIBUTE
|
||||||
|
* a2 const char *key in: attribute name to query
|
||||||
|
* a3 const char **attr_ptr out: matching value, or NULL if unknown
|
||||||
|
*
|
||||||
|
* DLCALL_ID_LOAD_LIBRARY (wraps dlopen)
|
||||||
|
* a2 const char *path in: library path
|
||||||
|
* a3 int flags in: dlopen() flags (e.g. RTLD_NOW)
|
||||||
|
* a4 void **handle_ptr out: library handle, or NULL on failure
|
||||||
|
*
|
||||||
|
* DLCALL_ID_GET_PROC_ADDRESS (wraps dlsym)
|
||||||
|
* a2 void *handle in: library handle
|
||||||
|
* a3 const char *name in: symbol name
|
||||||
|
* a4 void **entry_ptr out: symbol address, or NULL if not found
|
||||||
|
*
|
||||||
|
* DLCALL_ID_FREE_LIBRARY (wraps dlclose)
|
||||||
|
* a2 void *handle in: library handle
|
||||||
|
* a3 int *ret_ptr out: dlclose() return value (0 on success)
|
||||||
|
*
|
||||||
|
* DLCALL_ID_GET_LIBRARY_ERROR (wraps dlerror)
|
||||||
|
* a2 const char **error_ptr out: last libdl error string, or NULL
|
||||||
|
*
|
||||||
|
* DLCALL_ID_INVOKE_PROC (calls the symbol)
|
||||||
|
* a2 void *proc in: function pointer, signature
|
||||||
|
* void (*)(void *arg1, void *arg2)
|
||||||
|
* a3 void *arg1 in: first argument forwarded to proc
|
||||||
|
* a4 void *arg2 in: second argument forwarded to proc
|
||||||
|
*/
|
||||||
|
enum DlcallID {
|
||||||
|
DLCALL_ID_GET_HOST_ATTRIBUTE,
|
||||||
|
DLCALL_ID_LOAD_LIBRARY,
|
||||||
|
DLCALL_ID_GET_PROC_ADDRESS,
|
||||||
|
DLCALL_ID_FREE_LIBRARY,
|
||||||
|
DLCALL_ID_GET_LIBRARY_ERROR,
|
||||||
|
DLCALL_ID_INVOKE_PROC,
|
||||||
|
};
|
||||||
|
|
||||||
|
static inline const char *query_host_attribute(const char *key)
|
||||||
|
{
|
||||||
|
if (strcmp(key, "emu") == 0) {
|
||||||
|
return "qemu";
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void invoke_proc(void *proc, void *arg1, void *arg2)
|
||||||
|
{
|
||||||
|
typedef void (*Func)(void * /*arg1*/, void * /*arg2*/);
|
||||||
|
Func func = (Func) proc;
|
||||||
|
func(arg1, arg2);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool vcpu_syscall_filter(unsigned int vcpu_index,
|
||||||
|
int64_t num, uint64_t a1, uint64_t a2,
|
||||||
|
uint64_t a3, uint64_t a4, uint64_t a5,
|
||||||
|
uint64_t a6, uint64_t a7, uint64_t a8,
|
||||||
|
int64_t *sysret, void *userdata)
|
||||||
|
{
|
||||||
|
if (num == dlcall_syscall_num) {
|
||||||
|
switch (a1) {
|
||||||
|
/* Query host attribute by a reserved key. */
|
||||||
|
case DLCALL_ID_GET_HOST_ATTRIBUTE: {
|
||||||
|
const char *key = (const char *) a2;
|
||||||
|
const char **attr_ptr = (const char **) a3;
|
||||||
|
assert(attr_ptr);
|
||||||
|
*attr_ptr = query_host_attribute(key);
|
||||||
|
*sysret = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Load a shared library. */
|
||||||
|
case DLCALL_ID_LOAD_LIBRARY: {
|
||||||
|
const char *path = (const char *) a2;
|
||||||
|
int flags = (int) a3;
|
||||||
|
void **handle_ptr = (void **) a4;
|
||||||
|
assert(handle_ptr);
|
||||||
|
*handle_ptr = dlopen(path, flags);
|
||||||
|
*sysret = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Get the address of a function in a shared library. */
|
||||||
|
case DLCALL_ID_GET_PROC_ADDRESS: {
|
||||||
|
void *handle = (void *) a2;
|
||||||
|
const char *name = (const char *) a3;
|
||||||
|
void **entry_ptr = (void **) a4;
|
||||||
|
assert(entry_ptr);
|
||||||
|
*entry_ptr = dlsym(handle, name);
|
||||||
|
*sysret = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Free a shared library. */
|
||||||
|
case DLCALL_ID_FREE_LIBRARY: {
|
||||||
|
void *handle = (void *) a2;
|
||||||
|
int *ret_ptr = (int *) a3;
|
||||||
|
*ret_ptr = dlclose(handle);
|
||||||
|
*sysret = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Get the last error message for a library event. */
|
||||||
|
case DLCALL_ID_GET_LIBRARY_ERROR: {
|
||||||
|
const char **error_ptr = (const char **) a2;
|
||||||
|
*error_ptr = dlerror();
|
||||||
|
*sysret = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Invoke a function of a common interface. */
|
||||||
|
case DLCALL_ID_INVOKE_PROC: {
|
||||||
|
void *proc = (void *) a2;
|
||||||
|
void *arg1 = (void *) a3;
|
||||||
|
void *arg2 = (void *) a4;
|
||||||
|
assert(proc);
|
||||||
|
invoke_proc(proc, arg1, arg2);
|
||||||
|
*sysret = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
*sysret = -EINVAL;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
QEMU_PLUGIN_EXPORT int qemu_plugin_install(qemu_plugin_id_t id,
|
||||||
|
const qemu_info_t *info,
|
||||||
|
int argc, char **argv)
|
||||||
|
{
|
||||||
|
if (info->system_emulation) {
|
||||||
|
fprintf(stderr, "plugin dlcall: only useful for user emulation\n");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < argc; i++) {
|
||||||
|
char *opt = argv[i];
|
||||||
|
g_auto(GStrv) tokens = g_strsplit(opt, "=", 2);
|
||||||
|
if (g_strcmp0(tokens[0], "syscall_num") == 0) {
|
||||||
|
const char *val = tokens[1];
|
||||||
|
char *endptr = NULL;
|
||||||
|
guint64 num;
|
||||||
|
if (!val || *val == '\0') {
|
||||||
|
fprintf(stderr,
|
||||||
|
"plugin dlcall: missing value for syscall_num\n");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
num = g_ascii_strtoull(val, &endptr, 0);
|
||||||
|
if (*endptr != '\0' || g_strrstr(val, "-") != NULL) {
|
||||||
|
fprintf(stderr,
|
||||||
|
"plugin dlcall: invalid syscall_num '%s'\n", val);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (num < DLCALL_SYSCALL_MIN || num > G_MAXINT64) {
|
||||||
|
fprintf(stderr,
|
||||||
|
"plugin dlcall: syscall_num %s is out of range; "
|
||||||
|
"it must be >= %d to avoid clashing with a real "
|
||||||
|
"syscall\n", val, DLCALL_SYSCALL_MIN);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
dlcall_syscall_num = (int64_t) num;
|
||||||
|
} else {
|
||||||
|
fprintf(stderr, "plugin dlcall: unknown option '%s'\n", opt);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
qemu_plugin_register_vcpu_syscall_filter_cb(id, vcpu_syscall_filter, NULL);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@@ -19,6 +19,11 @@ if host_os != 'windows'
|
|||||||
contrib_plugins += 'lockstep.c'
|
contrib_plugins += 'lockstep.c'
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
if host_os == 'linux'
|
||||||
|
# dlcall passes guest calls through to host libraries; linux-user only
|
||||||
|
contrib_plugins += 'dlcall.c'
|
||||||
|
endif
|
||||||
|
|
||||||
if 'cpp' in all_languages
|
if 'cpp' in all_languages
|
||||||
contrib_plugins += 'cpp.cpp'
|
contrib_plugins += 'cpp.cpp'
|
||||||
endif
|
endif
|
||||||
|
|||||||
@@ -109,6 +109,8 @@ typedef enum {
|
|||||||
RISCV64_SUPERVISOR,
|
RISCV64_SUPERVISOR,
|
||||||
RISCV64_RESERVED,
|
RISCV64_RESERVED,
|
||||||
RISCV64_MACHINE,
|
RISCV64_MACHINE,
|
||||||
|
RISCV64_VUSER,
|
||||||
|
RISCV64_VSUPERVISOR,
|
||||||
RISCV64_PRIVILEGE_LEVEL_MAX,
|
RISCV64_PRIVILEGE_LEVEL_MAX,
|
||||||
} Riscv64PrivilegeLevel;
|
} Riscv64PrivilegeLevel;
|
||||||
|
|
||||||
@@ -153,8 +155,10 @@ static void uftrace_write_map(bool system_emulation)
|
|||||||
const char *path = "./uftrace.data/sid-0.map";
|
const char *path = "./uftrace.data/sid-0.map";
|
||||||
|
|
||||||
if (system_emulation && access(path, F_OK) == 0) {
|
if (system_emulation && access(path, F_OK) == 0) {
|
||||||
/* do not erase existing map in system emulation, as a custom one might
|
/*
|
||||||
* already have been generated by uftrace_symbols.py */
|
* do not erase existing map in system emulation, as a custom one might
|
||||||
|
* already have been generated by uftrace_symbols.py
|
||||||
|
*/
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -706,6 +710,8 @@ static const char *riscv64_get_privilege_level_name(uint8_t pl)
|
|||||||
case RISCV64_SUPERVISOR: return "Supervisor";
|
case RISCV64_SUPERVISOR: return "Supervisor";
|
||||||
case RISCV64_RESERVED: return "Unknown";
|
case RISCV64_RESERVED: return "Unknown";
|
||||||
case RISCV64_MACHINE: return "Machine";
|
case RISCV64_MACHINE: return "Machine";
|
||||||
|
case RISCV64_VUSER: return "VUser";
|
||||||
|
case RISCV64_VSUPERVISOR: return "VSupervisor";
|
||||||
default:
|
default:
|
||||||
g_assert_not_reached();
|
g_assert_not_reached();
|
||||||
}
|
}
|
||||||
@@ -986,7 +992,8 @@ QEMU_PLUGIN_EXPORT int qemu_plugin_install(qemu_plugin_id_t id,
|
|||||||
|
|
||||||
score = qemu_plugin_scoreboard_new(sizeof(Cpu));
|
score = qemu_plugin_scoreboard_new(sizeof(Cpu));
|
||||||
qemu_plugin_register_vcpu_init_cb(id, vcpu_init, NULL);
|
qemu_plugin_register_vcpu_init_cb(id, vcpu_init, NULL);
|
||||||
qemu_plugin_register_atexit_cb(id, at_exit, (void *) info->system_emulation);
|
qemu_plugin_register_atexit_cb(id, at_exit,
|
||||||
|
(void *) info->system_emulation);
|
||||||
qemu_plugin_register_vcpu_tb_trans_cb(id, vcpu_tb_trans, NULL);
|
qemu_plugin_register_vcpu_tb_trans_cb(id, vcpu_tb_trans, NULL);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|||||||
@@ -1046,6 +1046,164 @@ Count traps
|
|||||||
This plugin counts the number of interrupts (asynchronous events), exceptions
|
This plugin counts the number of interrupts (asynchronous events), exceptions
|
||||||
(synchronous events) and host calls (e.g. semihosting) per cpu.
|
(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
|
Other emulation features
|
||||||
------------------------
|
------------------------
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user