Skip to content

Commit 449aa90

Browse files
xzpeterbonzini
authored andcommitted
KVM: selftests: Add KVM_SET_GUEST_DEBUG test
Covers fundamental tests for KVM_SET_GUEST_DEBUG. It is very close to the debug test in kvm-unit-test, but doing it from outside the guest. Signed-off-by: Peter Xu <[email protected]> Message-Id: <[email protected]> Signed-off-by: Paolo Bonzini <[email protected]>
1 parent d5d260c commit 449aa90

File tree

4 files changed

+192
-0
lines changed

4 files changed

+192
-0
lines changed

tools/testing/selftests/kvm/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ TEST_GEN_PROGS_x86_64 += x86_64/vmx_dirty_log_test
2828
TEST_GEN_PROGS_x86_64 += x86_64/vmx_set_nested_state_test
2929
TEST_GEN_PROGS_x86_64 += x86_64/vmx_tsc_adjust_test
3030
TEST_GEN_PROGS_x86_64 += x86_64/xss_msr_test
31+
TEST_GEN_PROGS_x86_64 += x86_64/debug_regs
3132
TEST_GEN_PROGS_x86_64 += clear_dirty_log_test
3233
TEST_GEN_PROGS_x86_64 += demand_paging_test
3334
TEST_GEN_PROGS_x86_64 += dirty_log_test

tools/testing/selftests/kvm/include/kvm_util.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,8 @@ struct kvm_run *vcpu_state(struct kvm_vm *vm, uint32_t vcpuid);
143143
void vcpu_run(struct kvm_vm *vm, uint32_t vcpuid);
144144
int _vcpu_run(struct kvm_vm *vm, uint32_t vcpuid);
145145
void vcpu_run_complete_io(struct kvm_vm *vm, uint32_t vcpuid);
146+
void vcpu_set_guest_debug(struct kvm_vm *vm, uint32_t vcpuid,
147+
struct kvm_guest_debug *debug);
146148
void vcpu_set_mp_state(struct kvm_vm *vm, uint32_t vcpuid,
147149
struct kvm_mp_state *mp_state);
148150
void vcpu_regs_get(struct kvm_vm *vm, uint32_t vcpuid, struct kvm_regs *regs);

tools/testing/selftests/kvm/lib/kvm_util.c

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1201,6 +1201,15 @@ void vcpu_run_complete_io(struct kvm_vm *vm, uint32_t vcpuid)
12011201
ret, errno);
12021202
}
12031203

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

0 commit comments

Comments
 (0)