x86: consolidate hw watchpoint logic into new file

This is a prerequisite to using these functions outside of ddb, but also
provides some cleanup and minor refactoring. This code is almost
entirely duplicated between the two implementations, the only
significant difference being the lack of dbreg synchronization on i386.

Cleanups are:
 - demote some internal functions to static
 - use the constant NDBREGS instead of a '4' literal
 - remove K&R definitions
 - some added comments

Reviewed by:	kib, jhb
Sponsored by:	NetApp, Inc.
Sponsored by:	Klara, Inc.
MFC after:	1 week
Differential Revision:	https://reviews.freebsd.org/D29153
This commit is contained in:
Mitchell Horne 2021-03-19 16:39:12 -03:00
parent a1d803c162
commit c02c04f113
5 changed files with 281 additions and 379 deletions

View File

@ -128,11 +128,6 @@ static void db_nextframe(struct amd64_frame **, db_addr_t *, struct thread *);
static void db_print_stack_entry(const char *, db_addr_t, void *);
static void decode_syscall(int, struct thread *);
static const char * watchtype_str(int type);
int amd64_set_watch(int watchnum, unsigned long watchaddr, int size,
int access, struct dbreg *d);
int amd64_clr_watch(int watchnum, struct dbreg *d);
static void
db_print_stack_entry(const char *name, db_addr_t callpc, void *frame)
{
@ -391,223 +386,22 @@ db_trace_thread(struct thread *thr, int count)
}
int
amd64_set_watch(watchnum, watchaddr, size, access, d)
int watchnum;
unsigned long watchaddr;
int size;
int access;
struct dbreg *d;
db_md_set_watchpoint(db_expr_t addr, db_expr_t size)
{
int i, len;
if (watchnum == -1) {
for (i = 0; i < 4; i++)
if (!DBREG_DR7_ENABLED(d->dr[7], i))
break;
if (i < 4)
watchnum = i;
else
return (-1);
}
switch (access) {
case DBREG_DR7_EXEC:
size = 1; /* size must be 1 for an execution breakpoint */
/* fall through */
case DBREG_DR7_WRONLY:
case DBREG_DR7_RDWR:
break;
default:
return (-1);
}
/*
* we can watch a 1, 2, 4, or 8 byte sized location
*/
switch (size) {
case 1:
len = DBREG_DR7_LEN_1;
break;
case 2:
len = DBREG_DR7_LEN_2;
break;
case 4:
len = DBREG_DR7_LEN_4;
break;
case 8:
len = DBREG_DR7_LEN_8;
break;
default:
return (-1);
}
/* clear the bits we are about to affect */
d->dr[7] &= ~DBREG_DR7_MASK(watchnum);
/* set drN register to the address, N=watchnum */
DBREG_DRX(d, watchnum) = watchaddr;
/* enable the watchpoint */
d->dr[7] |= DBREG_DR7_SET(watchnum, len, access,
DBREG_DR7_GLOBAL_ENABLE);
return (watchnum);
return (dbreg_set_watchpoint((vm_offset_t)addr, (vm_size_t)size));
}
int
amd64_clr_watch(watchnum, d)
int watchnum;
struct dbreg *d;
db_md_clr_watchpoint(db_expr_t addr, db_expr_t size)
{
if (watchnum < 0 || watchnum >= 4)
return (-1);
d->dr[7] &= ~DBREG_DR7_MASK(watchnum);
DBREG_DRX(d, watchnum) = 0;
return (0);
}
int
db_md_set_watchpoint(addr, size)
db_expr_t addr;
db_expr_t size;
{
struct dbreg *d;
struct pcpu *pc;
int avail, c, cpu, i, wsize;
d = (struct dbreg *)PCPU_PTR(dbreg);
cpu = PCPU_GET(cpuid);
fill_dbregs(NULL, d);
avail = 0;
for (i = 0; i < 4; i++) {
if (!DBREG_DR7_ENABLED(d->dr[7], i))
avail++;
}
if (avail * 8 < size)
return (-1);
for (i = 0; i < 4 && size > 0; i++) {
if (!DBREG_DR7_ENABLED(d->dr[7], i)) {
if (size >= 8 || (avail == 1 && size > 4))
wsize = 8;
else if (size > 2)
wsize = 4;
else
wsize = size;
amd64_set_watch(i, addr, wsize, DBREG_DR7_WRONLY, d);
addr += wsize;
size -= wsize;
avail--;
}
}
set_dbregs(NULL, d);
CPU_FOREACH(c) {
if (c == cpu)
continue;
pc = pcpu_find(c);
memcpy(pc->pc_dbreg, d, sizeof(*d));
pc->pc_dbreg_cmd = PC_DBREG_CMD_LOAD;
}
return (0);
}
int
db_md_clr_watchpoint(addr, size)
db_expr_t addr;
db_expr_t size;
{
struct dbreg *d;
struct pcpu *pc;
int i, c, cpu;
d = (struct dbreg *)PCPU_PTR(dbreg);
cpu = PCPU_GET(cpuid);
fill_dbregs(NULL, d);
for (i = 0; i < 4; i++) {
if (DBREG_DR7_ENABLED(d->dr[7], i)) {
if (DBREG_DRX((d), i) >= addr &&
DBREG_DRX((d), i) < addr + size)
amd64_clr_watch(i, d);
}
}
set_dbregs(NULL, d);
CPU_FOREACH(c) {
if (c == cpu)
continue;
pc = pcpu_find(c);
memcpy(pc->pc_dbreg, d, sizeof(*d));
pc->pc_dbreg_cmd = PC_DBREG_CMD_LOAD;
}
return (0);
}
static const char *
watchtype_str(type)
int type;
{
switch (type) {
case DBREG_DR7_EXEC : return "execute"; break;
case DBREG_DR7_RDWR : return "read/write"; break;
case DBREG_DR7_WRONLY : return "write"; break;
default : return "invalid"; break;
}
return (dbreg_clr_watchpoint((vm_offset_t)addr, (vm_size_t)size));
}
void
db_md_list_watchpoints(void)
{
struct dbreg d;
int i, len, type;
fill_dbregs(NULL, &d);
db_printf("\nhardware watchpoints:\n");
db_printf(" watch status type len address\n");
db_printf(" ----- -------- ---------- --- ------------------\n");
for (i = 0; i < 4; i++) {
if (DBREG_DR7_ENABLED(d.dr[7], i)) {
type = DBREG_DR7_ACCESS(d.dr[7], i);
len = DBREG_DR7_LEN(d.dr[7], i);
if (len == DBREG_DR7_LEN_8)
len = 8;
else
len++;
db_printf(" %-5d %-8s %10s %3d ",
i, "enabled", watchtype_str(type), len);
db_printsym((db_addr_t)DBREG_DRX(&d, i), DB_STGY_ANY);
db_printf("\n");
} else {
db_printf(" %-5d disabled\n", i);
}
}
db_printf("\ndebug register values:\n");
for (i = 0; i < 8; i++)
if (i != 4 && i != 5)
db_printf(" dr%d 0x%016lx\n", i, DBREG_DRX(&d, i));
db_printf("\n");
}
void
amd64_db_resume_dbreg(void)
{
struct dbreg *d;
switch (PCPU_GET(dbreg_cmd)) {
case PC_DBREG_CMD_LOAD:
d = (struct dbreg *)PCPU_PTR(dbreg);
set_dbregs(NULL, d);
PCPU_SET(dbreg_cmd, PC_DBREG_CMD_NONE);
break;
}
dbreg_list_watchpoints();
}

