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