git: a0ca4af9455b - main - bhyve: Add arm64 support to the gdb stub

From: Mark Johnston <markj_at_FreeBSD.org>
Date: Tue, 04 Jun 2024 20:06:31 UTC
The branch main has been updated by markj:

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

commit a0ca4af9455b844c5e094fc1b09b1390ffa979fc
Author:     Mark Johnston <markj@FreeBSD.org>
AuthorDate: 2024-06-04 19:03:17 +0000
Commit:     Mark Johnston <markj@FreeBSD.org>
CommitDate: 2024-06-04 19:03:17 +0000

    bhyve: Add arm64 support to the gdb stub
    
    - Add -G to the arm64 getopt handler.
    - Add static register definitions and extensible XML definitions.
    - Provide definitions for MD bits such as breakpoint encoding and
      length.
    - Ensure that bhyve re-injects breakpoint exceptions that it is not
      responsible for.
    
    Reviewed by:    andrew
    Sponsored by:   Innovate UK
    Differential Revision:  https://reviews.freebsd.org/D44740
---
 usr.sbin/bhyve/aarch64/Makefile.inc       |   1 +
 usr.sbin/bhyve/aarch64/bhyverun_machdep.c |   6 +-
 usr.sbin/bhyve/aarch64/vmexit.c           |  20 +++-
 usr.sbin/bhyve/gdb.c                      | 151 +++++++++++++++++++++++++++---
 usr.sbin/bhyve/gdb/Makefile               |   3 +
 usr.sbin/bhyve/gdb/aarch64-core.xml       |  46 +++++++++
 6 files changed, 213 insertions(+), 14 deletions(-)

diff --git a/usr.sbin/bhyve/aarch64/Makefile.inc b/usr.sbin/bhyve/aarch64/Makefile.inc
index e2ea4414ca19..af458e719d44 100644
--- a/usr.sbin/bhyve/aarch64/Makefile.inc
+++ b/usr.sbin/bhyve/aarch64/Makefile.inc
@@ -7,3 +7,4 @@ SRCS+=	\
 SRCS+=	vmm_instruction_emul.c
 
 BHYVE_FDT_SUPPORT=
+BHYVE_GDB_SUPPORT=
diff --git a/usr.sbin/bhyve/aarch64/bhyverun_machdep.c b/usr.sbin/bhyve/aarch64/bhyverun_machdep.c
index a5fd3f054706..fbaba3128def 100644
--- a/usr.sbin/bhyve/aarch64/bhyverun_machdep.c
+++ b/usr.sbin/bhyve/aarch64/bhyverun_machdep.c
@@ -99,6 +99,7 @@ bhyve_usage(int code)
 	    "       -C: include guest memory in core file\n"
 	    "       -c: number of CPUs and/or topology specification\n"
 	    "       -D: destroy on power-off\n"
+	    "       -G: start a debug server\n"
 	    "       -h: help\n"
 	    "       -k: key=value flat config file\n"
 	    "       -m: memory size\n"
@@ -119,7 +120,7 @@ bhyve_optparse(int argc, char **argv)
 	const char *optstr;
 	int c;
 
