git: ca96a942cafb - main - bhyve: refactor gdbstub to enable single-stepping on AMD CPUs

From: John Baldwin <jhb_at_FreeBSD.org>
Date: Tue, 12 Dec 2023 23:29:13 UTC
The branch main has been updated by jhb:

URL: https://cgit.FreeBSD.org/src/commit/?id=ca96a942cafb58476e10e887240e594e7923a6e8

commit ca96a942cafb58476e10e887240e594e7923a6e8
Author:     Bojan Novković <bojan.novkovic@fer.hr>
AuthorDate: 2023-12-12 23:28:59 +0000
Commit:     John Baldwin <jhb@FreeBSD.org>
CommitDate: 2023-12-12 23:28:59 +0000

    bhyve: refactor gdbstub to enable single-stepping on AMD CPUs
    
    This patch refactors the existing Intel-specific single-stepping
    mechanism in bhyve's GDB stub to work with both AMD and Intel CPUs.
    
    Reviewed by:    jhb
    Sponsored by:   Google, Inc. (GSoC 2022)
    Differential Revision: https://reviews.freebsd.org/D42298
---
 usr.sbin/bhyve/amd64/vmexit.c | 15 +++++++
 usr.sbin/bhyve/gdb.c          | 94 +++++++++++++++++++++++++++++++++++--------
 usr.sbin/bhyve/gdb.h          |  1 +
 3 files changed, 93 insertions(+), 17 deletions(-)

diff --git a/usr.sbin/bhyve/amd64/vmexit.c b/usr.sbin/bhyve/amd64/vmexit.c
index 2c01c63f6454..e0b9aec2d17a 100644
--- a/usr.sbin/bhyve/amd64/vmexit.c
+++ b/usr.sbin/bhyve/amd64/vmexit.c
@@ -439,6 +439,20 @@ vmexit_debug(struct vmctx *ctx __unused, struct vcpu *vcpu,
 	return (VMEXIT_CONTINUE);
 }
 
+static int
+vmexit_db(struct vmctx *ctx __unused, struct vcpu *vcpu, struct vm_run *vmrun)
+{
+
+#ifdef BHYVE_SNAPSHOT
+	checkpoint_cpu_suspend(vcpu_id(vcpu));
+#endif
+	gdb_cpu_debug(vcpu, vmrun->vm_exit);
+#ifdef BHYVE_SNAPSHOT
+	checkpoint_cpu_resume(vcpu_id(vcpu));
+#endif
+	return (VMEXIT_CONTINUE);
+}
+
 static int
 vmexit_breakpoint(struct vmctx *ctx __unused, struct vcpu *vcpu,
     struct vm_run *vmrun)
@@ -503,4 +517,5 @@ const vmexit_handler_t vmexit_handlers[VM_EXITCODE_MAX] = {
 	[VM_EXITCODE_IPI] = vmexit_ipi,
 	[VM_EXITCODE_HLT] = vmexit_hlt,
 	[VM_EXITCODE_PAUSE] = vmexit_pause,
+	[VM_EXITCODE_DB] = vmexit_db,
 };
diff --git a/usr.sbin/bhyve/gdb.c b/usr.sbin/bhyve/gdb.c
index be730a75b3e6..2d49469c2e11 100644
--- a/usr.sbin/bhyve/gdb.c
+++ b/usr.sbin/bhyve/gdb.c
@@ -743,6 +743,43 @@ _gdb_cpu_suspend(struct vcpu *vcpu, bool report_stop)
 	debug("$vCPU %d resuming\n", vcpuid);
 }
 
+/*
+ * Requests vCPU single-stepping using a
+ * VMEXIT suitable for the host platform.
+ */
+static int
+_gdb_set_step(struct vcpu *vcpu, int val)
+{
+	int error;
+
+	/*
+	 * If the MTRAP cap fails, we are running on an AMD host.
+	 * In that case, we request DB exits caused by RFLAGS.TF.
+	 */
+	error = vm_set_capability(vcpu, VM_CAP_MTRAP_EXIT, val);
+	if (error != 0)
+		error = vm_set_capability(vcpu, VM_CAP_RFLAGS_TF, val);
+	if (error == 0)
+		(void)vm_set_capability(vcpu, VM_CAP_MASK_HWINTR, val);
+
+	return (error);
+}
+
+/*
+ * Checks whether single-stepping is enabled for a given vCPU.
+ */
+static int
+_gdb_check_step(struct vcpu *vcpu)
+{
+	int val;
+
+	if (vm_get_capability(vcpu, VM_CAP_MTRAP_EXIT, &val) != 0) {
+		if (vm_get_capability(vcpu, VM_CAP_RFLAGS_TF, &val) != 0)
+			return -1;
+	}
+	return 0;
+}
+
 /*
  * Invoked at the start of a vCPU thread's execution to inform the
  * debug server about the new thread.
@@ -797,10 +834,7 @@ gdb_cpu_resume(struct vcpu *vcpu)
 	assert(vs->hit_swbreak == false);
 	assert(vs->stepped == false);
 	if (vs->stepping) {
-		error = vm_set_capability(vcpu, VM_CAP_MTRAP_EXIT, 1);
-		assert(error == 0);
-
-		error = vm_set_capability(vcpu, VM_CAP_MASK_HWINTR, 1);
+		error = _gdb_set_step(vcpu, 1);
 		assert(error == 0);
 	}
 }
@@ -835,26 +869,24 @@ gdb_suspend_vcpus(void)
 }
 
 /*
- * Handler for VM_EXITCODE_MTRAP reported when a vCPU single-steps via
- * the VT-x-specific MTRAP exit.
+ * Invoked each time a vmexit handler needs to step a vCPU.
+ * Handles MTRAP and RFLAGS.TF vmexits.
  */
