diff mbox

[4/4] s390x/kvm: hw debugging support via guest PER facility

Message ID 1399896944-49359-5-git-send-email-jfrei@linux.vnet.ibm.com
State New
Headers show

Commit Message

Jens Freimann May 12, 2014, 12:15 p.m. UTC
From: David Hildenbrand <dahi@linux.vnet.ibm.com>

This patch makes use of the hw debugging support in kvm (provided by the guest's
PER facility) on s390. It enables the following features, available using the
gdbserver:
- single-stepping
- hw breakpoints
- hw watchpoints

Signed-off-by: David Hildenbrand <dahi@linux.vnet.ibm.com>
Signed-off-by: Jens Freimann <jfrei@linux.vnet.ibm.com>
---
 target-s390x/cpu-qom.h |   1 +
 target-s390x/helper.c  |  12 ++++
 target-s390x/kvm.c     | 146 ++++++++++++++++++++++++++++++++++++++++++++++++-
 3 files changed, 156 insertions(+), 3 deletions(-)

Comments

Alexander Graf May 30, 2014, 8:32 a.m. UTC | #1
On 12.05.14 14:15, Jens Freimann wrote:
> From: David Hildenbrand <dahi@linux.vnet.ibm.com>
>
> This patch makes use of the hw debugging support in kvm (provided by the guest's
> PER facility) on s390. It enables the following features, available using the
> gdbserver:
> - single-stepping
> - hw breakpoints
> - hw watchpoints
>
> Signed-off-by: David Hildenbrand <dahi@linux.vnet.ibm.com>
> Signed-off-by: Jens Freimann <jfrei@linux.vnet.ibm.com>
> ---
>   target-s390x/cpu-qom.h |   1 +
>   target-s390x/helper.c  |  12 ++++
>   target-s390x/kvm.c     | 146 ++++++++++++++++++++++++++++++++++++++++++++++++-
>   3 files changed, 156 insertions(+), 3 deletions(-)
>
> diff --git a/target-s390x/cpu-qom.h b/target-s390x/cpu-qom.h
> index ac0460e..f9c96d1 100644
> --- a/target-s390x/cpu-qom.h
> +++ b/target-s390x/cpu-qom.h
> @@ -86,6 +86,7 @@ int s390_cpu_write_elf64_note(WriteCoreDumpFunction f, CPUState *cs,
>   int s390_cpu_write_elf64_qemunote(WriteCoreDumpFunction f,
>                                     CPUState *cpu, void *opaque);
>   hwaddr s390_cpu_get_phys_page_debug(CPUState *cpu, vaddr addr);
> +hwaddr s390_cpu_get_phys_addr_debug(CPUState *cpu, vaddr addr);
>   int s390_cpu_gdb_read_register(CPUState *cpu, uint8_t *buf, int reg);
>   int s390_cpu_gdb_write_register(CPUState *cpu, uint8_t *buf, int reg);
>   
> diff --git a/target-s390x/helper.c b/target-s390x/helper.c
> index 7c76fc1..3d756ca 100644
> --- a/target-s390x/helper.c
> +++ b/target-s390x/helper.c
> @@ -489,6 +489,18 @@ hwaddr s390_cpu_get_phys_page_debug(CPUState *cs, vaddr vaddr)
>       return raddr;
>   }
>   
> +hwaddr s390_cpu_get_phys_addr_debug(CPUState *cs, vaddr vaddr)
> +{
> +    hwaddr phys_addr;
> +    target_ulong page;
> +
> +    page = vaddr & TARGET_PAGE_MASK;
> +    phys_addr = cpu_get_phys_page_debug(cs, page);
> +    phys_addr += (vaddr & ~TARGET_PAGE_MASK);
> +
> +    return phys_addr;
> +}
> +
>   void load_psw(CPUS390XState *env, uint64_t mask, uint64_t addr)
>   {
>       if (mask & PSW_MASK_WAIT) {
> diff --git a/target-s390x/kvm.c b/target-s390x/kvm.c
> index bf62c95..50af3e6 100644
> --- a/target-s390x/kvm.c
> +++ b/target-s390x/kvm.c
> @@ -36,6 +36,7 @@
>   #include "sysemu/device_tree.h"
>   #include "qapi/qmp/qjson.h"
>   #include "monitor/monitor.h"
> +#include "exec/gdbstub.h"
>   #include "trace.h"
>   
>   /* #define DEBUG_KVM */
> @@ -86,6 +87,14 @@
>   #define ICPT_CPU_STOP                   0x28
>   #define ICPT_IO                         0x40
>   
> +static CPUWatchpoint hw_watchpoint;
> +/*
> + * We don't use a list because this structure is also used to transmit the
> + * hardware breakpoints to the kernel.
> + */
> +static struct kvm_hw_breakpoint *hw_breakpoints;
> +static int nb_hw_breakpoints;
> +
>   const KVMCapabilityInfo kvm_arch_required_capabilities[] = {
>       KVM_CAP_LAST_INFO
>   };
> @@ -378,24 +387,126 @@ int kvm_arch_remove_sw_breakpoint(CPUState *cs, struct kvm_sw_breakpoint *bp)
>       return 0;
>   }
>   
> +static struct kvm_hw_breakpoint *find_hw_breakpoint(target_ulong addr,
> +                                                    int len, int type)
> +{
> +    int n;
> +
> +    for (n = 0; n < nb_hw_breakpoints; n++) {
> +        if (hw_breakpoints[n].addr == addr && hw_breakpoints[n].type == type &&
> +            (hw_breakpoints[n].len == len || len == -1)) {
> +            return &hw_breakpoints[n];
> +        }
> +    }
> +
> +    return NULL;
> +}
> +
> +static int insert_hw_breakpoint(target_ulong addr, int len, int type)
> +{
> +    int size;
> +
> +    if (find_hw_breakpoint(addr, len, type)) {
> +        return -EEXIST;
> +    }
> +
> +    size = (nb_hw_breakpoints + 1) * sizeof(struct kvm_hw_breakpoint);
> +
> +    if (!hw_breakpoints) {
> +        nb_hw_breakpoints = 0;
> +        hw_breakpoints = (struct kvm_hw_breakpoint *)g_try_malloc(size);
> +    } else {
> +        hw_breakpoints =
> +            (struct kvm_hw_breakpoint *)g_try_realloc(hw_breakpoints, size);
> +    }

Phew. Is there really nothing like a dynamically growing array helper in 
glib?

> +
> +    if (!hw_breakpoints) {
> +        nb_hw_breakpoints = 0;
> +        return -ENOMEM;
> +    }
> +
> +    hw_breakpoints[nb_hw_breakpoints].addr = addr;
> +    hw_breakpoints[nb_hw_breakpoints].len = len;
> +    hw_breakpoints[nb_hw_breakpoints].type = type;
> +
> +    nb_hw_breakpoints++;
> +
> +    return 0;
> +}
> +
>   int kvm_arch_insert_hw_breakpoint(target_ulong addr,
>                                     target_ulong len, int type)
>   {
> -    return -ENOSYS;
> +    switch (type) {
> +    case GDB_BREAKPOINT_HW:
> +        type = KVM_HW_BP;
> +        break;
> +    case GDB_WATCHPOINT_WRITE:
> +        if (len < 1) {
> +            return -EINVAL;
> +        }
> +        type = KVM_HW_WP_WRITE;
> +        break;
> +    default:
> +        return -ENOSYS;
> +    }
> +    return insert_hw_breakpoint(addr, len, type);
>   }
>   
>   int kvm_arch_remove_hw_breakpoint(target_ulong addr,
>                                     target_ulong len, int type)
>   {
> -    return -ENOSYS;
> +    int size;
> +    struct kvm_hw_breakpoint *bp = find_hw_breakpoint(addr, len, type);
> +
> +    if (bp == NULL) {
> +        return -ENOENT;
> +    }
> +
> +    nb_hw_breakpoints--;
> +    if (nb_hw_breakpoints > 0) {
> +        /*
> +         * In order to trim the array, move the last element to the position to
> +         * be removed - if necessary.
> +         */
> +        if (bp != &hw_breakpoints[nb_hw_breakpoints]) {
> +            *bp = hw_breakpoints[nb_hw_breakpoints];
> +        }
> +        size = nb_hw_breakpoints * sizeof(struct kvm_hw_breakpoint);
> +        hw_breakpoints =
> +             (struct kvm_hw_breakpoint *)g_realloc(hw_breakpoints, size);
> +    } else {
> +        g_free(hw_breakpoints);
> +        hw_breakpoints = NULL;
> +    }
> +
> +    return 0;
>   }
>   
>   void kvm_arch_remove_all_hw_breakpoints(void)
>   {
> +    nb_hw_breakpoints = 0;
> +    g_free(hw_breakpoints);
> +    hw_breakpoints = NULL;
>   }
>   
>   void kvm_arch_update_guest_debug(CPUState *cpu, struct kvm_guest_debug *dbg)
>   {
> +    int i;
> +
> +    if (nb_hw_breakpoints > 0) {
> +        dbg->arch.nr_hw_bp = nb_hw_breakpoints;
> +        dbg->arch.hw_bp = hw_breakpoints;
> +
> +        for (i = 0; i < nb_hw_breakpoints; ++i) {
> +            hw_breakpoints[i].phys_addr = s390_cpu_get_phys_addr_debug(cpu,
> +                                                       hw_breakpoints[i].addr);
> +        }
> +        dbg->control |= KVM_GUESTDBG_ENABLE | KVM_GUESTDBG_USE_HW_BP;
> +    } else {
> +        dbg->arch.nr_hw_bp = 0;
> +        dbg->arch.hw_bp = NULL;
> +    }
>   }
>   
>   void kvm_arch_pre_run(CPUState *cpu, struct kvm_run *run)
> @@ -880,7 +991,36 @@ static int handle_tsch(S390CPU *cpu)
>   
>   static int kvm_arch_handle_debug_exit(S390CPU *cpu)
>   {
> -    return -ENOSYS;
> +    CPUState *cs = CPU(cpu);
> +    struct kvm_run *run = cs->kvm_run;
> +
> +    int ret = 0;
> +    struct kvm_debug_exit_arch *arch_info = &run->debug.arch;
> +
> +    switch (arch_info->type) {
> +    case KVM_HW_WP_WRITE:
> +        if (find_hw_breakpoint(arch_info->addr, -1, arch_info->type)) {
> +            cs->watchpoint_hit = &hw_watchpoint;
> +            hw_watchpoint.vaddr = arch_info->addr;
> +            hw_watchpoint.flags = BP_MEM_WRITE;
> +            ret = EXCP_DEBUG;
> +        }
> +        break;
> +    case KVM_HW_BP:
> +        if (find_hw_breakpoint(arch_info->addr, -1, arch_info->type)) {
> +            ret = EXCP_DEBUG;
> +        }
> +        break;
> +    case KVM_SINGLESTEP:
> +        if (cs->singlestep_enabled) {
> +            ret = EXCP_DEBUG;
> +        }
> +        break;
> +    default:
> +        ret = -ENOSYS;
> +    }
> +
> +    return ret;

What happens to the diag 501 now? Are we safe to just drop it?


Alex

>   }
>   
>   int kvm_arch_handle_exit(CPUState *cs, struct kvm_run *run)
Christian Borntraeger May 30, 2014, 8:57 a.m. UTC | #2
On 30/05/14 10:32, Alexander Graf wrote:

>> +    case KVM_HW_BP:
>> +        if (find_hw_breakpoint(arch_info->addr, -1, arch_info->type)) {
>> +            ret = EXCP_DEBUG;
>> +        }
>> +        break;
>> +    case KVM_SINGLESTEP:
>> +        if (cs->singlestep_enabled) {
>> +            ret = EXCP_DEBUG;
>> +        }
>> +        break;
>> +    default:
>> +        ret = -ENOSYS;
>> +    }
>> +
>> +    return ret;
> 
> What happens to the diag 501 now? Are we safe to just drop it?

There can only be a small number of HW breakpoints (basically only one from-to range on s390).
So gdb can (and will) use both (hbreak vs. break)
Alexander Graf May 30, 2014, 9:01 a.m. UTC | #3
On 30.05.14 10:57, Christian Borntraeger wrote:
> On 30/05/14 10:32, Alexander Graf wrote:
>
>>> +    case KVM_HW_BP:
>>> +        if (find_hw_breakpoint(arch_info->addr, -1, arch_info->type)) {
>>> +            ret = EXCP_DEBUG;
>>> +        }
>>> +        break;
>>> +    case KVM_SINGLESTEP:
>>> +        if (cs->singlestep_enabled) {
>>> +            ret = EXCP_DEBUG;
>>> +        }
>>> +        break;
>>> +    default:
>>> +        ret = -ENOSYS;
>>> +    }
>>> +
>>> +    return ret;
>> What happens to the diag 501 now? Are we safe to just drop it?
> There can only be a small number of HW breakpoints (basically only one from-to range on s390).
> So gdb can (and will) use both (hbreak vs. break)

Ah, let me explain what I'm referring to here. On x86 (and PPC, though 
the patches are still missing), we use a generic "breakpoint" 
instruction for sw breakpoints. The specific breakpoint interrupt 
generated by that instruction traps into KVM which forwards it to QEMU.

