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 %%rax,%%rax\n\t" 44 "cpuid\n\t" 45 "movl $0x1a0,%%ecx\n\t" 46 "rdmsr\n\t" 47 : : : "rax", "ecx"); 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 3, /* 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 vcpu_set_cpuid(vm, VCPU_ID, kvm_get_supported_cpuid()); 89 run = vcpu_state(vm, VCPU_ID); 90 91 /* Test software BPs - int3 */ 92 CLEAR_DEBUG(); 93 debug.control = KVM_GUESTDBG_ENABLE | KVM_GUESTDBG_USE_SW_BP; 94 APPLY_DEBUG(); 95 vcpu_run(vm, VCPU_ID); 96 TEST_ASSERT(run->exit_reason == KVM_EXIT_DEBUG && 97 run->debug.arch.exception == BP_VECTOR && 98 run->debug.arch.pc == CAST_TO_RIP(sw_bp), 99 "INT3: exit %d exception %d rip 0x%llx (should be 0x%llx)", 100 run->exit_reason, run->debug.arch.exception, 101 run->debug.arch.pc, CAST_TO_RIP(sw_bp)); 102 MOVE_RIP(1); 103 104 /* Test instruction HW BP over DR[0-3] */ 105 for (i = 0; i < 4; i++) { 106 CLEAR_DEBUG(); 107 debug.control = KVM_GUESTDBG_ENABLE | KVM_GUESTDBG_USE_HW_BP; 108 debug.arch.debugreg[i] = CAST_TO_RIP(hw_bp); 109 debug.arch.debugreg[7] = 0x400 | (1UL << (2*i+1)); 110 APPLY_DEBUG(); 111 vcpu_run(vm, VCPU_ID); 112 target_dr6 = 0xffff0ff0 | (1UL << i); 113 TEST_ASSERT(run->exit_reason == KVM_EXIT_DEBUG && 114 run->debug.arch.exception == DB_VECTOR && 115 run->debug.arch.pc == CAST_TO_RIP(hw_bp) && 116 run->debug.arch.dr6 == target_dr6, 117 "INS_HW_BP (DR%d): exit %d exception %d rip 0x%llx " 118 "(should be 0x%llx) dr6 0x%llx (should be 0x%llx)", 119 i, run->exit_reason, run->debug.arch.exception, 120 run->debug.arch.pc, CAST_TO_RIP(hw_bp), 121 run->debug.arch.dr6, target_dr6); 122 } 123 /* Skip "nop" */ 124 MOVE_RIP(1); 125 126 /* Test data access HW BP over DR[0-3] */ 127 for (i = 0; i < 4; i++) { 128 CLEAR_DEBUG(); 129 debug.control = KVM_GUESTDBG_ENABLE | KVM_GUESTDBG_USE_HW_BP; 130 debug.arch.debugreg[i] = CAST_TO_RIP(guest_value); 131 debug.arch.debugreg[7] = 0x00000400 | (1UL << (2*i+1)) | 132 (0x000d0000UL << (4*i)); 133 APPLY_DEBUG(); 134 vcpu_run(vm, VCPU_ID); 135 target_dr6 = 0xffff0ff0 | (1UL << i); 136 TEST_ASSERT(run->exit_reason == KVM_EXIT_DEBUG && 137 run->debug.arch.exception == DB_VECTOR && 138 run->debug.arch.pc == CAST_TO_RIP(write_data) && 139 run->debug.arch.dr6 == target_dr6, 140 "DATA_HW_BP (DR%d): exit %d exception %d rip 0x%llx " 141 "(should be 0x%llx) dr6 0x%llx (should be 0x%llx)", 142 i, run->exit_reason, run->debug.arch.exception, 143 run->debug.arch.pc, CAST_TO_RIP(write_data), 144 run->debug.arch.dr6, target_dr6); 145 /* Rollback the 4-bytes "mov" */ 146 MOVE_RIP(-7); 147 } 148 /* Skip the 4-bytes "mov" */ 149 MOVE_RIP(7); 150 151 /* Test single step */ 152 target_rip = CAST_TO_RIP(ss_start); 153 target_dr6 = 0xffff4ff0ULL; 154 vcpu_regs_get(vm, VCPU_ID, ®s); 155 for (i = 0; i < (sizeof(ss_size) / sizeof(ss_size[0])); i++) { 156 target_rip += ss_size[i]; 157 CLEAR_DEBUG(); 158 debug.control = KVM_GUESTDBG_ENABLE | KVM_GUESTDBG_SINGLESTEP; 159 debug.arch.debugreg[7] = 0x00000400; 160 APPLY_DEBUG(); 161 vcpu_run(vm, VCPU_ID); 162 TEST_ASSERT(run->exit_reason == KVM_EXIT_DEBUG && 163 run->debug.arch.exception == DB_VECTOR && 164 run->debug.arch.pc == target_rip && 165 run->debug.arch.dr6 == target_dr6, 166 "SINGLE_STEP[%d]: exit %d exception %d rip 0x%llx " 167 "(should be 0x%llx) dr6 0x%llx (should be 0x%llx)", 168 i, run->exit_reason, run->debug.arch.exception, 169 run->debug.arch.pc, target_rip, run->debug.arch.dr6, 170 target_dr6); 171 } 172 173 /* Finally test global disable */ 174 CLEAR_DEBUG(); 175 debug.control = KVM_GUESTDBG_ENABLE | KVM_GUESTDBG_USE_HW_BP; 176 debug.arch.debugreg[7] = 0x400 | DR7_GD; 177 APPLY_DEBUG(); 178 vcpu_run(vm, VCPU_ID); 179 target_dr6 = 0xffff0ff0 | DR6_BD; 180 TEST_ASSERT(run->exit_reason == KVM_EXIT_DEBUG && 181 run->debug.arch.exception == DB_VECTOR && 182 run->debug.arch.pc == CAST_TO_RIP(bd_start) && 183 run->debug.arch.dr6 == target_dr6, 184 "DR7.GD: exit %d exception %d rip 0x%llx " 185 "(should be 0x%llx) dr6 0x%llx (should be 0x%llx)", 186 run->exit_reason, run->debug.arch.exception, 187 run->debug.arch.pc, target_rip, run->debug.arch.dr6, 188 target_dr6); 189 190 /* Disable all debug controls, run to the end */ 191 CLEAR_DEBUG(); 192 APPLY_DEBUG(); 193 194 vcpu_run(vm, VCPU_ID); 195 TEST_ASSERT(run->exit_reason == KVM_EXIT_IO, "KVM_EXIT_IO"); 196 cmd = get_ucall(vm, VCPU_ID, &uc); 197 TEST_ASSERT(cmd == UCALL_DONE, "UCALL_DONE"); 198 199 kvm_vm_free(vm); 200 201 return 0; 202 } 203