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