/* * kvm guest debug support * * Copyright IBM Corp. 2014 * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License (version 2 only) * as published by the Free Software Foundation. * * Author(s): David Hildenbrand */ #include #include #include "kvm-s390.h" #include "gaccess.h" /* * Extends the address range given by *start and *stop to include the address * range starting with estart and the length len. Takes care of overflowing * intervals and tries to minimize the overall intervall size. */ static void extend_address_range(u64 *start, u64 *stop, u64 estart, int len) { u64 estop; if (len > 0) len--; else len = 0; estop = estart + len; /* 0-0 range represents "not set" */ if ((*start == 0) && (*stop == 0)) { *start = estart; *stop = estop; } else if (*start <= *stop) { /* increase the existing range */ if (estart < *start) *start = estart; if (estop > *stop) *stop = estop; } else { /* "overflowing" interval, whereby *stop > *start */ if (estart <= *stop) { if (estop > *stop) *stop = estop; } else if (estop > *start) { if (estart < *start) *start = estart; } /* minimize the range */ else if ((estop - *stop) < (*start - estart)) *stop = estop; else *start = estart; } } #define MAX_INST_SIZE 6 static void enable_all_hw_bp(struct kvm_vcpu *vcpu) { unsigned long start, len; u64 *cr9 = &vcpu->arch.sie_block->gcr[9]; u64 *cr10 = &vcpu->arch.sie_block->gcr[10]; u64 *cr11 = &vcpu->arch.sie_block->gcr[11]; int i; if (vcpu->arch.guestdbg.nr_hw_bp <= 0 || vcpu->arch.guestdbg.hw_bp_info == NULL) return; /* * If the guest is not interrested in branching events, we can savely * limit them to the PER address range. */ if (!(*cr9 & PER_EVENT_BRANCH)) *cr9 |= PER_CONTROL_BRANCH_ADDRESS; *cr9 |= PER_EVENT_IFETCH | PER_EVENT_BRANCH; for (i = 0; i < vcpu->arch.guestdbg.nr_hw_bp; i++) { start = vcpu->arch.guestdbg.hw_bp_info[i].addr; len = vcpu->arch.guestdbg.hw_bp_info[i].len; /* * The instruction in front of the desired bp has to * report instruction-fetching events */ if (start < MAX_INST_SIZE) { len += start; start = 0; } else { start -= MAX_INST_SIZE; len += MAX_INST_SIZE; } extend_address_range(cr10, cr11, start, len); } } static void enable_all_hw_wp(struct kvm_vcpu *vcpu) { unsigned long start, len; u64 *cr9 = &vcpu->arch.sie_block->gcr[9]; u64 *cr10 = &vcpu->arch.sie_block->gcr[10]; u64 *cr11 = &vcpu->arch.sie_block->gcr[11]; int i; if (vcpu->arch.guestdbg.nr_hw_wp <= 0 || vcpu->arch.guestdbg.hw_wp_info == NULL) return; /* if host uses storage alternation for special address * spaces, enable all events and give all to the guest */ if (*cr9 & PER_EVENT_STORE && *cr9 & PER_CONTROL_ALTERATION) { *cr9 &= ~PER_CONTROL_ALTERATION; *cr10 = 0; *cr11 = PSW_ADDR_INSN; } else { *cr9 &= ~PER_CONTROL_ALTERATION; *cr9 |= PER_EVENT_STORE; for (i = 0; i < vcpu->arch.guestdbg.nr_hw_wp; i++) { start = vcpu->arch.guestdbg.hw_wp_info[i].addr; len = vcpu->arch.guestdbg.hw_wp_info[i].len; extend_address_range(cr10, cr11, start, len); } } } void kvm_s390_backup_guest_per_regs(struct kvm_vcpu *vcpu) { vcpu->arch.guestdbg.cr0 = vcpu->arch.sie_block->gcr[0]; vcpu->arch.guestdbg.cr9 = vcpu->arch.sie_block->gcr[9]; vcpu->arch.guestdbg.cr10 = vcpu->arch.sie_block->gcr[10]; vcpu->arch.guestdbg.cr11 = vcpu->arch.sie_block->gcr[11]; } void kvm_s390_restore_guest_per_regs(struct kvm_vcpu *vcpu) { vcpu->arch.sie_block->gcr[0] = vcpu->arch.guestdbg.cr0; vcpu->arch.sie_block->gcr[9] = vcpu->arch.guestdbg.cr9; vcpu->arch.sie_block->gcr[10] = vcpu->arch.guestdbg.cr10; vcpu->arch.sie_block->gcr[11] = vcpu->arch.guestdbg.cr11; } void kvm_s390_patch_guest_per_regs(struct kvm_vcpu *vcpu) { /* * TODO: if guest psw has per enabled, otherwise 0s! * This reduces the amount of reported events. * Need to intercept all psw changes! */ if (guestdbg_sstep_enabled(vcpu)) { vcpu->arch.sie_block->gcr[9] |= PER_EVENT_IFETCH; vcpu->arch.sie_block->gcr[10] = 0; vcpu->arch.sie_block->gcr[11] = PSW_ADDR_INSN; } if (guestdbg_hw_bp_enabled(vcpu)) { enable_all_hw_bp(vcpu); enable_all_hw_wp(vcpu); } /* TODO: Instruction-fetching-nullification not allowed for now */ if (vcpu->arch.sie_block->gcr[9] & PER_EVENT_NULLIFICATION) vcpu->arch.sie_block->gcr[9] &= ~PER_EVENT_NULLIFICATION; } #define MAX_WP_SIZE 100 static int __import_wp_info(struct kvm_vcpu *vcpu, struct kvm_hw_breakpoint *bp_data, struct kvm_hw_wp_info_arch *wp_info) { int ret = 0; wp_info->len = bp_data->len; wp_info->addr = bp_data->addr; wp_info->phys_addr = bp_data->phys_addr; wp_info->old_data = NULL; if (wp_info->len < 0 || wp_info->len > MAX_WP_SIZE) return -EINVAL; wp_info->old_data = kmalloc(bp_data->len, GFP_KERNEL); if (!wp_info->old_data) return -ENOMEM; /* try to backup the original value */ ret = read_guest(vcpu, wp_info->phys_addr, wp_info->old_data, wp_info->len); if (ret) { kfree(wp_info->old_data); wp_info->old_data = NULL; } return ret; } #define MAX_BP_COUNT 50 int kvm_s390_import_bp_data(struct kvm_vcpu *vcpu, struct kvm_guest_debug *dbg) { int ret = 0, nr_wp = 0, nr_bp = 0, i, size; struct kvm_hw_breakpoint *bp_data = NULL; struct kvm_hw_wp_info_arch *wp_info = NULL; struct kvm_hw_bp_info_arch *bp_info = NULL; if (dbg->arch.nr_hw_bp <= 0 || !dbg->arch.hw_bp) return 0; else if (dbg->arch.nr_hw_bp > MAX_BP_COUNT) return -EINVAL; size = dbg->arch.nr_hw_bp * sizeof(struct kvm_hw_breakpoint); bp_data = kmalloc(size, GFP_KERNEL); if (!bp_data) { ret = -ENOMEM; goto error; } ret = copy_from_user(bp_data, dbg->arch.hw_bp, size); if (ret) goto error; for (i = 0; i < dbg->arch.nr_hw_bp; i++) { switch (bp_data[i].type) { case KVM_HW_WP_WRITE: nr_wp++; break; case KVM_HW_BP: nr_bp++; break; default: break; } } size = nr_wp * sizeof(struct kvm_hw_wp_info_arch); if (size > 0) { wp_info = kmalloc(size, GFP_KERNEL); if (!wp_info) { ret = -ENOMEM; goto error; } } size = nr_bp * sizeof(struct kvm_hw_bp_info_arch); if (size > 0) { bp_info = kmalloc(size, GFP_KERNEL); if (!bp_info) { ret = -ENOMEM; goto error; } } for (nr_wp = 0, nr_bp = 0, i = 0; i < dbg->arch.nr_hw_bp; i++) { switch (bp_data[i].type) { case KVM_HW_WP_WRITE: ret = __import_wp_info(vcpu, &bp_data[i], &wp_info[nr_wp]); if (ret) goto error; nr_wp++; break; case KVM_HW_BP: bp_info[nr_bp].len = bp_data[i].len; bp_info[nr_bp].addr = bp_data[i].addr; nr_bp++; break; } } vcpu->arch.guestdbg.nr_hw_bp = nr_bp; vcpu->arch.guestdbg.hw_bp_info = bp_info; vcpu->arch.guestdbg.nr_hw_wp = nr_wp; vcpu->arch.guestdbg.hw_wp_info = wp_info; return 0; error: kfree(bp_data); kfree(wp_info); kfree(bp_info); return ret; } void kvm_s390_clear_bp_data(struct kvm_vcpu *vcpu) { int i; struct kvm_hw_wp_info_arch *hw_wp_info = NULL; for (i = 0; i < vcpu->arch.guestdbg.nr_hw_wp; i++) { hw_wp_info = &vcpu->arch.guestdbg.hw_wp_info[i]; kfree(hw_wp_info->old_data); hw_wp_info->old_data = NULL; } kfree(vcpu->arch.guestdbg.hw_wp_info); vcpu->arch.guestdbg.hw_wp_info = NULL; kfree(vcpu->arch.guestdbg.hw_bp_info); vcpu->arch.guestdbg.hw_bp_info = NULL; vcpu->arch.guestdbg.nr_hw_wp = 0; vcpu->arch.guestdbg.nr_hw_bp = 0; } static inline int in_addr_range(u64 addr, u64 a, u64 b) { if (a <= b) return (addr >= a) && (addr <= b); else /* "overflowing" interval */ return (addr <= a) && (addr >= b); } #define end_of_range(bp_info) (bp_info->addr + bp_info->len - 1) static struct kvm_hw_bp_info_arch *find_hw_bp(struct kvm_vcpu *vcpu, unsigned long addr) { struct kvm_hw_bp_info_arch *bp_info = vcpu->arch.guestdbg.hw_bp_info; int i; if (vcpu->arch.guestdbg.nr_hw_bp == 0) return NULL; for (i = 0; i < vcpu->arch.guestdbg.nr_hw_bp; i++) { /* addr is directly the start or in the range of a bp */ if (addr == bp_info->addr) goto found; if (bp_info->len > 0 && in_addr_range(addr, bp_info->addr, end_of_range(bp_info))) goto found; bp_info++; } return NULL; found: return bp_info; } static struct kvm_hw_wp_info_arch *any_wp_changed(struct kvm_vcpu *vcpu) { int i; struct kvm_hw_wp_info_arch *wp_info = NULL; void *temp = NULL; if (vcpu->arch.guestdbg.nr_hw_wp == 0) return NULL; for (i = 0; i < vcpu->arch.guestdbg.nr_hw_wp; i++) { wp_info = &vcpu->arch.guestdbg.hw_wp_info[i]; if (!wp_info || !wp_info->old_data || wp_info->len <= 0) continue; temp = kmalloc(wp_info->len, GFP_KERNEL); if (!temp) continue; /* refetch the wp data and compare it to the old value */ if (!read_guest(vcpu, wp_info->phys_addr, temp, wp_info->len)) { if (memcmp(temp, wp_info->old_data, wp_info->len)) { kfree(temp); return wp_info; } } kfree(temp); temp = NULL; } return NULL; } void kvm_s390_prepare_debug_exit(struct kvm_vcpu *vcpu) { vcpu->run->exit_reason = KVM_EXIT_DEBUG; vcpu->guest_debug &= ~KVM_GUESTDBG_EXIT_PENDING; } #define per_bp_event(code) \ (code & (PER_EVENT_IFETCH | PER_EVENT_BRANCH)) #define per_write_wp_event(code) \ (code & (PER_EVENT_STORE | PER_EVENT_STORE_REAL)) static int debug_exit_required(struct kvm_vcpu *vcpu) { u32 perc = (vcpu->arch.sie_block->perc << 24); struct kvm_debug_exit_arch *debug_exit = &vcpu->run->debug.arch; struct kvm_hw_wp_info_arch *wp_info = NULL; struct kvm_hw_bp_info_arch *bp_info = NULL; unsigned long addr = vcpu->arch.sie_block->gpsw.addr; unsigned long peraddr = vcpu->arch.sie_block->peraddr; if (guestdbg_hw_bp_enabled(vcpu)) { if (per_write_wp_event(perc) && vcpu->arch.guestdbg.nr_hw_wp > 0) { wp_info = any_wp_changed(vcpu); if (wp_info) { debug_exit->addr = wp_info->addr; debug_exit->type = KVM_HW_WP_WRITE; goto exit_required; } } if (per_bp_event(perc) && vcpu->arch.guestdbg.nr_hw_bp > 0) { bp_info = find_hw_bp(vcpu, addr); /* remove duplicate events if PC==PER address */ if (bp_info && (addr != peraddr)) { debug_exit->addr = addr; debug_exit->type = KVM_HW_BP; vcpu->arch.guestdbg.last_bp = addr; goto exit_required; } /* breakpoint missed */ bp_info = find_hw_bp(vcpu, peraddr); if (bp_info && vcpu->arch.guestdbg.last_bp != peraddr) { debug_exit->addr = peraddr; debug_exit->type = KVM_HW_BP; goto exit_required; } } } if (guestdbg_sstep_enabled(vcpu) && per_bp_event(perc)) { debug_exit->addr = addr; debug_exit->type = KVM_SINGLESTEP; goto exit_required; } return 0; exit_required: return 1; } #define guest_per_enabled(vcpu) \ (vcpu->arch.sie_block->gpsw.mask & PSW_MASK_PER) static void filter_guest_per_event(struct kvm_vcpu *vcpu) { u32 perc = vcpu->arch.sie_block->perc << 24; u64 peraddr = vcpu->arch.sie_block->peraddr; u64 addr = vcpu->arch.sie_block->gpsw.addr; u64 cr9 = vcpu->arch.sie_block->gcr[9]; u64 cr10 = vcpu->arch.sie_block->gcr[10]; u64 cr11 = vcpu->arch.sie_block->gcr[11]; /* filter all events, demanded by the guest */ u32 guest_perc = perc & cr9 & PER_EVENT_MASK; if (!guest_per_enabled(vcpu)) guest_perc = 0; /* filter "successful-branching" events */ if (guest_perc & PER_EVENT_BRANCH && cr9 & PER_CONTROL_BRANCH_ADDRESS && !in_addr_range(addr, cr10, cr11)) guest_perc &= ~PER_EVENT_BRANCH; /* filter "instruction-fetching" events */ if (guest_perc & PER_EVENT_IFETCH && !in_addr_range(peraddr, cr10, cr11)) guest_perc &= ~PER_EVENT_IFETCH; /* All other PER events will be given to the guest */ /* TODO: Check alterated address/address space */ vcpu->arch.sie_block->perc = guest_perc >> 24; if (!guest_perc) vcpu->arch.sie_block->iprcc &= ~PGM_PER; } void kvm_s390_handle_per_event(struct kvm_vcpu *vcpu) { if (debug_exit_required(vcpu)) vcpu->guest_debug |= KVM_GUESTDBG_EXIT_PENDING; filter_guest_per_event(vcpu); }