bhyve: Add arm64 support to the gdb stub

- Add -G to the arm64 getopt handler.
- Add static register definitions and extensible XML definitions.
- Provide definitions for MD bits such as breakpoint encoding and
  length.
- Ensure that bhyve re-injects breakpoint exceptions that it is not
  responsible for.

Reviewed by:	andrew
Sponsored by:	Innovate UK
Differential Revision:	https://reviews.freebsd.org/D44740
This commit is contained in:
Mark Johnston 2024-06-04 15:03:17 -04:00
parent 75cb949228
commit a0ca4af945
6 changed files with 213 additions and 14 deletions

View file

@ -7,3 +7,4 @@ SRCS+= \
SRCS+= vmm_instruction_emul.c
BHYVE_FDT_SUPPORT=
BHYVE_GDB_SUPPORT=

View file

@ -99,6 +99,7 @@ bhyve_usage(int code)
" -C: include guest memory in core file\n"
" -c: number of CPUs and/or topology specification\n"
" -D: destroy on power-off\n"
" -G: start a debug server\n"
" -h: help\n"
" -k: key=value flat config file\n"
" -m: memory size\n"
@ -119,7 +120,7 @@ bhyve_optparse(int argc, char **argv)
const char *optstr;
int c;
optstr = "hCDSWk:f:o:p:c:s:m:U:";
optstr = "hCDSWk:f:o:p:G:c:s:m:U:";
while ((c = getopt(argc, argv, optstr)) != -1) {
switch (c) {
case 'c':
@ -134,6 +135,9 @@ bhyve_optparse(int argc, char **argv)
case 'D':
set_config_bool("destroy_on_poweroff", true);
break;
case 'G':
bhyve_parse_gdb_options(optarg);
break;
case 'k':
bhyve_parse_simple_config_file(optarg);
break;

View file

@ -49,6 +49,7 @@
#include "bhyverun.h"
#include "config.h"
#include "debug.h"
#include "gdb.h"
#include "mem.h"
#include "vmexit.h"
@ -112,9 +113,10 @@ vmexit_suspend(struct vmctx *ctx, struct vcpu *vcpu, struct vm_run *vmrun)
}
static int
vmexit_debug(struct vmctx *ctx __unused, struct vcpu *vcpu __unused,
vmexit_debug(struct vmctx *ctx __unused, struct vcpu *vcpu,
struct vm_run *vmrun __unused)
{
gdb_cpu_suspend(vcpu);
return (VMEXIT_CONTINUE);
}
@ -250,6 +252,20 @@ vmexit_hyp(struct vmctx *ctx __unused, struct vcpu *vcpu __unused,
return (VMEXIT_ABORT);
}
static int
vmexit_brk(struct vmctx *ctx __unused, struct vcpu *vcpu, struct vm_run *vmrun)
{
gdb_cpu_breakpoint(vcpu, vmrun->vm_exit);
return (VMEXIT_CONTINUE);
}
static int
vmexit_ss(struct vmctx *ctx __unused, struct vcpu *vcpu, struct vm_run *vmrun)
{
gdb_cpu_debug(vcpu, vmrun->vm_exit);
return (VMEXIT_CONTINUE);
}
const vmexit_handler_t vmexit_handlers[VM_EXITCODE_MAX] = {
[VM_EXITCODE_BOGUS] = vmexit_bogus,
[VM_EXITCODE_INST_EMUL] = vmexit_inst_emul,
@ -257,4 +273,6 @@ const vmexit_handler_t vmexit_handlers[VM_EXITCODE_MAX] = {
[VM_EXITCODE_DEBUG] = vmexit_debug,
[VM_EXITCODE_SMCCC] = vmexit_smccc,
[VM_EXITCODE_HYP] = vmexit_hyp,
[VM_EXITCODE_BRK] = vmexit_brk,
[VM_EXITCODE_SS] = vmexit_ss,
};

View file

@ -36,10 +36,17 @@
#include <sys/socket.h>
#include <sys/stat.h>
#ifdef __aarch64__
#include <machine/armreg.h>
#endif
#include <machine/atomic.h>
#ifdef __amd64__
#include <machine/specialreg.h>
#endif
#include <machine/vmm.h>
#include <netinet/in.h>
#include <assert.h>
#ifndef WITHOUT_CAPSICUM
#include <capsicum_helpers.h>
@ -73,9 +80,19 @@
*/
#define GDB_SIGNAL_TRAP 5
#if defined(__amd64__)
#define GDB_BP_SIZE 1
#define GDB_BP_INSTR (uint8_t []){0xcc}
#define GDB_PC_REGNAME VM_REG_GUEST_RIP
#define GDB_BREAKPOINT_CAP VM_CAP_BPT_EXIT
#elif defined(__aarch64__)
#define GDB_BP_SIZE 4
#define GDB_BP_INSTR (uint8_t []){0x00, 0x00, 0x20, 0xd4}
#define GDB_PC_REGNAME VM_REG_GUEST_PC
#define GDB_BREAKPOINT_CAP VM_CAP_BRK_EXIT
#else
#error "Unsupported architecture"
#endif
_Static_assert(sizeof(GDB_BP_INSTR) == GDB_BP_SIZE,
"GDB_BP_INSTR has wrong size");
@ -146,10 +163,13 @@ static struct vcpu **vcpus;
static int cur_vcpu, stopped_vcpu;
static bool gdb_active = false;
static const struct gdb_reg {
struct gdb_reg {
enum vm_reg_name id;
int size;
} gdb_regset[] = {
}
#ifdef __amd64__
static const gdb_regset[] = {
{ .id = VM_REG_GUEST_RAX, .size = 8 },
{ .id = VM_REG_GUEST_RBX, .size = 8 },
{ .id = VM_REG_GUEST_RCX, .size = 8 },
@ -191,6 +211,44 @@ static const struct gdb_reg {
{ .id = VM_REG_GUEST_TPR, .size = 8 },
{ .id = VM_REG_GUEST_EFER, .size = 8 },
};
#else /* __aarch64__ */
static const gdb_regset[] = {
{ .id = VM_REG_GUEST_X0, .size = 8 },
{ .id = VM_REG_GUEST_X1, .size = 8 },
{ .id = VM_REG_GUEST_X2, .size = 8 },
{ .id = VM_REG_GUEST_X3, .size = 8 },
{ .id = VM_REG_GUEST_X4, .size = 8 },
{ .id = VM_REG_GUEST_X5, .size = 8 },
{ .id = VM_REG_GUEST_X6, .size = 8 },
{ .id = VM_REG_GUEST_X7, .size = 8 },
{ .id = VM_REG_GUEST_X8, .size = 8 },
{ .id = VM_REG_GUEST_X9, .size = 8 },
{ .id = VM_REG_GUEST_X10, .size = 8 },
{ .id = VM_REG_GUEST_X11, .size = 8 },
{ .id = VM_REG_GUEST_X12, .size = 8 },
{ .id = VM_REG_GUEST_X13, .size = 8 },
{ .id = VM_REG_GUEST_X14, .size = 8 },
{ .id = VM_REG_GUEST_X15, .size = 8 },
{ .id = VM_REG_GUEST_X16, .size = 8 },
{ .id = VM_REG_GUEST_X17, .size = 8 },
{ .id = VM_REG_GUEST_X18, .size = 8 },
{ .id = VM_REG_GUEST_X19, .size = 8 },
{ .id = VM_REG_GUEST_X20, .size = 8 },
{ .id = VM_REG_GUEST_X21, .size = 8 },
{ .id = VM_REG_GUEST_X22, .size = 8 },
{ .id = VM_REG_GUEST_X23, .size = 8 },
{ .id = VM_REG_GUEST_X24, .size = 8 },
{ .id = VM_REG_GUEST_X25, .size = 8 },
{ .id = VM_REG_GUEST_X26, .size = 8 },
{ .id = VM_REG_GUEST_X27, .size = 8 },
{ .id = VM_REG_GUEST_X28, .size = 8 },
{ .id = VM_REG_GUEST_X29, .size = 8 },
{ .id = VM_REG_GUEST_LR, .size = 8 },
{ .id = VM_REG_GUEST_SP, .size = 8 },
{ .id = VM_REG_GUEST_PC, .size = 8 },
{ .id = VM_REG_GUEST_CPSR, .size = 8 },
};
#endif
#ifdef GDB_LOG
#include <stdarg.h>
@ -228,6 +286,7 @@ static void remove_all_sw_breakpoints(void);
static int
guest_paging_info(struct vcpu *vcpu, struct vm_guest_paging *paging)
{
#ifdef __amd64__
uint64_t regs[4];
const int regset[4] = {
VM_REG_GUEST_CR0,
@ -262,6 +321,31 @@ guest_paging_info(struct vcpu *vcpu, struct vm_guest_paging *paging)
else
paging->paging_mode = PAGING_MODE_PAE;
return (0);
#else /* __aarch64__ */
uint64_t regs[6];
const int regset[6] = {
VM_REG_GUEST_TTBR0_EL1,
VM_REG_GUEST_TTBR1_EL1,
VM_REG_GUEST_TCR_EL1,
VM_REG_GUEST_TCR2_EL1,
VM_REG_GUEST_SCTLR_EL1,
VM_REG_GUEST_CPSR,
};
if (vm_get_register_set(vcpu, nitems(regset), regset, regs) == -1)
return (-1);
memset(paging, 0, sizeof(*paging));
paging->ttbr0_addr = regs[0] & ~(TTBR_ASID_MASK | TTBR_CnP);
paging->ttbr1_addr = regs[1] & ~(TTBR_ASID_MASK | TTBR_CnP);
paging->tcr_el1 = regs[2];
paging->tcr2_el1 = regs[3];
paging->flags = regs[5] & (PSR_M_MASK | PSR_M_32);
if ((regs[4] & SCTLR_M) != 0)
paging->flags |= VM_GP_MMU_ENABLED;
return (0);
#endif /* __aarch64__ */
}
/*
@ -294,7 +378,11 @@ guest_vaddr2paddr(struct vcpu *vcpu, uint64_t vaddr, uint64_t *paddr)
static uint64_t
guest_pc(struct vm_exit *vme)
{
#ifdef __amd64__
return (vme->rip);
#else /* __aarch64__ */
return (vme->pc);
#endif
}
static void
@ -762,6 +850,7 @@ _gdb_set_step(struct vcpu *vcpu, int val)
{
int error;
#ifdef __amd64__
/*
* If the MTRAP cap fails, we are running on an AMD host.
* In that case, we request DB exits caused by RFLAGS.TF.
@ -771,23 +860,31 @@ _gdb_set_step(struct vcpu *vcpu, int val)
error = vm_set_capability(vcpu, VM_CAP_RFLAGS_TF, val);
if (error == 0)
(void)vm_set_capability(vcpu, VM_CAP_MASK_HWINTR, val);
#else /* __aarch64__ */
error = vm_set_capability(vcpu, VM_CAP_SS_EXIT, val);
if (error == 0)
error = vm_set_capability(vcpu, VM_CAP_MASK_HWINTR, val);
#endif
return (error);
}
/*
* Checks whether single-stepping is enabled for a given vCPU.
* Checks whether single-stepping is supported for a given vCPU.
*/
static int
_gdb_check_step(struct vcpu *vcpu)
{
#ifdef __amd64__
int val;
if (vm_get_capability(vcpu, VM_CAP_MTRAP_EXIT, &val) != 0) {
if (vm_get_capability(vcpu, VM_CAP_RFLAGS_TF, &val) != 0)
return -1;
return (-1);
}
return 0;
#else /* __aarch64__ */
(void)vcpu;
#endif
return (0);
}
/*
@ -809,7 +906,7 @@ gdb_cpu_add(struct vcpu *vcpu)
vcpus[vcpuid] = vcpu;
CPU_SET(vcpuid, &vcpus_active);
if (!TAILQ_EMPTY(&breakpoints)) {
vm_set_capability(vcpu, VM_CAP_BPT_EXIT, 1);
vm_set_capability(vcpu, GDB_BREAKPOINT_CAP, 1);
debug("$vCPU %d enabled breakpoint exits\n", vcpuid);
}
@ -912,7 +1009,7 @@ gdb_cpu_step(struct vcpu *vcpu)
}
/*
* A general handler for VM_EXITCODE_DB.
* A general handler for single-step exceptions.
* Handles RFLAGS.TF exits on AMD SVM.
*/
void
@ -921,10 +1018,15 @@ gdb_cpu_debug(struct vcpu *vcpu, struct vm_exit *vmexit)
if (!gdb_active)
return;
#ifdef __amd64__
/* RFLAGS.TF exit? */
if (vmexit->u.dbg.trace_trap) {
gdb_cpu_step(vcpu);
}
#else /* __aarch64__ */
(void)vmexit;
gdb_cpu_step(vcpu);
#endif
}
/*
@ -998,11 +1100,19 @@ gdb_cpu_breakpoint(struct vcpu *vcpu, struct vm_exit *vmexit)
} else {
debug("$vCPU %d injecting breakpoint at rip %#lx\n", vcpuid,
guest_pc(vmexit));
#ifdef __amd64__
error = vm_set_register(vcpu, VM_REG_GUEST_ENTRY_INST_LENGTH,
vmexit->u.bpt.inst_length);
assert(error == 0);
error = vm_inject_exception(vcpu, IDT_BP, 0, 0, 0);
assert(error == 0);
#else /* __aarch64__ */
uint64_t esr;
esr = (EXCP_BRK << ESR_ELx_EC_SHIFT) | vmexit->u.hyp.esr_el2;
error = vm_inject_exception(vcpu, esr, 0);
assert(error == 0);
#endif
}
pthread_mutex_unlock(&gdb_lock);
}
@ -1053,8 +1163,10 @@ gdb_read_regs(void)
start_packet();
for (size_t i = 0; i < nitems(gdb_regset); i++) {
#ifdef GDB_REG_FIRST_EXT
if (gdb_regset[i].id == GDB_REG_FIRST_EXT)
break;
#endif
append_unsigned_native(regvals[i], gdb_regset[i].size);
}
finish_packet();
@ -1318,7 +1430,7 @@ set_breakpoint_caps(bool enable)
while (!CPU_EMPTY(&mask)) {
vcpu = CPU_FFS(&mask) - 1;
CPU_CLR(vcpu, &mask);
if (vm_set_capability(vcpus[vcpu], VM_CAP_BPT_EXIT,
if (vm_set_capability(vcpus[vcpu], GDB_BREAKPOINT_CAP,
enable ? 1 : 0) < 0)
return (false);
debug("$vCPU %d %sabled breakpoint exits\n", vcpu,
@ -1327,6 +1439,20 @@ set_breakpoint_caps(bool enable)
return (true);
}
static void
write_instr(uint8_t *dest, uint8_t *instr, size_t len)
{
memcpy(dest, instr, len);
#ifdef __arm64__
__asm __volatile(
"dc cvau, %0\n"
"dsb ish\n"
"ic ialluis\n"
"dsb ish\n"
: : "r" (dest) : "memory");
#endif
}
static void
remove_all_sw_breakpoints(void)
{
@ -1339,7 +1465,7 @@ remove_all_sw_breakpoints(void)
TAILQ_FOREACH_SAFE(bp, &breakpoints, link, nbp) {
debug("remove breakpoint at %#lx\n", bp->gpa);
cp = paddr_guest2host(ctx, bp->gpa, sizeof(bp->shadow_inst));
memcpy(cp, bp->shadow_inst, sizeof(bp->shadow_inst));
write_instr(cp, bp->shadow_inst, sizeof(bp->shadow_inst));
TAILQ_REMOVE(&breakpoints, bp, link);
free(bp);
}
@ -1395,14 +1521,15 @@ update_sw_breakpoint(uint64_t gva, int kind, bool insert)
bp = malloc(sizeof(*bp));
bp->gpa = gpa;
memcpy(bp->shadow_inst, cp, sizeof(bp->shadow_inst));
memcpy(cp, GDB_BP_INSTR, sizeof(bp->shadow_inst));
write_instr(cp, GDB_BP_INSTR, sizeof(bp->shadow_inst));
TAILQ_INSERT_TAIL(&breakpoints, bp, link);
debug("new breakpoint at %#lx\n", gpa);
}
} else {
if (bp != NULL) {
debug("remove breakpoint at %#lx\n", gpa);
memcpy(cp, bp->shadow_inst, sizeof(bp->shadow_inst));
write_instr(cp, bp->shadow_inst,
sizeof(bp->shadow_inst));
TAILQ_REMOVE(&breakpoints, bp, link);
free(bp);
if (TAILQ_EMPTY(&breakpoints))

View file

@ -6,6 +6,9 @@ FILES+= target.xml
.if ${MACHINE_ARCH} == "amd64"
XMLARCH= i386:x86-64
FILES+= amd64.xml
.elif ${MACHINE_ARCH} == "aarch64"
XMLARCH= aarch64
FILES+= aarch64-core.xml
.endif
.if !make(install*)

View file

@ -0,0 +1,46 @@
<?xml version="1.0"?>
<!-- Copyright (C) 2009-2012 Free Software Foundation, Inc.
Contributed by ARM Ltd.
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. -->
<!DOCTYPE feature SYSTEM "gdb-target.dtd">
<feature name="org.gnu.gdb.aarch64.core">
<reg name="x0" bitsize="64"/>
<reg name="x1" bitsize="64"/>
<reg name="x2" bitsize="64"/>
<reg name="x3" bitsize="64"/>
<reg name="x4" bitsize="64"/>
<reg name="x5" bitsize="64"/>
<reg name="x6" bitsize="64"/>
<reg name="x7" bitsize="64"/>
<reg name="x8" bitsize="64"/>
<reg name="x9" bitsize="64"/>
<reg name="x10" bitsize="64"/>
<reg name="x11" bitsize="64"/>
<reg name="x12" bitsize="64"/>
<reg name="x13" bitsize="64"/>
<reg name="x14" bitsize="64"/>
<reg name="x15" bitsize="64"/>
<reg name="x16" bitsize="64"/>
<reg name="x17" bitsize="64"/>
<reg name="x18" bitsize="64"/>
<reg name="x19" bitsize="64"/>
<reg name="x20" bitsize="64"/>
<reg name="x21" bitsize="64"/>
<reg name="x22" bitsize="64"/>
<reg name="x23" bitsize="64"/>
<reg name="x24" bitsize="64"/>
<reg name="x25" bitsize="64"/>
<reg name="x26" bitsize="64"/>
<reg name="x27" bitsize="64"/>
<reg name="x28" bitsize="64"/>
<reg name="x29" bitsize="64"/>
<reg name="x30" bitsize="64"/>
<reg name="sp" bitsize="64" type="data_ptr"/>
<reg name="pc" bitsize="64" type="code_ptr"/>
<reg name="cpsr" bitsize="64"/>
</feature>