Add support for setting hardware breakpoints from ptrace on arm64.

Implement get/fill_dbregs on arm64. This is used by ptrace with the
PT_GETDBREGS and PT_SETDBREGS requests. It allows userspace to set hardware
breakpoints.

The struct dbreg is based on Linux to ease adding hardware breakpoint
support to debuggers.

Reviewed by:	jhb
Sponsored by:	DARPA, AFRL
Differential Revision:	https://reviews.freebsd.org/D22195
This commit is contained in:
Andrew Turner 2019-11-03 15:42:08 +00:00
parent 21da9f14f6
commit 05f39d1a2d
8 changed files with 127 additions and 6 deletions

View File

@ -36,6 +36,7 @@ __FBSDID("$FreeBSD$");
#include <sys/types.h> #include <sys/types.h>
#include <sys/kdb.h> #include <sys/kdb.h>
#include <sys/pcpu.h> #include <sys/pcpu.h>
#include <sys/proc.h>
#include <sys/systm.h> #include <sys/systm.h>
#include <machine/armreg.h> #include <machine/armreg.h>
@ -59,6 +60,10 @@ static struct debug_monitor_state kernel_monitor = {
.dbg_flags = DBGMON_KERNEL .dbg_flags = DBGMON_KERNEL
}; };
/* Called from the exception handlers */
void dbg_monitor_enter(struct thread *);
void dbg_monitor_exit(struct thread *, struct trapframe *);
/* Watchpoints/breakpoints control register bitfields */ /* Watchpoints/breakpoints control register bitfields */
#define DBG_WATCH_CTRL_LEN_1 (0x1 << 5) #define DBG_WATCH_CTRL_LEN_1 (0x1 << 5)
#define DBG_WATCH_CTRL_LEN_2 (0x3 << 5) #define DBG_WATCH_CTRL_LEN_2 (0x3 << 5)
@ -497,3 +502,57 @@ dbg_monitor_init(void)
dbg_enable(); dbg_enable();
} }
void
dbg_monitor_enter(struct thread *thread)
{
int i;
if ((kernel_monitor.dbg_flags & DBGMON_ENABLED) != 0) {
/* Install the kernel version of the registers */
dbg_register_sync(&kernel_monitor);
} else if ((thread->td_pcb->pcb_dbg_regs.dbg_flags & DBGMON_ENABLED) != 0) {
/* Disable the user breakpoints until we return to userspace */
for (i = 0; i < dbg_watchpoint_num; i++) {
dbg_wb_write_reg(DBG_REG_BASE_WCR, i, 0);
dbg_wb_write_reg(DBG_REG_BASE_WVR, i, 0);
}
for (i = 0; i < dbg_breakpoint_num; ++i) {
dbg_wb_write_reg(DBG_REG_BASE_BCR, i, 0);
dbg_wb_write_reg(DBG_REG_BASE_BVR, i, 0);
}
WRITE_SPECIALREG(mdscr_el1,
READ_SPECIALREG(mdscr_el1) &
~(DBG_MDSCR_MDE | DBG_MDSCR_KDE));
isb();
}
}
void
dbg_monitor_exit(struct thread *thread, struct trapframe *frame)
{
int i;
frame->tf_spsr |= PSR_D;
if ((thread->td_pcb->pcb_dbg_regs.dbg_flags & DBGMON_ENABLED) != 0) {
/* Install the kernel version of the registers */
dbg_register_sync(&thread->td_pcb->pcb_dbg_regs);
frame->tf_spsr &= ~PSR_D;
} else if ((kernel_monitor.dbg_flags & DBGMON_ENABLED) != 0) {
/* Disable the user breakpoints until we return to userspace */
for (i = 0; i < dbg_watchpoint_num; i++) {
dbg_wb_write_reg(DBG_REG_BASE_WCR, i, 0);
dbg_wb_write_reg(DBG_REG_BASE_WVR, i, 0);
}
for (i = 0; i < dbg_breakpoint_num; ++i) {
dbg_wb_write_reg(DBG_REG_BASE_BCR, i, 0);
dbg_wb_write_reg(DBG_REG_BASE_BVR, i, 0);
}
WRITE_SPECIALREG(mdscr_el1,
READ_SPECIALREG(mdscr_el1) &
~(DBG_MDSCR_MDE | DBG_MDSCR_KDE));
isb();
}
}

View File