-void
-gdb_cpu_mtrap(struct vcpu *vcpu)
+static void
+gdb_cpu_step(struct vcpu *vcpu)
 {
 	struct vcpu_state *vs;
-	int vcpuid;
+	int vcpuid = vcpu_id(vcpu);
+	int error;
 
-	if (!gdb_active)
-		return;
-	vcpuid = vcpu_id(vcpu);
-	debug("$vCPU %d MTRAP\n", vcpuid);
+	debug("$vCPU %d stepped\n", vcpuid);
 	pthread_mutex_lock(&gdb_lock);
 	vs = &vcpu_state[vcpuid];
 	if (vs->stepping) {
 		vs->stepping = false;
 		vs->stepped = true;
-		vm_set_capability(vcpu, VM_CAP_MTRAP_EXIT, 0);
-		vm_set_capability(vcpu, VM_CAP_MASK_HWINTR, 0);
+		error = _gdb_set_step(vcpu, 0);
+		assert(error == 0);
 
 		while (vs->stepped) {
 			if (stopped_vcpu == -1) {
@@ -869,6 +901,34 @@ gdb_cpu_mtrap(struct vcpu *vcpu)
 	pthread_mutex_unlock(&gdb_lock);
 }
 
+/*
+ * A general handler for VM_EXITCODE_DB.
+ * Handles RFLAGS.TF exits on AMD SVM.
+ */
+void
+gdb_cpu_debug(struct vcpu *vcpu, struct vm_exit *vmexit)
+{
+	if (!gdb_active)
+		return;
+
+	/* RFLAGS.TF exit? */
+	if (vmexit->u.dbg.trace_trap) {
+		gdb_cpu_step(vcpu);
+	}
+}
+
+/*
+ * Handler for VM_EXITCODE_MTRAP reported when a vCPU single-steps via
+ * the VT-x-specific MTRAP exit.
+ */
+void
+gdb_cpu_mtrap(struct vcpu *vcpu)
+{
+	if (!gdb_active)
+		return;
+	gdb_cpu_step(vcpu);
+}
+
 static struct breakpoint *
 find_breakpoint(uint64_t gpa)
 {
@@ -940,11 +1000,11 @@ gdb_cpu_breakpoint(struct vcpu *vcpu, struct vm_exit *vmexit)
 static bool
 gdb_step_vcpu(struct vcpu *vcpu)
 {
-	int error, val, vcpuid;
+	int error, vcpuid;
 
 	vcpuid = vcpu_id(vcpu);
 	debug("$vCPU %d step\n", vcpuid);
-	error = vm_get_capability(vcpu, VM_CAP_MTRAP_EXIT, &val);
+	error = _gdb_check_step(vcpu);
 	if (error < 0)
 		return (false);
 
diff --git a/usr.sbin/bhyve/gdb.h b/usr.sbin/bhyve/gdb.h
index f06375d0d591..98f9ece2f60c 100644
--- a/usr.sbin/bhyve/gdb.h
+++ b/usr.sbin/bhyve/gdb.h
@@ -32,6 +32,7 @@ void	gdb_cpu_add(struct vcpu *vcpu);
 void	gdb_cpu_breakpoint(struct vcpu *vcpu, struct vm_exit *vmexit);
 void	gdb_cpu_mtrap(struct vcpu *vcpu);
 void	gdb_cpu_suspend(struct vcpu *vcpu);
+void	gdb_cpu_debug(struct vcpu *vcpu, struct vm_exit *vmexit);
 void	init_gdb(struct vmctx *ctx);
 
 #endif /* !__GDB_H__ */