[PATCH v4 4/4] tests: check KVM ioctl commands decoding

Dmitry V. Levin ldv at altlinux.org
Thu Dec 7 14:04:05 UTC 2017


On Mon, Dec 04, 2017 at 10:08:17PM +0900, Masatake YAMATO wrote:
> * tests/Makefile.am (check_PROGRAMS): Add ioctl_kvm.
> (DECODER_TESTS): Add ioctl_kvm.test.
> (EXTRA_DIST): Add ioctl_kvm.expected.
> * tests/ioctl_kvm.c: New test target file.
>   Taken from https://lwn.net/Articles/658512/.
> * tests/ioctl_kvm.expected: New expected file.
> * tests/ioctl_kvm.test: New test driver.
> 
> Changes in v2:
> * Skip the test case if kvm.h is not available.
> * Skip the test case if opening /dev/kvm is failed.
> * Include sys/typtes.h first.
> 
>   All items are suggested by ldv.
> 
> Highlights in v3:
> * Build the new test case when __x86_64__ is defined.
> * Don't use macros/functions in err.h.
>   Use macros/functions in tests.h instead.
> * Remove redundant inclusion of header files.
> 
>   All above items are suggested by ldv.
> 
> * Add .test and .expected file to Makefile.am.
> 
> No Change in v4.

Yet the test doesn't quite fit the test suite, so I took the liberty
to tweak it here and there, please have a look.

I also renamed it to make room for more formal KVM_* ioctl tests like
those we already have (tests/ioctl_*.c) for other ioctl decoders.

* tests/ioctl_kvm_run.c: New file.
* tests/ioctl_kvm_run.test: New test.
* tests/Makefile.am (DECODER_TESTS): Add ioctl_kvm_run.test.
* tests/pure_executables.list: Add ioctl_kvm_run.
* tests/.gitignore: Likewise.

Co-authored-by: Dmitry V. Levin <ldv at altlinux.org>
---
 tests/.gitignore            |   1 +
 tests/Makefile.am           |   1 +
 tests/ioctl_kvm_run.c       | 220 ++++++++++++++++++++++++++++++++++++++++++++
 tests/ioctl_kvm_run.test    |  11 +++
 tests/pure_executables.list |   1 +
 5 files changed, 234 insertions(+)
 create mode 100644 tests/ioctl_kvm_run.c
 create mode 100755 tests/ioctl_kvm_run.test

diff --git a/tests/.gitignore b/tests/.gitignore
index 93c980f..7838a5b 100644
--- a/tests/.gitignore
+++ b/tests/.gitignore
@@ -128,6 +128,7 @@ ioctl_dm
 ioctl_dm-v
 ioctl_evdev
 ioctl_evdev-v
+ioctl_kvm_run
 ioctl_loop
 ioctl_loop-nv
 ioctl_loop-v
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 7bc2872..22a39d7 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -206,6 +206,7 @@ DECODER_TESTS = \
 	ioctl.test \
 	ioctl_dm-v.test \
 	ioctl_dm.test \
+	ioctl_kvm_run.test \
 	ioctl_loop-nv.test \
 	ioctl_nsfs.test \
 	ioctl_sock_gifconf.test \
