xref: /kvm-unit-tests/x86/debug.c (revision dca3f4c041143c8e8dc70c6890a19a5730310230)
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 #include <asm/debugreg.h>
12 
13 #include "atomic.h"
14 #include "libcflat.h"
15 #include "processor.h"
16 #include "desc.h"
17 #include "usermode.h"
18 
19 static volatile unsigned long bp_addr;
20 static volatile unsigned long db_addr[10], dr6[10];
21 static volatile unsigned int n;
22 static volatile unsigned long value;
23 
write_dr4(ulong val)24 static inline void write_dr4(ulong val)
25 {
26     asm volatile ("mov %0, %%dr4" : : "r"(val) : "memory");
27 }
28 
read_dr4(void)29 static inline ulong read_dr4(void)
30 {
31     ulong val;
32     asm volatile ("mov %%dr4, %0" : "=r"(val));
33     return val;
34 }
35 
handle_db(struct ex_regs * regs)36 static void handle_db(struct ex_regs *regs)
37 {
38 	db_addr[n] = regs->rip;
39 	dr6[n] = read_dr6();
40 
41 	if (dr6[n] & 0x1)
42 		regs->rflags |= X86_EFLAGS_RF;
43 
44 	if (++n >= 10) {
45 		regs->rflags &= ~X86_EFLAGS_TF;
46 		write_dr7(0x00000400);
47 	}
48 }
49 
is_single_step_db(unsigned long dr6_val)50 static inline bool is_single_step_db(unsigned long dr6_val)
51 {
52 	return dr6_val == (DR6_ACTIVE_LOW | DR6_BS);
53 }
54 
is_general_detect_db(unsigned long dr6_val)55 static inline bool is_general_detect_db(unsigned long dr6_val)
56 {
57 	return dr6_val == (DR6_ACTIVE_LOW | DR6_BD);
58 }
59 
is_icebp_db(unsigned long dr6_val)60 static inline bool is_icebp_db(unsigned long dr6_val)
61 {
62 	return dr6_val == DR6_ACTIVE_LOW;
63 }
64 
65 extern unsigned char handle_db_save_rip;
66 asm("handle_db_save_rip:\n"
67    "stc\n"
68    "nop;nop;nop\n"
69    "rclq $1, n(%rip)\n"
70    "iretq\n");
71 
handle_bp(struct ex_regs * regs)72 static void handle_bp(struct ex_regs *regs)
73 {
74 	bp_addr = regs->rip;
75 }
76 
77 bool got_ud;
handle_ud(struct ex_regs * regs)78 static void handle_ud(struct ex_regs *regs)
79 {
80 	unsigned long cr4 = read_cr4();
81 	write_cr4(cr4 & ~X86_CR4_DE);
82 	got_ud = 1;
83 }
84 
85 static bool got_ac;
handle_ac(struct ex_regs * regs)86 static void handle_ac(struct ex_regs *regs)
87 {
88 	got_ac = true;
89 }
90 
91 typedef unsigned long (*db_test_fn)(void);
92 typedef void (*db_report_fn)(unsigned long, const char *);
93 
94 static unsigned long singlestep_with_movss_blocking_and_dr7_gd(void);
95 static unsigned long singlestep_with_sti_hlt(void);
96 
__run_single_step_db_test(db_test_fn test,db_report_fn report_fn)97 static void __run_single_step_db_test(db_test_fn test, db_report_fn report_fn)
98 {
99 	unsigned long start;
100 	bool ign;
101 
102 	n = 0;
103 	write_dr6(0);
104 
105 	start = test();
106 	report_fn(start, "");
107 
108 	/*
109 	 * MOV DR #GPs at CPL>0, don't try to run the DR7.GD test in usermode.
110 	 * Likewise for HLT.
111 	 */
112 	if (test == singlestep_with_movss_blocking_and_dr7_gd
113 	    || test == singlestep_with_sti_hlt)
114 		return;
115 
116 	n = 0;
117 	write_dr6(0);
118 
119 	/*
120 	 * Run the test in usermode.  Use the expected start RIP from the first
121 	 * run, the usermode framework doesn't make it easy to get the expected
122 	 * RIP out of the test, and it shouldn't change in any case.  Run the
123 	 * test with IOPL=3 so that it can use OUT, CLI, STI, etc...
124 	 */
125 	set_iopl(3);
126 	run_in_user((usermode_func)test, GP_VECTOR, 0, 0, 0, 0, &ign);
127 	set_iopl(0);
128 
129 	report_fn(start, "Usermode ");
130 }
131 
132 #define run_ss_db_test(name) __run_single_step_db_test(name, report_##name)
133 
report_singlestep_basic(unsigned long start,const char * usermode)134 static void report_singlestep_basic(unsigned long start, const char *usermode)
135 {
136 	report(n == 3 &&
137 	       is_single_step_db(dr6[0]) && db_addr[0] == start &&
138 	       is_single_step_db(dr6[1]) && db_addr[1] == start + 1 &&
139 	       is_single_step_db(dr6[2]) && db_addr[2] == start + 1 + 1,
140 	       "%sSingle-step #DB basic test", usermode);
141 }
142 
singlestep_basic(void)143 static noinline unsigned long singlestep_basic(void)
144 {
145 	unsigned long start;
146 
147 	/*
148 	 * After being enabled, single-step breakpoints have a one instruction
149 	 * delay before the first #DB is generated.
150 	 */
151 	asm volatile (
152 		"pushf\n\t"
153 		"pop %%rax\n\t"
154 		"or $(1<<8),%%rax\n\t"
155 		"push %%rax\n\t"
156 		"popf\n\t"
157 		"and $~(1<<8),%%rax\n\t"
158 		"1:push %%rax\n\t"
159 		"popf\n\t"
160 		"lea 1b(%%rip), %0\n\t"
161 		: "=r" (start) : : "rax"
162 	);
163 	return start;
164 }
165 
report_singlestep_emulated_instructions(unsigned long start,const char * usermode)166 static void report_singlestep_emulated_instructions(unsigned long start,
167 						    const char *usermode)
168 {
169 	report(n == 7 &&
170 	       is_single_step_db(dr6[0]) && db_addr[0] == start &&
171 	       is_single_step_db(dr6[1]) && db_addr[1] == start + 1 &&
172 	       is_single_step_db(dr6[2]) && db_addr[2] == start + 1 + 3 &&
173 	       is_single_step_db(dr6[3]) && db_addr[3] == start + 1 + 3 + 2 &&
174 	       is_single_step_db(dr6[4]) && db_addr[4] == start + 1 + 3 + 2 + 5 &&
175 	       is_single_step_db(dr6[5]) && db_addr[5] == start + 1 + 3 + 2 + 5 + 1 &&
176 	       is_single_step_db(dr6[6]) && db_addr[6] == start + 1 + 3 + 2 + 5 + 1 + 1,
177 	       "%sSingle-step #DB on emulated instructions", usermode);
178 }
179 
singlestep_emulated_instructions(void)180 static noinline unsigned long singlestep_emulated_instructions(void)
181 {
182 	unsigned long start;
183 
184 	/*
185 	 * Verify single-step #DB are generated correctly on emulated
186 	 * instructions, e.g. CPUID and RDMSR.
187 	 */
188 	asm volatile (
189 		"pushf\n\t"
190 		"pop %%rax\n\t"
191 		"or $(1<<8),%%rax\n\t"
192 		"push %%rax\n\t"
193 		"popf\n\t"
194 		"and $~(1<<8),%%rax\n\t"
195 		"1:push %%rax\n\t"
196 		"xor %%rax,%%rax\n\t"
197 		"cpuid\n\t"
198 		"movl $0x3fd, %%edx\n\t"
199 		"inb %%dx, %%al\n\t"
200 		"popf\n\t"
201 		"lea 1b(%%rip),%0\n\t"
202 		: "=r" (start) : : "rax", "ebx", "ecx", "edx"
203 	);
204 	return start;
205 }
206 
report_singlestep_with_sti_blocking(unsigned long start,const char * usermode)207 static void report_singlestep_with_sti_blocking(unsigned long start,
208 						const char *usermode)
209 {
210 	report(n == 4 &&
211 	       is_single_step_db(dr6[0]) && db_addr[0] == start &&
212 	       is_single_step_db(dr6[1]) && db_addr[1] == start + 6 &&
213 	       is_single_step_db(dr6[2]) && db_addr[2] == start + 6 + 1 &&
214 	       is_single_step_db(dr6[3]) && db_addr[3] == start + 6 + 1 + 1,
215 	       "%sSingle-step #DB w/ STI blocking", usermode);
216 }
217 
218 
singlestep_with_sti_blocking(void)219 static noinline unsigned long singlestep_with_sti_blocking(void)
220 {
221 	unsigned long start_rip;
222 
223 	/*
224 	 * STI blocking doesn't suppress #DBs, thus the first single-step #DB
225 	 * should arrive after the standard one instruction delay.
226 	 */
227 	asm volatile(
228 		"cli\n\t"
229 		"pushf\n\t"
230 		"pop %%rax\n\t"
231 		"or $(1<<8),%%rax\n\t"
232 		"push %%rax\n\t"
233 		"popf\n\t"
234 		"sti\n\t"
235 		"1:and $~(1<<8),%%rax\n\t"
236 		"push %%rax\n\t"
237 		"popf\n\t"
238 		"lea 1b(%%rip),%0\n\t"
239 		: "=r" (start_rip) : : "rax"
240 	);
241 	return start_rip;
242 }
243 
report_singlestep_with_movss_blocking(unsigned long start,const char * usermode)244 static void report_singlestep_with_movss_blocking(unsigned long start,
245 						  const char *usermode)
246 {
247 	report(n == 3 &&
248 	       is_single_step_db(dr6[0]) && db_addr[0] == start &&
249 	       is_single_step_db(dr6[1]) && db_addr[1] == start + 1 &&
250 	       is_single_step_db(dr6[2]) && db_addr[2] == start + 1 + 1,
251 	       "%sSingle-step #DB w/ MOVSS blocking", usermode);
252 }
253 
singlestep_with_movss_blocking(void)254 static noinline unsigned long singlestep_with_movss_blocking(void)
255 {
256 	unsigned long start_rip;
257 
258 	/*
259 	 * MOVSS blocking suppresses single-step #DBs (and select other #DBs),
260 	 * thus the first single-step #DB should occur after MOVSS blocking
261 	 * expires, i.e. two instructions after #DBs are enabled in this case.
262 	 */
263 	asm volatile(
264 		"pushf\n\t"
265 		"pop %%rax\n\t"
266 		"or $(1<<8),%%rax\n\t"
267 		"push %%rax\n\t"
268 		"mov %%ss, %%ax\n\t"
269 		"popf\n\t"
270 		"mov %%ax, %%ss\n\t"
271 		"and $~(1<<8),%%rax\n\t"
272 		"1: push %%rax\n\t"
273 		"popf\n\t"
274 		"lea 1b(%%rip),%0\n\t"
275 		: "=r" (start_rip) : : "rax"
276 	);
277 	return start_rip;
278 }
279 
280 
report_singlestep_with_movss_blocking_and_icebp(unsigned long start,const char * usermode)281 static void report_singlestep_with_movss_blocking_and_icebp(unsigned long start,
282 							    const char *usermode)
283 {
284 	report(n == 4 &&
285 	       is_icebp_db(dr6[0]) && db_addr[0] == start &&
286 	       is_single_step_db(dr6[1]) && db_addr[1] == start + 6 &&
287 	       is_single_step_db(dr6[2]) && db_addr[2] == start + 6 + 1 &&
288 	       is_single_step_db(dr6[3]) && db_addr[3] == start + 6 + 1 + 1,
289 	       "%sSingle-Step + ICEBP #DB w/ MOVSS blocking", usermode);
290 }
291 
singlestep_with_movss_blocking_and_icebp(void)292 static noinline unsigned long singlestep_with_movss_blocking_and_icebp(void)
293 {
294 	unsigned long start;
295 
296 	/*
297 	 * ICEBP, a.k.a. INT1 or int1icebrk, is an oddball.  It generates a
298 	 * trap-like #DB, is intercepted if #DBs are intercepted, and manifests
299 	 * as a #DB VM-Exit, but the VM-Exit occurs on the ICEBP itself, i.e.
300 	 * it's treated as an instruction intercept.  Verify that ICEBP is
301 	 * correctly emulated as a trap-like #DB when intercepted, and that
302 	 * MOVSS blocking is handled correctly with respect to single-step
303 	 * breakpoints being enabled.
304 	 */
305 	asm volatile(
306 		"pushf\n\t"
307 		"pop %%rax\n\t"
308 		"or $(1<<8),%%rax\n\t"
309 		"push %%rax\n\t"
310 		"mov %%ss, %%ax\n\t"
311 		"popf\n\t"
312 		"mov %%ax, %%ss\n\t"
313 		".byte 0xf1;"
314 		"1:and $~(1<<8),%%rax\n\t"
315 		"push %%rax\n\t"
316 		"popf\n\t"
317 		"lea 1b(%%rip),%0\n\t"
318 		: "=r" (start) : : "rax"
319 	);
320 	return start;
321 }
322 
report_singlestep_with_movss_blocking_and_dr7_gd(unsigned long start,const char * ign)323 static void report_singlestep_with_movss_blocking_and_dr7_gd(unsigned long start,
324 							     const char *ign)
325 {
326 	report(n == 5 &&
327 	       is_general_detect_db(dr6[0]) && db_addr[0] == start &&
328 	       is_single_step_db(dr6[1]) && db_addr[1] == start + 3 &&
329 	       is_single_step_db(dr6[2]) && db_addr[2] == start + 3 + 6 &&
330 	       is_single_step_db(dr6[3]) && db_addr[3] == start + 3 + 6 + 1 &&
331 	       is_single_step_db(dr6[4]) && db_addr[4] == start + 3 + 6 + 1 + 1,
332 	       "Single-step #DB w/ MOVSS blocking and DR7.GD=1");
333 }
334 
singlestep_with_movss_blocking_and_dr7_gd(void)335 static noinline unsigned long singlestep_with_movss_blocking_and_dr7_gd(void)
336 {
337 	unsigned long start_rip;
338 
339 	write_dr7(DR7_GD);
340 
341 	/*
342 	 * MOVSS blocking does NOT suppress General Detect #DBs, which have
343 	 * fault-like behavior.  Note, DR7.GD is cleared by the CPU upon
344 	 * successful delivery of the #DB.  DR6.BD is NOT cleared by the CPU,
345 	 * but the MOV DR6 below will be re-executed after handling the
346 	 * General Detect #DB.
347 	 */
348 	asm volatile(
349 		"xor %0, %0\n\t"
350 		"pushf\n\t"
351 		"pop %%rax\n\t"
352 		"or $(1<<8),%%rax\n\t"
353 		"push %%rax\n\t"
354 		"mov %%ss, %%ax\n\t"
355 		"popf\n\t"
356 		"mov %%ax, %%ss\n\t"
357 		"1: mov %0, %%dr6\n\t"
358 		"and $~(1<<8),%%rax\n\t"
359 		"push %%rax\n\t"
360 		"popf\n\t"
361 		"lea 1b(%%rip),%0\n\t"
362 		: "=r" (start_rip) : : "rax"
363 	);
364 	return start_rip;
365 }
366 
report_singlestep_with_sti_hlt(unsigned long start,const char * usermode)367 static void report_singlestep_with_sti_hlt(unsigned long start,
368 						const char *usermode)
369 {
370 	report(n == 5 &&
371 	       is_single_step_db(dr6[0]) && db_addr[0] == start &&
372 	       is_single_step_db(dr6[1]) && db_addr[1] == start + 1 &&
373 	       is_single_step_db(dr6[2]) && db_addr[2] == start + 1 + 6 &&
374 	       is_single_step_db(dr6[3]) && db_addr[3] == start + 1 + 6 + 1 &&
375 	       is_single_step_db(dr6[4]) && db_addr[4] == start + 1 + 6 + 1 + 1,
376 	       "%sSingle-step #DB w/ STI;HLT", usermode);
377 }
378 
379 #define APIC_LVT_TIMER_VECTOR    (0xee)
380 
lvtt_handler(isr_regs_t * regs)381 static void lvtt_handler(isr_regs_t *regs)
382 {
383         eoi();
384 }
385 
singlestep_with_sti_hlt(void)386 static noinline unsigned long singlestep_with_sti_hlt(void)
387 {
388 	unsigned long start_rip;
389 
390 	cli();
391 
392 	handle_irq(APIC_LVT_TIMER_VECTOR, lvtt_handler);
393 	apic_write(APIC_LVTT, APIC_LVT_TIMER_ONESHOT |
394 		   APIC_LVT_TIMER_VECTOR);
395 	apic_write(APIC_TDCR, 0x0000000b);
396 	apic_write(APIC_TMICT, 1000000);
397 
398 	/*
399 	 * STI blocking doesn't suppress #DBs, thus the first single-step #DB
400 	 * should arrive after the standard one instruction delay.
401 	 */
402 	asm volatile(
403 		"pushf\n\t"
404 		"pop %%rax\n\t"
405 		"or $(1<<8),%%rax\n\t"
406 		"push %%rax\n\t"
407 		"popf\n\t"
408 		"sti\n\t"
409 		"1:hlt;\n\t"
410 		"and $~(1<<8),%%rax\n\t"
411 		"push %%rax\n\t"
412 		"popf\n\t"
413 		"lea 1b(%%rip),%0\n\t"
414 		: "=r" (start_rip) : : "rax"
415 	);
416 	return start_rip;
417 }
418 
bus_lock(uint64_t magic)419 static noinline uint64_t bus_lock(uint64_t magic)
420 {
421 	uint8_t buffer[128] __attribute__((aligned(64))) = { };
422 	atomic64_t *val = (atomic64_t *)&buffer[60];
423 
424 	atomic64_cmpxchg(val, 0, magic);
425 	return READ_ONCE(*(uint64_t *)val);
426 }
427 
bus_lock_test(void)428 static void bus_lock_test(void)
429 {
430 	const uint64_t magic = 0xdeadbeefdeadbeefull;
431 	bool bus_lock_db = false;
432 	uint64_t val;
433 
434 	/*
435 	 * Generate a bus lock (via a locked access that splits cache lines)
436 	 * in CPL0 and again in CPL3 (Bus Lock Detect only affects CPL3), and
437 	 * verify that no #AC or #DB is generated (the relevant features are
438 	 * not enabled).
439 	 */
440 	val = bus_lock(magic);
441 	report(!got_ac && !n && val == magic,
442 	       "CPL0 Split Lock #AC = %u (#DB = %u), val = %lx (wanted %lx)",
443 	       got_ac, n, val, magic);
444 
445 	val = run_in_user((usermode_func)bus_lock, DB_VECTOR, magic, 0, 0, 0, &bus_lock_db);
446 	report(!bus_lock_db && val == magic,
447 	       "CPL3 Bus Lock #DB = %u, val = %lx (wanted %lx)",
448 	       bus_lock_db, val, magic);
449 
450 	n = 0;
451 	got_ac = false;
452 }
453 
main(int ac,char ** av)454 int main(int ac, char **av)
455 {
456 	unsigned long cr4;
457 
458 	handle_exception(DB_VECTOR, handle_db);
459 	handle_exception(BP_VECTOR, handle_bp);
460 	handle_exception(UD_VECTOR, handle_ud);
461 	handle_exception(AC_VECTOR, handle_ac);
462 
463 	bus_lock_test();
464 
465 	/*
466 	 * DR4 is an alias for DR6 (and DR5 aliases DR7) if CR4.DE is NOT set,
467 	 * and is reserved if CR4.DE=1 (Debug Extensions enabled).
468 	 */
469 	got_ud = 0;
470 	cr4 = read_cr4();
471 	write_cr4(cr4 & ~X86_CR4_DE);
472 	write_dr4(0);
473 	write_dr6(DR6_ACTIVE_LOW | DR6_BS | DR6_TRAP1);
474 	report(read_dr4() == (DR6_ACTIVE_LOW | DR6_BS | DR6_TRAP1) && !got_ud,
475 	       "DR4==DR6 with CR4.DE == 0");
476 
477 	cr4 = read_cr4();
478 	write_cr4(cr4 | X86_CR4_DE);
479 	read_dr4();
480 	report(got_ud, "DR4 read got #UD with CR4.DE == 1");
481 	write_dr6(0);
482 
483 	extern unsigned char sw_bp;
484 	asm volatile("int3; sw_bp:");
485 	report(bp_addr == (unsigned long)&sw_bp, "#BP");
486 
487 	/*
488 	 * The CPU sets/clears bits 0-3 (trap bits for DR0-3) on #DB based on
489 	 * whether or not the corresponding DR0-3 got a match.  All other bits
490 	 * in DR6 are set if and only if their associated breakpoint condition
491 	 * is active, and are never cleared by the CPU.  Verify a match on DR0
492 	 * is reported correctly, and that DR6.BS is not set when single-step
493 	 * breakpoints are disabled, but is left set (if set by software).
494 	 */
495 	n = 0;
496 	extern unsigned char hw_bp1;
497 	write_dr0(&hw_bp1);
498 	write_dr7(DR7_FIXED_1 | DR7_GLOBAL_ENABLE_DR0);
499 	asm volatile("hw_bp1: nop");
500 	report(n == 1 &&
501 	       db_addr[0] == ((unsigned long)&hw_bp1) &&
502 	       dr6[0] == (DR6_ACTIVE_LOW | DR6_TRAP0),
503 	       "hw breakpoint (test that dr6.BS is not set)");
504 
505 	n = 0;
506 	extern unsigned char hw_bp2;
507 	write_dr0(&hw_bp2);
508 	write_dr6(DR6_BS | DR6_TRAP1);
509 	asm volatile("hw_bp2: nop");
510 	report(n == 1 &&
511 	       db_addr[0] == ((unsigned long)&hw_bp2) &&
512 	       dr6[0] == (DR6_ACTIVE_LOW | DR6_BS | DR6_TRAP0),
513 	       "hw breakpoint (test that dr6.BS is not cleared)");
514 
515 	run_ss_db_test(singlestep_basic);
516 	run_ss_db_test(singlestep_emulated_instructions);
517 	run_ss_db_test(singlestep_with_sti_blocking);
518 	run_ss_db_test(singlestep_with_movss_blocking);
519 	run_ss_db_test(singlestep_with_movss_blocking_and_icebp);
520 	run_ss_db_test(singlestep_with_movss_blocking_and_dr7_gd);
521 	run_ss_db_test(singlestep_with_sti_hlt);
522 
523 	n = 0;
524 	write_dr1((void *)&value);
525 	write_dr6(DR6_BS);
526 	write_dr7(0x00d0040a); // 4-byte write
527 
528 	extern unsigned char hw_wp1;
529 	asm volatile(
530 		"mov $42,%%rax\n\t"
531 		"mov %%rax,%0\n\t; hw_wp1:"
532 		: "=m" (value) : : "rax");
533 	report(n == 1 &&
534 	       db_addr[0] == ((unsigned long)&hw_wp1) &&
535 	       dr6[0] == (DR6_ACTIVE_LOW | DR6_BS | DR6_TRAP1),
536 	       "hw watchpoint (test that dr6.BS is not cleared)");
537 
538 	n = 0;
539 	write_dr6(0);
540 
541 	extern unsigned char hw_wp2;
542 	asm volatile(
543 		"mov $42,%%rax\n\t"
544 		"mov %%rax,%0\n\t; hw_wp2:"
545 		: "=m" (value) : : "rax");
546 	report(n == 1 &&
547 	       db_addr[0] == ((unsigned long)&hw_wp2) &&
548 	       dr6[0] == (DR6_ACTIVE_LOW | DR6_TRAP1),
549 	       "hw watchpoint (test that dr6.BS is not set)");
550 
551 	n = 0;
552 	write_dr6(0);
553 	extern unsigned char sw_icebp;
554 	asm volatile(".byte 0xf1; sw_icebp:");
555 	report(n == 1 &&
556 	       db_addr[0] == (unsigned long)&sw_icebp && dr6[0] == DR6_ACTIVE_LOW,
557 	       "icebp");
558 
559 	write_dr7(0x400);
560 	value = KERNEL_DS;
561 	write_dr7(0x00f0040a); // 4-byte read or write
562 
563 	/*
564 	 * Each invocation of the handler should shift n by 1 and set bit 0 to 1.
565 	 * We expect a single invocation, so n should become 3.  If the entry
566 	 * RIP is wrong, or if the handler is executed more than once, the value
567 	 * will not match.
568 	 */
569 	set_idt_entry(1, &handle_db_save_rip, 0);
570 
571 	n = 1;
572 	asm volatile(
573 		"clc\n\t"
574 		"mov %0,%%ss\n\t"
575 		".byte 0x2e, 0x2e, 0xf1"
576 		: "=m" (value) : : "rax");
577 	report(n == 3, "MOV SS + watchpoint + ICEBP");
578 
579 	/*
580 	 * Here the #DB handler is invoked twice, once as a software exception
581 	 * and once as a software interrupt.
582 	 */
583 	n = 1;
584 	asm volatile(
585 		"clc\n\t"
586 		"mov %0,%%ss\n\t"
587 		"int $1"
588 		: "=m" (value) : : "rax");
589 	report(n == 7, "MOV SS + watchpoint + int $1");
590 
591 	/*
592 	 * Here the #DB and #BP handlers are invoked once each.
593 	 */
594 	n = 1;
595 	bp_addr = 0;
596 	asm volatile(
597 		"mov %0,%%ss\n\t"
598 		".byte 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0xcc\n\t"
599 		"sw_bp2:"
600 		: "=m" (value) : : "rax");
601 	extern unsigned char sw_bp2;
602 	report(n == 3 && bp_addr == (unsigned long)&sw_bp2,
603 	       "MOV SS + watchpoint + INT3");
604 	return report_summary();
605 }
606