-	optstr = "hCDSWk:f:o:p:c:s:m:U:";
+	optstr = "hCDSWk:f:o:p:G:c:s:m:U:";
 	while ((c = getopt(argc, argv, optstr)) != -1) {
 		switch (c) {
 		case 'c':
@@ -134,6 +135,9 @@ bhyve_optparse(int argc, char **argv)
 		case 'D':
 			set_config_bool("destroy_on_poweroff", true);
 			break;
+		case 'G':
+			bhyve_parse_gdb_options(optarg);
+			break;
 		case 'k':
 			bhyve_parse_simple_config_file(optarg);
 			break;
diff --git a/usr.sbin/bhyve/aarch64/vmexit.c b/usr.sbin/bhyve/aarch64/vmexit.c
index 0d328ab4ff85..03fc2f8b21e3 100644
--- a/usr.sbin/bhyve/aarch64/vmexit.c
+++ b/usr.sbin/bhyve/aarch64/vmexit.c
@@ -49,6 +49,7 @@
 #include "bhyverun.h"
 #include "config.h"
 #include "debug.h"
+#include "gdb.h"
 #include "mem.h"
 #include "vmexit.h"
 
@@ -112,9 +113,10 @@ vmexit_suspend(struct vmctx *ctx, struct vcpu *vcpu, struct vm_run *vmrun)
 }
 
 static int
-vmexit_debug(struct vmctx *ctx __unused, struct vcpu *vcpu __unused,
+vmexit_debug(struct vmctx *ctx __unused, struct vcpu *vcpu,
     struct vm_run *vmrun __unused)
 {
+	gdb_cpu_suspend(vcpu);
 	return (VMEXIT_CONTINUE);
 }
 
@@ -250,6 +252,20 @@ vmexit_hyp(struct vmctx *ctx __unused, struct vcpu *vcpu __unused,
 	return (VMEXIT_ABORT);
 }
 
+static int
+vmexit_brk(struct vmctx *ctx __unused, struct vcpu *vcpu, struct vm_run *vmrun)
+{
+	gdb_cpu_breakpoint(vcpu, vmrun->vm_exit);
+	return (VMEXIT_CONTINUE);
+}
+
+static int
+vmexit_ss(struct vmctx *ctx __unused, struct vcpu *vcpu, struct vm_run *vmrun)
+{
+	gdb_cpu_debug(vcpu, vmrun->vm_exit);
+	return (VMEXIT_CONTINUE);
+}
+
 const vmexit_handler_t vmexit_handlers[VM_EXITCODE_MAX] = {
 	[VM_EXITCODE_BOGUS]  = vmexit_bogus,
 	[VM_EXITCODE_INST_EMUL] = vmexit_inst_emul,
@@ -257,4 +273,6 @@ const vmexit_handler_t vmexit_handlers[VM_EXITCODE_MAX] = {
 	[VM_EXITCODE_DEBUG] = vmexit_debug,
 	[VM_EXITCODE_SMCCC] = vmexit_smccc,
 	[VM_EXITCODE_HYP] = vmexit_hyp,
+	[VM_EXITCODE_BRK] = vmexit_brk,
+	[VM_EXITCODE_SS] = vmexit_ss,
 };
diff --git a/usr.sbin/bhyve/gdb.c b/usr.sbin/bhyve/gdb.c
index 479ad407dbef..6df8026a0245 100644
--- a/usr.sbin/bhyve/gdb.c
+++ b/usr.sbin/bhyve/gdb.c
@@ -36,10 +36,17 @@
 #include <sys/socket.h>
 #include <sys/stat.h>
 
+#ifdef __aarch64__
+#include <machine/armreg.h>
+#endif
 #include <machine/atomic.h>
+#ifdef __amd64__
 #include <machine/specialreg.h>
+#endif
 #include <machine/vmm.h>
+
 #include <netinet/in.h>
+
 #include <assert.h>
 #ifndef WITHOUT_CAPSICUM
 #include <capsicum_helpers.h>
@@ -73,9 +80,19 @@
  */
 #define	GDB_SIGNAL_TRAP		5
 
+#if defined(__amd64__)
 #define	GDB_BP_SIZE		1
 #define	GDB_BP_INSTR		(uint8_t []){0xcc}
 #define	GDB_PC_REGNAME		VM_REG_GUEST_RIP
+#define	GDB_BREAKPOINT_CAP	VM_CAP_BPT_EXIT
+#elif defined(__aarch64__)
+#define	GDB_BP_SIZE		4
+#define	GDB_BP_INSTR		(uint8_t []){0x00, 0x00, 0x20, 0xd4}
+#define	GDB_PC_REGNAME		VM_REG_GUEST_PC
+#define	GDB_BREAKPOINT_CAP	VM_CAP_BRK_EXIT
+#else
+#error "Unsupported architecture"
+#endif
 
 _Static_assert(sizeof(GDB_BP_INSTR) == GDB_BP_SIZE,
     "GDB_BP_INSTR has wrong size");
@@ -146,10 +163,13 @@ static struct vcpu **vcpus;
 static int cur_vcpu, stopped_vcpu;
 static bool gdb_active = false;
 
-static const struct gdb_reg {
+struct gdb_reg {
 	enum vm_reg_name id;
 	int size;
-} gdb_regset[] = {
+}
+
+#ifdef __amd64__
+static const gdb_regset[] = {
 	{ .id = VM_REG_GUEST_RAX, .size = 8 },
 	{ .id = VM_REG_GUEST_RBX, .size = 8 },
 	{ .id = VM_REG_GUEST_RCX, .size = 8 },
@@ -191,6 +211,44 @@ static const struct gdb_reg {
 	{ .id = VM_REG_GUEST_TPR, .size = 8 },
 	{ .id = VM_REG_GUEST_EFER, .size = 8 },
 };
+#else /* __aarch64__ */
+static const gdb_regset[] = {
+	{ .id = VM_REG_GUEST_X0, .size = 8 },
+	{ .id = VM_REG_GUEST_X1, .size = 8 },
+	{ .id = VM_REG_GUEST_X2, .size = 8 },
+	{ .id = VM_REG_GUEST_X3, .size = 8 },
+	{ .id = VM_REG_GUEST_X4, .size = 8 },
+	{ .id = VM_REG_GUEST_X5, .size = 8 },
+	{ .id = VM_REG_GUEST_X6, .size = 8 },
+	{ .id = VM_REG_GUEST_X7, .size = 8 },
+	{ .id = VM_REG_GUEST_X8, .size = 8 },
+	{ .id = VM_REG_GUEST_X9, .size = 8 },
+	{ .id = VM_REG_GUEST_X10, .size = 8 },
+	{ .id = VM_REG_GUEST_X11, .size = 8 },
+	{ .id = VM_REG_GUEST_X12, .size = 8 },
+	{ .id = VM_REG_GUEST_X13, .size = 8 },
+	{ .id = VM_REG_GUEST_X14, .size = 8 },
+	{ .id = VM_REG_GUEST_X15, .size = 8 },
+	{ .id = VM_REG_GUEST_X16, .size = 8 },
+	{ .id = VM_REG_GUEST_X17, .size = 8 },
+	{ .id = VM_REG_GUEST_X18, .size = 8 },
+	{ .id = VM_REG_GUEST_X19, .size = 8 },
+	{ .id = VM_REG_GUEST_X20, .size = 8 },
+	{ .id = VM_REG_GUEST_X21, .size = 8 },
+	{ .id = VM_REG_GUEST_X22, .size = 8 },
+	{ .id = VM_REG_GUEST_X23, .size = 8 },
+	{ .id = VM_REG_GUEST_X24, .size = 8 },
+	{ .id = VM_REG_GUEST_X25, .size = 8 },
+	{ .id = VM_REG_GUEST_X26, .size = 8 },
+	{ .id = VM_REG_GUEST_X27, .size = 8 },
+	{ .id = VM_REG_GUEST_X28, .size = 8 },
+	{ .id = VM_REG_GUEST_X29, .size = 8 },
+	{ .id = VM_REG_GUEST_LR, .size = 8 },
+	{ .id = VM_REG_GUEST_SP, .size = 8 },
+	{ .id = VM_REG_GUEST_PC, .size = 8 },
+	{ .id = VM_REG_GUEST_CPSR, .size = 8 },
+};
+#endif
 
 #ifdef GDB_LOG
 #include <stdarg.h>
@@ -228,6 +286,7 @@ static void	remove_all_sw_breakpoints(void);
 static int
 guest_paging_info(struct vcpu *vcpu, struct vm_guest_paging *paging)
 {
+#ifdef __amd64__
 	uint64_t regs[4];
 	const int regset[4] = {
 		VM_REG_GUEST_CR0,
@@ -262,6 +321,31 @@ guest_paging_info(struct vcpu *vcpu, struct vm_guest_paging *paging)
 	else
 		paging->paging_mode = PAGING_MODE_PAE;
 	return (0);
+#else /* __aarch64__ */
+	uint64_t regs[6];
+	const int regset[6] = {
+		VM_REG_GUEST_TTBR0_EL1,
+		VM_REG_GUEST_TTBR1_EL1,
+		VM_REG_GUEST_TCR_EL1,
+		VM_REG_GUEST_TCR2_EL1,
+		VM_REG_GUEST_SCTLR_EL1,
+		VM_REG_GUEST_CPSR,
+	};
+
+	if (vm_get_register_set(vcpu, nitems(regset), regset, regs) == -1)
+		return (-1);
+
+	memset(paging, 0, sizeof(*paging));
+	paging->ttbr0_addr = regs[0] & ~(TTBR_ASID_MASK | TTBR_CnP);
+	paging->ttbr1_addr = regs[1] & ~(TTBR_ASID_MASK | TTBR_CnP);
+	paging->tcr_el1 = regs[2];
+	paging->tcr2_el1 = regs[3];
+	paging->flags = regs[5] & (PSR_M_MASK | PSR_M_32);
+	if ((regs[4] & SCTLR_M) != 0)
+		paging->flags |= VM_GP_MMU_ENABLED;
+
+	return (0);
+#endif /* __aarch64__ */
 }
 
 /*
@@ -294,7 +378,11 @@ guest_vaddr2paddr(struct vcpu *vcpu, uint64_t vaddr, uint64_t *paddr)
 static uint64_t
 guest_pc(struct vm_exit *vme)
 {
+#ifdef __amd64__
 	return (vme->rip);
+#else /* __aarch64__ */
+	return (vme->pc);
+#endif
 }
 
 static void
@@ -762,6 +850,7 @@ _gdb_set_step(struct vcpu *vcpu, int val)
 {
 	int error;
 
+#ifdef __amd64__
 	/*
 	 * If the MTRAP cap fails, we are running on an AMD host.
 	 * In that case, we request DB exits caused by RFLAGS.TF.
@@ -771,23 +860,31 @@ _gdb_set_step(struct vcpu *vcpu, int val)
 		error = vm_set_capability(vcpu, VM_CAP_RFLAGS_TF, val);
 	if (error == 0)
 		(void)vm_set_capability(vcpu, VM_CAP_MASK_HWINTR, val);
-
+#else /* __aarch64__ */
+	error = vm_set_capability(vcpu, VM_CAP_SS_EXIT, val);
+	if (error == 0)
+		error = vm_set_capability(vcpu, VM_CAP_MASK_HWINTR, val);
+#endif
 	return (error);
 }
 
 /*
- * Checks whether single-stepping is enabled for a given vCPU.
+ * Checks whether single-stepping is supported for a given vCPU.
  */
 static int
 _gdb_check_step(struct vcpu *vcpu)
 {
+#ifdef __amd64__
 	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 (-1);
 	}
-	return 0;
+#else /* __aarch64__ */
+	(void)vcpu;
+#endif
+	return (0);
 }
 
 /*
@@ -809,7 +906,7 @@ gdb_cpu_add(struct vcpu *vcpu)
 	vcpus[vcpuid] = vcpu;
 	CPU_SET(vcpuid, &vcpus_active);
 	if (!TAILQ_EMPTY(&breakpoints)) {
-		vm_set_capability(vcpu, VM_CAP_BPT_EXIT, 1);
+		vm_set_capability(vcpu, GDB_BREAKPOINT_CAP, 1);
 		debug("$vCPU %d enabled breakpoint exits\n", vcpuid);
 	}
 
@@ -912,7 +1009,7 @@ gdb_cpu_step(struct vcpu *vcpu)
 }
 
 /*
- * A general handler for VM_EXITCODE_DB.
+ * A general handler for single-step exceptions.
  * Handles RFLAGS.TF exits on AMD SVM.
  */
 void
@@ -921,10 +1018,15 @@ gdb_cpu_debug(struct vcpu *vcpu, struct vm_exit *vmexit)
 	if (!gdb_active)
 		return;
 
+#ifdef __amd64__
 	/* RFLAGS.TF exit? */
 	if (vmexit->u.dbg.trace_trap) {
 		gdb_cpu_step(vcpu);
 	}
+#else /* __aarch64__ */
+	(void)vmexit;
+	gdb_cpu_step(vcpu);
+#endif
 }
 
 /*
@@ -998,11 +1100,19 @@ gdb_cpu_breakpoint(struct vcpu *vcpu, struct vm_exit *vmexit)
 	} else {
 		debug("$vCPU %d injecting breakpoint at rip %#lx\n", vcpuid,
 		    guest_pc(vmexit));
+#ifdef __amd64__
 		error = vm_set_register(vcpu, VM_REG_GUEST_ENTRY_INST_LENGTH,
 		    vmexit->u.bpt.inst_length);
 		assert(error == 0);
 		error = vm_inject_exception(vcpu, IDT_BP, 0, 0, 0);
 		assert(error == 0);
+#else /* __aarch64__ */
+		uint64_t esr;
+
+		esr = (EXCP_BRK << ESR_ELx_EC_SHIFT) | vmexit->u.hyp.esr_el2;
+		error = vm_inject_exception(vcpu, esr, 0);
+		assert(error == 0);
+#endif
 	}
 	pthread_mutex_unlock(&gdb_lock);
 }
@@ -1053,8 +1163,10 @@ gdb_read_regs(void)
 
 	start_packet();
 	for (size_t i = 0; i < nitems(gdb_regset); i++) {
+#ifdef GDB_REG_FIRST_EXT
 		if (gdb_regset[i].id == GDB_REG_FIRST_EXT)
 			break;
+#endif
 		append_unsigned_native(regvals[i], gdb_regset[i].size);
 	}
 	finish_packet();
@@ -1318,7 +1430,7 @@ set_breakpoint_caps(bool enable)
 	while (!CPU_EMPTY(&mask)) {
 		vcpu = CPU_FFS(&mask) - 1;
 		CPU_CLR(vcpu, &mask);
-		if (vm_set_capability(vcpus[vcpu], VM_CAP_BPT_EXIT,
+		if (vm_set_capability(vcpus[vcpu], GDB_BREAKPOINT_CAP,
 		    enable ? 1 : 0) < 0)
 			return (false);
 		debug("$vCPU %d %sabled breakpoint exits\n", vcpu,
@@ -1327,6 +1439,20 @@ set_breakpoint_caps(bool enable)
 	return (true);
 }
 
+static void
+write_instr(uint8_t *dest, uint8_t *instr, size_t len)
+{
+	memcpy(dest, instr, len);
+#ifdef __arm64__
+	__asm __volatile(
+	    "dc cvau, %0\n"
+	    "dsb ish\n"
+	    "ic ialluis\n"
+	    "dsb ish\n"
+	    : : "r" (dest) : "memory");
+#endif
+}
+
 static void
 remove_all_sw_breakpoints(void)
 {
@@ -1339,7 +1465,7 @@ remove_all_sw_breakpoints(void)
 	TAILQ_FOREACH_SAFE(bp, &breakpoints, link, nbp) {
 		debug("remove breakpoint at %#lx\n", bp->gpa);
 		cp = paddr_guest2host(ctx, bp->gpa, sizeof(bp->shadow_inst));
-		memcpy(cp, bp->shadow_inst, sizeof(bp->shadow_inst));
+		write_instr(cp, bp->shadow_inst, sizeof(bp->shadow_inst));
 		TAILQ_REMOVE(&breakpoints, bp, link);
 		free(bp);
 	}
@@ -1395,14 +1521,15 @@ update_sw_breakpoint(uint64_t gva, int kind, bool insert)
 			bp = malloc(sizeof(*bp));
 			bp->gpa = gpa;
 			memcpy(bp->shadow_inst, cp, sizeof(bp->shadow_inst));
-			memcpy(cp, GDB_BP_INSTR, sizeof(bp->shadow_inst));
+			write_instr(cp, GDB_BP_INSTR, sizeof(bp->shadow_inst));
 			TAILQ_INSERT_TAIL(&breakpoints, bp, link);
 			debug("new breakpoint at %#lx\n", gpa);
 		}
 	} else {
 		if (bp != NULL) {
 			debug("remove breakpoint at %#lx\n", gpa);
-			memcpy(cp, bp->shadow_inst, sizeof(bp->shadow_inst));
+			write_instr(cp, bp->shadow_inst,
+			    sizeof(bp->shadow_inst));
 			TAILQ_REMOVE(&breakpoints, bp, link);
 			free(bp);
 			if (TAILQ_EMPTY(&breakpoints))
diff --git a/usr.sbin/bhyve/gdb/Makefile b/usr.sbin/bhyve/gdb/Makefile
index cc9ba4d224da..d9edbe02aea4 100644
--- a/usr.sbin/bhyve/gdb/Makefile
+++ b/usr.sbin/bhyve/gdb/Makefile
@@ -6,6 +6,9 @@ FILES+=		target.xml
 .if ${MACHINE_ARCH} == "amd64"
 XMLARCH=	i386:x86-64
 FILES+=		amd64.xml
+.elif ${MACHINE_ARCH} == "aarch64"
+XMLARCH=	aarch64
+FILES+=		aarch64-core.xml
 .endif
 
 .if !make(install*)
diff --git a/usr.sbin/bhyve/gdb/aarch64-core.xml b/usr.sbin/bhyve/gdb/aarch64-core.xml
new file mode 100644
index 000000000000..c44b679405b7
--- /dev/null
+++ b/usr.sbin/bhyve/gdb/aarch64-core.xml
@@ -0,0 +1,46 @@
+<?xml version="1.0"?>
+<!-- Copyright (C) 2009-2012 Free Software Foundation, Inc.
+     Contributed by ARM Ltd.
+
+     Copying and distribution of this file, with or without modification,
+     are permitted in any medium without royalty provided the copyright
+     notice and this notice are preserved.  -->
+
+<!DOCTYPE feature SYSTEM "gdb-target.dtd">
+<feature name="org.gnu.gdb.aarch64.core">
+  <reg name="x0" bitsize="64"/>
+  <reg name="x1" bitsize="64"/>
+  <reg name="x2" bitsize="64"/>
+  <reg name="x3" bitsize="64"/>
+  <reg name="x4" bitsize="64"/>
+  <reg name="x5" bitsize="64"/>
+  <reg name="x6" bitsize="64"/>
+  <reg name="x7" bitsize="64"/>
+  <reg name="x8" bitsize="64"/>
+  <reg name="x9" bitsize="64"/>
+  <reg name="x10" bitsize="64"/>
+  <reg name="x11" bitsize="64"/>
+  <reg name="x12" bitsize="64"/>
+  <reg name="x13" bitsize="64"/>
+  <reg name="x14" bitsize="64"/>
+  <reg name="x15" bitsize="64"/>
+  <reg name="x16" bitsize="64"/>
+  <reg name="x17" bitsize="64"/>
+  <reg name="x18" bitsize="64"/>
+  <reg name="x19" bitsize="64"/>
+  <reg name="x20" bitsize="64"/>
+  <reg name="x21" bitsize="64"/>
+  <reg name="x22" bitsize="64"/>
+  <reg name="x23" bitsize="64"/>
+  <reg name="x24" bitsize="64"/>
+  <reg name="x25" bitsize="64"/>
+  <reg name="x26" bitsize="64"/>
+  <reg name="x27" bitsize="64"/>
+  <reg name="x28" bitsize="64"/>
+  <reg name="x29" bitsize="64"/>
+  <reg name="x30" bitsize="64"/>
+  <reg name="sp" bitsize="64" type="data_ptr"/>
+
+  <reg name="pc" bitsize="64" type="code_ptr"/>
+  <reg name="cpsr" bitsize="64"/>
+</feature>