xref: /kvm-unit-tests/x86/debug.c (revision c865f654ffe4c5955038aaf74f702ba62f3eb014)
1 /*
2  * Test for x86 debugging facilities
3  *
4  * Copyright (c) Siemens AG, 2014
5  *
6  * Authors:
7  *  Jan Kiszka <jan.kiszka@siemens.com>
8  *
9  * This work is licensed under the terms of the GNU GPL, version 2.
10  */
11 
12 #include "libcflat.h"
13 #include "processor.h"
14 #include "desc.h"
15 
16 static volatile unsigned long bp_addr;
17 static volatile unsigned long db_addr[10], dr6[10];
18 static volatile unsigned int n;
19 static volatile unsigned long value;
20 
21 static unsigned long get_dr4(void)
22 {
23 	unsigned long value;
24 
25 	asm volatile("mov %%dr4, %0" : "=r" (value));
26 	return value;
27 }
28 
29 static unsigned long get_dr6(void)
30 {
31 	unsigned long value;
32 
33 	asm volatile("mov %%dr6,%0" : "=r" (value));
34 	return value;
35 }
36 
37 static void set_dr0(void *value)
38 {
39 	asm volatile("mov %0,%%dr0" : : "r" (value));
40 }
41 
42 static void set_dr1(void *value)
43 {
44 	asm volatile("mov %0,%%dr1" : : "r" (value));
45 }
46 
47 static void set_dr4(unsigned long value)
48 {
49 	asm volatile("mov %0,%%dr4" : : "r" (value));
50 }
51 
52 static void set_dr6(unsigned long value)
53 {
54 	asm volatile("mov %0,%%dr6" : : "r" (value));
55 }
56 
57 static void set_dr7(unsigned long value)
58 {
59 	asm volatile("mov %0,%%dr7" : : "r" (value));
60 }
61 
62 static void handle_db(struct ex_regs *regs)
63 {
64 	db_addr[n] = regs->rip;
65 	dr6[n] = get_dr6();
66 
67 	if (dr6[n] & 0x1)
68 		regs->rflags |= (1 << 16);
69 
70 	if (++n >= 10) {
71 		regs->rflags &= ~(1 << 8);
72 		set_dr7(0x00000400);
73 	}
74 }
75 
76 extern unsigned char handle_db_save_rip;
77 asm("handle_db_save_rip:\n"
78    "stc\n"
79    "nop;nop;nop\n"
80    "rclq $1, n(%rip)\n"
81    "iretq\n");
82 
83 static void handle_bp(struct ex_regs *regs)
84 {
85 	bp_addr = regs->rip;
86 }
87 
88 bool got_ud;
89 static void handle_ud(struct ex_regs *regs)
90 {
91 	unsigned long cr4 = read_cr4();
92 	write_cr4(cr4 & ~X86_CR4_DE);
93 	got_ud = 1;
94 }
95 
96 int main(int ac, char **av)
97 {
98 	unsigned long start;
99 	unsigned long cr4;
100 
101 	handle_exception(DB_VECTOR, handle_db);
102 	handle_exception(BP_VECTOR, handle_bp);
103 	handle_exception(UD_VECTOR, handle_ud);
104 
105 	got_ud = 0;
106 	cr4 = read_cr4();
107 	write_cr4(cr4 & ~X86_CR4_DE);
108 	set_dr4(0);
109 	set_dr6(0xffff4ff2);
110 	report(get_dr4() == 0xffff4ff2 && !got_ud, "reading DR4 with CR4.DE == 0");
111 
112 	cr4 = read_cr4();
113 	write_cr4(cr4 | X86_CR4_DE);
114 	get_dr4();
115 	report(got_ud, "reading DR4 with CR4.DE == 1");
116 	set_dr6(0);
117 
118 	extern unsigned char sw_bp;
119 	asm volatile("int3; sw_bp:");
120 	report(bp_addr == (unsigned long)&sw_bp, "#BP");
121 
122 	n = 0;
123 	extern unsigned char hw_bp1;
124 	set_dr0(&hw_bp1);
125 	set_dr7(0x00000402);
126 	asm volatile("hw_bp1: nop");
127 	report(n == 1 &&
128 	       db_addr[0] == ((unsigned long)&hw_bp1) && dr6[0] == 0xffff0ff1,
129 	       "hw breakpoint (test that dr6.BS is not set)");
130 
131 	n = 0;
132 	extern unsigned char hw_bp2;
133 	set_dr0(&hw_bp2);
134 	set_dr6(0x00004002);
135 	asm volatile("hw_bp2: nop");
136 	report(n == 1 &&
137 	       db_addr[0] == ((unsigned long)&hw_bp2) && dr6[0] == 0xffff4ff1,
138 	       "hw breakpoint (test that dr6.BS is not cleared)");
139 
140 	n = 0;
141 	set_dr6(0);
142 	asm volatile(
143 		"pushf\n\t"
144 		"pop %%rax\n\t"
145 		"or $(1<<8),%%rax\n\t"
146 		"push %%rax\n\t"
147 		"lea (%%rip),%0\n\t"
148 		"popf\n\t"
149 		"and $~(1<<8),%%rax\n\t"
150 		"push %%rax\n\t"
151 		"popf\n\t"
152 		: "=r" (start) : : "rax");
153 	report(n == 3 &&
154 	       db_addr[0] == start + 1 + 6 && dr6[0] == 0xffff4ff0 &&
155 	       db_addr[1] == start + 1 + 6 + 1 && dr6[1] == 0xffff4ff0 &&
156 	       db_addr[2] == start + 1 + 6 + 1 + 1 && dr6[2] == 0xffff4ff0,
157 	       "single step");
158 
159 	/*
160 	 * cpuid and rdmsr (among others) trigger VM exits and are then
161 	 * emulated. Test that single stepping works on emulated instructions.
162 	 */
163 	n = 0;
164 	set_dr6(0);
165 	asm volatile(
166 		"pushf\n\t"
167 		"pop %%rax\n\t"
168 		"or $(1<<8),%%rax\n\t"
169 		"push %%rax\n\t"
170 		"lea (%%rip),%0\n\t"
171 		"popf\n\t"
172 		"and $~(1<<8),%%rax\n\t"
173 		"push %%rax\n\t"
174 		"xor %%rax,%%rax\n\t"
175 		"cpuid\n\t"
176 		"movl $0x1a0,%%ecx\n\t"
177 		"rdmsr\n\t"
178 		"popf\n\t"
179 		: "=r" (start) : : "rax", "ebx", "ecx", "edx");
180 	report(n == 7 &&
181 	       db_addr[0] == start + 1 + 6 && dr6[0] == 0xffff4ff0 &&
182 	       db_addr[1] == start + 1 + 6 + 1 && dr6[1] == 0xffff4ff0 &&
183 	       db_addr[2] == start + 1 + 6 + 1 + 3 && dr6[2] == 0xffff4ff0 &&
184 	       db_addr[3] == start + 1 + 6 + 1 + 3 + 2 && dr6[3] == 0xffff4ff0 &&
185 	       db_addr[4] == start + 1 + 6 + 1 + 3 + 2 + 5 && dr6[4] == 0xffff4ff0 &&
186 	       db_addr[5] == start + 1 + 6 + 1 + 3 + 2 + 5 + 2 && dr6[5] == 0xffff4ff0 &&
187 	       db_addr[6] == start + 1 + 6 + 1 + 3 + 2 + 5 + 2 + 1 && dr6[6] == 0xffff4ff0,
188 	       "single step emulated instructions");
189 
190 	n = 0;
191 	set_dr1((void *)&value);
192 	set_dr7(0x00d0040a); // 4-byte write
193 
194 	extern unsigned char hw_wp1;
195 	asm volatile(
196 		"mov $42,%%rax\n\t"
197 		"mov %%rax,%0\n\t; hw_wp1:"
198 		: "=m" (value) : : "rax");
199 	report(n == 1 &&
200 	       db_addr[0] == ((unsigned long)&hw_wp1) && dr6[0] == 0xffff4ff2,
201 	       "hw watchpoint (test that dr6.BS is not cleared)");
202 
203 	n = 0;
204 	set_dr6(0);
205 
206 	extern unsigned char hw_wp2;
207 	asm volatile(
208 		"mov $42,%%rax\n\t"
209 		"mov %%rax,%0\n\t; hw_wp2:"
210 		: "=m" (value) : : "rax");
211 	report(n == 1 &&
212 	       db_addr[0] == ((unsigned long)&hw_wp2) && dr6[0] == 0xffff0ff2,
213 	       "hw watchpoint (test that dr6.BS is not set)");
214 
215 	n = 0;
216 	set_dr6(0);
217 	extern unsigned char sw_icebp;
218 	asm volatile(".byte 0xf1; sw_icebp:");
219 	report(n == 1 &&
220 	       db_addr[0] == (unsigned long)&sw_icebp && dr6[0] == 0xffff0ff0,
221 	       "icebp");
222 
223 	set_dr7(0x400);
224 	value = KERNEL_DS;
225 	set_dr7(0x00f0040a); // 4-byte read or write
226 
227 	/*
228 	 * Each invocation of the handler should shift n by 1 and set bit 0 to 1.
229 	 * We expect a single invocation, so n should become 3.  If the entry
230 	 * RIP is wrong, or if the handler is executed more than once, the value
231 	 * will not match.
232 	 */
233 	set_idt_entry(1, &handle_db_save_rip, 0);
234 
235 	n = 1;
236 	asm volatile(
237 		"clc\n\t"
238 		"mov %0,%%ss\n\t"
239 		".byte 0x2e, 0x2e, 0xf1"
240 		: "=m" (value) : : "rax");
241 	report(n == 3, "MOV SS + watchpoint + ICEBP");
242 
243 	/*
244 	 * Here the #DB handler is invoked twice, once as a software exception
245 	 * and once as a software interrupt.
246 	 */
247 	n = 1;
248 	asm volatile(
249 		"clc\n\t"
250 		"mov %0,%%ss\n\t"
251 		"int $1"
252 		: "=m" (value) : : "rax");
253 	report(n == 7, "MOV SS + watchpoint + int $1");
254 
255 	/*
256 	 * Here the #DB and #BP handlers are invoked once each.
257 	 */
258 	n = 1;
259 	bp_addr = 0;
260 	asm volatile(
261 		"mov %0,%%ss\n\t"
262 		".byte 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0xcc\n\t"
263 		"sw_bp2:"
264 		: "=m" (value) : : "rax");
265 	extern unsigned char sw_bp2;
266 	report(n == 3 && bp_addr == (unsigned long)&sw_bp2,
267 	       "MOV SS + watchpoint + INT3");
268 	return report_summary();
269 }
270