@ -73,6 +73,9 @@ __FBSDID("$FreeBSD$");
mov w0, #1 mov w0, #1
blr x1 blr x1
1: 1:
ldr x0, [x18, #(PC_CURTHREAD)]
bl dbg_monitor_enter
.endif .endif
msr daifclr, #8 /* Enable the debug exception */ msr daifclr, #8 /* Enable the debug exception */
.endm .endm
@ -87,6 +90,10 @@ __FBSDID("$FreeBSD$");
msr daifset, #10 msr daifset, #10
.endif .endif
.if \el == 0 .if \el == 0
ldr x0, [x18, #PC_CURTHREAD]
mov x1, sp
bl dbg_monitor_exit
/* Remove the SSBD (CVE-2018-3639) workaround if needed */ /* Remove the SSBD (CVE-2018-3639) workaround if needed */
ldr x1, [x18, #PC_SSBD] ldr x1, [x18, #PC_SSBD]
cbz x1, 1f cbz x1, 1f

View File

@ -320,7 +320,7 @@ static struct mrs_field id_aa64dfr0_fields[] = {
MRS_FIELD(ID_AA64DFR0, CTX_CMPs, false, MRS_EXACT, MRS_FIELD(ID_AA64DFR0, CTX_CMPs, false, MRS_EXACT,
id_aa64dfr0_ctx_cmps), id_aa64dfr0_ctx_cmps),
MRS_FIELD(ID_AA64DFR0, WRPs, false, MRS_EXACT, id_aa64dfr0_wrps), MRS_FIELD(ID_AA64DFR0, WRPs, false, MRS_EXACT, id_aa64dfr0_wrps),
MRS_FIELD(ID_AA64DFR0, BRPs, false, MRS_EXACT, id_aa64dfr0_brps), 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, PMUVer, false, MRS_EXACT, id_aa64dfr0_pmuver),
MRS_FIELD(ID_AA64DFR0, TraceVer, false, MRS_EXACT, MRS_FIELD(ID_AA64DFR0, TraceVer, false, MRS_EXACT,
id_aa64dfr0_tracever), id_aa64dfr0_tracever),

View File

@ -281,17 +281,60 @@ set_fpregs(struct thread *td, struct fpreg *regs)
int int
fill_dbregs(struct thread *td, struct dbreg *regs) fill_dbregs(struct thread *td, struct dbreg *regs)
{ {
struct debug_monitor_state *monitor;
int count, i;
uint8_t debug_ver, nbkpts;
printf("ARM64TODO: fill_dbregs"); memset(regs, 0, sizeof(*regs));
return (EDOOFUS);
extract_user_id_field(ID_AA64DFR0_EL1, ID_AA64DFR0_DebugVer_SHIFT,
&debug_ver);
extract_user_id_field(ID_AA64DFR0_EL1, ID_AA64DFR0_BRPs_SHIFT,
&nbkpts);
/*
* 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.
*/
count = nbkpts + 1;
regs->db_info = debug_ver;
regs->db_info <<= 8;
regs->db_info |= count;
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];
}
}
return (0);
} }
int int
set_dbregs(struct thread *td, struct dbreg *regs) set_dbregs(struct thread *td, struct dbreg *regs)
{ {
struct debug_monitor_state *monitor;
int count;
int i;
printf("ARM64TODO: set_dbregs"); monitor = &td->td_pcb->pcb_dbg_regs;
return (EDOOFUS); count = 0;
monitor->dbg_enable_count = 0;
for (i = 0; i < DBG_BRP_MAX; i++) {
/* TODO: Check these values */
monitor->dbg_bvr[i] = regs->db_regs[i].dbr_addr;
monitor->dbg_bcr[i] = regs->db_regs[i].dbr_ctrl;
if ((monitor->dbg_bcr[i] & 1) != 0)
monitor->dbg_enable_count++;
}
if (monitor->dbg_enable_count > 0)
monitor->dbg_flags |= DBGMON_ENABLED;
return (0);
} }
#ifdef COMPAT_FREEBSD32 #ifdef COMPAT_FREEBSD32

View File

@ -482,6 +482,7 @@ do_el0_sync(struct thread *td, struct trapframe *frame)
call_trapsignal(td, SIGBUS, BUS_ADRALN, (void *)frame->tf_elr); call_trapsignal(td, SIGBUS, BUS_ADRALN, (void *)frame->tf_elr);
userret(td, frame); userret(td, frame);
break; break;
case EXCP_BRKPT_EL0:
case EXCP_BRK: case EXCP_BRK:
call_trapsignal(td, SIGTRAP, TRAP_BRKPT, (void *)frame->tf_elr); call_trapsignal(td, SIGTRAP, TRAP_BRKPT, (void *)frame->tf_elr);
userret(td, frame); userret(td, frame);

View File

@ -163,6 +163,7 @@
#define EXCP_SP_ALIGN 0x26 /* SP slignment fault */ #define EXCP_SP_ALIGN 0x26 /* SP slignment fault */
#define EXCP_TRAP_FP 0x2c /* Trapped FP exception */ #define EXCP_TRAP_FP 0x2c /* Trapped FP exception */
#define EXCP_SERROR 0x2f /* SError interrupt */ #define EXCP_SERROR 0x2f /* SError interrupt */
#define EXCP_BRKPT_EL0 0x30 /* Hardware breakpoint, from same EL */
#define EXCP_SOFTSTP_EL0 0x32 /* Software Step, from lower EL */ #define EXCP_SOFTSTP_EL0 0x32 /* Software Step, from lower EL */
#define EXCP_SOFTSTP_EL1 0x33 /* Software Step, from same EL */ #define EXCP_SOFTSTP_EL1 0x33 /* Software Step, from same EL */
#define EXCP_WATCHPT_EL1 0x35 /* Watchpoint, from same EL */ #define EXCP_WATCHPT_EL1 0x35 /* Watchpoint, from same EL */

View File

@ -31,6 +31,7 @@
#ifndef LOCORE #ifndef LOCORE
#include <machine/debug_monitor.h>
#include <machine/vfp.h> #include <machine/vfp.h>
struct trapframe; struct trapframe;
@ -66,6 +67,8 @@ struct pcb {
* Place last to simplify the asm to access the rest if the struct. * Place last to simplify the asm to access the rest if the struct.
*/ */
struct vfpstate pcb_fpustate; struct vfpstate pcb_fpustate;
struct debug_monitor_state pcb_dbg_regs;
}; };
#ifdef _KERNEL #ifdef _KERNEL

View File

@ -60,7 +60,14 @@ struct fpreg32 {
}; };
struct dbreg { struct dbreg {
int dummy; uint32_t db_info;
uint32_t db_pad;
struct {
uint64_t dbr_addr;
uint32_t dbr_ctrl;
uint32_t dbr_pad;
} db_regs[16];
}; };
struct dbreg32 { struct dbreg32 {