If QEMU now detects that it didn't put the breakpoint into place, it 
assumes that it's the guest that wanted the breakpoint to happen, so it 
deflects a breakpoint interrupt into the guest.

My question here is whether we need something similar on s390x. With 
DIAG, I think we're safe, as the guest can't expect that one to do 
anything useful, but if we want to switch to a 2-byte breakpoint 
instruction instead, it might make sense to implement the deflection 
mechanism.


Alex
Christian Borntraeger May 30, 2014, 9:12 a.m. UTC | #4
On 30/05/14 11:01, Alexander Graf wrote:
> 
> On 30.05.14 10:57, Christian Borntraeger wrote:
>> On 30/05/14 10:32, Alexander Graf wrote:
>>
>>>> +    case KVM_HW_BP:
>>>> +        if (find_hw_breakpoint(arch_info->addr, -1, arch_info->type)) {
>>>> +            ret = EXCP_DEBUG;
>>>> +        }
>>>> +        break;
>>>> +    case KVM_SINGLESTEP:
>>>> +        if (cs->singlestep_enabled) {
>>>> +            ret = EXCP_DEBUG;
>>>> +        }
>>>> +        break;
>>>> +    default:
>>>> +        ret = -ENOSYS;
>>>> +    }
>>>> +
>>>> +    return ret;
>>> What happens to the diag 501 now? Are we safe to just drop it?
>> There can only be a small number of HW breakpoints (basically only one from-to range on s390).
>> So gdb can (and will) use both (hbreak vs. break)
> 
> Ah, let me explain what I'm referring to here. On x86 (and PPC, though the patches are still missing), we use a generic "breakpoint" instruction for sw breakpoints. The specific breakpoint interrupt generated by that instruction traps into KVM which forwards it to QEMU.
> 
> If QEMU now detects that it didn't put the breakpoint into place, it assumes that it's the guest that wanted the breakpoint to happen, so it deflects a breakpoint interrupt into the guest.
> 
> My question here is whether we need something similar on s390x. With DIAG, I think we're safe, as the guest can't expect that one to do anything useful, but if we want to switch to a 2-byte breakpoint instruction instead, it might make sense to implement the deflection mechanism.

