1 // SPDX-License-Identifier: GPL-2.0 2 /* 3 * KVM guest debug register tests 4 * 5 * Copyright (C) 2020, Red Hat, Inc. 6 */ 7 #include <stdio.h> 8 #include <string.h> 9 #include "kvm_util.h" 10 #include "processor.h" 11 #include "apic.h" 12 13 #define VCPU_ID 0 14 15 #define DR6_BD (1 << 13) 16 #define DR7_GD (1 << 13) 17 18 #define IRQ_VECTOR 0xAA 19 20 /* For testing data access debug BP */ 21 uint32_t guest_value; 22 23 extern unsigned char sw_bp, hw_bp, write_data, ss_start, bd_start; 24 25 static void guest_code(void) 26 { 27 /* Create a pending interrupt on current vCPU */ 28 x2apic_enable(); 29 x2apic_write_reg(APIC_ICR, APIC_DEST_SELF | APIC_INT_ASSERT | 30 APIC_DM_FIXED | IRQ_VECTOR); 31 32 /* 33 * Software BP tests. 34 * 35 * NOTE: sw_bp need to be before the cmd here, because int3 is an 36 * exception rather than a normal trap for KVM_SET_GUEST_DEBUG (we 37 * capture it using the vcpu exception bitmap). 38 */ 39 asm volatile("sw_bp: int3"); 40 41 /* Hardware instruction BP test */ 42 asm volatile("hw_bp: nop"); 43 44 /* Hardware data BP test */ 45 asm volatile("mov $1234,%%rax;\n\t" 46 "mov %%rax,%0;\n\t write_data:" 47 : "=m" (guest_value) : : "rax"); 48 49 /* 50 * Single step test, covers 2 basic instructions and 2 emulated 51 * 52 * Enable interrupts during the single stepping to see that 53 * pending interrupt we raised is not handled due to KVM_GUESTDBG_BLOCKIRQ 54 */ 55 asm volatile("ss_start: " 56 "sti\n\t" 57 "xor %%eax,%%eax\n\t" 58 "cpuid\n\t" 59 "movl $0x1a0,%%ecx\n\t" 60 "rdmsr\n\t" 61 "cli\n\t" 62 : : : "eax", "ebx", "ecx", "edx"); 63 64 /* DR6.BD test */ 65 asm volatile("bd_start: mov %%dr0, %%rax" : : : "rax"); 66 GUEST_DONE(); 67 } 68 69 #define CLEAR_DEBUG() memset(&debug, 0, sizeof(debug)) 70 #define APPLY_DEBUG() vcpu_set_guest_debug(vm, VCPU_ID, &debug) 71 #define CAST_TO_RIP(v) ((unsigned long long)&(v)) 72 #define SET_RIP(v) do { \ 73 vcpu_regs_get(vm, VCPU_ID, ®s); \ 74 regs.rip = (v); \ 75 vcpu_regs_set(vm, VCPU_ID, ®s); \ 76 } while (0) 77 #define MOVE_RIP(v) SET_RIP(regs.rip + (v)); 78 79 int main(void) 80 { 81 struct kvm_guest_debug debug; 82 unsigned long long target_dr6, target_rip; 83 struct kvm_regs regs; 84 struct kvm_run *run; 85 struct kvm_vm *vm; 86 struct ucall uc; 87 uint64_t cmd; 88 int i; 89 /* Instruction lengths starting at ss_start */ 90 int ss_size[6] = { 91 1, /* sti*/ 92 2, /* xor */ 93 2, /* cpuid */ 94 5, /* mov */ 95 2, /* rdmsr */ 96 1, /* cli */ 97 }; 98 99 if (!kvm_check_cap(KVM_CAP_SET_GUEST_DEBUG)) { 100 print_skip("KVM_CAP_SET_GUEST_DEBUG not supported"); 101 return 0; 102 } 103 104 vm = vm_create_default(VCPU_ID, 0, guest_code); 105 run = vcpu_state(vm, VCPU_ID); 106 107 /* Test software BPs - int3 */ 108 CLEAR_DEBUG(); 109 debug.control = KVM_GUESTDBG_ENABLE | KVM_GUESTDBG_USE_SW_BP; 110 APPLY_DEBUG(); 111 vcpu_run(vm, VCPU_ID); 112 TEST_ASSERT(run->exit_reason == KVM_EXIT_DEBUG && 113 run->debug.arch.exception == BP_VECTOR && 114 run->debug.arch.pc == CAST_TO_RIP(sw_bp), 115 "INT3: exit %d exception %d rip 0x%llx (should be 0x%llx)", 116 run->exit_reason, run->debug.arch.exception, 117 run->debug.arch.pc, CAST_TO_RIP(sw_bp)); 118 MOVE_RIP(1); 119 120 /* Test instruction HW BP over DR[0-3] */ 121 for (i = 0; i < 4; i++) { 122 CLEAR_DEBUG(); 123 debug.control = KVM_GUESTDBG_ENABLE | KVM_GUESTDBG_USE_HW_BP; 124 debug.arch.debugreg[i] = CAST_TO_RIP(hw_bp); 125 debug.arch.debugreg[7] = 0x400 | (1UL << (2*i+1)); 126 APPLY_DEBUG(); 127 vcpu_run(vm, VCPU_ID); 128 target_dr6 = 0xffff0ff0 | (1UL << i); 129 TEST_ASSERT(run->exit_reason == KVM_EXIT_DEBUG && 130 run->debug.arch.exception == DB_VECTOR && 131 run->debug.arch.pc == CAST_TO_RIP(hw_bp) && 132 run->debug.arch.dr6 == target_dr6, 133 "INS_HW_BP (DR%d): exit %d exception %d rip 0x%llx " 134 "(should be 0x%llx) dr6 0x%llx (should be 0x%llx)", 135 i, run->exit_reason, run->debug.arch.exception, 136 run->debug.arch.pc, CAST_TO_RIP(hw_bp), 137 run->debug.arch.dr6, target_dr6); 138 } 139 /* Skip "nop" */ 140 MOVE_RIP(1); 141 142 /* Test data access HW BP over DR[0-3] */ 143 for (i = 0; i < 4; i++) { 144 CLEAR_DEBUG(); 145 debug.control = KVM_GUESTDBG_ENABLE | KVM_GUESTDBG_USE_HW_BP; 146 debug.arch.debugreg[i] = CAST_TO_RIP(guest_value); 147 debug.arch.debugreg[7] = 0x00000400 | (1UL << (2*i+1)) | 148 (0x000d0000UL << (4*i)); 149 APPLY_DEBUG(); 150 vcpu_run(vm, VCPU_ID); 151 target_dr6 = 0xffff0ff0 | (1UL << i); 152 TEST_ASSERT(run->exit_reason == KVM_EXIT_DEBUG && 153 run->debug.arch.exception == DB_VECTOR && 154 run->debug.arch.pc == CAST_TO_RIP(write_data) && 155 run->debug.arch.dr6 == target_dr6, 156 "DATA_HW_BP (DR%d): exit %d exception %d rip 0x%llx " 157 "(should be 0x%llx) dr6 0x%llx (should be 0x%llx)", 158 i, run->exit_reason, run->debug.arch.exception, 159 run->debug.arch.pc, CAST_TO_RIP(write_data), 160 run->debug.arch.dr6, target_dr6); 161 /* Rollback the 4-bytes "mov" */ 162 MOVE_RIP(-7); 163 } 164 /* Skip the 4-bytes "mov" */ 165 MOVE_RIP(7); 166 167 /* Test single step */ 168 target_rip = CAST_TO_RIP(ss_start); 169 target_dr6 = 0xffff4ff0ULL; 170 vcpu_regs_get(vm, VCPU_ID, ®s); 171 for (i = 0; i < (sizeof(ss_size) / sizeof(ss_size[0])); i++) { 172 target_rip += ss_size[i]; 173 CLEAR_DEBUG(); 174 debug.control = KVM_GUESTDBG_ENABLE | KVM_GUESTDBG_SINGLESTEP | 175 KVM_GUESTDBG_BLOCKIRQ; 176 debug.arch.debugreg[7] = 0x00000400; 177 APPLY_DEBUG(); 178 vcpu_run(vm, VCPU_ID); 179 TEST_ASSERT(run->exit_reason == KVM_EXIT_DEBUG && 180 run->debug.arch.exception == DB_VECTOR && 181 run->debug.arch.pc == target_rip && 182 run->debug.arch.dr6 == target_dr6, 183 "SINGLE_STEP[%d]: exit %d exception %d rip 0x%llx " 184 "(should be 0x%llx) dr6 0x%llx (should be 0x%llx)", 185 i, run->exit_reason, run->debug.arch.exception, 186 run->debug.arch.pc, target_rip, run->debug.arch.dr6, 187 target_dr6); 188 } 189 190 /* Finally test global disable */ 191 CLEAR_DEBUG(); 192 debug.control = KVM_GUESTDBG_ENABLE | KVM_GUESTDBG_USE_HW_BP; 193 debug.arch.debugreg[7] = 0x400 | DR7_GD; 194 APPLY_DEBUG(); 195 vcpu_run(vm, VCPU_ID); 196 target_dr6 = 0xffff0ff0 | DR6_BD; 197 TEST_ASSERT(run->exit_reason == KVM_EXIT_DEBUG && 198 run->debug.arch.exception == DB_VECTOR && 199 run->debug.arch.pc == CAST_TO_RIP(bd_start) && 200 run->debug.arch.dr6 == target_dr6, 201 "DR7.GD: exit %d exception %d rip 0x%llx " 202 "(should be 0x%llx) dr6 0x%llx (should be 0x%llx)", 203 run->exit_reason, run->debug.arch.exception, 204 run->debug.arch.pc, target_rip, run->debug.arch.dr6, 205 target_dr6); 206 207 /* Disable all debug controls, run to the end */ 208 CLEAR_DEBUG(); 209 APPLY_DEBUG(); 210 211 vcpu_run(vm, VCPU_ID); 212 TEST_ASSERT(run->exit_reason == KVM_EXIT_IO, "KVM_EXIT_IO"); 213 cmd = get_ucall(vm, VCPU_ID, &uc); 214 TEST_ASSERT(cmd == UCALL_DONE, "UCALL_DONE"); 215 216 kvm_vm_free(vm); 217 218 return 0; 219 } 220