diff --git a/tests/ioctl_kvm_run.c b/tests/ioctl_kvm_run.c
new file mode 100644
index 0000000..90fffba
--- /dev/null
+++ b/tests/ioctl_kvm_run.c
@@ -0,0 +1,220 @@
+/*
+ * Check decoding of KVM_* commands of ioctl syscall using /dev/kvm API.
+ * Based on kvmtest.c from https://lwn.net/Articles/658512/
+ *
+ * kvmtest.c author: Josh Triplett <josh at joshtriplett.org>
+ * Copyright (c) 2015 Intel Corporation
+ * Copyright (c) 2017 The strace developers.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+#include "tests.h"
+
+#if defined HAVE_LINUX_KVM_H				\
+ && defined HAVE_STRUCT_KVM_REGS			\
+ && defined HAVE_STRUCT_KVM_SREGS			\
+ && defined HAVE_STRUCT_KVM_USERSPACE_MEMORY_REGION	\
+ &&(defined __x86_64__ || defined __i386__)
+
+# include <fcntl.h>
+# include <stdint.h>
+# include <stdio.h>
+# include <stdlib.h>
+# include <string.h>
+# include <sys/ioctl.h>
+# include <sys/mman.h>
+# include <linux/kvm.h>
+
+static int
+kvm_ioctl(int fd, unsigned long cmd, const char *cmd_str, void *arg)
+{
+	int rc = ioctl(fd, cmd, arg);
+	if (rc < 0)
+		perror_msg_and_skip("%s", cmd_str);
+	return rc;
+}
+
+#define KVM_IOCTL(fd_, cmd_, arg_)	\
+	kvm_ioctl((fd_), (cmd_), #cmd_, (arg_))
+
+static const char dev[] = "/dev/kvm";
+static const char vm_dev[] = "anon_inode:kvm-vm";
+static const char vcpu_dev[] = "anon_inode:kvm-vcpu";
+static size_t page_size;
+
+static void
+code(void)
+{
+	__asm__("mov $0xd80003f8, %edx; mov $'\n', %al; out %al, (%dx); hlt");
+}
+
+static void
+run_kvm(const int vcpu_fd, struct kvm_run *const run, const size_t mmap_size,
+	void *const mem)
+{
+	/* Initialize CS to point at 0, via a read-modify-write of sregs. */
+	struct kvm_sregs sregs;
+	KVM_IOCTL(vcpu_fd, KVM_GET_SREGS, &sregs);
+	printf("ioctl(%d<%s>, KVM_GET_SREGS, {cs={base=%#jx, limit=%u, selector=%u"
+	       ", type=%u, present=%u, dpl=%u, db=%u, s=%u, l=%u, g=%u, avl=%u}"
+	       ", ...}) = 0\n", vcpu_fd, vcpu_dev, (uintmax_t) sregs.cs.base,
+	       sregs.cs.limit, sregs.cs.selector, sregs.cs.type,
+	       sregs.cs.present, sregs.cs.dpl, sregs.cs.db, sregs.cs.s,
+	       sregs.cs.l, sregs.cs.g, sregs.cs.avl);
+
+	sregs.cs.base = 0;
+	sregs.cs.selector = 0;
+	KVM_IOCTL(vcpu_fd, KVM_SET_SREGS, &sregs);
+	printf("ioctl(%d<%s>, KVM_SET_SREGS, {cs={base=%#jx, limit=%u"
+	       ", selector=%u, type=%u, present=%u, dpl=%u, db=%u, s=%u"
+	       ", l=%u, g=%u, avl=%u}, ...}) = 0\n",
+	       vcpu_fd, vcpu_dev, (uintmax_t) sregs.cs.base,
+	       sregs.cs.limit, sregs.cs.selector, sregs.cs.type,
+	       sregs.cs.present, sregs.cs.dpl, sregs.cs.db, sregs.cs.s,
+	       sregs.cs.l, sregs.cs.g, sregs.cs.avl);
+
+	/*
+	 * Initialize registers: instruction pointer for our code, addends,
+	 * and initial flags required by x86 architecture.
+	 */
+	struct kvm_regs regs = {
+		.rip = page_size,
+		.rax = 2,
+		.rbx = 2,
+		.rflags = 0x2,
+	};
+	KVM_IOCTL(vcpu_fd, KVM_SET_REGS, &regs);
+	printf("ioctl(%d<%s>, KVM_SET_REGS, {rax=%#jx, ..."
+	       ", rsp=%#jx, rbp=%#jx, ..., rip=%#jx, rflags=%#jx}) = 0\n",
+	       vcpu_fd, vcpu_dev, (uintmax_t) regs.rax,
+	       (uintmax_t) regs.rsp, (uintmax_t) regs.rbp,
+	       (uintmax_t) regs.rip, (uintmax_t) regs.rflags);
+
+	/* Copy the code till the end of page */
+	size_t code_size = page_size - ((uintptr_t) code & (page_size - 1));
+	if (code_size < 16)
+		code_size = 16;
+	memcpy(mem, code, code_size);
+
+	const char *p = "\n";
+
+	/* Repeatedly run code and handle VM exits. */
+	for (;;) {
+		KVM_IOCTL(vcpu_fd, KVM_RUN, NULL);
+		printf("ioctl(%d<%s>, KVM_RUN, 0) = 0\n", vcpu_fd, vcpu_dev);
+
+		switch (run->exit_reason) {
+		case KVM_EXIT_HLT:
+			if (p)
+				error_msg_and_fail("premature KVM_EXIT_HLT");
+			return;
+		case KVM_EXIT_IO:
+			if (run->io.direction == KVM_EXIT_IO_OUT
+			    && run->io.size == 1
+			    && run->io.port == 0x03f8
+			    && run->io.count == 1
+			    && run->io.data_offset < mmap_size
+			    && p && *p == ((char *) run)[run->io.data_offset])
+				p = NULL;
+			else
+				error_msg_and_fail("unhandled KVM_EXIT_IO");
+			break;
+		default:
+			error_msg_and_fail("exit_reason = %#x",
+					   run->exit_reason);
+		}
+	}
+}
+
+int
+main(void)
+{
+	skip_if_unavailable("/proc/self/fd/");
+
+	int kvm = open(dev, O_RDWR);
+	if (kvm < 0)
+		perror_msg_and_skip("open: %s", dev);
+
+	/* Make sure we have the stable version of the API */
+	int ret = KVM_IOCTL(kvm, KVM_GET_API_VERSION, 0);
+	if (ret != KVM_API_VERSION)
+		error_msg_and_skip("KVM_GET_API_VERSION returned %d"
+				   ", KVM_API_VERSION is %d",
+				   kvm, KVM_API_VERSION);
+	printf("ioctl(%d<%s>, KVM_GET_API_VERSION, 0) = %d\n",
+	       kvm, dev, ret);
+
+	int vm_fd = KVM_IOCTL(kvm, KVM_CREATE_VM, 0);
+	printf("ioctl(%d<%s>, KVM_CREATE_VM, 0) = %d<%s>\n",
+	       kvm, dev, vm_fd, vm_dev);
+
+	/* Allocate one aligned page of guest memory to hold the code. */
+	page_size = get_page_size();
+	void *const mem = mmap(NULL, page_size, PROT_READ | PROT_WRITE,
+				  MAP_SHARED | MAP_ANONYMOUS, -1, 0);
+	if (mem == MAP_FAILED)
+		perror_msg_and_fail("mmap page");
+
+	/* Map it to the second page frame (to avoid the real-mode IDT at 0). */
+	struct kvm_userspace_memory_region region = {
+		.slot = 0,
+		.guest_phys_addr = page_size,
+		.memory_size = page_size,
+		.userspace_addr = (uintptr_t) mem,
+	};
+	KVM_IOCTL(vm_fd, KVM_SET_USER_MEMORY_REGION, &region);
+	printf("ioctl(%d<%s>, KVM_SET_USER_MEMORY_REGION"
+	       ", {slot=0, flags=0, guest_phys_addr=%#lx, memory_size=%lu"
+	       ", userspace_addr=%p) = 0\n", vm_fd, vm_dev,
+	       (unsigned long) page_size, (unsigned long) page_size, mem);
+
+	int vcpu_fd = KVM_IOCTL(vm_fd, KVM_CREATE_VCPU, NULL);
+	printf("ioctl(%d<%s>, KVM_CREATE_VCPU, 0) = %d<%s>\n",
+	       vm_fd, vm_dev, vcpu_fd, vcpu_dev);
+
+	/* Map the shared kvm_run structure and following data. */
+	ret = KVM_IOCTL(kvm, KVM_GET_VCPU_MMAP_SIZE, NULL);
+	struct kvm_run *run;
+	if (ret < (int) sizeof(*run))
+		error_msg_and_fail("KVM_GET_VCPU_MMAP_SIZE returned %d < %d",
+				   ret, (int) sizeof(*run));
+	printf("ioctl(%d<%s>, KVM_GET_VCPU_MMAP_SIZE, 0) = %d\n",
+	       kvm, dev, ret);
+
+	const size_t mmap_size = (ret + page_size - 1) & -page_size;
+	run = mmap(NULL, mmap_size, PROT_READ | PROT_WRITE,
+		   MAP_SHARED, vcpu_fd, 0);
+	if (run == MAP_FAILED)
+		perror_msg_and_fail("mmap vcpu");
+
+	run_kvm(vcpu_fd, run, mmap_size, mem);
+
+	puts("+++ exited with 0 +++");
+	return 0;
+}
+
+#else /* !HAVE_LINUX_KVM_H */
+
+SKIP_MAIN_UNDEFINED("HAVE_LINUX_KVM_H && HAVE_STRUCT_KVM_REGS && "
+		    "HAVE_STRUCT_KVM_SREGS && "
+		    "HAVE_STRUCT_KVM_USERSPACE_MEMORY_REGION && "
+		    "(__x86_64__ || __i386__)")
+
+#endif
diff --git a/tests/ioctl_kvm_run.test b/tests/ioctl_kvm_run.test
new file mode 100755
index 0000000..a23cbeb
--- /dev/null
+++ b/tests/ioctl_kvm_run.test
@@ -0,0 +1,11 @@
+#!/bin/sh
+
+# Check decoding of KVM_* ioctl commands.
+
+. "${srcdir=.}/init.sh"
+
+check_prog grep
+run_prog > /dev/null
+run_strace -a36 -y -eioctl $args > "$EXP"
+grep -v '^ioctl([012],' < "$LOG" > "$OUT"
+match_diff "$OUT" "$EXP"
diff --git a/tests/pure_executables.list b/tests/pure_executables.list
index 7ab52ef..0d1964d 100755
--- a/tests/pure_executables.list
+++ b/tests/pure_executables.list
@@ -103,6 +103,7 @@ ioctl
 ioctl_block
 ioctl_dm
 ioctl_evdev
+ioctl_kvm_run
 ioctl_loop
 ioctl_mtd
 ioctl_rtc

-- 
ldv
-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 801 bytes
Desc: not available
URL: <http://lists.strace.io/pipermail/strace-devel/attachments/20171207/6aa77b93/attachment.bin>


More information about the Strace-devel mailing list