arm64/vmm: Add breakpoint and single-stepping support

This will be used to implement parts of bhyve's gdb stub.

Three VM capabilities are added, similar to amd64 without monitor mode.
Two cause breakpoint and single-step exceptions to be raised to EL2 and
then down to bhyve.  One lets the gdb stub mask hardware interrupts
while single-stepping, since otherwise the guest will handle a timer
interrupt before executing the target instruction and thus fail
to make progress.

Reviewed by:	bnovkov, andrew
Sponsored by:	Innovate UK
Differential Revision:	https://reviews.freebsd.org/D44739
This commit is contained in:
Mark Johnston 2024-06-04 14:58:08 -04:00
parent afa166be99
commit 75cb949228
5 changed files with 95 additions and 3 deletions

View file

@ -295,9 +295,11 @@ struct vre {
*/
enum vm_cap_type {
VM_CAP_HALT_EXIT,
VM_CAP_MTRAP_EXIT,
VM_CAP_PAUSE_EXIT,
VM_CAP_UNRESTRICTED_GUEST,
VM_CAP_BRK_EXIT,
VM_CAP_SS_EXIT,
VM_CAP_MASK_HWINTR,
VM_CAP_MAX
};
@ -312,6 +314,8 @@ enum vm_exitcode {
VM_EXITCODE_PAGING,
VM_EXITCODE_SMCCC,
VM_EXITCODE_DEBUG,
VM_EXITCODE_BRK,
VM_EXITCODE_SS,
VM_EXITCODE_MAX
};

View file

@ -39,6 +39,9 @@
struct vgic_v3;
struct vgic_v3_cpu;
/*
* Per-vCPU hypervisor state.
*/
struct hypctx {
struct trapframe tf;
@ -104,6 +107,12 @@ struct hypctx {
struct vtimer_cpu vtimer_cpu;
uint64_t setcaps; /* Currently enabled capabilities. */
/* vCPU state used to handle guest debugging. */
uint64_t debug_spsr; /* Saved guest SPSR */
uint64_t debug_mdscr; /* Saved guest MDSCR */
struct vgic_v3_regs vgic_v3_regs;
struct vgic_v3_cpu *vgic_cpu;
bool has_exception;

View file

@ -700,7 +700,14 @@ handle_el1_sync_excp(struct hypctx *hypctx, struct vm_exit *vme_ret,
arm64_gen_reg_emul_data(esr_iss, vme_ret);
vme_ret->exitcode = VM_EXITCODE_REG_EMUL;
break;
case EXCP_BRK:
vmm_stat_incr(hypctx->vcpu, VMEXIT_BRK, 1);
vme_ret->exitcode = VM_EXITCODE_BRK;
break;
case EXCP_SOFTSTP_EL0:
vmm_stat_incr(hypctx->vcpu, VMEXIT_SS, 1);
vme_ret->exitcode = VM_EXITCODE_SS;
break;
case EXCP_INSN_ABORT_L:
case EXCP_DATA_ABORT_L:
vmm_stat_incr(hypctx->vcpu, esr_ec == EXCP_DATA_ABORT_L ?
@ -1313,6 +1320,7 @@ vmmops_exception(void *vcpui, uint64_t esr, uint64_t far)
int
vmmops_getcap(void *vcpui, int num, int *retval)
{
struct hypctx *hypctx = vcpui;
int ret;
ret = ENOENT;
@ -1322,6 +1330,11 @@ vmmops_getcap(void *vcpui, int num, int *retval)
*retval = 1;
ret = 0;
break;
case VM_CAP_BRK_EXIT:
case VM_CAP_SS_EXIT:
case VM_CAP_MASK_HWINTR:
*retval = (hypctx->setcaps & (1ul << num)) != 0;
break;
default:
break;
}
@ -1332,6 +1345,68 @@ vmmops_getcap(void *vcpui, int num, int *retval)
int
vmmops_setcap(void *vcpui, int num, int val)
{
struct hypctx *hypctx = vcpui;
int ret;
return (ENOENT);
ret = 0;
switch (num) {
case VM_CAP_BRK_EXIT:
if ((val != 0) == (hypctx->setcaps & (1ul << num)) != 0)
break;
if (val != 0)
hypctx->mdcr_el2 |= MDCR_EL2_TDE;
else
hypctx->mdcr_el2 &= ~MDCR_EL2_TDE;
break;
case VM_CAP_SS_EXIT:
if ((val != 0) == (hypctx->setcaps & (1ul << num)) != 0)
break;
if (val != 0) {
hypctx->debug_spsr |= (hypctx->tf.tf_spsr & PSR_SS);
hypctx->debug_mdscr |= hypctx->mdscr_el1 &
(MDSCR_SS | MDSCR_KDE);
hypctx->tf.tf_spsr |= PSR_SS;
hypctx->mdscr_el1 |= MDSCR_SS | MDSCR_KDE;
hypctx->mdcr_el2 |= MDCR_EL2_TDE;
} else {
hypctx->tf.tf_spsr &= ~PSR_SS;
hypctx->tf.tf_spsr |= hypctx->debug_spsr;
hypctx->debug_spsr &= ~PSR_SS;
hypctx->mdscr_el1 &= ~(MDSCR_SS | MDSCR_KDE);
hypctx->mdscr_el1 |= hypctx->debug_mdscr;
hypctx->debug_mdscr &= ~(MDSCR_SS | MDSCR_KDE);
hypctx->mdcr_el2 &= ~MDCR_EL2_TDE;
}
break;
case VM_CAP_MASK_HWINTR:
if ((val != 0) == (hypctx->setcaps & (1ul << num)) != 0)
break;
if (val != 0) {
hypctx->debug_spsr |= (hypctx->tf.tf_spsr &
(PSR_I | PSR_F));
hypctx->tf.tf_spsr |= PSR_I | PSR_F;
} else {
hypctx->tf.tf_spsr &= ~(PSR_I | PSR_F);
hypctx->tf.tf_spsr |= (hypctx->debug_spsr &
(PSR_I | PSR_F));
hypctx->debug_spsr &= ~(PSR_I | PSR_F);
}
break;
default:
ret = ENOENT;
break;
}
if (ret == 0) {
if (val == 0)
hypctx->setcaps &= ~(1ul << num);
else
hypctx->setcaps |= (1ul << num);
}
return (ret);
}

View file

@ -161,5 +161,7 @@ VMM_STAT(VMEXIT_INSN_ABORT, "number of vmexits for an instruction abort");
VMM_STAT(VMEXIT_UNHANDLED_SYNC, "number of vmexits for an unhandled synchronous exception");
VMM_STAT(VMEXIT_IRQ, "number of vmexits for an irq");
VMM_STAT(VMEXIT_FIQ, "number of vmexits for an interrupt");
VMM_STAT(VMEXIT_BRK, "number of vmexits for a breakpoint exception");
VMM_STAT(VMEXIT_SS, "number of vmexits for a single-step exception");
VMM_STAT(VMEXIT_UNHANDLED_EL2, "number of vmexits for an unhandled EL2 exception");
VMM_STAT(VMEXIT_UNHANDLED, "number of vmexits for an unhandled exception");

View file

@ -140,6 +140,8 @@ VMM_STAT_DECLARE(VMEXIT_INSN_ABORT);
VMM_STAT_DECLARE(VMEXIT_UNHANDLED_SYNC);
VMM_STAT_DECLARE(VMEXIT_IRQ);
VMM_STAT_DECLARE(VMEXIT_FIQ);
VMM_STAT_DECLARE(VMEXIT_BRK);
VMM_STAT_DECLARE(VMEXIT_SS);
VMM_STAT_DECLARE(VMEXIT_UNHANDLED_EL2);
VMM_STAT_DECLARE(VMEXIT_UNHANDLED);
#endif