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:
parent
3e1fb10c2e
commit
e9b47a6d69
@ -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>
|
||||
|
@ -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:
|
||||
|
@ -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
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
39
usr.sbin/bhyve/gdb.h
Normal 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__ */
|
@ -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)
|
||||
{
|
||||
|
@ -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);
|
||||
|
Loading…
Reference in New Issue
Block a user