Oh, I though "What happens to the diag 501 now? Are we safe to just drop it?" was a question if we can get rid of the code.
Regarding deflection, yes if guest and host hardware breakpoints (PER) we need to handle that (The host kernel is doing that in filter_guest_per_event)
With software breakpoints: yes diag501 is safe to use. When we change the instruction later on then we have to see if we need deflection (could be).

Christian
David Hildenbrand June 4, 2014, 8:31 a.m. UTC | #5
> On 30/05/14 11:01, Alexander Graf wrote:
> > 
> > On 30.05.14 10:57, Christian Borntraeger wrote:
> >> On 30/05/14 10:32, Alexander Graf wrote:
> >>
> >>>> +    case KVM_HW_BP:
> >>>> +        if (find_hw_breakpoint(arch_info->addr, -1, arch_info->type)) {
> >>>> +            ret = EXCP_DEBUG;
> >>>> +        }
> >>>> +        break;
> >>>> +    case KVM_SINGLESTEP:
> >>>> +        if (cs->singlestep_enabled) {
> >>>> +            ret = EXCP_DEBUG;
> >>>> +        }
> >>>> +        break;
> >>>> +    default:
> >>>> +        ret = -ENOSYS;
> >>>> +    }
> >>>> +
> >>>> +    return ret;
> >>> What happens to the diag 501 now? Are we safe to just drop it?
> >> There can only be a small number of HW breakpoints (basically only one from-to range on s390).
> >> So gdb can (and will) use both (hbreak vs. break)
> > 
> > Ah, let me explain what I'm referring to here. On x86 (and PPC, though the patches are still missing), we use a generic "breakpoint" instruction for sw breakpoints. The specific breakpoint interrupt generated by that instruction traps into KVM which forwards it to QEMU.
> > 
> > If QEMU now detects that it didn't put the breakpoint into place, it assumes that it's the guest that wanted the breakpoint to happen, so it deflects a breakpoint interrupt into the guest.
> > 
> > My question here is whether we need something similar on s390x. With DIAG, I think we're safe, as the guest can't expect that one to do anything useful, but if we want to switch to a 2-byte breakpoint instruction instead, it might make sense to implement the deflection mechanism.
> 
> Oh, I though "What happens to the diag 501 now? Are we safe to just drop it?" was a question if we can get rid of the code.
> Regarding deflection, yes if guest and host hardware breakpoints (PER) we need to handle that (The host kernel is doing that in filter_guest_per_event)
> With software breakpoints: yes diag501 is safe to use. When we change the instruction later on then we have to see if we need deflection (could be).
> 
> Christian

