Initial debug server for bhyve.

This commit adds a new debug server to bhyve.  Unlike the existing -g
option which provides an efficient connection to a debug server
running in the guest OS, this debug server permits inspection and
control of the guest from within the hypervisor itself without
requiring any cooperation from the guest.  It is similar to the debug
server provided by qemu.

To avoid conflicting with the existing -g option, a new -G option has
been added that accepts a TCP port.  An IPv4 socket is bound to this
port and listens for connections from debuggers.  In addition, if the
port begins with the character 'w', the hypervisor will pause the
guest at the first instruction until a debugger attaches and
explicitly continues the guest.  Note that only a single debugger can
attach to a guest at a time.

Virtual CPUs are exposed to the remote debugger as threads.  General
purpose register values can be read for each virtual CPU.  Other
registers cannot currently be read, and no register values can be
changed by the debugger.

The remote debugger can read guest memory but not write to guest
memory.  To facilitate source-level debugging of the guest, memory
addresses from the debugger are treated as virtual addresses (rather
than physical addresses) and are resolved to a physical address using
the active virtual address translation of the current virtual CPU.
Memory reads should honor memory mapped I/O regions, though the debug
server does not attempt to honor any alignment or size constraints
when accessing MMIO.

The debug server provides limited support for controlling the guest.
The guest is suspended when a debugger is attached and resumes when a
debugger detaches.  A debugger can suspend a guest by sending a Ctrl-C
request (e.g. via Ctrl-C in GDB).  A debugger can also continue a
suspended guest while remaining attached.  Breakpoints are not yet
supported.  Single stepping is supported on Intel CPUs that support
MTRAP VM exits, but is not available on other systems.

While the current debug server has limited functionality, it should
at least be usable for basic debugging now.  It is also a useful
checkpoint to serve as a base for adding additional features.

Reviewed by:	grehan
Differential Revision:	https://reviews.freebsd.org/D15022
This commit is contained in:
John Baldwin 2018-05-01 15:17:46 +00:00
parent faf6d96b45
commit cd377eb369
Notes: svn2git 2020-12-20 02:59:44 +00:00
svn path=/head/; revision=333140
7 changed files with 1483 additions and 10 deletions

View File

@ -24,6 +24,7 @@ SRCS= \
consport.c \
dbgport.c \
fwctl.c \
gdb.c \
inout.c \
ioapic.c \
mem.c \
@ -75,6 +76,10 @@ CFLAGS+= -I${BHYVE_SYSDIR}/sys/dev/e1000
CFLAGS+= -I${BHYVE_SYSDIR}/sys/dev/mii
CFLAGS+= -I${BHYVE_SYSDIR}/sys/dev/usb/controller
.ifdef GDB_LOG
CFLAGS+=-DGDB_LOG
.endif
WARNS?= 2
.include <bsd.prog.mk>

View File

@ -24,7 +24,7 @@
.\"
.\" $FreeBSD$
.\"
.Dd April 6, 2018
.Dd May 1, 2018
.Dt BHYVE 8
.Os
.Sh NAME
@ -48,6 +48,7 @@
.Op Fl m Ar memsize Ns Op Ar K|k|M|m|G|g|T|t
.Op Fl p Ar vcpu:hostcpu
.Op Fl s Ar slot,emulation Ns Op , Ns Ar conf
.Op Fl G Ar port
.Op Fl U Ar uuid
.Ar vmname
.Sh DESCRIPTION
@ -125,6 +126,19 @@ kernels compiled with
allow a remote kernel kgdb to be relayed to the guest kernel gdb stub
via a local IPv4 address and this port.
This option will be deprecated in a future version.
.It Fl G Ar port
Start a debug server that uses the GDB protocol to export guest state to a
debugger.
An IPv4 TCP socket will be bound to the supplied
.Ar port
to listen for debugger connections.
Only a single debugger may be attached to the debug server at a time.
If
.Ar port
begins with
.Sq w ,
.Nm
will pause execution at the first instruction waiting for a debugger to attach.
.It Fl h
Print help message and exit.
.It Fl H
@ -438,6 +452,24 @@ Alphanumeric name of the guest.
This should be the same as that created by
.Xr bhyveload 8 .
.El
.Sh DEBUG SERVER
The current debug server provides limited support for debuggers.
.Ss Registers
Each virtual CPU is exposed to the debugger as a thread.
.Pp
General purpose registers can be queried for each virtual CPU, but other
registers such as floating-point and system registers cannot be queried.
.Ss Memory
Memory (including memory mapped I/O regions) can be read by the debugger,
but not written. Memory operations use virtual addresses that are resolved
to physical addresses via the current virtual CPU's active address translation.
.Ss Control
The running guest can be interrupted by the debugger at any time
.Pq for example, by pressing Ctrl-C in the debugger .
.Pp
Single stepping is only supported on Intel CPUs supporting the MTRAP VM exit.
.Pp
Breakpoints are not supported.
.Sh SIGNAL HANDLING
.Nm
deals with the following signals:

View File

@ -71,6 +71,7 @@ __FBSDID("$FreeBSD$");
#include "inout.h"
#include "dbgport.h"
#include "fwctl.h"
#include "gdb.h"
#include "ioapic.h"
#include "mem.h"
#include "mevent.h"
@ -338,6 +339,8 @@ fbsdrun_start_thread(void *param)
snprintf(tname, sizeof(tname), "vcpu %d", vcpu);
pthread_set_name_np(mtp->mt_thr, tname);
gdb_cpu_add(vcpu);
vm_loop(mtp->mt_ctx, vcpu, vmexit[vcpu].rip);
/* not reached */
@ -601,6 +604,8 @@ vmexit_mtrap(struct vmctx *ctx, struct vm_exit *vmexit, int *pvcpu)
stats.vmexit_mtrap++;
gdb_cpu_mtrap(*pvcpu);
return (VMEXIT_CONTINUE);
}
@ -675,6 +680,14 @@ vmexit_suspend(struct vmctx *ctx, struct vm_exit *vmexit, int *pvcpu)
return (0); /* NOTREACHED */
}
static int
vmexit_debug(struct vmctx *ctx, struct vm_exit *vmexit, int *pvcpu)
{
gdb_cpu_suspend(*pvcpu);
return (VMEXIT_CONTINUE);
}
static vmexit_handler_t handler[VM_EXITCODE_MAX] = {
[VM_EXITCODE_INOUT] = vmexit_inout,
[VM_EXITCODE_INOUT_STR] = vmexit_inout,
@ -689,6 +702,7 @@ static vmexit_handler_t handler[VM_EXITCODE_MAX] = {
[VM_EXITCODE_SPINUP_AP] = vmexit_spinup_ap,
[VM_EXITCODE_SUSPENDED] = vmexit_suspend,
[VM_EXITCODE_TASK_SWITCH] = vmexit_task_switch,
[VM_EXITCODE_DEBUG] = vmexit_debug,
};
static void
@ -877,9 +891,10 @@ do_open(const char *vmname)
int
main(int argc, char *argv[])
{
int c, error, gdb_port, err, bvmcons;
int c, error, dbg_port, gdb_port, err, bvmcons;
int max_vcpus, mptgen, memflags;
int rtc_localtime;
bool gdb_stop;
struct vmctx *ctx;
uint64_t rip;
size_t memsize;
@ -887,7 +902,9 @@ main(int argc, char *argv[])
bvmcons = 0;
progname = basename(argv[0]);
dbg_port = 0;
gdb_port = 0;
gdb_stop = false;
guest_ncpus = 1;
sockets = cores = threads = 1;
maxcpus = 0;
@ -896,7 +913,7 @@ main(int argc, char *argv[])
rtc_localtime = 1;
memflags = 0;
optstr = "abehuwxACHIPSWYp:g:c:s:m:l:U:";
optstr = "abehuwxACHIPSWYp:g:G:c:s:m:l:U:";
while ((c = getopt(argc, argv, optstr)) != -1) {
switch (c) {
case 'a':
@ -924,6 +941,13 @@ main(int argc, char *argv[])
memflags |= VM_MEM_F_INCORE;
break;
case 'g':
dbg_port = atoi(optarg);
break;
case 'G':
if (optarg[0] == 'w') {
gdb_stop = true;
optarg++;
}
gdb_port = atoi(optarg);
break;
case 'l':
@ -1033,8 +1057,11 @@ main(int argc, char *argv[])
if (init_pci(ctx) != 0)
exit(1);
if (dbg_port != 0)
init_dbgport(dbg_port);
if (gdb_port != 0)
init_dbgport(gdb_port);
init_gdb(ctx, gdb_port, gdb_stop);
if (bvmcons)
init_bvmcons();

1313
usr.sbin/bhyve/gdb.c Normal file

File diff suppressed because it is too large Load Diff

39
usr.sbin/bhyve/gdb.h Normal file
View File

@ -0,0 +1,39 @@
/*-
* SPDX-License-Identifier: BSD-2-Clause-FreeBSD
*
* Copyright (c) 2017 John H. Baldwin <jhb@FreeBSD.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* $FreeBSD$
*/
#ifndef __GDB_H__
#define __GDB_H__
void gdb_cpu_add(int vcpu);
void gdb_cpu_mtrap(int vcpu);
void gdb_cpu_suspend(int vcpu);
void init_gdb(struct vmctx *ctx, int sport, bool wait);
#endif /* !__GDB_H__ */

View File

@ -136,6 +136,9 @@ mmio_rb_dump(struct mmio_rb_tree *rbt)
RB_GENERATE(mmio_rb_tree, mmio_rb_range, mr_link, mmio_rb_range_compare);
typedef int (mem_cb_t)(struct vmctx *ctx, int vcpu, uint64_t gpa,
struct mem_range *mr, void *arg);
static int
mem_read(void *ctx, int vcpu, uint64_t gpa, uint64_t *rval, int size, void *arg)
{
@ -158,10 +161,9 @@ mem_write(void *ctx, int vcpu, uint64_t gpa, uint64_t wval, int size, void *arg)
return (error);
}
int
emulate_mem(struct vmctx *ctx, int vcpu, uint64_t paddr, struct vie *vie,
struct vm_guest_paging *paging)
static int
access_memory(struct vmctx *ctx, int vcpu, uint64_t paddr, mem_cb_t *cb,
void *arg)
{
struct mmio_rb_range *entry;
int err, immutable;
@ -204,8 +206,7 @@ emulate_mem(struct vmctx *ctx, int vcpu, uint64_t paddr, struct vie *vie,
if (immutable)
pthread_rwlock_unlock(&mmio_rwlock);
err = vmm_emulate_instruction(ctx, vcpu, paddr, vie, paging,
mem_read, mem_write, &entry->mr_param);
err = cb(ctx, vcpu, paddr, &entry->mr_param, arg);
if (!immutable)
pthread_rwlock_unlock(&mmio_rwlock);
@ -213,6 +214,60 @@ emulate_mem(struct vmctx *ctx, int vcpu, uint64_t paddr, struct vie *vie,
return (err);
}
struct emulate_mem_args {
struct vie *vie;
struct vm_guest_paging *paging;
};
static int
emulate_mem_cb(struct vmctx *ctx, int vcpu, uint64_t paddr, struct mem_range *mr,
void *arg)
{
struct emulate_mem_args *ema;
ema = arg;
return (vmm_emulate_instruction(ctx, vcpu, paddr, ema->vie, ema->paging,
mem_read, mem_write, mr));
}
int
emulate_mem(struct vmctx *ctx, int vcpu, uint64_t paddr, struct vie *vie,
struct vm_guest_paging *paging)
{
struct emulate_mem_args ema;
ema.vie = vie;
ema.paging = paging;
return (access_memory(ctx, vcpu, paddr, emulate_mem_cb, &ema));
}
struct read_mem_args {
uint64_t *rval;
int size;
};
static int
read_mem_cb(struct vmctx *ctx, int vcpu, uint64_t paddr, struct mem_range *mr,
void *arg)
{
struct read_mem_args *rma;
rma = arg;
return (mr->handler(ctx, vcpu, MEM_F_READ, paddr, rma->size,
rma->rval, mr->arg1, mr->arg2));
}
int
read_mem(struct vmctx *ctx, int vcpu, uint64_t gpa, uint64_t *rval, int size)
{
struct read_mem_args rma;
rma.rval = rval;
rma.size = size;
return (access_memory(ctx, vcpu, gpa, read_mem_cb, &rma));
}
static int
register_mem_int(struct mmio_rb_tree *rbt, struct mem_range *memp)
{

View File

@ -56,6 +56,8 @@ void init_mem(void);
int emulate_mem(struct vmctx *, int vcpu, uint64_t paddr, struct vie *vie,
struct vm_guest_paging *paging);
int read_mem(struct vmctx *ctx, int vcpu, uint64_t gpa, uint64_t *rval,
int size);
int register_mem(struct mem_range *memp);
int register_mem_fallback(struct mem_range *memp);
int unregister_mem(struct mem_range *memp);