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