Hi Alex,

I am already working on a solution for 2 byte software breakpoints.
The solution will most likely look like what we have on x86: A generic
breakpoint instruction (e.g. invalid opcode 0x0001) that is filtered in
QEMU. We'll need kernel support to allow invalid instructions to be
intercepted and handled in QEMU. I already have a prototype running.

David
diff mbox

Patch

diff --git a/target-s390x/cpu-qom.h b/target-s390x/cpu-qom.h
index ac0460e..f9c96d1 100644
--- a/target-s390x/cpu-qom.h
+++ b/target-s390x/cpu-qom.h
@@ -86,6 +86,7 @@  int s390_cpu_write_elf64_note(WriteCoreDumpFunction f, CPUState *cs,
 int s390_cpu_write_elf64_qemunote(WriteCoreDumpFunction f,
                                   CPUState *cpu, void *opaque);
 hwaddr s390_cpu_get_phys_page_debug(CPUState *cpu, vaddr addr);
+hwaddr s390_cpu_get_phys_addr_debug(CPUState *cpu, vaddr addr);
 int s390_cpu_gdb_read_register(CPUState *cpu, uint8_t *buf, int reg);
 int s390_cpu_gdb_write_register(CPUState *cpu, uint8_t *buf, int reg);
 
diff --git a/target-s390x/helper.c b/target-s390x/helper.c
index 7c76fc1..3d756ca 100644
--- a/target-s390x/helper.c
+++ b/target-s390x/helper.c
@@ -489,6 +489,18 @@  hwaddr s390_cpu_get_phys_page_debug(CPUState *cs, vaddr vaddr)
     return raddr;
 }
 
