git: 683947e13fb8 - stable/14 - bhyve: Add support for XML register definitions

From: Mark Johnston <markj_at_FreeBSD.org>
Date: Fri, 29 Mar 2024 18:11:51 UTC
The branch stable/14 has been updated by markj:

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

commit 683947e13fb8fdf5bee443cedd1b916238619998
Author:     Mark Johnston <markj@FreeBSD.org>
AuthorDate: 2024-02-21 00:21:29 +0000
Commit:     Mark Johnston <markj@FreeBSD.org>
CommitDate: 2024-03-29 13:53:05 +0000

    bhyve: Add support for XML register definitions
    
    This is useful for exposing additional registers to debuggers.  For
    instance, control registers are now available on amd64 when using gdb to
    debug a guest.
    
    The stub indicates support by including the string
    "qXfer:features:read+" in its feature list.  The debugger queries for
    target descriptions by sending the query "qXfer:features:read:" followed
    by a file path.
    
    The XML definitions are copied from QEMU and installed to
    /usr/share/bhyve/gdb.
    
    Note that we currently don't handle the SIMD registers at all, since
    that's of somewhat limited utility (for me at least) and since that
    requires new ioctls to fetch the register values.
    
    Reviewed by:    jhb
    MFC after:      2 weeks
    Sponsored by:   Innovate UK
    Differential Revision:  https://reviews.freebsd.org/D43666
    
    (cherry picked from commit f81cdf24ba5436367377f7c8e8f51f6df2a75ca7)
---
 etc/mtree/BSD.usr.dist       |   2 +
 usr.sbin/bhyve/Makefile      |   1 +
 usr.sbin/bhyve/gdb.c         | 106 ++++++++++++++++++++++++++-
 usr.sbin/bhyve/gdb/Makefile  |  21 ++++++
 usr.sbin/bhyve/gdb/amd64.xml | 165 +++++++++++++++++++++++++++++++++++++++++++
 5 files changed, 294 insertions(+), 1 deletion(-)

diff --git a/etc/mtree/BSD.usr.dist b/etc/mtree/BSD.usr.dist
index a7738aaf6f78..0d43b657530b 100644
--- a/etc/mtree/BSD.usr.dist
+++ b/etc/mtree/BSD.usr.dist
@@ -184,6 +184,8 @@
         atf             tags=package=tests
         ..
         bhyve
+            gdb
+            ..
             kbdlayout
             ..
         ..
diff --git a/usr.sbin/bhyve/Makefile b/usr.sbin/bhyve/Makefile
index b6cad38a6c39..c9d571daebbc 100644
--- a/usr.sbin/bhyve/Makefile
+++ b/usr.sbin/bhyve/Makefile
@@ -75,6 +75,7 @@ CFLAGS+= -DBHYVE_GDB
 .ifdef GDB_LOG
 CFLAGS+=-DGDB_LOG
 .endif
+SUBDIR+= gdb
 .endif
 
 CFLAGS+=-I${.CURDIR}		\
diff --git a/usr.sbin/bhyve/gdb.c b/usr.sbin/bhyve/gdb.c
index 6bdfbc5e8536..2a075cd10ca6 100644
--- a/usr.sbin/bhyve/gdb.c
+++ b/usr.sbin/bhyve/gdb.c
@@ -35,6 +35,8 @@
 #include <sys/mman.h>
 #include <sys/queue.h>
 #include <sys/socket.h>
+#include <sys/stat.h>
+
 #include <machine/atomic.h>
 #include <machine/specialreg.h>
 #include <machine/vmm.h>
@@ -64,6 +66,8 @@
 #include "mem.h"
 #include "mevent.h"
 
