arm64: extend struct db_reg to include watchpoint registers

The motivation is to provide access to these registers from userspace
via ptrace(2) requests PT_GETDBREGS and PT_SETDBREGS.

This change breaks the ABI of these particular requests, but is
justified by the fact that the intended consumers (debuggers) have not
been taught to use them yet. Making this change now enables active
upstream work on lldb to begin using this interface, and take advantage
of the hardware debugging registers available on the platform.

PR:		252860
Reported by:	Michał Górny (mgorny@gentoo.org)
Reviewed by:	andrew, markj (earlier version)
Tested by:	Michał Górny (mgorny@gentoo.org)
MFC after:	1 week
Sponsored by:	The FreeBSD Foundation
Differential Revision:	https://reviews.freebsd.org/D28415
This commit is contained in:
Mitchell Horne 2021-01-28 13:49:47 -04:00
parent bd012c7159
commit f2583be110
4 changed files with 92 additions and 16 deletions

View File

@ -350,7 +350,7 @@ static struct mrs_field id_aa64dfr0_fields[] = {
MRS_FIELD(ID_AA64DFR0, PMSVer, false, MRS_EXACT, id_aa64dfr0_pmsver),
MRS_FIELD(ID_AA64DFR0, CTX_CMPs, false, MRS_EXACT,
id_aa64dfr0_ctx_cmps),
MRS_FIELD(ID_AA64DFR0, WRPs, false, MRS_EXACT, id_aa64dfr0_wrps),
MRS_FIELD(ID_AA64DFR0, WRPs, false, MRS_LOWER, id_aa64dfr0_wrps),
MRS_FIELD(ID_AA64DFR0, BRPs, false, MRS_LOWER, id_aa64dfr0_brps),
MRS_FIELD(ID_AA64DFR0, PMUVer, false, MRS_EXACT, id_aa64dfr0_pmuver),
MRS_FIELD(ID_AA64DFR0, TraceVer, false, MRS_EXACT,

View File

@ -321,8 +321,8 @@ int
fill_dbregs(struct thread *td, struct dbreg *regs)
{
struct debug_monitor_state *monitor;
int count, i;
uint8_t debug_ver, nbkpts;
int i;
uint8_t debug_ver, nbkpts, nwtpts;
memset(regs, 0, sizeof(*regs));
@ -330,23 +330,30 @@ fill_dbregs(struct thread *td, struct dbreg *regs)
&debug_ver);
extract_user_id_field(ID_AA64DFR0_EL1, ID_AA64DFR0_BRPs_SHIFT,
&nbkpts);
extract_user_id_field(ID_AA64DFR0_EL1, ID_AA64DFR0_WRPs_SHIFT,
&nwtpts);
/*
* The BRPs field contains the number of breakpoints - 1. Armv8-A
* allows the hardware to provide 2-16 breakpoints so this won't
* overflow an 8 bit value.
* overflow an 8 bit value. The same applies to the WRPs field.
*/
count = nbkpts + 1;
nbkpts++;
nwtpts++;
regs->db_info = debug_ver;
regs->db_info <<= 8;
regs->db_info |= count;
regs->db_debug_ver = debug_ver;
regs->db_nbkpts = nbkpts;
regs->db_nwtpts = nwtpts;
monitor = &td->td_pcb->pcb_dbg_regs;
if ((monitor->dbg_flags & DBGMON_ENABLED) != 0) {
for (i = 0; i < count; i++) {
regs->db_regs[i].dbr_addr = monitor->dbg_bvr[i];
regs->db_regs[i].dbr_ctrl = monitor->dbg_bcr[i];
for (i = 0; i < nbkpts; i++) {
regs->db_breakregs[i].dbr_addr = monitor->dbg_bvr[i];
regs->db_breakregs[i].dbr_ctrl = monitor->dbg_bcr[i];
}
for (i = 0; i < nwtpts; i++) {
regs->db_watchregs[i].dbw_addr = monitor->dbg_wvr[i];
regs->db_watchregs[i].dbw_ctrl = monitor->dbg_wcr[i];
}
}
@ -365,9 +372,10 @@ set_dbregs(struct thread *td, struct dbreg *regs)
monitor = &td->td_pcb->pcb_dbg_regs;
count = 0;
monitor->dbg_enable_count = 0;
for (i = 0; i < DBG_BRP_MAX; i++) {
addr = regs->db_regs[i].dbr_addr;
ctrl = regs->db_regs[i].dbr_ctrl;
addr = regs->db_breakregs[i].dbr_addr;
ctrl = regs->db_breakregs[i].dbr_ctrl;
/* Don't let the user set a breakpoint on a kernel address. */
if (addr >= VM_MAXUSER_ADDRESS)
@ -399,6 +407,45 @@ set_dbregs(struct thread *td, struct dbreg *regs)
monitor->dbg_bvr[i] = addr;
monitor->dbg_bcr[i] = ctrl;
}
for (i = 0; i < DBG_WRP_MAX; i++) {
addr = regs->db_watchregs[i].dbw_addr;
ctrl = regs->db_watchregs[i].dbw_ctrl;
/* Don't let the user set a watchpoint on a kernel address. */
if (addr >= VM_MAXUSER_ADDRESS)
return (EINVAL);
/*
* Some control fields are ignored, and other bits reserved.
* Only unlinked watchpoints are supported.
*/
ctrl &= DBG_WCR_EN | DBG_WCR_PAC | DBG_WCR_LSC | DBG_WCR_BAS |
DBG_WCR_MASK;
if ((ctrl & DBG_WCR_EN) != 0) {
/* Only target EL0. */
if ((ctrl & DBG_WCR_PAC) != DBG_WCR_PAC_EL0)
return (EINVAL);
/* Must set at least one of the load/store bits. */
if ((ctrl & DBG_WCR_LSC) == 0)
return (EINVAL);
/*
* When specifying the address range with BAS, the MASK
* field must be zero.
*/
if ((ctrl & DBG_WCR_BAS) != DBG_WCR_BAS_MASK &&
(ctrl & DBG_WCR_MASK) != 0)
return (EINVAL);
monitor->dbg_enable_count++;
}
monitor->dbg_wvr[i] = addr;
monitor->dbg_wcr[i] = ctrl;
}
if (monitor->dbg_enable_count > 0)
monitor->dbg_flags |= DBGMON_ENABLED;

View File

@ -962,6 +962,28 @@
#define DBG_BCR_BT_SHIFT 20
#define DBG_BCR_BT (0xf << DBG_BCR_BT_SHIFT)
/* Debug Watchpoint Control Registers */
#define DBG_WCR_EN 0x1
#define DBG_WCR_PAC_SHIFT 1
#define DBG_WCR_PAC (0x3 << DBG_WCR_PAC_SHIFT)
#define DBG_WCR_PAC_EL1 (0x1 << DBG_WCR_PAC_SHIFT)
#define DBG_WCR_PAC_EL0 (0x2 << DBG_WCR_PAC_SHIFT)
#define DBG_WCR_LSC_SHIFT 3
#define DBG_WCR_LSC (0x3 << DBG_WCR_LSC_SHIFT)
#define DBG_WCR_BAS_SHIFT 5
#define DBG_WCR_BAS (0xff << DBG_WCR_BAS_SHIFT)
#define DBG_WCR_BAS_MASK DBG_WCR_BAS
#define DBG_WCR_HMC_SHIFT 13
#define DBG_WCR_HMC (0x1 << DBG_WCR_HMC_SHIFT)
#define DBG_WCR_SSC_SHIFT 14
#define DBG_WCR_SSC (0x3 << DBG_WCR_SSC_SHIFT)
#define DBG_WCR_LBN_SHIFT 16
#define DBG_WCR_LBN (0xf << DBG_WCR_LBN_SHIFT)
#define DBG_WCR_WT_SHIFT 20
#define DBG_WCR_WT (0x1 << DBG_WCR_WT_SHIFT)
#define DBG_WCR_MASK_SHIFT 24
#define DBG_WCR_MASK (0x1f << DBG_WCR_MASK_SHIFT)
/* Perfomance Monitoring Counters */
#define PMCR_E (1 << 0) /* Enable all counters */
#define PMCR_P (1 << 1) /* Reset all counters */

View File

@ -60,14 +60,21 @@ struct fpreg32 {
};
struct dbreg {
uint32_t db_info;
uint32_t db_pad;
uint8_t db_debug_ver;
uint8_t db_nbkpts;
uint8_t db_nwtpts;
uint8_t db_pad[5];
struct {
uint64_t dbr_addr;
uint32_t dbr_ctrl;
uint32_t dbr_pad;
} db_regs[16];
} db_breakregs[16];
struct {
uint64_t dbw_addr;
uint32_t dbw_ctrl;
uint32_t dbw_pad;
} db_watchregs[16];
};
struct dbreg32 {