From nobody Fri Dec 19 14:45:44 2025 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=gmail.com ARC-Seal: i=1; a=rsa-sha256; t=1747965930; cv=none; d=zohomail.com; s=zohoarc; b=Rl35+5kQe0VT78eYShgmwpSYaVhR1Ma8Gzpxt2lPJiIihMmSYsEXmThJOiMQKuH23lTxvs1e8fdk0JW65pKVT6C9I88orBroK/bwqrTDwQNAGjdRf6K4VhxzDF4Qah+zF5LJ7FxQMENX1jIUj7JWfDilTsElbG1uLyXFN33TTFM= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1747965930; 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=ezXosNFfgXBg6PJCV3m2vrTMDcaA5X2aQFU8P/ZfaMo=; b=DJDbxciSI6NMiwvA3a4kr0GJ96C7HMxPpJYjS+LK15GULkw6ecK0MVMzhiRKCzteerHnHt+pSRbnHLKfW7RQSyJS3PKmUtcuPc0W3hHkDIXkK0SZMagoZjpiE47PbuKEk7UDJegXZyWUgEwXz78VNdXjWiy6sWYaE+4OxUXJIbs= 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 lists.gnu.org (lists.gnu.org [209.51.188.17]) by mx.zohomail.com with SMTPS id 1747965930409783.6848552805435; Thu, 22 May 2025 19:05:30 -0700 (PDT) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1uIHlX-0001HC-AQ; Thu, 22 May 2025 22:04:07 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1uIHlR-0001Fd-PQ for qemu-devel@nongnu.org; Thu, 22 May 2025 22:04:03 -0400 Received: from mail-pf1-x42f.google.com ([2607:f8b0:4864:20::42f]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from ) id 1uIHlN-0007kt-CF for qemu-devel@nongnu.org; Thu, 22 May 2025 22:04:01 -0400 Received: by mail-pf1-x42f.google.com with SMTP id d2e1a72fcca58-742c46611b6so7243507b3a.1 for ; Thu, 22 May 2025 19:03:55 -0700 (PDT) Received: from shemhazi.lan ([50.46.174.34]) by smtp.gmail.com with ESMTPSA id d2e1a72fcca58-742a98a242asm11820818b3a.164.2025.05.22.19.03.50 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 22 May 2025 19:03:50 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1747965834; x=1748570634; darn=nongnu.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=ezXosNFfgXBg6PJCV3m2vrTMDcaA5X2aQFU8P/ZfaMo=; b=H1hfdPFFCC43CfqBgM4EZ8eClFIWcKHfoghKgTl2L98W6rYiu3KE6uZ/Uu9XSan14K 3BRq6MAC5dVBRtfCDogGCjT0Zz2QEf3w0C9FDq4OwMTvUrvFZOLW+9G9AxPrCJ7sL7u7 9lE2f4vjocSJxXvJdVFV52Cg2FuOCjhU0EDpi4zi+66HS1zXfIMZNMdaJiYLTWUHKKo/ cVah/L2Z+fFmIRVXki2leVmhuUcvIU18Bmh09jWYxHcOJ3YLTXqVLUvOqd+PkDOrlLNM yQnfZY77VtH6g/GTfGpsVLX0hES8Bb/VlOcnU/lne0W9jamfvYs7fVIlz0vY5CO1zv2Z hDOw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1747965834; x=1748570634; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=ezXosNFfgXBg6PJCV3m2vrTMDcaA5X2aQFU8P/ZfaMo=; b=sZ01kN96LVZHZI83Pcl1ewvyLvQdTJFegbKRa6dNFb1h6547oC2/IhQ53B9WEJvnAy 7mxBL3J9ZEwbksFnhZvC3Z83WOahgaf8ZwZciV4/nCyjV9Um+GoIggYnXXA+CTG92cQf Fw80vXAE/z0l5agPY4Gky1WJc0LhIGHB5wAH5zJDL5Ta6nu/LW7/RzCWvIdGmOT5ps87 4rA1TYPxtMdwrULnz7nH++nkB4CC0eDZGWs0SCu3Ui6qOUKGwCwc5w4tlUIIBo0VRthV Tlv8qZt2CCiPggVohwNuB90J9LjYKuz1UsxN6R63Vvspqjm56e/KAOyC0SceRn9IGOHb 5tKQ== X-Gm-Message-State: AOJu0YzDIsp+EQiOiEOCDHFtYLtmpIaOerQAkhIvR9IH4brmvaAb0VfB PE0B5k4k4Y9mvvzuJVwvLd+HXGNDn3TQC31Xh5e9bijQxZe8BPyr4KO2Xo/jKz/hGx8= X-Gm-Gg: ASbGncsUbhYnsOGhN9dzbKl7KnL9J7PbQi7VUAQezAVIuouAPdc+O7qsA+RhXDHuw5/ JkALEGjhJcYUqH+C0jgYXimhwrzSMW8z7JwCCiKFj1rGnDuSDpAu5h9/Orxn950bHfl/NzIuWL6 LnI1+2wPpUnCgHG6PiodKu589QE50coI95xCttAxRYA/N1w8nM/m5pgV9WglCWbIUxY9bR0H/Eg OwAFUH5lhHoYtqFE2BcKAzUZAZRybOS+8EPjD8KxIEr1avDbdsM8qbGeCv3ma5SV9N5TrUAWCvf iTf0xwwCqGsO6xOLW+NYCt+U89ioJewUzTUFFqzn99MqrPkOyUo= X-Google-Smtp-Source: AGHT+IG58M6+Ltyt/slfa5cmevXL5Vh8JvS1OHm/lj44fvRpUR2VKAzlDwAsw4usYvIn6+ZqYiYg7g== X-Received: by 2002:a05:6a00:3492:b0:742:9bd3:cd1f with SMTP id d2e1a72fcca58-742acd728eamr35672043b3a.23.1747965831520; Thu, 22 May 2025 19:03:51 -0700 (PDT) From: Rowan Hart To: qemu-devel@nongnu.org Cc: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= , Pierrick Bouvier , =?UTF-8?q?Alex=20Benn=C3=A9e?= , Richard Henderson , Paolo Bonzini , Mahmoud Mandour , Alexandre Iooss , Eduardo Habkost , novafacing Subject: [PATCH v4 7/9] plugins: Add hypercalls plugin and test Date: Thu, 22 May 2025 19:03:41 -0700 Message-ID: <20250523020344.1341179-8-rowanbhart@gmail.com> X-Mailer: git-send-email 2.49.0 In-Reply-To: <20250523020344.1341179-1-rowanbhart@gmail.com> References: <20250523020344.1341179-1-rowanbhart@gmail.com> 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=lists.gnu.org; Received-SPF: pass client-ip=2607:f8b0:4864:20::42f; envelope-from=rowanbhart@gmail.com; helo=mail-pf1-x42f.google.com 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, FREEMAIL_FROM=0.001, RCVD_IN_DNSWL_NONE=-0.0001, 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: 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 @gmail.com) X-ZM-MESSAGEID: 1747965931097116600 Content-Type: text/plain; charset="utf-8" From: novafacing This patch adds a plugin that implements a simple form of hypercalls from guest code to the plugin by using the register read API. It accepts only one hypercall, which writes a magic value to guest memory. Signed-off-by: novafacing Signed-off-by: Rowan Hart --- tests/tcg/Makefile.target | 1 + tests/tcg/plugins/hypercalls.c | 552 ++++++++++++++++++ tests/tcg/plugins/meson.build | 2 +- tests/tcg/x86_64/Makefile.softmmu-target | 6 +- tests/tcg/x86_64/system/hypercalls-target.c | 45 ++ .../tcg/x86_64/system/validate-hypercalls.py | 40 ++ 6 files changed, 644 insertions(+), 2 deletions(-) create mode 100644 tests/tcg/plugins/hypercalls.c create mode 100644 tests/tcg/x86_64/system/hypercalls-target.c create mode 100755 tests/tcg/x86_64/system/validate-hypercalls.py diff --git a/tests/tcg/Makefile.target b/tests/tcg/Makefile.target index 4b709a9d18..5ac9638102 100644 --- a/tests/tcg/Makefile.target +++ b/tests/tcg/Makefile.target @@ -177,6 +177,7 @@ RUN_TESTS+=3D$(EXTRA_RUNS) # exercise things. We can define them on a per-test basis here. run-plugin-%-with-libmem.so: PLUGIN_ARGS=3D$(COMMA)inline=3Dtrue run-plugin-%-with-libpatch.so: PLUGIN_ARGS=3D$(COMMA)target=3Dffffffff$(CO= MMA)patch=3D00000000 +run-plugin-%-with-libhypercalls.so: PLUGIN_ARGS=3D$(COMMA)ignore_unsupport= ed=3Dtrue =20 ifeq ($(filter %-softmmu, $(TARGET)),) run-%: % diff --git a/tests/tcg/plugins/hypercalls.c b/tests/tcg/plugins/hypercalls.c new file mode 100644 index 0000000000..3abbe1bf30 --- /dev/null +++ b/tests/tcg/plugins/hypercalls.c @@ -0,0 +1,552 @@ +/* + * SPDX-License-Identifier: GPL-2.0-or-later + * + * Copyright (C) 2024, Rowan Hart + * + * License: GNU GPL, version 2 or later. + * See the COPYING file in the top-level directory. + * + * This plugin implements a simple hypercall interface for guests (both sy= stem + * and user mode) to call certain operations from the host. + */ +#include "glib.h" +#include "glibconfig.h" +#include +#include +#include +#include +#include +#include +#include + +#include + +QEMU_PLUGIN_EXPORT int qemu_plugin_version =3D QEMU_PLUGIN_VERSION; + +#define AARCH64_N_HYPERCALL_INSNS (28) +#define AARCH64_HYPERCALL_INSN_LEN (4) +#define AARCH64_HYPERCALL_MAX (AARCH64_N_HYPERCALL_INSNS) +#define ARM_N_HYPERCALL_INSNS (12) +#define ARM_HYPERCALL_INSN_LEN (4) +#define ARM_HYPERCALL_MAX (ARM_N_HYPERCALL_INSNS) +#define X86_HYPERCALL_INSN_LEN (2) +#define X86_HYPERCALL_VALUE_BASE (0x4711) +#define X86_HYPERCALL_MAX (0x10000) +#define N_HYPERCALL_ARGS (4) + +static bool ignore_unsupported; + +static struct qemu_plugin_register *get_register(const char *name); +static uint64_t byte_array_to_uint64(GByteArray *buf); + +enum HypercallInsnType { + CONSTANT, + CALLBACK, +}; + + +/* + * Checks an instruction and returns its hypercall number, if it is + * a hypercall instruction, or -1 if it is not. Called at execution + * time. + */ +typedef int32_t (*hypercall_nr_cb)(GByteArray *); + +/* + * Checks an instruction and returns whether it is a hypercall, or -1 if i= t is + * not. Called at execution time. + */ +typedef bool (*is_hypercall_cb)(GByteArray *); + +/* + * Specifies a Hypercall for an architecture: + * + * - Architecture name + * - Whether it is enabled + * - The hypercall instruction + * - The register names to pass the hypercall # and args + */ +struct HypercallSpec { + const bool enabled; + const char *name; + const bool le; + const char *args[N_HYPERCALL_ARGS]; + const hypercall_nr_cb hypercall_nr_cb; + const is_hypercall_cb is_hypercall_cb; +}; + +static int32_t aarch64_hypercall_nr_cb(GByteArray *insn) +{ + if (insn->len !=3D AARCH64_HYPERCALL_INSN_LEN) { + return -1; + } + + static const uint8_t + hypercall_insns[AARCH64_N_HYPERCALL_INSNS][AARCH64_HYPERCALL_INSN_LEN]= =3D { + { 0xaa, 0x4, 0x0, 0x84 }, + { 0xaa, 0x5, 0x0, 0xa5 }, + { 0xaa, 0x6, 0x0, 0xc6 }, + { 0xaa, 0x7, 0x0, 0xe7 }, + { 0xaa, 0x8, 0x1, 0x8 }, + { 0xaa, 0x9, 0x1, 0x29 }, + { 0xaa, 0xa, 0x1, 0x4a }, + { 0xaa, 0xb, 0x1, 0x6b }, + { 0xaa, 0xc, 0x1, 0x8c }, + { 0xaa, 0xd, 0x1, 0xad }, + { 0xaa, 0xe, 0x1, 0xce }, + { 0xaa, 0xf, 0x1, 0xef }, + { 0xaa, 0x10, 0x2, 0x10 }, + { 0xaa, 0x11, 0x2, 0x31 }, + { 0xaa, 0x12, 0x2, 0x52 }, + { 0xaa, 0x13, 0x2, 0x73 }, + { 0xaa, 0x14, 0x2, 0x94 }, + { 0xaa, 0x15, 0x2, 0xb5 }, + { 0xaa, 0x16, 0x2, 0xd6 }, + { 0xaa, 0x17, 0x2, 0xf7 }, + { 0xaa, 0x18, 0x3, 0x18 }, + { 0xaa, 0x19, 0x3, 0x39 }, + { 0xaa, 0x1a, 0x3, 0x5a }, + { 0xaa, 0x1b, 0x3, 0x7b }, + { 0xaa, 0x1c, 0x3, 0x9c }, + { 0xaa, 0x1d, 0x3, 0xbd }, + { 0xaa, 0x1e, 0x3, 0xde }, + { 0xaa, 0x1f, 0x3, 0xff }, + }; + + for (int32_t i =3D 0; i < AARCH64_N_HYPERCALL_INSNS; i++) { + if (!memcmp(hypercall_insns[i], insn->data, insn->len)) { + return i; + } + } + return -1; +} + +static bool aarch64_is_hypercall_cb(GByteArray *insn) +{ + return aarch64_hypercall_nr_cb(insn) < 0; +} + + +static int32_t aarch64_be_hypercall_nr_cb(GByteArray *insn) +{ + if (insn->len !=3D AARCH64_HYPERCALL_INSN_LEN) { + return -1; + } + + static const uint8_t + hypercall_insns[AARCH64_N_HYPERCALL_INSNS][AARCH64_HYPERCALL_INSN_LEN]= =3D { + {0x84, 0x0, 0x4, 0xaa}, + {0xa5, 0x0, 0x5, 0xaa}, + {0xc6, 0x0, 0x6, 0xaa}, + {0xe7, 0x0, 0x7, 0xaa}, + {0x8, 0x1, 0x8, 0xaa}, + {0x29, 0x1, 0x9, 0xaa}, + {0x4a, 0x1, 0xa, 0xaa}, + {0x6b, 0x1, 0xb, 0xaa}, + {0x8c, 0x1, 0xc, 0xaa}, + {0xad, 0x1, 0xd, 0xaa}, + {0xce, 0x1, 0xe, 0xaa}, + {0xef, 0x1, 0xf, 0xaa}, + {0x10, 0x2, 0x10, 0xaa}, + {0x31, 0x2, 0x11, 0xaa}, + {0x52, 0x2, 0x12, 0xaa}, + {0x73, 0x2, 0x13, 0xaa}, + {0x94, 0x2, 0x14, 0xaa}, + {0xb5, 0x2, 0x15, 0xaa}, + {0xd6, 0x2, 0x16, 0xaa}, + {0xf7, 0x2, 0x17, 0xaa}, + {0x18, 0x3, 0x18, 0xaa}, + {0x39, 0x3, 0x19, 0xaa}, + {0x5a, 0x3, 0x1a, 0xaa}, + {0x7b, 0x3, 0x1b, 0xaa}, + {0x9c, 0x3, 0x1c, 0xaa}, + {0xbd, 0x3, 0x1d, 0xaa}, + {0xde, 0x3, 0x1e, 0xaa}, + {0xff, 0x3, 0x1f, 0xaa}, + }; + + for (int32_t i =3D 0; i < AARCH64_N_HYPERCALL_INSNS; i++) { + if (!memcmp(hypercall_insns[i], insn->data, insn->len)) { + return i; + } + } + return -1; +} + +static bool aarch64_be_is_hypercall_cb(GByteArray *insn) +{ + return aarch64_be_hypercall_nr_cb(insn) < 0; +} + + +static int32_t arm_hypercall_nr_cb(GByteArray *insn) +{ + if (insn->len !=3D ARM_HYPERCALL_INSN_LEN) { + return -1; + } + + static const uint8_t + hypercall_insns[ARM_N_HYPERCALL_INSNS][ARM_HYPERCALL_INSN_LEN] =3D { + { 0xe1, 0x84, 0x40, 0x4 }, + { 0xe1, 0x85, 0x50, 0x5 }, + { 0xe1, 0x86, 0x60, 0x6 }, + { 0xe1, 0x87, 0x70, 0x7 }, + { 0xe1, 0x88, 0x80, 0x8 }, + { 0xe1, 0x89, 0x90, 0x9 }, + { 0xe1, 0x8a, 0xa0, 0xa }, + { 0xe1, 0x8b, 0xb0, 0xb }, + { 0xe1, 0x8c, 0xc0, 0xc }, + { 0xe1, 0x8d, 0xd0, 0xd }, + { 0xe1, 0x8e, 0xe0, 0xe }, + { 0xe1, 0x8f, 0xf0, 0xf }, + }; + + for (int32_t i =3D 0; i < ARM_N_HYPERCALL_INSNS; i++) { + if (!memcmp(hypercall_insns[i], insn->data, insn->len)) { + return i; + } + } + return -1; +} + +static bool arm_is_hypercall_cb(GByteArray *insn) +{ + return arm_hypercall_nr_cb(insn) < 0; +} + +static int32_t arm_be_hypercall_nr_cb(GByteArray *insn) +{ + if (insn->len !=3D ARM_HYPERCALL_INSN_LEN) { + return -1; + } + + static const uint8_t + hypercall_insns[ARM_N_HYPERCALL_INSNS][ARM_HYPERCALL_INSN_LEN] =3D { + {0x4, 0x40, 0x84, 0xe1}, + {0x5, 0x50, 0x85, 0xe1}, + {0x6, 0x60, 0x86, 0xe1}, + {0x7, 0x70, 0x87, 0xe1}, + {0x8, 0x80, 0x88, 0xe1}, + {0x9, 0x90, 0x89, 0xe1}, + {0xa, 0xa0, 0x8a, 0xe1}, + {0xb, 0xb0, 0x8b, 0xe1}, + {0xc, 0xc0, 0x8c, 0xe1}, + {0xd, 0xd0, 0x8d, 0xe1}, + {0xe, 0xe0, 0x8e, 0xe1}, + {0xf, 0xf0, 0x8f, 0xe1}, + }; + + for (int32_t i =3D 0; i < ARM_N_HYPERCALL_INSNS; i++) { + if (!memcmp(hypercall_insns[i], insn->data, insn->len)) { + return i; + } + } + return -1; +} + +static bool arm_be_is_hypercall_cb(GByteArray *insn) +{ + return arm_be_hypercall_nr_cb(insn) < 0; +} + +static int32_t x86_64_hypercall_nr_cb(GByteArray *insn) +{ + if (insn->len !=3D X86_HYPERCALL_INSN_LEN) { + return -1; + } + + uint8_t cpuid[] =3D { 0x0f, 0xa2 }; + if (!memcmp(cpuid, insn->data, insn->len)) { + GByteArray *reg =3D g_byte_array_new(); + qemu_plugin_read_register(get_register("rax"), reg); + uint64_t value =3D byte_array_to_uint64(reg); + g_byte_array_free(reg, true); + + if (!(value & X86_HYPERCALL_VALUE_BASE)) { + return -1; + } + + value =3D (value >> 16) & 0xffff; + + if (value >=3D X86_HYPERCALL_MAX) { + return -1; + } + + return (int32_t)value; + } + + return -1; +} + +static bool x86_64_is_hypercall_cb(GByteArray *insn) +{ + if (insn->len !=3D X86_HYPERCALL_INSN_LEN) { + return false; + } + + uint8_t cpuid[] =3D { 0x0f, 0xa2 }; + if (!memcmp(cpuid, insn->data, insn->len)) { + return true; + } + + return false; +} + +static int32_t i386_hypercall_nr_cb(GByteArray *insn) +{ + if (insn->len !=3D X86_HYPERCALL_INSN_LEN) { + return -1; + } + + uint8_t cpuid[] =3D { 0x0f, 0xa2 }; + if (!memcmp(cpuid, insn->data, insn->len)) { + GByteArray *reg =3D g_byte_array_new(); + qemu_plugin_read_register(get_register("eax"), reg); + uint64_t value =3D byte_array_to_uint64(reg); + g_byte_array_free(reg, true); + + if (!(value & X86_HYPERCALL_VALUE_BASE)) { + return -1; + } + + value =3D (value >> 16) & 0xffff; + + if (value >=3D X86_HYPERCALL_MAX) { + return -1; + } + return (int32_t)value; + } + + return -1; + +} + +static bool i386_is_hypercall_cb(GByteArray *insn) +{ + if (insn->len !=3D X86_HYPERCALL_INSN_LEN) { + return false; + } + + uint8_t cpuid[] =3D { 0x0f, 0xa2 }; + if (!memcmp(cpuid, insn->data, insn->len)) { + return true; + } + + return false; + +} + +static const struct HypercallSpec *hypercall_spec; + +static const struct HypercallSpec hypercall_specs[] =3D { + { true, "aarch64", true, { + "x0", "x1", "x2", "x3", + }, aarch64_hypercall_nr_cb, aarch64_is_hypercall_cb + }, + { true, "aarch64_be", false, { + "x0", "x1", "x2", "x3", + }, aarch64_be_hypercall_nr_cb, aarch64_be_is_hypercall_cb + }, + { true, "arm", true, { + "r0", "r1", "r2", "r3", + }, arm_hypercall_nr_cb, arm_is_hypercall_cb + }, + { true, "armeb", false, { + "r0", "r1", "r2", "r3" + }, arm_be_hypercall_nr_cb, arm_be_is_hypercall_cb + }, + { true, "i386", true, { + "edi", "esi", "edx", "ecx" + }, i386_hypercall_nr_cb, i386_is_hypercall_cb + }, + { true, "x86_64", true, { + "rdi", "rsi", "rdx", "rcx" + + }, x86_64_hypercall_nr_cb, x86_64_is_hypercall_cb + }, + { false, NULL, .le =3D false, {NULL, NULL, NULL, NULL}, NULL}, +}; + +static GArray *hypercall_insns; + +/* + * Returns a handle to a register with a given name, or NULL if there is no + * such register. + */ +static struct qemu_plugin_register *get_register(const char *name) +{ + GArray *registers =3D qemu_plugin_get_registers(); + + struct qemu_plugin_register *handle =3D NULL; + + qemu_plugin_reg_descriptor *reg_descriptors =3D + (qemu_plugin_reg_descriptor *)registers->data; + + for (size_t i =3D 0; i < registers->len; i++) { + if (!strcmp(reg_descriptors[i].name, name)) { + handle =3D reg_descriptors[i].handle; + } + } + + g_array_free(registers, true); + + return handle; +} + +/* + * Transforms a byte array with at most 8 entries into a uint64_t + * depending on the target machine's endianness. + */ +static uint64_t byte_array_to_uint64(GByteArray *buf) +{ + uint64_t value =3D 0; + if (hypercall_spec->le) { + for (int i =3D 0; i < buf->len && i < sizeof(uint64_t); i++) { + value |=3D ((uint64_t)buf->data[i]) << (i * 8); + } + } else { + for (int i =3D 0; i < buf->len && i < sizeof(uint64_t); i++) { + value |=3D ((uint64_t)buf->data[i]) << ((buf->len - 1 - i) * 8= ); + } + } + return value; +} + +/* + * Handle a "hypercall" instruction, which has some special meaning for th= is + * plugin. + */ +static void hypercall(unsigned int vcpu_index, void *userdata) +{ + GByteArray *insn_data =3D (GByteArray *)userdata; + int32_t hypercall_nr =3D hypercall_spec->hypercall_nr_cb(insn_data); + + if (hypercall_nr < 0) { + return; + } + + uint64_t args[N_HYPERCALL_ARGS] =3D {0}; + GByteArray *buf =3D g_byte_array_new(); + for (size_t i =3D 0; i < N_HYPERCALL_ARGS; i++) { + g_byte_array_set_size(buf, 0); + struct qemu_plugin_register *reg =3D + get_register(hypercall_spec->args[i]); + qemu_plugin_read_register(reg, buf); + args[i] =3D byte_array_to_uint64(buf); + } + g_byte_array_free(buf, true); + + switch (hypercall_nr) { + /* + * The write hypercall (#0x01) tells the plugin to write random bytes + * of a given size into the memory of the emulated system at a particu= lar + * vaddr + */ + case 1: { + GByteArray *data =3D g_byte_array_new(); + g_byte_array_set_size(data, args[1]); + for (uint64_t i =3D 0; i < args[1]; i++) { + data->data[i] =3D (uint8_t)g_random_int(); + } + qemu_plugin_write_memory_vaddr(args[0], data); + break; + } + default: + break; + } +} + +/* + * Callback on translation of a translation block. + */ +static void vcpu_tb_trans_cb(qemu_plugin_id_t id, struct qemu_plugin_tb *t= b) +{ + for (size_t i =3D 0; i < qemu_plugin_tb_n_insns(tb); i++) { + struct qemu_plugin_insn *insn =3D qemu_plugin_tb_get_insn(tb, i); + GByteArray *insn_data =3D g_byte_array_new(); + size_t insn_len =3D qemu_plugin_insn_size(insn); + g_byte_array_set_size(insn_data, insn_len); + qemu_plugin_insn_data(insn, insn_data->data, insn_data->len); + + if (hypercall_spec->is_hypercall_cb(insn_data)) { + g_array_append_val(hypercall_insns, insn_data); + qemu_plugin_register_vcpu_insn_exec_cb(insn, hypercall, + QEMU_PLUGIN_CB_R_REGS, + (void *)insn_data); + } else { + g_byte_array_free(insn_data, true); + } + + } +} + +static void atexit_cb(qemu_plugin_id_t id, void *userdata) +{ + for (size_t i =3D 0; i < hypercall_insns->len; i++) { + g_byte_array_free(g_array_index(hypercall_insns, GByteArray *, i), + true); + } + + g_array_free(hypercall_insns, true); +} + +static void usage(void) +{ + fprintf(stderr, + "Usage: ,[ignore_unsupported=3D]"); +} + +/* + * Called when the plugin is installed + */ +QEMU_PLUGIN_EXPORT int qemu_plugin_install(qemu_plugin_id_t id, + const qemu_info_t *info, int ar= gc, + char **argv) +{ + if (argc > 1) { + usage(); + return -1; + } + + for (size_t 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], "ignore_unsupported") =3D=3D 0) { + if (!qemu_plugin_bool_parse(tokens[0], + tokens[1], &ignore_unsupported)) { + fprintf(stderr, + "Failed to parse argument ignore_unsupported\n"); + return -1; + } + } else { + fprintf(stderr, "Unknown argument: %s\n", tokens[0]); + usage(); + return -1; + } + } + + + hypercall_spec =3D &hypercall_specs[0]; + + while (hypercall_spec->name !=3D NULL) { + if (!strcmp(hypercall_spec->name, info->target_name)) { + break; + } + hypercall_spec++; + } + + if (hypercall_spec->name =3D=3D NULL || !hypercall_spec->enabled) { + qemu_plugin_outs("Error: no hypercall spec."); + if (ignore_unsupported) { + return 0; + } + return -1; + } + + hypercall_insns =3D g_array_new(true, true, sizeof(GByteArray *)); + + qemu_plugin_register_vcpu_tb_trans_cb(id, vcpu_tb_trans_cb); + qemu_plugin_register_atexit_cb(id, atexit_cb, NULL); + + return 0; +} diff --git a/tests/tcg/plugins/meson.build b/tests/tcg/plugins/meson.build index 163042e601..909bf3005a 100644 --- a/tests/tcg/plugins/meson.build +++ b/tests/tcg/plugins/meson.build @@ -1,6 +1,6 @@ t =3D [] if get_option('plugins') - foreach i : ['bb', 'empty', 'inline', 'insn', 'mem', 'reset', 'syscall',= 'patch'] + foreach i : ['bb', 'empty', 'inline', 'insn', 'mem', 'reset', 'syscall',= 'hypercalls', 'patch'] if host_os =3D=3D 'windows' t +=3D shared_module(i, files(i + '.c') + '../../../contrib/plugins/= win32_linker.c', include_directories: '../../../include/qemu', diff --git a/tests/tcg/x86_64/Makefile.softmmu-target b/tests/tcg/x86_64/Ma= kefile.softmmu-target index 8d3a067c33..8cb2a19461 100644 --- a/tests/tcg/x86_64/Makefile.softmmu-target +++ b/tests/tcg/x86_64/Makefile.softmmu-target @@ -46,14 +46,18 @@ EXTRA_RUNS+=3D$(MULTIARCH_RUNS) =20 memory: CFLAGS+=3D-DCHECK_UNALIGNED=3D1 patch-target: CFLAGS+=3D-O0 +hypercalls-target: CFLAGS+=3D-O0 =20 # Running QEMU_OPTS+=3D-device isa-debugcon,chardev=3Doutput -device isa-debug-exit,= iobase=3D0xf4,iosize=3D0x4 -kernel =20 # Add patch-target to ADDITIONAL_PLUGINS_TESTS ADDITIONAL_PLUGINS_TESTS +=3D patch-target +ADDITIONAL_PLUGINS_TESTS +=3D hypercalls-target =20 run-plugin-patch-target-with-libpatch.so: \ PLUGIN_ARGS=3D$(COMMA)target=3Dffc0$(COMMA)patch=3D9090$(COMMA)use_hwaddr= =3Dtrue$(COMMA)debug_insns=3Dfalse run-plugin-patch-target-with-libpatch.so: \ - CHECK_PLUGIN_OUTPUT_COMMAND=3D$(X86_64_SYSTEM_SRC)/validate-patch.py $@.o= ut \ No newline at end of file + CHECK_PLUGIN_OUTPUT_COMMAND=3D$(X86_64_SYSTEM_SRC)/validate-patch.py $@.o= ut +run-plugin-hypercalls-target-with-libhypercalls.so: \ + CHECK_PLUGIN_OUTPUT_COMMAND=3D$(X86_64_SYSTEM_SRC)/validate-hypercalls.py= $@.out diff --git a/tests/tcg/x86_64/system/hypercalls-target.c b/tests/tcg/x86_64= /system/hypercalls-target.c new file mode 100644 index 0000000000..643d489e9c --- /dev/null +++ b/tests/tcg/x86_64/system/hypercalls-target.c @@ -0,0 +1,45 @@ +/* + * SPDX-License-Identifier: GPL-2.0-or-later + * + * Copyright (C) 2025, Rowan Hart + * + * License: GNU GPL, version 2 or later. + * See the COPYING file in the top-level directory. + * + * This test target invokes a hypercall to write the value 0x1337 to a + * variable. + * + */ +#include +#include +#include + +#define _hypercall(num, arg0, arg1, arg2, arg3) \ + unsigned int a __attribute__((unused)) =3D 0; \ + unsigned int b __attribute__((unused)) =3D 0; \ + unsigned int c __attribute__((unused)) =3D 0; \ + unsigned int d __attribute__((unused)) =3D 0; \ + __asm__ __volatile__("cpuid\n\t" \ + : "=3Da"(a), "=3Db"(b), "=3Dc"(c), "=3Dd"(d) = \ + : "a"(num), "D"(arg0), "S"(arg1), \ + "d"(arg2), "c"(arg3)); + +#define hypercall(num, arg0, arg1, arg2, arg3) \ + { \ + unsigned int __num =3D 0x4711 | (num << 16); \ + _hypercall(__num, arg0, arg1, arg2, arg3); \ + } + +int main(void) +{ + uint16_t value =3D 0; + + for (size_t i =3D 0; i < 1000000; i++) { + hypercall(1, &value, sizeof(value), 0, 0); + if (value =3D=3D 0x1337) { + ml_printf("Victory!\n"); + return 0; + } + } + return 0; +} diff --git a/tests/tcg/x86_64/system/validate-hypercalls.py b/tests/tcg/x86= _64/system/validate-hypercalls.py new file mode 100755 index 0000000000..6e7c980706 --- /dev/null +++ b/tests/tcg/x86_64/system/validate-hypercalls.py @@ -0,0 +1,40 @@ +#!/usr/bin/env python3 +# +# validate-patch.py: check the patch applies +# +# This program takes two inputs: +# - the plugin output +# - the binary output +# +# Copyright (C) 2024 +# +# SPDX-License-Identifier: GPL-2.0-or-later + +import sys +from argparse import ArgumentParser + +def main() -> None: + """ + Process the arguments, injest the program and plugin out and + verify they match up and report if they do not. + """ + parser =3D ArgumentParser(description=3D"Validate patch") + parser.add_argument('test_output', + help=3D"The output from the test itself") + parser.add_argument('plugin_output', + help=3D"The output from plugin") + args =3D parser.parse_args() + + with open(args.test_output, 'r') as f: + test_data =3D f.read() + with open(args.plugin_output, 'r') as f: + plugin_data =3D f.read() + + if "Victory" in test_data: + sys.exit(0) + else: + sys.exit(1) + +if __name__ =3D=3D "__main__": + main() + --=20 2.49.0