+#define	_PATH_GDB_XML		"/usr/share/bhyve/gdb"
+
 /*
  * GDB_SIGNAL_* numbers are part of the GDB remote protocol.  Most stops
  * use SIGTRAP.
@@ -86,6 +90,7 @@ static cpuset_t vcpus_active, vcpus_suspended, vcpus_waiting;
 static pthread_mutex_t gdb_lock;
 static pthread_cond_t idle_vcpus;
 static bool first_stop, report_next_stop, swbreak_enabled;
+static int xml_dfd = -1;
 
 /*
  * An I/O buffer contains 'capacity' bytes of room at 'data'.  For a
@@ -170,8 +175,25 @@ static const struct gdb_reg {
 	{ .id = VM_REG_GUEST_ES, .size = 4 },
 	{ .id = VM_REG_GUEST_FS, .size = 4 },
 	{ .id = VM_REG_GUEST_GS, .size = 4 },
+	/*
+	 * Registers past this point are not included in a reply to a 'g' query,
+	 * to provide compatibility with debuggers that do not fetch a target
+	 * description.  The debugger can query them individually with 'p' if it
+	 * knows about them.
+	 */
+#define	GDB_REG_FIRST_EXT	VM_REG_GUEST_FS_BASE
+	{ .id = VM_REG_GUEST_FS_BASE, .size = 8 },
+	{ .id = VM_REG_GUEST_GS_BASE, .size = 8 },
+	{ .id = VM_REG_GUEST_KGS_BASE, .size = 8 },
+	{ .id = VM_REG_GUEST_CR0, .size = 8 },
+	{ .id = VM_REG_GUEST_CR2, .size = 8 },
+	{ .id = VM_REG_GUEST_CR3, .size = 8 },
+	{ .id = VM_REG_GUEST_CR4, .size = 8 },
+	{ .id = VM_REG_GUEST_TPR, .size = 8 },
+	{ .id = VM_REG_GUEST_EFER, .size = 8 },
 };
 
+#define	GDB_LOG
 #ifdef GDB_LOG
 #include <stdarg.h>
 #include <stdio.h>
@@ -1030,9 +1052,13 @@ gdb_read_regs(void)
 		send_error(errno);
 		return;
 	}
+
 	start_packet();
-	for (size_t i = 0; i < nitems(gdb_regset); i++)
+	for (size_t i = 0; i < nitems(gdb_regset); i++) {
+		if (gdb_regset[i].id == GDB_REG_FIRST_EXT)
+			break;
 		append_unsigned_native(regvals[i], gdb_regset[i].size);
+	}
 	finish_packet();
 }
 
@@ -1520,6 +1546,7 @@ check_features(const uint8_t *data, size_t len)
 	/* This is an arbitrary limit. */
 	append_string("PacketSize=4096");
 	append_string(";swbreak+");
+	append_string(";qXfer:features:read+");
 	finish_packet();
 }
 
@@ -1591,6 +1618,71 @@ gdb_query(const uint8_t *data, size_t len)
 		start_packet();
 		append_asciihex(buf);
 		finish_packet();
+	} else if (command_equals(data, len, "qXfer:features:read:")) {
+		struct stat sb;
+		const char *xml;
+		const uint8_t *pathend;
+		char buf[64], path[PATH_MAX];
+		size_t xmllen;
+		unsigned int doff, dlen;
+		int fd;
+
+		data += strlen("qXfer:features:read:");
+		len -= strlen("qXfer:features:read:");
+
+		pathend = memchr(data, ':', len);
+		if (pathend == NULL ||
+		    (size_t)(pathend - data) >= sizeof(path) - 1) {
+			send_error(EINVAL);
+			return;
+		}
+		memcpy(path, data, pathend - data);
+		path[pathend - data] = '\0';
+		data += (pathend - data) + 1;
+		len -= (pathend - data) + 1;
+
+		if (len > sizeof(buf) - 1) {
+			send_error(EINVAL);
+			return;
+		}
+		memcpy(buf, data, len);
+		buf[len] = '\0';
+		if (sscanf(buf, "%x,%x", &doff, &dlen) != 2) {
+			send_error(EINVAL);
+			return;
+		}
+
+		fd = openat(xml_dfd, path, O_RDONLY | O_RESOLVE_BENEATH);
+		if (fd < 0) {
+			send_error(errno);
+			return;
+		}
+		if (fstat(fd, &sb) < 0) {
+			send_error(errno);
+			close(fd);
+			return;
+		}
+		xml = mmap(NULL, sb.st_size, PROT_READ, MAP_SHARED, fd, 0);
+		if (xml == MAP_FAILED) {
+			send_error(errno);
+			close(fd);
+			return;
+		}
+		close(fd);
+		xmllen = sb.st_size;
+
+		start_packet();
+		if (doff >= xmllen) {
+			append_char('l');
+		} else if (doff + dlen >= xmllen) {
+			append_char('l');
+			append_packet_data(xml + doff, xmllen - doff);
+		} else {
+			append_char('m');
+			append_packet_data(xml + doff, dlen);
+		}
+		finish_packet();
+		(void)munmap(__DECONST(void *, xml), xmllen);
 	} else
 		send_empty_response();
 }
