[v2,1/6] KVM: s390: interrupt: Fix single-stepping into interrupt handlers

Message ID 20230721120046.2262291-2-iii@linux.ibm.com
State New
Headers
Series KVM: s390: interrupt: Fix stepping into interrupt handlers |

Commit Message

Ilya Leoshkevich July 21, 2023, 11:57 a.m. UTC
  After single-stepping an instruction that generates an interrupt, GDB
ends up on the second instruction of the respective interrupt handler.

The reason is that vcpu_pre_run() manually delivers the interrupt, and
then __vcpu_run() runs the first handler instruction using the
CPUSTAT_P flag. This causes a KVM_SINGLESTEP exit on the second handler
instruction.

Fix by delaying the KVM_SINGLESTEP exit until after the manual
interrupt delivery.

Signed-off-by: Ilya Leoshkevich <iii@linux.ibm.com>
---
 arch/s390/kvm/interrupt.c | 10 ++++++++++
 arch/s390/kvm/kvm-s390.c  |  4 ++--
 2 files changed, 12 insertions(+), 2 deletions(-)
  

Comments

David Hildenbrand July 24, 2023, 8:22 a.m. UTC | #1
On 21.07.23 13:57, Ilya Leoshkevich wrote:
> After single-stepping an instruction that generates an interrupt, GDB
> ends up on the second instruction of the respective interrupt handler.
> 
> The reason is that vcpu_pre_run() manually delivers the interrupt, and
> then __vcpu_run() runs the first handler instruction using the
> CPUSTAT_P flag. This causes a KVM_SINGLESTEP exit on the second handler
> instruction.
> 
> Fix by delaying the KVM_SINGLESTEP exit until after the manual
> interrupt delivery.
> 
> Signed-off-by: Ilya Leoshkevich <iii@linux.ibm.com>
> ---
>   arch/s390/kvm/interrupt.c | 10 ++++++++++
>   arch/s390/kvm/kvm-s390.c  |  4 ++--
>   2 files changed, 12 insertions(+), 2 deletions(-)
> 
> diff --git a/arch/s390/kvm/interrupt.c b/arch/s390/kvm/interrupt.c
> index 9bd0a873f3b1..2cebe4227b8e 100644
> --- a/arch/s390/kvm/interrupt.c
> +++ b/arch/s390/kvm/interrupt.c
> @@ -1392,6 +1392,7 @@ int __must_check kvm_s390_deliver_pending_interrupts(struct kvm_vcpu *vcpu)
>   {
>   	struct kvm_s390_local_interrupt *li = &vcpu->arch.local_int;
>   	int rc = 0;
> +	bool delivered = false;
>   	unsigned long irq_type;
>   	unsigned long irqs;
>   
> @@ -1465,6 +1466,15 @@ int __must_check kvm_s390_deliver_pending_interrupts(struct kvm_vcpu *vcpu)
>   			WARN_ONCE(1, "Unknown pending irq type %ld", irq_type);
>   			clear_bit(irq_type, &li->pending_irqs);
>   		}
> +		delivered |= !rc;
> +	}
> +


Can we add a comment like

/*
  * We delivered at least one interrupt and modified the PC. Force a
  * singlestep event now.
  */

> +	if (delivered && guestdbg_sstep_enabled(vcpu)) {
> +		struct kvm_debug_exit_arch *debug_exit = &vcpu->run->debug.arch;
> +
> +		debug_exit->addr = vcpu->arch.sie_block->gpsw.addr;
> +		debug_exit->type = KVM_SINGLESTEP;
> +		vcpu->guest_debug |= KVM_GUESTDBG_EXIT_PENDING;
>   	}

I do wonder if we, instead, want to do this whenever we modify the PSW.

That way we could catch any PC changes and only have to add checks for 
guestdbg_exit_pending().


But this is simpler and should work as well.

Acked-by: David Hildenbrand <david@redhat.com>
  
Ilya Leoshkevich July 24, 2023, 8:42 a.m. UTC | #2
On Mon, 2023-07-24 at 10:22 +0200, David Hildenbrand wrote:
> On 21.07.23 13:57, Ilya Leoshkevich wrote:
> > After single-stepping an instruction that generates an interrupt,
> > GDB
> > ends up on the second instruction of the respective interrupt
> > handler.
> > 
> > The reason is that vcpu_pre_run() manually delivers the interrupt,
> > and
> > then __vcpu_run() runs the first handler instruction using the
> > CPUSTAT_P flag. This causes a KVM_SINGLESTEP exit on the second
> > handler
> > instruction.
> > 
> > Fix by delaying the KVM_SINGLESTEP exit until after the manual
> > interrupt delivery.
> > 
> > Signed-off-by: Ilya Leoshkevich <iii@linux.ibm.com>
> > ---
> >   arch/s390/kvm/interrupt.c | 10 ++++++++++
> >   arch/s390/kvm/kvm-s390.c  |  4 ++--
> >   2 files changed, 12 insertions(+), 2 deletions(-)

[...]
> 

> Can we add a comment like
> 
> /*
>   * We delivered at least one interrupt and modified the PC. Force a
>   * singlestep event now.
>   */

Ok, will do.

