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