1 // SPDX-License-Identifier: GPL-2.0
2 /*
3  * KVM guest debug register tests
4  *
5  * Copyright (C) 2020, Red Hat, Inc.
6  */
7 #include <stdio.h>
8 #include <string.h>
9 #include "kvm_util.h"
10 #include "processor.h"
11 #include "apic.h"
12 
13 #define VCPU_ID 0
14 
15 #define DR6_BD		(1 << 13)
16 #define DR7_GD		(1 << 13)
17 
18 #define IRQ_VECTOR 0xAA
19 
20 /* For testing data access debug BP */
21 uint32_t guest_value;
22 
23 extern unsigned char sw_bp, hw_bp, write_data, ss_start, bd_start;
24 
25 static void guest_code(void)
26 {
27 	/* Create a pending interrupt on current vCPU */
28 	x2apic_enable();
29 	x2apic_write_reg(APIC_ICR, APIC_DEST_SELF | APIC_INT_ASSERT |
30 			 APIC_DM_FIXED | IRQ_VECTOR);
31 
32 	/*
33 	 * Software BP tests.
34 	 *
35 	 * NOTE: sw_bp need to be before the cmd here, because int3 is an
36 	 * exception rather than a normal trap for KVM_SET_GUEST_DEBUG (we
37 	 * capture it using the vcpu exception bitmap).
38 	 */
39 	asm volatile("sw_bp: int3");
40 
41 	/* Hardware instruction BP test */
42 	asm volatile("hw_bp: nop");
43 
44 	/* Hardware data BP test */
45 	asm volatile("mov $1234,%%rax;\n\t"
46 		     "mov %%rax,%0;\n\t write_data:"
47 		     : "=m" (guest_value) : : "rax");
48 
49 	/*
50 	 * Single step test, covers 2 basic instructions and 2 emulated
51 	 *
52 	 * Enable interrupts during the single stepping to see that
53 	 * pending interrupt we raised is not handled due to KVM_GUESTDBG_BLOCKIRQ
54 	 */
55 	asm volatile("ss_start: "
56 		     "sti\n\t"
57 		     "xor %%eax,%%eax\n\t"
58 		     "cpuid\n\t"
59 		     "movl $0x1a0,%%ecx\n\t"
60 		     "rdmsr\n\t"
61 		     "cli\n\t"
62 		     : : : "eax", "ebx", "ecx", "edx");
63 
64 	/* DR6.BD test */
65 	asm volatile("bd_start: mov %%dr0, %%rax" : : : "rax");
66 	GUEST_DONE();
67 }
68 
69 #define  CLEAR_DEBUG()  memset(&debug, 0, sizeof(debug))
70 #define  APPLY_DEBUG()  vcpu_set_guest_debug(vm, VCPU_ID, &debug)
71 #define  CAST_TO_RIP(v)  ((unsigned long long)&(v))
72 #define  SET_RIP(v)  do {				\
73 		vcpu_regs_get(vm, VCPU_ID, &regs);	\
74 		regs.rip = (v);				\
75 		vcpu_regs_set(vm, VCPU_ID, &regs);	\
76 	} while (0)
77 #define  MOVE_RIP(v)  SET_RIP(regs.rip + (v));
78 
79 int main(void)
80 {
81 	struct kvm_guest_debug debug;
82 	unsigned long long target_dr6, target_rip;
83 	struct kvm_regs regs;
84 	struct kvm_run *run;
85 	struct kvm_vm *vm;
86 	struct ucall uc;
87 	uint64_t cmd;
88 	int i;
89 	/* Instruction lengths starting at ss_start */
90 	int ss_size[6] = {
91 		1,		/* sti*/
92 		2,		/* xor */
93 		2,		/* cpuid */
94 		5,		/* mov */
95 		2,		/* rdmsr */
96 		1,		/* cli */
97 	};
98 
99 	if (!kvm_check_cap(KVM_CAP_SET_GUEST_DEBUG)) {
100 		print_skip("KVM_CAP_SET_GUEST_DEBUG not supported");
101 		return 0;
102 	}
103 
104 	vm = vm_create_default(VCPU_ID, 0, guest_code);
105 	run = vcpu_state(vm, VCPU_ID);
106 
107 	/* Test software BPs - int3 */
108 	CLEAR_DEBUG();
109 	debug.control = KVM_GUESTDBG_ENABLE | KVM_GUESTDBG_USE_SW_BP;
110 	APPLY_DEBUG();
111 	vcpu_run(vm, VCPU_ID);
112 	TEST_ASSERT(run->exit_reason == KVM_EXIT_DEBUG &&
113 		    run->debug.arch.exception == BP_VECTOR &&
114 		    run->debug.arch.pc == CAST_TO_RIP(sw_bp),
115 		    "INT3: exit %d exception %d rip 0x%llx (should be 0x%llx)",
116 		    run->exit_reason, run->debug.arch.exception,
117 		    run->debug.arch.pc, CAST_TO_RIP(sw_bp));
118 	MOVE_RIP(1);
119 
120 	/* Test instruction HW BP over DR[0-3] */
121 	for (i = 0; i < 4; i++) {
122 		CLEAR_DEBUG();
123 		debug.control = KVM_GUESTDBG_ENABLE | KVM_GUESTDBG_USE_HW_BP;
124 		debug.arch.debugreg[i] = CAST_TO_RIP(hw_bp);
125 		debug.arch.debugreg[7] = 0x400 | (1UL << (2*i+1));
126 		APPLY_DEBUG();
127 		vcpu_run(vm, VCPU_ID);
128 		target_dr6 = 0xffff0ff0 | (1UL << i);
129 		TEST_ASSERT(run->exit_reason == KVM_EXIT_DEBUG &&
130 			    run->debug.arch.exception == DB_VECTOR &&
131 			    run->debug.arch.pc == CAST_TO_RIP(hw_bp) &&
132 			    run->debug.arch.dr6 == target_dr6,
133 			    "INS_HW_BP (DR%d): exit %d exception %d rip 0x%llx "
134 			    "(should be 0x%llx) dr6 0x%llx (should be 0x%llx)",
135 			    i, run->exit_reason, run->debug.arch.exception,
136 			    run->debug.arch.pc, CAST_TO_RIP(hw_bp),
137 			    run->debug.arch.dr6, target_dr6);
138 	}
139 	/* Skip "nop" */
140 	MOVE_RIP(1);
141 
142 	/* Test data access HW BP over DR[0-3] */
143 	for (i = 0; i < 4; i++) {
144 		CLEAR_DEBUG();
145 		debug.control = KVM_GUESTDBG_ENABLE | KVM_GUESTDBG_USE_HW_BP;
146 		debug.arch.debugreg[i] = CAST_TO_RIP(guest_value);
147 		debug.arch.debugreg[7] = 0x00000400 | (1UL << (2*i+1)) |
148 		    (0x000d0000UL << (4*i));
149 		APPLY_DEBUG();
150 		vcpu_run(vm, VCPU_ID);
151 		target_dr6 = 0xffff0ff0 | (1UL << i);
152 		TEST_ASSERT(run->exit_reason == KVM_EXIT_DEBUG &&
153 			    run->debug.arch.exception == DB_VECTOR &&
154 			    run->debug.arch.pc == CAST_TO_RIP(write_data) &&
155 			    run->debug.arch.dr6 == target_dr6,
156 			    "DATA_HW_BP (DR%d): exit %d exception %d rip 0x%llx "
157 			    "(should be 0x%llx) dr6 0x%llx (should be 0x%llx)",
158 			    i, run->exit_reason, run->debug.arch.exception,
159 			    run->debug.arch.pc, CAST_TO_RIP(write_data),
160 			    run->debug.arch.dr6, target_dr6);
161 		/* Rollback the 4-bytes "mov" */
162 		MOVE_RIP(-7);
163 	}
164 	/* Skip the 4-bytes "mov" */
165 	MOVE_RIP(7);
166 
167 	/* Test single step */
168 	target_rip = CAST_TO_RIP(ss_start);
169 	target_dr6 = 0xffff4ff0ULL;
170 	vcpu_regs_get(vm, VCPU_ID, &regs);
171 	for (i = 0; i < (sizeof(ss_size) / sizeof(ss_size[0])); i++) {
172 		target_rip += ss_size[i];
173 		CLEAR_DEBUG();
174 		debug.control = KVM_GUESTDBG_ENABLE | KVM_GUESTDBG_SINGLESTEP |
175 				KVM_GUESTDBG_BLOCKIRQ;
176 		debug.arch.debugreg[7] = 0x00000400;
177 		APPLY_DEBUG();
178 		vcpu_run(vm, VCPU_ID);
179 		TEST_ASSERT(run->exit_reason == KVM_EXIT_DEBUG &&
180 			    run->debug.arch.exception == DB_VECTOR &&
181 			    run->debug.arch.pc == target_rip &&
182 			    run->debug.arch.dr6 == target_dr6,
183 			    "SINGLE_STEP[%d]: exit %d exception %d rip 0x%llx "
184 			    "(should be 0x%llx) dr6 0x%llx (should be 0x%llx)",
185 			    i, run->exit_reason, run->debug.arch.exception,
186 			    run->debug.arch.pc, target_rip, run->debug.arch.dr6,
187 			    target_dr6);
188 	}
189 
190 	/* Finally test global disable */
191 	CLEAR_DEBUG();
192 	debug.control = KVM_GUESTDBG_ENABLE | KVM_GUESTDBG_USE_HW_BP;
193 	debug.arch.debugreg[7] = 0x400 | DR7_GD;
194 	APPLY_DEBUG();
195 	vcpu_run(vm, VCPU_ID);
196 	target_dr6 = 0xffff0ff0 | DR6_BD;
197 	TEST_ASSERT(run->exit_reason == KVM_EXIT_DEBUG &&
198 		    run->debug.arch.exception == DB_VECTOR &&
199 		    run->debug.arch.pc == CAST_TO_RIP(bd_start) &&
200 		    run->debug.arch.dr6 == target_dr6,
201 			    "DR7.GD: exit %d exception %d rip 0x%llx "
202 			    "(should be 0x%llx) dr6 0x%llx (should be 0x%llx)",
203 			    run->exit_reason, run->debug.arch.exception,
204 			    run->debug.arch.pc, target_rip, run->debug.arch.dr6,
205 			    target_dr6);
206 
207 	/* Disable all debug controls, run to the end */
208 	CLEAR_DEBUG();
209 	APPLY_DEBUG();
210 
211 	vcpu_run(vm, VCPU_ID);
212 	TEST_ASSERT(run->exit_reason == KVM_EXIT_IO, "KVM_EXIT_IO");
213 	cmd = get_ucall(vm, VCPU_ID, &uc);
214 	TEST_ASSERT(cmd == UCALL_DONE, "UCALL_DONE");
215 
216 	kvm_vm_free(vm);
217 
218 	return 0;
219 }
220