View File

@ -320,6 +320,7 @@ x86/x86/bus_machdep.c standard
x86/x86/busdma_bounce.c standard
x86/x86/busdma_machdep.c standard
x86/x86/cpu_machdep.c standard
x86/x86/dbreg.c optional ddb
x86/x86/dump_machdep.c standard
x86/x86/fdt_machdep.c optional fdt
x86/x86/identcpu.c standard

View File

@ -198,11 +198,6 @@ static void db_print_stack_entry(const char *, int, char **, int *, db_addr_t,
void *);
static void decode_syscall(int, struct thread *);
static const char * watchtype_str(int type);
int i386_set_watch(int watchnum, unsigned int watchaddr, int size, int access,
struct dbreg *d);
int i386_clr_watch(int watchnum, struct dbreg *d);
/*
* Figure out how many arguments were passed into the frame at "fp".
*/
@ -618,180 +613,22 @@ db_trace_thread(struct thread *thr, int count)
}
int
i386_set_watch(watchnum, watchaddr, size, access, d)
int watchnum;
unsigned int watchaddr;
int size;
int access;
struct dbreg *d;
db_md_set_watchpoint(db_expr_t addr, db_expr_t size)
{
int i, len;
if (watchnum == -1) {
for (i = 0; i < 4; i++)
if (!DBREG_DR7_ENABLED(d->dr[7], i))
break;
if (i < 4)
watchnum = i;
else
return (-1);
}
switch (access) {
case DBREG_DR7_EXEC:
size = 1; /* size must be 1 for an execution breakpoint */
/* fall through */
case DBREG_DR7_WRONLY:
case DBREG_DR7_RDWR:
break;
default:
return (-1);
}
/*
* we can watch a 1, 2, or 4 byte sized location
*/
switch (size) {
case 1:
len = DBREG_DR7_LEN_1;
break;
case 2:
len = DBREG_DR7_LEN_2;
break;
case 4:
len = DBREG_DR7_LEN_4;
break;
default:
return (-1);
}
/* clear the bits we are about to affect */
d->dr[7] &= ~DBREG_DR7_MASK(watchnum);
/* set drN register to the address, N=watchnum */
DBREG_DRX(d, watchnum) = watchaddr;
/* enable the watchpoint */
d->dr[7] |= DBREG_DR7_SET(watchnum, len, access,
DBREG_DR7_GLOBAL_ENABLE);
return (watchnum);
return (dbreg_set_watchpoint((vm_offset_t)addr, (vm_size_t)size));
}
int
i386_clr_watch(watchnum, d)
int watchnum;
struct dbreg *d;
db_md_clr_watchpoint(db_expr_t addr, db_expr_t size)
{
if (watchnum < 0 || watchnum >= 4)
return (-1);
d->dr[7] &= ~DBREG_DR7_MASK(watchnum);
DBREG_DRX(d, watchnum) = 0;
return (0);
}
int
db_md_set_watchpoint(addr, size)
db_expr_t addr;
db_expr_t size;
{
struct dbreg d;
int avail, i, wsize;
fill_dbregs(NULL, &d);
avail = 0;
for(i = 0; i < 4; i++) {
if (!DBREG_DR7_ENABLED(d.dr[7], i))
avail++;
}
if (avail * 4 < size)
return (-1);
for (i = 0; i < 4 && (size > 0); i++) {
if (!DBREG_DR7_ENABLED(d.dr[7], i)) {
if (size > 2)
wsize = 4;
else
wsize = size;
i386_set_watch(i, addr, wsize,
DBREG_DR7_WRONLY, &d);
addr += wsize;
size -= wsize;
}
}
set_dbregs(NULL, &d);
return(0);
}
int
db_md_clr_watchpoint(addr, size)
db_expr_t addr;
db_expr_t size;
{
struct dbreg d;
int i;
fill_dbregs(NULL, &d);
for(i = 0; i < 4; i++) {
if (DBREG_DR7_ENABLED(d.dr[7], i)) {
if ((DBREG_DRX((&d), i) >= addr) &&
(DBREG_DRX((&d), i) < addr+size))
i386_clr_watch(i, &d);
}
}
set_dbregs(NULL, &d);
return(0);
}
static const char *
watchtype_str(type)
int type;
{
switch (type) {
case DBREG_DR7_EXEC : return "execute"; break;
case DBREG_DR7_RDWR : return "read/write"; break;
case DBREG_DR7_WRONLY : return "write"; break;
default : return "invalid"; break;
}
return (dbreg_clr_watchpoint((vm_offset_t)addr, (vm_size_t)size));
}
void
db_md_list_watchpoints(void)
{
struct dbreg d;
int i, len, type;
fill_dbregs(NULL, &d);
db_printf("\nhardware watchpoints:\n");
db_printf(" watch status type len address\n");
db_printf(" ----- -------- ---------- --- ----------\n");
for (i = 0; i < 4; i++) {
if (DBREG_DR7_ENABLED(d.dr[7], i)) {
type = DBREG_DR7_ACCESS(d.dr[7], i);
len = DBREG_DR7_LEN(d.dr[7], i);
db_printf(" %-5d %-8s %10s %3d ",
i, "enabled", watchtype_str(type), len + 1);
db_printsym((db_addr_t)DBREG_DRX(&d, i), DB_STGY_ANY);
db_printf("\n");
} else {
db_printf(" %-5d disabled\n", i);
}
}
db_printf("\ndebug register values:\n");
for (i = 0; i < 8; i++)
if (i != 4 && i != 5)
db_printf(" dr%d 0x%08x\n", i, DBREG_DRX(&d, i));
db_printf("\n");
dbreg_list_watchpoints();
}

