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