> > +       if (delivered && guestdbg_sstep_enabled(vcpu)) {
> > +               struct kvm_debug_exit_arch *debug_exit = &vcpu-
> > >run->debug.arch;
> > +
> > +               debug_exit->addr = vcpu->arch.sie_block->gpsw.addr;
> > +               debug_exit->type = KVM_SINGLESTEP;
> > +               vcpu->guest_debug |= KVM_GUESTDBG_EXIT_PENDING;
> >         }
> 
> I do wonder if we, instead, want to do this whenever we modify the
> PSW.
> 
> That way we could catch any PC changes and only have to add checks
> for 
> guestdbg_exit_pending().

Wouldn't this break a corner case where the first instruction of the
interrupt handler causes the same interrupt?

> But this is simpler and should work as well.
> 
> Acked-by: David Hildenbrand <david@redhat.com>
  
David Hildenbrand July 24, 2023, 8:56 a.m. UTC | #3
On 24.07.23 10:42, Ilya Leoshkevich wrote:
> On Mon, 2023-07-24 at 10:22 +0200, David Hildenbrand wrote:
>> On 21.07.23 13:57, Ilya Leoshkevich wrote:
>>> After single-stepping an instruction that generates an interrupt,
>>> GDB
>>> ends up on the second instruction of the respective interrupt
>>> handler.
>>>
>>> The reason is that vcpu_pre_run() manually delivers the interrupt,
>>> and
>>> then __vcpu_run() runs the first handler instruction using the
>>> CPUSTAT_P flag. This causes a KVM_SINGLESTEP exit on the second
>>> handler
>>> instruction.
>>>
>>> Fix by delaying the KVM_SINGLESTEP exit until after the manual
>>> interrupt delivery.
>>>
>>> Signed-off-by: Ilya Leoshkevich <iii@linux.ibm.com>
>>> ---
>>>    arch/s390/kvm/interrupt.c | 10 ++++++++++
>>>    arch/s390/kvm/kvm-s390.c  |  4 ++--
>>>    2 files changed, 12 insertions(+), 2 deletions(-)
> 
> [...]
>>
> 
>> Can we add a comment like
>>
>> /*
>>    * We delivered at least one interrupt and modified the PC. Force a
>>    * singlestep event now.
>>    */
> 
> Ok, will do.
> 
>>> +       if (delivered && guestdbg_sstep_enabled(vcpu)) {
>>> +               struct kvm_debug_exit_arch *debug_exit = &vcpu-
>>>> run->debug.arch;
>>> +
>>> +               debug_exit->addr = vcpu->arch.sie_block->gpsw.addr;
>>> +               debug_exit->type = KVM_SINGLESTEP;
>>> +               vcpu->guest_debug |= KVM_GUESTDBG_EXIT_PENDING;
>>>          }
>>
>> I do wonder if we, instead, want to do this whenever we modify the
>> PSW.
>>
>> That way we could catch any PC changes and only have to add checks
>> for
>> guestdbg_exit_pending().
> 
> Wouldn't this break a corner case where the first instruction of the
> interrupt handler causes the same interrupt?

Could be, there are many possible corner cases (PGM interrupt at the 
first instruction of PGM interrupt handler -- our PSW address might not 
even change)
  

Patch

diff --git a/arch/s390/kvm/interrupt.c b/arch/s390/kvm/interrupt.c
index 9bd0a873f3b1..2cebe4227b8e 100644
--- a/arch/s390/kvm/interrupt.c
+++ b/arch/s390/kvm/interrupt.c
@@ -1392,6 +1392,7 @@  int __must_check kvm_s390_deliver_pending_interrupts(struct kvm_vcpu *vcpu)
 {
 	struct kvm_s390_local_interrupt *li = &vcpu->arch.local_int;
 	int rc = 0;
+	bool delivered = false;
 	unsigned long irq_type;
 	unsigned long irqs;
 
@@ -1465,6 +1466,15 @@  int __must_check kvm_s390_deliver_pending_interrupts(struct kvm_vcpu *vcpu)
 			WARN_ONCE(1, "Unknown pending irq type %ld", irq_type);
 			clear_bit(irq_type, &li->pending_irqs);
 		}
+		delivered |= !rc;
+	}
+
+	if (delivered && guestdbg_sstep_enabled(vcpu)) {
+		struct kvm_debug_exit_arch *debug_exit = &vcpu->run->debug.arch;
+
+		debug_exit->addr = vcpu->arch.sie_block->gpsw.addr;
+		debug_exit->type = KVM_SINGLESTEP;
+		vcpu->guest_debug |= KVM_GUESTDBG_EXIT_PENDING;
 	}
 
 	set_intercept_indicators(vcpu);
diff --git a/arch/s390/kvm/kvm-s390.c b/arch/s390/kvm/kvm-s390.c
index d1e768bcfe1d..0c6333b108ba 100644
--- a/arch/s390/kvm/kvm-s390.c
+++ b/arch/s390/kvm/kvm-s390.c
@@ -4611,7 +4611,7 @@  static int vcpu_pre_run(struct kvm_vcpu *vcpu)
 
 	if (!kvm_is_ucontrol(vcpu->kvm)) {
 		rc = kvm_s390_deliver_pending_interrupts(vcpu);
-		if (rc)
+		if (rc || guestdbg_exit_pending(vcpu))
 			return rc;
 	}
 
@@ -4738,7 +4738,7 @@  static int __vcpu_run(struct kvm_vcpu *vcpu)
 
 	do {
 		rc = vcpu_pre_run(vcpu);
-		if (rc)
+		if (rc || guestdbg_exit_pending(vcpu))
 			break;
 
 		kvm_vcpu_srcu_read_unlock(vcpu);