View File

@ -120,6 +120,9 @@ vm_paddr_t cpu_getmaxphyaddr(void);
bool cpu_mwait_usable(void);
void cpu_probe_amdc1e(void);
void cpu_setregs(void);
int dbreg_set_watchpoint(vm_offset_t addr, vm_size_t size);
int dbreg_clr_watchpoint(vm_offset_t addr, vm_size_t size);
void dbreg_list_watchpoints(void);
bool disable_wp(void);
void restore_wp(bool old_wp);
void finishidentcpu(void);

267
sys/x86/x86/dbreg.c Normal file
View File

@ -0,0 +1,267 @@
/*-
* Mach Operating System
* Copyright (c) 1991,1990 Carnegie Mellon University
* All Rights Reserved.
*
* Permission to use, copy, modify and distribute this software and its
* documentation is hereby granted, provided that both the copyright
* notice and this permission notice appear in all copies of the
* software, derivative works or modified versions, and any portions
* thereof, and that both notices appear in supporting documentation.
*
* CARNEGIE MELLON ALLOWS FREE USE OF THIS SOFTWARE IN ITS
* CONDITION. CARNEGIE MELLON DISCLAIMS ANY LIABILITY OF ANY KIND FOR
* ANY DAMAGES WHATSOEVER RESULTING FROM THE USE OF THIS SOFTWARE.
*
* Carnegie Mellon requests users of this software to return to
*
* Software Distribution Coordinator or Software.Distribution@CS.CMU.EDU
* School of Computer Science
* Carnegie Mellon University
* Pittsburgh PA 15213-3890
*
* any improvements or extensions that they make and grant Carnegie the
* rights to redistribute these changes.
*/
#include "opt_ddb.h"
#include <sys/types.h>
#include <sys/pcpu.h>
#include <sys/smp.h>
#include <sys/systm.h>
#include <machine/frame.h>
#include <machine/md_var.h>
#include <ddb/ddb.h>
#include <ddb/db_sym.h>
#define NDBREGS 4
#ifdef __amd64__
#define MAXWATCHSIZE 8
#else
#define MAXWATCHSIZE 4
#endif
/*
* Set a watchpoint in the debug register denoted by 'watchnum'.
*/
static void
dbreg_set_watchreg(int watchnum, vm_offset_t watchaddr, vm_size_t size,
int access, struct dbreg *d)
{
int len;
MPASS(watchnum >= 0 && watchnum < NDBREGS);
/* size must be 1 for an execution breakpoint */
if (access == DBREG_DR7_EXEC)
size = 1;
/*
* we can watch a 1, 2, or 4 byte sized location
*/
switch (size) {
case 1:
len = DBREG_DR7_LEN_1;
break;
case 2:
len = DBREG_DR7_LEN_2;
break;
case 4:
len = DBREG_DR7_LEN_4;
break;
#if MAXWATCHSIZE >= 8
case 8:
len = DBREG_DR7_LEN_8;
break;
#endif
default:
return;
}
/* clear the bits we are about to affect */
d->dr[7] &= ~DBREG_DR7_MASK(watchnum);
/* set drN register to the address, N=watchnum */
DBREG_DRX(d, watchnum) = watchaddr;
/* enable the watchpoint */
d->dr[7] |= DBREG_DR7_SET(watchnum, len, access,
DBREG_DR7_GLOBAL_ENABLE);
}
/*
* Remove a watchpoint from the debug register denoted by 'watchnum'.
*/
static void
dbreg_clr_watchreg(int watchnum, struct dbreg *d)
{
MPASS(watchnum >= 0 && watchnum < NDBREGS);
d->dr[7] &= ~DBREG_DR7_MASK(watchnum);
DBREG_DRX(d, watchnum) = 0;
}
/*
* Sync the debug registers. Other cores will read these values from the PCPU
* area when they resume. See amd64_db_resume_dbreg() below.
*/
static void
dbreg_sync(struct dbreg *dp)
{
#ifdef __amd64__
struct pcpu *pc;
int cpu, c;
cpu = PCPU_GET(cpuid);
CPU_FOREACH(c) {
if (c == cpu)
continue;
pc = pcpu_find(c);
memcpy(pc->pc_dbreg, dp, sizeof(*dp));
pc->pc_dbreg_cmd = PC_DBREG_CMD_LOAD;
}
#endif
}
int
dbreg_set_watchpoint(vm_offset_t addr, vm_size_t size)
{
struct dbreg *d;
int avail, i, wsize;
#ifdef __amd64__
d = (struct dbreg *)PCPU_PTR(dbreg);
#else
/* debug registers aren't stored in PCPU on i386. */
struct dbreg d_temp;
d = &d_temp;
#endif
fill_dbregs(NULL, d);
/*
* Check if there are enough available registers to cover the desired
* area.
*/
avail = 0;
for (i = 0; i < NDBREGS; i++) {
if (!DBREG_DR7_ENABLED(d->dr[7], i))
avail++;
}
if (avail * MAXWATCHSIZE < size)
return (-1);
for (i = 0; i < NDBREGS && size > 0; i++) {
if (!DBREG_DR7_ENABLED(d->dr[7], i)) {
if ((size >= 8 || (avail == 1 && size > 4)) &&
MAXWATCHSIZE == 8)
wsize = 8;
else if (size > 2)
wsize = 4;
else
wsize = size;
dbreg_set_watchreg(i, addr, wsize, DBREG_DR7_WRONLY, d);
addr += wsize;
size -= wsize;
avail--;
}
}
set_dbregs(NULL, d);
dbreg_sync(d);
return (0);
}
int
dbreg_clr_watchpoint(vm_offset_t addr, vm_size_t size)
{
struct dbreg *d;
int i;
#ifdef __amd64__
d = (struct dbreg *)PCPU_PTR(dbreg);
#else
/* debug registers aren't stored in PCPU on i386. */
struct dbreg d_temp;
d = &d_temp;
#endif
fill_dbregs(NULL, d);
for (i = 0; i < NDBREGS; i++) {
if (DBREG_DR7_ENABLED(d->dr[7], i)) {
if (DBREG_DRX((d), i) >= addr &&
DBREG_DRX((d), i) < addr + size)
dbreg_clr_watchreg(i, d);
}
}
set_dbregs(NULL, d);
dbreg_sync(d);
return (0);
}
#ifdef DDB
static const char *
watchtype_str(int type)
{
switch (type) {
case DBREG_DR7_EXEC:
return ("execute");
case DBREG_DR7_RDWR:
return ("read/write");
case DBREG_DR7_WRONLY:
return ("write");
default:
return ("invalid");
}
}
void
dbreg_list_watchpoints(void)
{
struct dbreg d;
int i, len, type;
fill_dbregs(NULL, &d);
db_printf("\nhardware watchpoints:\n");
db_printf(" watch status type len address\n");
db_printf(" ----- -------- ---------- --- ----------\n");
for (i = 0; i < NDBREGS; i++) {
if (DBREG_DR7_ENABLED(d.dr[7], i)) {
type = DBREG_DR7_ACCESS(d.dr[7], i);
len = DBREG_DR7_LEN(d.dr[7], i);
db_printf(" %-5d %-8s %10s %3d ",
i, "enabled", watchtype_str(type), len + 1);
db_printsym((db_addr_t)DBREG_DRX(&d, i), DB_STGY_ANY);
db_printf("\n");
} else {
db_printf(" %-5d disabled\n", i);
}
}
}
#endif
#ifdef __amd64__
/* Sync debug registers when resuming from debugger. */
void
amd64_db_resume_dbreg(void)
{
struct dbreg *d;
switch (PCPU_GET(dbreg_cmd)) {
case PC_DBREG_CMD_LOAD:
d = (struct dbreg *)PCPU_PTR(dbreg);
set_dbregs(NULL, d);
PCPU_SET(dbreg_cmd, PC_DBREG_CMD_NONE);
break;
}
}
#endif