@@ -1918,6 +2010,9 @@ limit_gdb_socket(int s)
 void
 init_gdb(struct vmctx *_ctx)
 {
+#ifndef WITHOUT_CAPSICUM
+	cap_rights_t rights;
+#endif
 	int error, flags, optval, s;
 	struct addrinfo hints;
 	struct addrinfo *gdbaddr;
@@ -1998,4 +2093,13 @@ init_gdb(struct vmctx *_ctx)
 	gdb_active = true;
 	freeaddrinfo(gdbaddr);
 	free(sport);
+
+	xml_dfd = open(_PATH_GDB_XML, O_DIRECTORY);
+	if (xml_dfd == -1)
+		err(1, "Failed to open gdb xml directory");
+#ifndef WITHOUT_CAPSICUM
+	cap_rights_init(&rights, CAP_FSTAT, CAP_LOOKUP, CAP_MMAP_R, CAP_PREAD);
+	if (caph_rights_limit(xml_dfd, &rights) == -1)
+		err(1, "cap_rights_init");
+#endif
 }
diff --git a/usr.sbin/bhyve/gdb/Makefile b/usr.sbin/bhyve/gdb/Makefile
new file mode 100644
index 000000000000..a444b0cc4c69
--- /dev/null
+++ b/usr.sbin/bhyve/gdb/Makefile
@@ -0,0 +1,21 @@
+PACKAGE=	bhyve
+FILESDIR=	${SHAREDIR}/bhyve/gdb
+
+FILES+=		target.xml
+
+.if ${MACHINE_ARCH} == "amd64"
+XMLARCH=	i386:x86-64
+FILES+=		amd64.xml
+.endif
+
+target.xml: .PHONY
+	@echo "<?xml version=\"1.0\"?>" > ${.TARGET}
+	@echo "<!DOCTYPE target SYSTEM \"gdb-target.dtd\">" >> ${.TARGET}
+	@echo "<target>" >> ${.TARGET}
+	@echo "  <architecture>${XMLARCH}</architecture>" >> ${.TARGET}
+.for file in ${FILES:Ntarget.xml}
+	@echo "  <xi:include href=\"${file}\"/>" >> ${.TARGET}
+.endfor
+	@echo "</target>" >> ${.TARGET}
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/bhyve/gdb/amd64.xml b/usr.sbin/bhyve/gdb/amd64.xml
new file mode 100644
index 000000000000..15bd270120d8
--- /dev/null
+++ b/usr.sbin/bhyve/gdb/amd64.xml
@@ -0,0 +1,165 @@
+<?xml version="1.0"?>
+<!-- Copyright (C) 2010-2017 Free Software Foundation, Inc.
+
+     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.  -->
+
+<!-- x86_64 64bit -->
+
+<!DOCTYPE target SYSTEM "gdb-target.dtd">
+
+<feature name="org.gnu.gdb.i386.core">
+  <flags id="x64_eflags" size="4">
+	<field name="" start="22" end="31"/>
+	<field name="ID" start="21" end="21"/>
+	<field name="VIP" start="20" end="20"/>
+	<field name="VIF" start="19" end="19"/>
+	<field name="AC" start="18" end="18"/>
+	<field name="VM" start="17" end="17"/>
+	<field name="RF" start="16" end="16"/>
+	<field name="" start="15" end="15"/>
+	<field name="NT" start="14" end="14"/>
+	<field name="IOPL" start="12" end="13"/>
+	<field name="OF" start="11" end="11"/>
+	<field name="DF" start="10" end="10"/>
+	<field name="IF" start="9" end="9"/>
+	<field name="TF" start="8" end="8"/>
+	<field name="SF" start="7" end="7"/>
+	<field name="ZF" start="6" end="6"/>
+	<field name="" start="5" end="5"/>
+	<field name="AF" start="4" end="4"/>
+	<field name="" start="3" end="3"/>
+	<field name="PF" start="2" end="2"/>
+	<field name="" start="1" end="1"/>
+	<field name="CF" start="0" end="0"/>
+  </flags>
+
+  <!-- General registers -->
+
+  <reg name="rax" bitsize="64" type="int64" regnum="0"/>
+  <reg name="rbx" bitsize="64" type="int64"/>
+  <reg name="rcx" bitsize="64" type="int64"/>
+  <reg name="rdx" bitsize="64" type="int64"/>
+  <reg name="rsi" bitsize="64" type="int64"/>
+  <reg name="rdi" bitsize="64" type="int64"/>
+  <reg name="rbp" bitsize="64" type="data_ptr"/>
+  <reg name="rsp" bitsize="64" type="data_ptr"/>
+  <reg name="r8" bitsize="64" type="int64"/>
+  <reg name="r9" bitsize="64" type="int64"/>
+  <reg name="r10" bitsize="64" type="int64"/>
+  <reg name="r11" bitsize="64" type="int64"/>
+  <reg name="r12" bitsize="64" type="int64"/>
+  <reg name="r13" bitsize="64" type="int64"/>
+  <reg name="r14" bitsize="64" type="int64"/>
+  <reg name="r15" bitsize="64" type="int64"/>
+
+  <reg name="rip" bitsize="64" type="code_ptr"/>
+  <reg name="eflags" bitsize="32" type="x64_eflags"/>
+
+  <!-- Segment registers -->
+
+  <reg name="cs" bitsize="32" type="int32"/>
+  <reg name="ss" bitsize="32" type="int32"/>
+  <reg name="ds" bitsize="32" type="int32"/>
+  <reg name="es" bitsize="32" type="int32"/>
+  <reg name="fs" bitsize="32" type="int32"/>
+  <reg name="gs" bitsize="32" type="int32"/>
+
+  <!-- Segment descriptor caches and TLS base MSRs -->
+
+  <!--reg name="cs_base" bitsize="64" type="int64"/>
+  <reg name="ss_base" bitsize="64" type="int64"/>
+  <reg name="ds_base" bitsize="64" type="int64"/>
+  <reg name="es_base" bitsize="64" type="int64"/-->
+  <reg name="fs_base" bitsize="64" type="int64"/>
+  <reg name="gs_base" bitsize="64" type="int64"/>
+  <reg name="k_gs_base" bitsize="64" type="int64"/>
+
+  <!-- Control registers -->
+
+  <flags id="x64_cr0" size="8">
+	<field name="PG" start="31" end="31"/>
+	<field name="CD" start="30" end="30"/>
+	<field name="NW" start="29" end="29"/>
+	<field name="AM" start="18" end="18"/>
+	<field name="WP" start="16" end="16"/>
+	<field name="NE" start="5" end="5"/>
+	<field name="ET" start="4" end="4"/>
+	<field name="TS" start="3" end="3"/>
+	<field name="EM" start="2" end="2"/>
+	<field name="MP" start="1" end="1"/>
+	<field name="PE" start="0" end="0"/>
+  </flags>
+
+  <flags id="x64_cr3" size="8">
+	<field name="PDBR" start="12" end="63"/>
+	<!--field name="" start="3" end="11"/>
+	<field name="WT" start="2" end="2"/>
+	<field name="CD" start="1" end="1"/>
+	<field name="" start="0" end="0"/-->
+	<field name="PCID" start="0" end="11"/>
+  </flags>
+
+  <flags id="x64_cr4" size="8">
+	<field name="PKE" start="22" end="22"/>
+	<field name="SMAP" start="21" end="21"/>
+	<field name="SMEP" start="20" end="20"/>
+	<field name="OSXSAVE" start="18" end="18"/>
+	<field name="PCIDE" start="17" end="17"/>
+	<field name="FSGSBASE" start="16" end="16"/>
+	<field name="SMXE" start="14" end="14"/>
+	<field name="VMXE" start="13" end="13"/>
+	<field name="LA57" start="12" end="12"/>
+	<field name="UMIP" start="11" end="11"/>
+	<field name="OSXMMEXCPT" start="10" end="10"/>
+	<field name="OSFXSR" start="9" end="9"/>
+	<field name="PCE" start="8" end="8"/>
+	<field name="PGE" start="7" end="7"/>
+	<field name="MCE" start="6" end="6"/>
+	<field name="PAE" start="5" end="5"/>
+	<field name="PSE" start="4" end="4"/>
+	<field name="DE" start="3" end="3"/>
+	<field name="TSD" start="2" end="2"/>
+	<field name="PVI" start="1" end="1"/>
+	<field name="VME" start="0" end="0"/>
+  </flags>
+
+  <flags id="x64_efer" size="8">
+	<field name="TCE" start="15" end="15"/>
+	<field name="FFXSR" start="14" end="14"/>
+	<field name="LMSLE" start="13" end="13"/>
+	<field name="SVME" start="12" end="12"/>
+	<field name="NXE" start="11" end="11"/>
+	<field name="LMA" start="10" end="10"/>
+	<field name="LME" start="8" end="8"/>
+	<field name="SCE" start="0" end="0"/>
+  </flags>
+
+  <reg name="cr0" bitsize="64" type="x64_cr0"/>
+  <reg name="cr2" bitsize="64" type="int64"/>
+  <reg name="cr3" bitsize="64" type="x64_cr3"/>
+  <reg name="cr4" bitsize="64" type="x64_cr4"/>
+  <reg name="cr8" bitsize="64" type="int64"/>
+  <reg name="efer" bitsize="64" type="x64_efer"/>
+
+  <!-- x87 FPU -->
+
+  <reg name="st0" bitsize="80" type="i387_ext"/>
+  <reg name="st1" bitsize="80" type="i387_ext"/>
+  <reg name="st2" bitsize="80" type="i387_ext"/>
+  <reg name="st3" bitsize="80" type="i387_ext"/>
+  <reg name="st4" bitsize="80" type="i387_ext"/>
+  <reg name="st5" bitsize="80" type="i387_ext"/>
+  <reg name="st6" bitsize="80" type="i387_ext"/>
+  <reg name="st7" bitsize="80" type="i387_ext"/>
+
+  <reg name="fctrl" bitsize="32" type="int" group="float"/>
+  <reg name="fstat" bitsize="32" type="int" group="float"/>
+  <reg name="ftag" bitsize="32" type="int" group="float"/>
+  <reg name="fiseg" bitsize="32" type="int" group="float"/>
+  <reg name="fioff" bitsize="32" type="int" group="float"/>
+  <reg name="foseg" bitsize="32" type="int" group="float"/>
+  <reg name="fooff" bitsize="32" type="int" group="float"/>
+  <reg name="fop" bitsize="32" type="int" group="float"/>
+</feature>