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
faf6d96b45
commit
cd377eb369
Notes:
svn2git
2020-12-20 02:59:44 +00:00
svn path=/head/; revision=333140
@ -24,6 +24,7 @@ SRCS= \
|
|||||||
consport.c \
|
consport.c \
|
||||||
dbgport.c \
|
dbgport.c \
|
||||||
fwctl.c \
|
fwctl.c \
|
||||||
|
gdb.c \
|
||||||
inout.c \
|
inout.c \
|
||||||
ioapic.c \
|
ioapic.c \
|
||||||
mem.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/mii
|
||||||
CFLAGS+= -I${BHYVE_SYSDIR}/sys/dev/usb/controller
|
CFLAGS+= -I${BHYVE_SYSDIR}/sys/dev/usb/controller
|
||||||
|
|
||||||
|
.ifdef GDB_LOG
|
||||||
|
CFLAGS+=-DGDB_LOG
|
||||||
|
.endif
|
||||||
|
|
||||||
WARNS?= 2
|
WARNS?= 2
|
||||||
|
|
||||||
.include <bsd.prog.mk>
|
.include <bsd.prog.mk>
|
||||||
|
@ -24,7 +24,7 @@
|
|||||||
.\"
|
.\"
|
||||||
.\" $FreeBSD$
|
.\" $FreeBSD$
|
||||||
.\"
|
.\"
|
||||||
.Dd April 6, 2018
|
.Dd May 1, 2018
|
||||||
.Dt BHYVE 8
|
.Dt BHYVE 8
|
||||||
.Os
|
.Os
|
||||||
.Sh NAME
|
.Sh NAME
|
||||||
@ -48,6 +48,7 @@
|
|||||||
.Op Fl m Ar memsize Ns Op Ar K|k|M|m|G|g|T|t
|
.Op Fl m Ar memsize Ns Op Ar K|k|M|m|G|g|T|t
|
||||||
.Op Fl p Ar vcpu:hostcpu
|
.Op Fl p Ar vcpu:hostcpu
|
||||||
.Op Fl s Ar slot,emulation Ns Op , Ns Ar conf
|
.Op Fl s Ar slot,emulation Ns Op , Ns Ar conf
|
||||||
|
.Op Fl G Ar port
|
||||||
.Op Fl U Ar uuid
|
.Op Fl U Ar uuid
|
||||||
.Ar vmname
|
.Ar vmname
|
||||||
.Sh DESCRIPTION
|
.Sh DESCRIPTION
|
||||||
@ -125,6 +126,19 @@ kernels compiled with
|
|||||||
allow a remote kernel kgdb to be relayed to the guest kernel gdb stub
|
allow a remote kernel kgdb to be relayed to the guest kernel gdb stub
|
||||||
via a local IPv4 address and this port.
|
via a local IPv4 address and this port.
|
||||||
This option will be deprecated in a future version.
|
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
|
.It Fl h
|
||||||
Print help message and exit.
|
Print help message and exit.
|
||||||
.It Fl H
|
.It Fl H
|
||||||
@ -438,6 +452,24 @@ Alphanumeric name of the guest.
|
|||||||
This should be the same as that created by
|
This should be the same as that created by
|
||||||
.Xr bhyveload 8 .
|
.Xr bhyveload 8 .
|
||||||
.El
|
.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
|
.Sh SIGNAL HANDLING
|
||||||
.Nm
|
.Nm
|
||||||
deals with the following signals:
|
deals with the following signals:
|
||||||
|
@ -71,6 +71,7 @@ __FBSDID("$FreeBSD$");
|
|||||||
#include "inout.h"
|
#include "inout.h"
|
||||||
#include "dbgport.h"
|
#include "dbgport.h"
|
||||||
#include "fwctl.h"
|
#include "fwctl.h"
|
||||||
|
#include "gdb.h"
|
||||||
#include "ioapic.h"
|
#include "ioapic.h"
|
||||||
#include "mem.h"
|
#include "mem.h"
|
||||||
#include "mevent.h"
|
#include "mevent.h"
|
||||||
@ -338,6 +339,8 @@ fbsdrun_start_thread(void *param)
|
|||||||
snprintf(tname, sizeof(tname), "vcpu %d", vcpu);
|
snprintf(tname, sizeof(tname), "vcpu %d", vcpu);
|
||||||
pthread_set_name_np(mtp->mt_thr, tname);
|
pthread_set_name_np(mtp->mt_thr, tname);
|
||||||
|
|
||||||
|
gdb_cpu_add(vcpu);
|
||||||
|
|
||||||
vm_loop(mtp->mt_ctx, vcpu, vmexit[vcpu].rip);
|
vm_loop(mtp->mt_ctx, vcpu, vmexit[vcpu].rip);
|
||||||
|
|
||||||
/* not reached */
|
/* not reached */
|
||||||
@ -601,6 +604,8 @@ vmexit_mtrap(struct vmctx *ctx, struct vm_exit *vmexit, int *pvcpu)
|
|||||||
|
|
||||||
stats.vmexit_mtrap++;
|
stats.vmexit_mtrap++;
|
||||||
|
|
||||||
|
gdb_cpu_mtrap(*pvcpu);
|
||||||
|
|
||||||
return (VMEXIT_CONTINUE);
|
return (VMEXIT_CONTINUE);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -675,6 +680,14 @@ vmexit_suspend(struct vmctx *ctx, struct vm_exit *vmexit, int *pvcpu)
|
|||||||
return (0); /* NOTREACHED */
|
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] = {
|
static vmexit_handler_t handler[VM_EXITCODE_MAX] = {
|
||||||
[VM_EXITCODE_INOUT] = vmexit_inout,
|
[VM_EXITCODE_INOUT] = vmexit_inout,
|
||||||
[VM_EXITCODE_INOUT_STR] = 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_SPINUP_AP] = vmexit_spinup_ap,
|
||||||
[VM_EXITCODE_SUSPENDED] = vmexit_suspend,
|
[VM_EXITCODE_SUSPENDED] = vmexit_suspend,
|
||||||
[VM_EXITCODE_TASK_SWITCH] = vmexit_task_switch,
|
[VM_EXITCODE_TASK_SWITCH] = vmexit_task_switch,
|
||||||
|
[VM_EXITCODE_DEBUG] = vmexit_debug,
|
||||||
};
|
};
|
||||||
|
|
||||||
static void
|
static void
|
||||||
@ -877,9 +891,10 @@ do_open(const char *vmname)
|
|||||||
int
|
int
|
||||||
main(int argc, char *argv[])
|
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 max_vcpus, mptgen, memflags;
|
||||||
int rtc_localtime;
|
int rtc_localtime;
|
||||||
|
bool gdb_stop;
|
||||||
struct vmctx *ctx;
|
struct vmctx *ctx;
|
||||||
uint64_t rip;
|
uint64_t rip;
|
||||||
size_t memsize;
|
size_t memsize;
|
||||||
@ -887,7 +902,9 @@ main(int argc, char *argv[])
|
|||||||
|
|
||||||
bvmcons = 0;
|
bvmcons = 0;
|
||||||
progname = basename(argv[0]);
|
progname = basename(argv[0]);
|
||||||
|
dbg_port = 0;
|
||||||
gdb_port = 0;
|
gdb_port = 0;
|
||||||
|
gdb_stop = false;
|
||||||
guest_ncpus = 1;
|
guest_ncpus = 1;
|
||||||
sockets = cores = threads = 1;
|
sockets = cores = threads = 1;
|
||||||
maxcpus = 0;
|
maxcpus = 0;
|
||||||
@ -896,7 +913,7 @@ main(int argc, char *argv[])
|
|||||||
rtc_localtime = 1;
|
rtc_localtime = 1;
|
||||||
memflags = 0;
|
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) {
|
while ((c = getopt(argc, argv, optstr)) != -1) {
|
||||||
switch (c) {
|
switch (c) {
|
||||||
case 'a':
|
case 'a':
|
||||||
@ -924,6 +941,13 @@ main(int argc, char *argv[])
|
|||||||
memflags |= VM_MEM_F_INCORE;
|
memflags |= VM_MEM_F_INCORE;
|
||||||
break;
|
break;
|
||||||
case 'g':
|
case 'g':
|
||||||
|
dbg_port = atoi(optarg);
|
||||||
|
break;
|
||||||
|
case 'G':
|
||||||
|
if (optarg[0] == 'w') {
|
||||||
|
gdb_stop = true;
|
||||||
|
optarg++;
|
||||||
|
}
|
||||||
gdb_port = atoi(optarg);
|
gdb_port = atoi(optarg);
|
||||||
break;
|
break;
|
||||||
case 'l':
|
case 'l':
|
||||||
@ -1033,8 +1057,11 @@ main(int argc, char *argv[])
|
|||||||
if (init_pci(ctx) != 0)
|
if (init_pci(ctx) != 0)
|
||||||
exit(1);
|
exit(1);
|
||||||
|
|
||||||
|
if (dbg_port != 0)
|
||||||
|
init_dbgport(dbg_port);
|
||||||
|
|
||||||
if (gdb_port != 0)
|
if (gdb_port != 0)
|
||||||
init_dbgport(gdb_port);
|
init_gdb(ctx, gdb_port, gdb_stop);
|
||||||
|
|
||||||
if (bvmcons)
|
if (bvmcons)
|
||||||
init_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);
|
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
|
static int
|
||||||
mem_read(void *ctx, int vcpu, uint64_t gpa, uint64_t *rval, int size, void *arg)
|
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);
|
return (error);
|
||||||
}
|
}
|
||||||
|
|
||||||
int
|
static int
|
||||||
emulate_mem(struct vmctx *ctx, int vcpu, uint64_t paddr, struct vie *vie,
|
access_memory(struct vmctx *ctx, int vcpu, uint64_t paddr, mem_cb_t *cb,
|
||||||
struct vm_guest_paging *paging)
|
void *arg)
|
||||||
|
|
||||||
{
|
{
|
||||||
struct mmio_rb_range *entry;
|
struct mmio_rb_range *entry;
|
||||||
int err, immutable;
|
int err, immutable;
|
||||||
@ -204,8 +206,7 @@ emulate_mem(struct vmctx *ctx, int vcpu, uint64_t paddr, struct vie *vie,
|
|||||||
if (immutable)
|
if (immutable)
|
||||||
pthread_rwlock_unlock(&mmio_rwlock);
|
pthread_rwlock_unlock(&mmio_rwlock);
|
||||||
|
|
||||||
err = vmm_emulate_instruction(ctx, vcpu, paddr, vie, paging,
|
err = cb(ctx, vcpu, paddr, &entry->mr_param, arg);
|
||||||
mem_read, mem_write, &entry->mr_param);
|
|
||||||
|
|
||||||
if (!immutable)
|
if (!immutable)
|
||||||
pthread_rwlock_unlock(&mmio_rwlock);
|
pthread_rwlock_unlock(&mmio_rwlock);
|
||||||
@ -213,6 +214,60 @@ emulate_mem(struct vmctx *ctx, int vcpu, uint64_t paddr, struct vie *vie,
|
|||||||
return (err);
|
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
|
static int
|
||||||
register_mem_int(struct mmio_rb_tree *rbt, struct mem_range *memp)
|
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,
|
int emulate_mem(struct vmctx *, int vcpu, uint64_t paddr, struct vie *vie,
|
||||||
struct vm_guest_paging *paging);
|
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(struct mem_range *memp);
|
||||||
int register_mem_fallback(struct mem_range *memp);
|
int register_mem_fallback(struct mem_range *memp);
|
||||||
int unregister_mem(struct mem_range *memp);
|
int unregister_mem(struct mem_range *memp);
|
||||||
|
Loading…
Reference in New Issue
Block a user