xref: /kvm-unit-tests/x86/debug.c (revision a094abdde3a4dec487bb0f837818542bc099f5bd)
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 "desc.h"
14 
15 static volatile unsigned long bp_addr;
16 static volatile unsigned long db_addr[10], dr6[10];
17 static volatile unsigned int n;
18 static volatile unsigned long value;
19 
20 static unsigned long get_dr6(void)
21 {
22 	unsigned long value;
23 
24 	asm volatile("mov %%dr6,%0" : "=r" (value));
25 	return value;
26 }
27 
28 static void set_dr0(void *value)
29 {
30 	asm volatile("mov %0,%%dr0" : : "r" (value));
31 }
32 
33 static void set_dr1(void *value)
34 {
35 	asm volatile("mov %0,%%dr1" : : "r" (value));
36 }
37 
38 static void set_dr6(unsigned long value)
39 {
40 	asm volatile("mov %0,%%dr6" : : "r" (value));
41 }
42 
43 static void set_dr7(unsigned long value)
44 {
45 	asm volatile("mov %0,%%dr7" : : "r" (value));
46 }
47 
48 static void handle_db(struct ex_regs *regs)
49 {
50 	db_addr[n] = regs->rip;
51 	dr6[n] = get_dr6();
52 
53 	if (dr6[n] & 0x1)
54 		regs->rflags |= (1 << 16);
55 
56 	if (++n >= 10) {
57 		regs->rflags &= ~(1 << 8);
58 		set_dr7(0x00000400);
59 	}
60 }
61 
62 extern unsigned char handle_db_save_rip;
63 asm("handle_db_save_rip:\n"
64    "stc\n"
65    "nop;nop;nop\n"
66    "rclq $1, n(%rip)\n"
67    "iretq\n");
68 
69 static void handle_bp(struct ex_regs *regs)
70 {
71 	bp_addr = regs->rip;
72 }
73 
74 int main(int ac, char **av)
75 {
76 	unsigned long start;
77 
78 	setup_idt();
79 	handle_exception(DB_VECTOR, handle_db);
80 	handle_exception(BP_VECTOR, handle_bp);
81 
82 	extern unsigned char sw_bp;
83 	asm volatile("int3; sw_bp:");
84 	report("#BP", bp_addr == (unsigned long)&sw_bp);
85 
86 	n = 0;
87 	extern unsigned char hw_bp1;
88 	set_dr0(&hw_bp1);
89 	set_dr7(0x00000402);
90 	asm volatile("hw_bp1: nop");
91 	report("hw breakpoint (test that dr6.BS is not set)",
92 	       n == 1 &&
93 	       db_addr[0] == ((unsigned long)&hw_bp1) && dr6[0] == 0xffff0ff1);
94 
95 	n = 0;
96 	extern unsigned char hw_bp2;
97 	set_dr0(&hw_bp2);
98 	set_dr6(0x00004002);
99 	asm volatile("hw_bp2: nop");
100 	report("hw breakpoint (test that dr6.BS is not cleared)",
101 	       n == 1 &&
102 	       db_addr[0] == ((unsigned long)&hw_bp2) && dr6[0] == 0xffff4ff1);
103 
104 	n = 0;
105 	set_dr6(0);
106 	asm volatile(
107 		"pushf\n\t"
108 		"pop %%rax\n\t"
109 		"or $(1<<8),%%rax\n\t"
110 		"push %%rax\n\t"
111 		"lea (%%rip),%0\n\t"
112 		"popf\n\t"
113 		"and $~(1<<8),%%rax\n\t"
114 		"push %%rax\n\t"
115 		"popf\n\t"
116 		: "=g" (start) : : "rax");
117 	report("single step",
118 	       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 
123 	/*
124 	 * cpuid and rdmsr (among others) trigger VM exits and are then
125 	 * emulated. Test that single stepping works on emulated instructions.
126 	 */
127 	n = 0;
128 	set_dr6(0);
129 	asm volatile(
130 		"pushf\n\t"
131 		"pop %%rax\n\t"
132 		"or $(1<<8),%%rax\n\t"
133 		"push %%rax\n\t"
134 		"lea (%%rip),%0\n\t"
135 		"popf\n\t"
136 		"and $~(1<<8),%%rax\n\t"
137 		"push %%rax\n\t"
138 		"xor %%rax,%%rax\n\t"
139 		"cpuid\n\t"
140 		"movl $0x1a0,%%ecx\n\t"
141 		"rdmsr\n\t"
142 		"popf\n\t"
143 		: "=g" (start) : : "rax", "ebx", "ecx", "edx");
144 	report("single step emulated instructions",
145 	       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 
154 	n = 0;
155 	set_dr1((void *)&value);
156 	set_dr7(0x00d0040a); // 4-byte write
157 
158 	extern unsigned char hw_wp1;
159 	asm volatile(
160 		"mov $42,%%rax\n\t"
161 		"mov %%rax,%0\n\t; hw_wp1:"
162 		: "=m" (value) : : "rax");
163 	report("hw watchpoint (test that dr6.BS is not cleared)",
164 	       n == 1 &&
165 	       db_addr[0] == ((unsigned long)&hw_wp1) && dr6[0] == 0xffff4ff2);
166 
167 	n = 0;
168 	set_dr6(0);
169 
170 	extern unsigned char hw_wp2;
171 	asm volatile(
172 		"mov $42,%%rax\n\t"
173 		"mov %%rax,%0\n\t; hw_wp2:"
174 		: "=m" (value) : : "rax");
175 	report("hw watchpoint (test that dr6.BS is not set)",
176 	       n == 1 &&
177 	       db_addr[0] == ((unsigned long)&hw_wp2) && dr6[0] == 0xffff0ff2);
178 
179 	n = 0;
180 	set_dr6(0);
181 	extern unsigned char sw_icebp;
182 	asm volatile(".byte 0xf1; sw_icebp:");
183 	report("icebp",
184 	       n == 1 &&
185 	       db_addr[0] == (unsigned long)&sw_icebp &&
186 	       dr6[0] == 0xffff0ff0);
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("MOV SS + watchpoint + ICEBP", n == 3);
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("MOV SS + watchpoint + int $1", n == 7);
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("MOV SS + watchpoint + INT3",
232 	       n == 3 && bp_addr == (unsigned long)&sw_bp2);
233 	return report_summary();
234 }
235