From nobody Sun Jul 26 10:07:33 2026 Delivered-To: importer@patchew.org Authentication-Results: mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass(p=none dis=none) header.from=sjtu.edu.cn ARC-Seal: i=1; a=rsa-sha256; t=1783560030; cv=none; d=zohomail.com; s=zohoarc; b=QfUovEdRZJAHQWZhDokagSxEHA7TGguTbdI7dIrANqSs8uZFXJ0kg6nKcC/e0GrgLlkyer+bpBPUlDTw/s+PdrQD4hUqjXHtKS7g0+AWsSXWqOYAmS4xWDIa6D2C88JBLCTcPcHKoed7qB8fKJIhmHnV4KCOHB3iSOUVbrr0Qcg= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1783560030; h=Content-Transfer-Encoding:Cc:Cc:Date:Date:From:From:In-Reply-To:List-Subscribe:List-Post:List-Id:List-Archive:List-Help:List-Unsubscribe:MIME-Version:Message-ID:References:Sender:Subject:Subject:To:To:Message-Id:Reply-To; bh=dCZYt/WXax7mRTtMyTNGnnSjtdyLJrEmptcUz2Xh/Oc=; b=Ui6vaZCrB1DLjNiYwc8v7MYjKZDl9XMh2DrnFa1R+4jTFlaPlLVbJF3JiG5pVQ0nPR3msbM27JnVe0l6Au4tvF3sDuf0OHfvyBhh3EJalaEptatpGNcp5eQ3yJ8tpdnL83HrKzolariR6B5I/krNK/3HHI6NbS8VtYUuMF1TQ1Q= ARC-Authentication-Results: i=1; mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass header.from= (p=none dis=none) Return-Path: Received: from lists1p.gnu.org (lists1p.gnu.org [209.51.188.17]) by mx.zohomail.com with SMTPS id 1783560029671541.1010964846994; Wed, 8 Jul 2026 18:20:29 -0700 (PDT) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists1p.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1whdR5-0003Nj-Jt; Wed, 08 Jul 2026 21:20:20 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists1p.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1whdR0-0003NV-0e for qemu-devel@nongnu.org; Wed, 08 Jul 2026 21:20:14 -0400 Received: from smtp186.sjtu.edu.cn ([202.120.2.186]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1whdQx-0001ie-Gv for qemu-devel@nongnu.org; Wed, 08 Jul 2026 21:20:13 -0400 Received: from proxy188.sjtu.edu.cn (smtp188.sjtu.edu.cn [202.120.2.188]) by smtp186.sjtu.edu.cn (Postfix) with ESMTPS id 7B4B130012D; Thu, 9 Jul 2026 01:19:49 +0000 (UTC) Received: from pc.. (unknown [202.120.40.100]) by proxy188.sjtu.edu.cn (Postfix) with ESMTPSA id 3FE57380D23; Thu, 9 Jul 2026 09:19:48 +0800 (CST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=sjtu.edu.cn; s=default; t=1783559989; bh=JQNoImtzZFI1DJif8R/dIUH8/U36CaUAE24kYC9Sd94=; h=From:To:Cc:Subject:Date:In-Reply-To:References; b=We36e/HhWw3u79FG10aBTH7aouAONNFlf3pOWdzdVATKBtYQ/ET6haxMgStBo3gkg g7fCEULYOzTrnouiyF57o+VgVJwUnEgvair+NvFuvNXIrB8y+i1xot+O1gIo4Gu/8w 41swVLVO/dY8XlI581PuIdJacBpVdVm4j7eCemzw0toKY4ixYHWuNYlWNT3480oZZk 1OfJZYGzlf0VsJO3XZc8oCa4iX7Pg1spB+PfYqn1tZFX5RaHqtBgk8Wr6o9PEeli6q KEg9Jyzn3gjciLHUWcg9STuOS/MhKNAetJuDPqQPyBo3ZOCLzwLIF3ZAEEx1Uh+5I4 tAAnWE2zOfNmQ== From: Ziyang Zhang To: qemu-devel Cc: Riku Voipio , Laurent Vivier , Alex Bennee , Alexandre Iooss , Mahmoud Mandour , Pierrick Bouvier , Richard Henderson , Zhengwei Qi , Yun Wang , Mingyuan Xia , Kailiang Xu , Ziyang Zhang Subject: [PATCH v8 1/2] contrib/plugins: add a minimal dlcall plugin Date: Thu, 9 Jul 2026 09:19:44 +0800 Message-Id: <20260709011945.282457-2-functioner@sjtu.edu.cn> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20260709011945.282457-1-functioner@sjtu.edu.cn> References: <20260709011945.282457-1-functioner@sjtu.edu.cn> MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Received-SPF: pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) client-ip=209.51.188.17; envelope-from=qemu-devel-bounces+importer=patchew.org@nongnu.org; helo=lists1p.gnu.org; Received-SPF: pass client-ip=202.120.2.186; envelope-from=functioner@sjtu.edu.cn; helo=smtp186.sjtu.edu.cn X-Spam_score_int: -20 X-Spam_score: -2.1 X-Spam_bar: -- X-Spam_report: (-2.1 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, SPF_HELO_NONE=0.001, SPF_PASS=-0.001 autolearn=ham autolearn_force=no X-Spam_action: no action X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: qemu development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+importer=patchew.org@nongnu.org Sender: qemu-devel-bounces+importer=patchew.org@nongnu.org X-ZohoMail-DKIM: pass (identity @sjtu.edu.cn) X-ZM-MESSAGEID: 1783560035377158500 Content-Type: text/plain; charset="utf-8" Add a minimal dlcall plugin that lets the guest invoke host functions through magic system calls. The plugin registers a vCPU syscall filter callback that intercepts a reserved syscall number and dispatches a set of pass-through operations: querying host attributes, loading and freeing shared libraries, resolving symbols, retrieving the last library error, and invoking a host function through a common interface. The magic syscall number defaults to 4096 and can be overridden at load time with the "syscall_num=3DN" argument; values low enough to clash with a real syscall are rejected. Co-authored-by: Kailiang Xu Co-authored-by: Mingyuan Xia Signed-off-by: Ziyang Zhang --- contrib/plugins/dlcall.c | 248 ++++++++++++++++++++++++++++++++++++ contrib/plugins/meson.build | 5 + 2 files changed, 253 insertions(+) create mode 100644 contrib/plugins/dlcall.c diff --git a/contrib/plugins/dlcall.c b/contrib/plugins/dlcall.c new file mode 100644 index 0000000000..9d2230b2d1 --- /dev/null +++ b/contrib/plugins/dlcall.c @@ -0,0 +1,248 @@ +/* + * Copyright (C) 2026, Ziyang Zhang + * + * dlcall (Dynamic Linking Call) plugin: lets a linux-user guest invoke ho= st + * functions by issuing a magic system call. The guest can ask QEMU to dlo= pen() + * 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 examp= les. + * + * WARNING: trusted guests only. The guest can load arbitrary host librari= es + * 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 =3D=3D 0, which is qemu-user's default. Po= inter + * operands are dereferenced as host addresses directly, and the invoked h= ost + * functions dereference guest pointers with no address translation, so gu= est + * 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 +#include +#include +#include +#include +#include + +#include + +QEMU_PLUGIN_EXPORT int qemu_plugin_version =3D 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=3DN" argument. To avoid hijacking a real syscall = the + * guest might issue, N must be at least DLCALL_SYSCALL_MIN: every Linux A= BI + * keeps its syscall numbers well below this; numbers from here up are fre= e. + */ +enum { + DLCALL_SYSCALL_DEFAULT =3D 4096, + DLCALL_SYSCALL_MIN =3D 4096, +}; + +static int64_t dlcall_syscall_num =3D 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 valu= e. + * + * The syscall return value (*sysret) only reports dispatch status: 0 on a + * recognised ID, -EINVAL for an unknown one. The actual success/failure o= f 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 succes= s) + * + * 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") =3D=3D 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 =3D (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 =3D=3D dlcall_syscall_num) { + switch (a1) { + /* Query host attribute by a reserved key. */ + case DLCALL_ID_GET_HOST_ATTRIBUTE: { + const char *key =3D (const char *) a2; + const char **attr_ptr =3D (const char **) a3; + assert(attr_ptr); + *attr_ptr =3D query_host_attribute(key); + *sysret =3D 0; + break; + } + + /* Load a shared library. */ + case DLCALL_ID_LOAD_LIBRARY: { + const char *path =3D (const char *) a2; + int flags =3D (int) a3; + void **handle_ptr =3D (void **) a4; + assert(handle_ptr); + *handle_ptr =3D dlopen(path, flags); + *sysret =3D 0; + break; + } + + /* Get the address of a function in a shared library. */ + case DLCALL_ID_GET_PROC_ADDRESS: { + void *handle =3D (void *) a2; + const char *name =3D (const char *) a3; + void **entry_ptr =3D (void **) a4; + assert(entry_ptr); + *entry_ptr =3D dlsym(handle, name); + *sysret =3D 0; + break; + } + + /* Free a shared library. */ + case DLCALL_ID_FREE_LIBRARY: { + void *handle =3D (void *) a2; + int *ret_ptr =3D (int *) a3; + *ret_ptr =3D dlclose(handle); + *sysret =3D 0; + break; + } + + /* Get the last error message for a library event. */ + case DLCALL_ID_GET_LIBRARY_ERROR: { + const char **error_ptr =3D (const char **) a2; + *error_ptr =3D dlerror(); + *sysret =3D 0; + break; + } + + /* Invoke a function of a common interface. */ + case DLCALL_ID_INVOKE_PROC: { + void *proc =3D (void *) a2; + void *arg1 =3D (void *) a3; + void *arg2 =3D (void *) a4; + assert(proc); + invoke_proc(proc, arg1, arg2); + *sysret =3D 0; + break; + } + + default: + *sysret =3D -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 =3D 0; i < argc; i++) { + char *opt =3D argv[i]; + g_auto(GStrv) tokens =3D g_strsplit(opt, "=3D", 2); + if (g_strcmp0(tokens[0], "syscall_num") =3D=3D 0) { + const char *val =3D tokens[1]; + char *endptr =3D NULL; + guint64 num; + if (!val || *val =3D=3D '\0') { + fprintf(stderr, + "plugin dlcall: missing value for syscall_num\n"); + return -1; + } + num =3D g_ascii_strtoull(val, &endptr, 0); + if (*endptr !=3D '\0' || g_strrstr(val, "-") !=3D 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 >=3D %d to avoid clashing with a real " + "syscall\n", val, DLCALL_SYSCALL_MIN); + return -1; + } + dlcall_syscall_num =3D (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, N= ULL); + + return 0; +} diff --git a/contrib/plugins/meson.build b/contrib/plugins/meson.build index 099319e7a1..e7fc4d6d8f 100644 --- a/contrib/plugins/meson.build +++ b/contrib/plugins/meson.build @@ -19,6 +19,11 @@ if host_os !=3D 'windows' contrib_plugins +=3D 'lockstep.c' endif =20 +if host_os =3D=3D 'linux' + # dlcall passes guest calls through to host libraries; linux-user only + contrib_plugins +=3D 'dlcall.c' +endif + if 'cpp' in all_languages contrib_plugins +=3D 'cpp.cpp' endif --=20 2.34.1 From nobody Sun Jul 26 10:07:33 2026 Delivered-To: importer@patchew.org Authentication-Results: mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass(p=none dis=none) header.from=sjtu.edu.cn ARC-Seal: i=1; a=rsa-sha256; t=1783560047; cv=none; d=zohomail.com; s=zohoarc; b=nJolghNZu6fc9YYIjeX65kMBFWMd4Ja+BqswoO6GcMYjRarabK/t3ab1lc43/wlqLrQqZ6Vgb+3Ks4U4pCNniquAzXU4tFp4ppL5/dmJC4Sz10b8NpdW2CIM0LI6RiJ9tSAONwQJ3X3LQisi1vEUkam81ocSUTxOXEeHzmiPoB8= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1783560047; h=Content-Transfer-Encoding:Cc:Cc:Date:Date:From:From:In-Reply-To:List-Subscribe:List-Post:List-Id:List-Archive:List-Help:List-Unsubscribe:MIME-Version:Message-ID:References:Sender:Subject:Subject:To:To:Message-Id:Reply-To; bh=U2nfiy8aydoBvnPqOXqkkWAnZeq1w3Wwj5Lmw+ASw/0=; b=Q888HY2oP0tfQAFb2iIs18f1Nbekfs7orAsC8wLnLN1bT72Quil3NfDjHgCkqBjcmcooMfUwhD8b4+rKR8xQiB2zOZyCaUPoJUAQkZNwPO0iG97hZ5DiVKim1gnOVkWfyP+GjFivN0RXylDHWqHNx55HuAQC3Wd76h6q7vjuo0E= ARC-Authentication-Results: i=1; mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass header.from= (p=none dis=none) Return-Path: Received: from lists1p.gnu.org (lists1p.gnu.org [209.51.188.17]) by mx.zohomail.com with SMTPS id 1783560046558347.04634232142246; Wed, 8 Jul 2026 18:20:46 -0700 (PDT) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists1p.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1whdRE-0003U0-TB; Wed, 08 Jul 2026 21:20:28 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists1p.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1whdRD-0003Pg-1m for qemu-devel@nongnu.org; Wed, 08 Jul 2026 21:20:27 -0400 Received: from smtp186.sjtu.edu.cn ([202.120.2.186]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1whdRA-0001uR-55 for qemu-devel@nongnu.org; Wed, 08 Jul 2026 21:20:26 -0400 Received: from proxy188.sjtu.edu.cn (smtp188.sjtu.edu.cn [202.120.2.188]) by smtp186.sjtu.edu.cn (Postfix) with ESMTPS id C02F6300147; Thu, 9 Jul 2026 01:19:50 +0000 (UTC) Received: from pc.. (unknown [202.120.40.100]) by proxy188.sjtu.edu.cn (Postfix) with ESMTPSA id 33635380D26; Thu, 9 Jul 2026 09:19:49 +0800 (CST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=sjtu.edu.cn; s=default; t=1783559990; bh=GziRZJHGUn10SSqb/0ViEbOW/3rtSVxYLwE1Ud45JtM=; h=From:To:Cc:Subject:Date:In-Reply-To:References; b=EhVIrqzhozxU69YPqKrih0hTKtQg21tPZ/FWMPUoVTToscYEPyrOaX3ai7b8fD/HH YtrblAxdsNuRGa81RI6xT7e+c9P49o5H3rmq/eINkKIyDsx+mDA0276H+PtgilRiUc VoQD4vb3sn3RIAHH0cBOvvClde1fDep1GZPMBzcLS89eSKE/3aCgYx5ZJx+wJ6fRsa W4gdCYqO63P0a/o7v82KgHVhzMmPkgrOI/owCquCqtPfFReWl7/WlKWI3Gzpdh/N7o pCgZpJbJB74zqrs2KAiebkbo9N3SRRteM7ADVyWp8ISF2pr/jWBJtrUsyIfmJAu4nf 3AaBgqfQomYSw== From: Ziyang Zhang To: qemu-devel Cc: Riku Voipio , Laurent Vivier , Alex Bennee , Alexandre Iooss , Mahmoud Mandour , Pierrick Bouvier , Richard Henderson , Zhengwei Qi , Yun Wang , Mingyuan Xia , Kailiang Xu , Ziyang Zhang Subject: [PATCH v8 2/2] docs/about/emulation: document the dlcall plugin Date: Thu, 9 Jul 2026 09:19:45 +0800 Message-Id: <20260709011945.282457-3-functioner@sjtu.edu.cn> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20260709011945.282457-1-functioner@sjtu.edu.cn> References: <20260709011945.282457-1-functioner@sjtu.edu.cn> MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Received-SPF: pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) client-ip=209.51.188.17; envelope-from=qemu-devel-bounces+importer=patchew.org@nongnu.org; helo=lists1p.gnu.org; Received-SPF: pass client-ip=202.120.2.186; envelope-from=functioner@sjtu.edu.cn; helo=smtp186.sjtu.edu.cn X-Spam_score_int: -20 X-Spam_score: -2.1 X-Spam_bar: -- X-Spam_report: (-2.1 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, SPF_HELO_NONE=0.001, SPF_PASS=-0.001 autolearn=ham autolearn_force=no X-Spam_action: no action X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: qemu development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+importer=patchew.org@nongnu.org Sender: qemu-devel-bounces+importer=patchew.org@nongnu.org X-ZohoMail-DKIM: pass (identity @sjtu.edu.cn) X-ZM-MESSAGEID: 1783560049797158500 Content-Type: text/plain; charset="utf-8" Document the dlcall plugin under Example Plugins: what it does, the trusted- guests and guest_base =3D=3D 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 Co-authored-by: Mingyuan Xia Signed-off-by: Ziyang Zhang --- 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), excepti= ons (synchronous events) and host calls (e.g. semihosting) per cpu. =20 +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 p= lugin +runs native code in its place instead of the guest emulating it. Intercept= ion +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 instruc= tion +by instruction. + +The guest issues a reserved "magic" system call (4096 by default, configur= able +with ``syscall_num=3DN``) whose first argument selects a pass-through oper= ation: +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 =3D=3D 0`` (qemu-u= ser'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 lib= rary +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 ... + +`Lorelei `_ is one end-to-end usersp= ace +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 gu= est +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 buil= d tags +it ``from host``. An unmodified guest program ``main`` calls ``hello("worl= d", +7)``, and the thunk makes that same binary reach the host build in place o= f 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 + + 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 + + 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 + + # is your host architecture: x86_64, aarch64 or riscv64 + wget https://github.com/rover2024/lorelei/releases/download/v/= lorelei-devkit--.tar.xz + tar -xf lorelei-devkit--.tar.xz + DEVKIT=3Dlorelei-devkit- + +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/libhell= o.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 -lhel= lo \ + -Wl,-rpath,'$ORIGIN' -o build/guest/main + +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 7 + +Now generate the thunk from the host ``libhello.so``. This produces a gues= t-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, with the generated guest ``libhell= o.so`` +ahead of its own on ``LD_LIBRARY_PATH``. The call reaches the host build n= ow, +from the same unmodified binary: + +.. code-block:: shell + + LORELEI_THUNK_PATH=3Dthunks \ + LD_LIBRARY_PATH=3D$DEVKIT/lib:build/host \ + qemu-x86_64 -plugin contrib/plugins/libdlcall.so \ + -E LD_LIBRARY_PATH=3Dthunks/x86_64/lib/x86_64-LoreGTL:$DEVKIT/x86_6= 4/lib \ + build/guest/main + +which prints:: + + hello from host: world, lucky 7 + +The guest ``LD_LIBRARY_PATH``, passed with ``-E``, is searched before the +program's rpath, so its generated ``libhello.so`` replaces the guest build: + +* ``thunks/x86_64/lib/x86_64-LoreGTL`` holds the generated guest ``libhell= o.so``. +* ``$DEVKIT/x86_64/lib`` holds the guest runtime support shipped with the = devkit. + +The host ``LD_LIBRARY_PATH`` is QEMU's own search path: + +* ``$DEVKIT/lib`` holds the host runtime support shipped with the devkit. +* ``build/host`` holds the host ``libhello.so`` the host thunk dispatches = to. + +The finished tree:: + + src/ + hello.h + hello_guest.c + hello_host.c + main.c + build/ + guest/ + libhello.so (guest build of the library) + main (the guest program) + host/ + libhello.so (host build of the library) + thunks/ + lib/x86_64-LoreHTL/libhello_HTL.so (host thunk) + x86_64/lib/x86_64-LoreGTL/libhello.so (guest thunk, the drop-in) + +See the runnable +`hello `_ +(minimal) and +`demo `_ +(variadic functions and a callback that reenters the guest) examples, and +`Lorelei `_ for the toolchain and the +runtime environment they expect. + +.. list-table:: Dynamic Linking Call arguments + :widths: 20 80 + :header-rows: 1 + + * - Option + - Description + * - syscall_num=3DN + - The magic syscall number the guest issues (default 4096). Must be hi= gh + enough not to clash with a real syscall. + Other emulation features ------------------------ =20 --=20 2.34.1