diff mbox series

[kvm-unit-tests,v4,3/3] x86/emulator: Add some tests for far jmp instruction emulation

Message ID 62121e1866da786aac864b76091b8bfba21e0dcd.1645672780.git.houwenlong.hwl@antgroup.com (mailing list archive)
State New, archived
Headers show
Series x86/emulator: Add some tests for loading segment descriptor in emulator | expand

Commit Message

Hou Wenlong Feb. 25, 2022, 9:49 a.m. UTC
Per Intel's SDM on the "Instruction Set Reference", when
loading segment descriptor for far jmp, not-present segment
check should be after all type and privilege checks. However,
__load_segment_descriptor() in x86's emulator does not-present
segment check first, so it would trigger #NP instead of #GP
if type or privilege checks fail and the segment is not present.

So add some tests for far jmp instruction, and it will test
those tests on hardware and emulator. Enable
kvm.force_emulation_prefix when try to test them on emulator.

Signed-off-by: Hou Wenlong <houwenlong.hwl@antgroup.com>
Reviewed-and-tested-by: Sean Christopherson <seanjc@google.com>
---
 x86/emulator.c | 71 +++++++++++++++++++++++++++++++++++++++++---------
 1 file changed, 58 insertions(+), 13 deletions(-)
diff mbox series

Patch

diff --git a/x86/emulator.c b/x86/emulator.c
index 7925ad48c36d..76f3c3ce8eb4 100644
--- a/x86/emulator.c
+++ b/x86/emulator.c
@@ -36,6 +36,7 @@  struct far_xfer_test_case {
 
 enum far_xfer_insn {
 	FAR_XFER_RET,
+	FAR_XFER_JMP,
 };
 
 struct far_xfer_test {
@@ -64,6 +65,25 @@  static struct far_xfer_test far_ret_test = {
 	.nr_testcases = sizeof(far_ret_testcases) / sizeof(struct far_xfer_test_case),
 };
 
+static struct far_xfer_test_case far_jmp_testcases[] = {
+	{0, DS_TYPE,		 0, 0, false, GP_VECTOR, "desc.type!=code && desc.p=0"},
+	{0, NON_CONFORM_CS_TYPE, 3, 0, false, GP_VECTOR, "non-conforming && dpl!=cpl && desc.p=0"},
+	{3, NON_CONFORM_CS_TYPE, 0, 0, false, GP_VECTOR, "conforming && rpl>cpl && desc.p=0"},
+	{0, CONFORM_CS_TYPE,	 3, 0, false, GP_VECTOR, "conforming && dpl>cpl && desc.p=0"},
+	{0, NON_CONFORM_CS_TYPE, 0, 0, false, NP_VECTOR, "desc.p=0"},
+	{3, CONFORM_CS_TYPE,	 0, 1, true,  -1,	 "dpl<cpl"},
+};
+
+static struct far_xfer_test far_jmp_test = {
+	.insn = FAR_XFER_JMP,
+	.insn_name = "far jmp",
+	.testcases = &far_jmp_testcases[0],
+	.nr_testcases = sizeof(far_jmp_testcases) / sizeof(struct far_xfer_test_case),
+};
+
+static unsigned long fep_jmp_buf[2];
+static unsigned long *fep_jmp_buf_ptr = &fep_jmp_buf[0];
+
 #define TEST_FAR_RET_ASM(seg, prefix)		\
 ({						\
 	asm volatile("pushq %[asm_seg]\n\t"	\
@@ -77,6 +97,17 @@  static struct far_xfer_test far_ret_test = {
 		     : "eax", "memory");	\
 })
 
+#define TEST_FAR_JMP_ASM(seg, prefix)			\
+({							\
+	*(uint16_t *)(&fep_jmp_buf[1]) = seg;		\
+	asm volatile("lea 1f(%%rip), %%rax\n\t"		\
+		     "movq %%rax, (%[mem])\n\t"		\
+		      prefix "rex64 ljmp *(%[mem])\n\t" \
+		     "1:"				\
+		     : : [mem]"r"(fep_jmp_buf_ptr)	\
+		     : "eax", "memory");		\
+})
+
 struct regs {
 	u64 rax, rbx, rcx, rdx;
 	u64 rsi, rdi, rsp, rbp;
@@ -359,19 +390,6 @@  static void test_pop(void *mem)
 	       "enter");
 }
 
-static void test_far_jmp(void *mem)
-{
-    unsigned char *m = mem;
-    volatile int res = 1;
-
-    *(unsigned long**)m = &&jmpf;
-    asm volatile ("data16 mov %%cs, %0":"=m"(*(m + sizeof(unsigned long))));
-    asm volatile ("rex64 ljmp *%0"::"m"(*m));
-    res = 0;
-jmpf:
-    report(res, "far jmp, via emulated MMIO");
-}
-
 static void test_incdecnotneg(void *mem)
 {
     unsigned long *m = mem, v = 1234;
@@ -967,6 +985,12 @@  static void __test_far_xfer(enum far_xfer_insn insn, uint16_t seg,
 		else
 			TEST_FAR_RET_ASM(seg, "");
 		break;
+	case FAR_XFER_JMP:
+		if (force_emulation)
+			TEST_FAR_JMP_ASM(seg, KVM_FEP);
+		else
+			TEST_FAR_JMP_ASM(seg, "");
+		break;
 	default:
 		report_fail("Unexpected insn enum = %d\n", insn);
 		break;
@@ -1013,6 +1037,26 @@  static void test_far_xfer(bool force_emulation, struct far_xfer_test *test)
 	handle_exception(NP_VECTOR, 0);
 }
 
+static void test_far_jmp(uint64_t *mem)
+{
+	unsigned char *m = (unsigned char *)mem;
+	volatile int res = 1;
+
+	*(unsigned long**)m = &&jmpf;
+	asm volatile ("data16 mov %%cs, %0":"=m"(*(m + sizeof(unsigned long))));
+	asm volatile ("rex64 ljmp *%0"::"m"(*m));
+	res = 0;
+jmpf:
+	report(res, "far jmp, via emulated MMIO");
+
+	test_far_xfer(false, &far_jmp_test);
+}
+
+static void test_em_far_jmp(uint64_t *mem)
+{
+	test_far_xfer(true, &far_jmp_test);
+}
+
 static void test_far_ret(uint64_t *mem)
 {
 	test_far_xfer(false, &far_ret_test);
@@ -1322,6 +1366,7 @@  int main(void)
 		test_smsw_reg(mem);
 		test_nop(mem);
 		test_mov_dr(mem);
+		test_em_far_jmp(mem);
 		test_em_far_ret(mem);
 	} else {
 		report_skip("skipping register-only tests, "