+hwaddr s390_cpu_get_phys_addr_debug(CPUState *cs, vaddr vaddr)
+{
+    hwaddr phys_addr;
+    target_ulong page;
+
+    page = vaddr & TARGET_PAGE_MASK;
+    phys_addr = cpu_get_phys_page_debug(cs, page);
+    phys_addr += (vaddr & ~TARGET_PAGE_MASK);
+
+    return phys_addr;
+}
+
 void load_psw(CPUS390XState *env, uint64_t mask, uint64_t addr)
 {
     if (mask & PSW_MASK_WAIT) {
diff --git a/target-s390x/kvm.c b/target-s390x/kvm.c
index bf62c95..50af3e6 100644
--- a/target-s390x/kvm.c
+++ b/target-s390x/kvm.c
@@ -36,6 +36,7 @@ 
 #include "sysemu/device_tree.h"
 #include "qapi/qmp/qjson.h"
 #include "monitor/monitor.h"
+#include "exec/gdbstub.h"
 #include "trace.h"
 
 /* #define DEBUG_KVM */
@@ -86,6 +87,14 @@ 
 #define ICPT_CPU_STOP                   0x28
 #define ICPT_IO                         0x40
 
+static CPUWatchpoint hw_watchpoint;
+/*
+ * We don't use a list because this structure is also used to transmit the
+ * hardware breakpoints to the kernel.
+ */
+static struct kvm_hw_breakpoint *hw_breakpoints;
+static int nb_hw_breakpoints;
+
 const KVMCapabilityInfo kvm_arch_required_capabilities[] = {
     KVM_CAP_LAST_INFO
 };
@@ -378,24 +387,126 @@  int kvm_arch_remove_sw_breakpoint(CPUState *cs, struct kvm_sw_breakpoint *bp)
     return 0;
 }
 
+static struct kvm_hw_breakpoint *find_hw_breakpoint(target_ulong addr,
+                                                    int len, int type)
+{
+    int n;
+
+    for (n = 0; n < nb_hw_breakpoints; n++) {
+        if (hw_breakpoints[n].addr == addr && hw_breakpoints[n].type == type &&
+            (hw_breakpoints[n].len == len || len == -1)) {
+            return &hw_breakpoints[n];
+        }
+    }
+
+    return NULL;
+}
+
+static int insert_hw_breakpoint(target_ulong addr, int len, int type)
+{
+    int size;
+
+    if (find_hw_breakpoint(addr, len, type)) {
+        return -EEXIST;
+    }
+
+    size = (nb_hw_breakpoints + 1) * sizeof(struct kvm_hw_breakpoint);
+
+    if (!hw_breakpoints) {
+        nb_hw_breakpoints = 0;
+        hw_breakpoints = (struct kvm_hw_breakpoint *)g_try_malloc(size);
+    } else {
+        hw_breakpoints =
+            (struct kvm_hw_breakpoint *)g_try_realloc(hw_breakpoints, size);
+    }
+
+    if (!hw_breakpoints) {
+        nb_hw_breakpoints = 0;
+        return -ENOMEM;
+    }
+
+    hw_breakpoints[nb_hw_breakpoints].addr = addr;
+    hw_breakpoints[nb_hw_breakpoints].len = len;
+    hw_breakpoints[nb_hw_breakpoints].type = type;
+
+    nb_hw_breakpoints++;
+
+    return 0;
+}
+
 int kvm_arch_insert_hw_breakpoint(target_ulong addr,
                                   target_ulong len, int type)
 {
-    return -ENOSYS;
+    switch (type) {
+    case GDB_BREAKPOINT_HW:
+        type = KVM_HW_BP;
+        break;
+    case GDB_WATCHPOINT_WRITE:
+        if (len < 1) {
+            return -EINVAL;
+        }
+        type = KVM_HW_WP_WRITE;
+        break;
+    default:
+        return -ENOSYS;
+    }
+    return insert_hw_breakpoint(addr, len, type);
 }
 
 int kvm_arch_remove_hw_breakpoint(target_ulong addr,
                                   target_ulong len, int type)
 {
-    return -ENOSYS;
+    int size;
+    struct kvm_hw_breakpoint *bp = find_hw_breakpoint(addr, len, type);
+
+    if (bp == NULL) {
+        return -ENOENT;
+    }
+
+    nb_hw_breakpoints--;
+    if (nb_hw_breakpoints > 0) {
+        /*
+         * In order to trim the array, move the last element to the position to
+         * be removed - if necessary.
+         */
+        if (bp != &hw_breakpoints[nb_hw_breakpoints]) {
+            *bp = hw_breakpoints[nb_hw_breakpoints];
+        }
+        size = nb_hw_breakpoints * sizeof(struct kvm_hw_breakpoint);
+        hw_breakpoints =
+             (struct kvm_hw_breakpoint *)g_realloc(hw_breakpoints, size);
+    } else {
+        g_free(hw_breakpoints);
+        hw_breakpoints = NULL;
+    }
+
+    return 0;
 }
 
 void kvm_arch_remove_all_hw_breakpoints(void)
 {
+    nb_hw_breakpoints = 0;
+    g_free(hw_breakpoints);
+    hw_breakpoints = NULL;
 }
 
 void kvm_arch_update_guest_debug(CPUState *cpu, struct kvm_guest_debug *dbg)
 {
+    int i;
+
+    if (nb_hw_breakpoints > 0) {
+        dbg->arch.nr_hw_bp = nb_hw_breakpoints;
+        dbg->arch.hw_bp = hw_breakpoints;
+
+        for (i = 0; i < nb_hw_breakpoints; ++i) {
+            hw_breakpoints[i].phys_addr = s390_cpu_get_phys_addr_debug(cpu,
+                                                       hw_breakpoints[i].addr);
+        }
+        dbg->control |= KVM_GUESTDBG_ENABLE | KVM_GUESTDBG_USE_HW_BP;
+    } else {
+        dbg->arch.nr_hw_bp = 0;
+        dbg->arch.hw_bp = NULL;
+    }
 }
 
 void kvm_arch_pre_run(CPUState *cpu, struct kvm_run *run)
@@ -880,7 +991,36 @@  static int handle_tsch(S390CPU *cpu)
 
 static int kvm_arch_handle_debug_exit(S390CPU *cpu)
 {
-    return -ENOSYS;
+    CPUState *cs = CPU(cpu);
+    struct kvm_run *run = cs->kvm_run;
+
+    int ret = 0;
+    struct kvm_debug_exit_arch *arch_info = &run->debug.arch;
+
+    switch (arch_info->type) {
+    case KVM_HW_WP_WRITE:
+        if (find_hw_breakpoint(arch_info->addr, -1, arch_info->type)) {
+            cs->watchpoint_hit = &hw_watchpoint;
+            hw_watchpoint.vaddr = arch_info->addr;
+            hw_watchpoint.flags = BP_MEM_WRITE;
+            ret = EXCP_DEBUG;
+        }
+        break;
+    case KVM_HW_BP:
+        if (find_hw_breakpoint(arch_info->addr, -1, arch_info->type)) {
+            ret = EXCP_DEBUG;
+        }
+        break;
+    case KVM_SINGLESTEP:
+        if (cs->singlestep_enabled) {
+            ret = EXCP_DEBUG;
+        }
+        break;
+    default:
+        ret = -ENOSYS;
+    }
+
+    return ret;
 }
 
 int kvm_arch_handle_exit(CPUState *cs, struct kvm_run *run)