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
This commit is contained in:
Mark Johnston 2024-02-20 19:21:29 -05:00
parent 3668e1fa0a
commit f81cdf24ba
5 changed files with 294 additions and 1 deletions

View file

@ -184,6 +184,8 @@
atf tags=package=tests
..
bhyve
gdb
..
kbdlayout
..
..

View file

@ -75,6 +75,7 @@ CFLAGS+= -DBHYVE_GDB
.ifdef GDB_LOG
CFLAGS+=-DGDB_LOG
.endif
SUBDIR+= gdb
.endif
CFLAGS+=-I${.CURDIR} \

View file

@ -34,6 +34,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>
@ -63,6 +65,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.
@ -85,6 +89,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
@ -169,8 +174,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>
@ -1029,9 +1051,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();
}
@ -1519,6 +1545,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();
}
@ -1590,6 +1617,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();
}
@ -1917,6 +2009,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;
@ -1997,4 +2092,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
}

View file

@ -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>

View file

@ -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>