diff --git a/etc/mtree/BSD.usr.dist b/etc/mtree/BSD.usr.dist --- 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 --- a/usr.sbin/bhyve/Makefile +++ b/usr.sbin/bhyve/Makefile @@ -75,6 +75,7 @@ .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 --- a/usr.sbin/bhyve/gdb.c +++ b/usr.sbin/bhyve/gdb.c @@ -34,6 +34,8 @@ #include #include #include +#include + #include #include #include @@ -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. @@ -72,7 +76,8 @@ #define GDB_BP_SIZE 1 #define GDB_BP_INSTR (uint8_t []){0xcc} #define GDB_PC_REGNAME VM_REG_GUEST_RIP - +#define GDB_XML_ARCH "i386:x86-64" +#define GDB_XML_BASE "amd64.xml" _Static_assert(sizeof(GDB_BP_INSTR) == GDB_BP_SIZE, "GDB_BP_INSTR has wrong size"); @@ -85,6 +90,7 @@ 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,6 +175,22 @@ { .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 }, }; #ifdef GDB_LOG @@ -1029,9 +1051,13 @@ 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 @@ /* This is an arbitrary limit. */ append_string("PacketSize=4096"); append_string(";swbreak+"); + append_string(";qXfer:features:read+"); finish_packet(); } @@ -1590,6 +1617,94 @@ start_packet(); append_asciihex(buf); finish_packet(); + } else if (command_equals(data, len, "qXfer:features:read:")) { + const char *xml; + const uint8_t *pathend; + char buf[64], path[PATH_MAX]; + size_t xmllen; + unsigned int doff, dlen; + bool unmap; + + 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; + } + + /* + * "target.xml" is the base target description, which is + * hard-coded here. It references other files which may be + * accessed via xml_dfd. + */ + if (strcmp(path, "target.xml") == 0) { + xml = + "" + "" + "" + "" GDB_XML_ARCH "" + "" + ""; + xmllen = strlen(xml); + unmap = false; + } else { + struct stat sb; + int fd; + + fd = openat(xml_dfd, path, O_RDONLY); + 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; + unmap = true; + } + + start_packet(); + if (doff >= xmllen) { + append_char('l'); + dlen = 0; + } 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(); + if (unmap) + (void)munmap(__DECONST(void *, xml), xmllen); } else send_empty_response(); } @@ -1917,6 +2032,9 @@ 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 +2115,13 @@ 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 --- /dev/null +++ b/usr.sbin/bhyve/gdb/Makefile @@ -0,0 +1,8 @@ +PACKAGE= bhyve +FILESDIR= ${SHAREDIR}/bhyve/gdb + +.if ${MACHINE_ARCH} == "amd64" +FILES+= amd64.xml +.endif + +.include diff --git a/usr.sbin/bhyve/gdb/amd64.xml b/usr.sbin/bhyve/gdb/amd64.xml new file mode 100644 --- /dev/null +++ b/usr.sbin/bhyve/gdb/amd64.xml @@ -0,0 +1,216 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +