linux/tools/testing/selftests/kvm/x86_64/debug_regs.c
<<
>>
Prefs
   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 */
  21uint32_t guest_value;
  22
  23extern unsigned char sw_bp, hw_bp, write_data, ss_start, bd_start;
  24
  25static 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, &regs);      \
  74                regs.rip = (v);                         \
  75                vcpu_regs_set(vm, VCPU_ID, &regs);      \
  76        } while (0)
  77#define  MOVE_RIP(v)  SET_RIP(regs.rip + (v));
  78
  79int 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, &regs);
 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