kernel: add support for hardware watchpoints

front
aiju 2017-06-12 19:03:07 +00:00
parent 1cfa405d0a
commit 773be02aa1
24 changed files with 465 additions and 5 deletions

View File

@ -584,3 +584,10 @@ cmpswap(long *addr, long old, long new)
{
return cas32(addr, old, new);
}
void
setupwatchpts(Proc *, Watchpt *, int n)
{
if(n > 0)
error("no watchpoints");
}

View File

@ -638,3 +638,10 @@ cmpswap(long *addr, long old, long new)
{
return cas32(addr, old, new);
}
void
setupwatchpts(Proc *, Watchpt *, int n)
{
if(n > 0)
error("no watchpoints");
}

View File

@ -436,3 +436,10 @@ cistrncmp(char *a, char *b, int n)
return 0;
}
void
setupwatchpts(Proc *, Watchpt *, int n)
{
if(n > 0)
error("no watchpoints");
}

View File

@ -654,3 +654,10 @@ cmpswap(long *addr, long old, long new)
{
return cas32(addr, old, new);
}
void
setupwatchpts(Proc *, Watchpt *, int n)
{
if(n > 0)
error("no watchpoints");
}

View File

@ -159,6 +159,8 @@ struct PMMU
Segdesc gdt[NPROCSEG]; /* per process descriptors */
Segdesc *ldt; /* local descriptor table */
int nldt; /* number of ldt descriptors allocated */
u32int dr[8]; /* debug registers */
};
/*
@ -252,6 +254,7 @@ struct Mach
char* cpuidtype;
int havetsc;
int havepge;
int havewatchpt8;
uvlong tscticks;
int pdballoc;
int pdbfree;

View File

@ -49,6 +49,9 @@ enum { /* cpuid standard function codes */
Procsig,
Proctlbcache,
Procserial,
Highextfunc = 0x80000000,
Procextfeat,
};
typedef long Rdwrfn(Chan*, void*, long, vlong);
@ -874,6 +877,23 @@ cpuidentify(void)
hwrandbuf = rdrandbuf;
else
hwrandbuf = nil;
/* 8-byte watchpoints are supported in Long Mode */
if(sizeof(uintptr) == 8)
m->havewatchpt8 = 1;
else if(strcmp(m->cpuidid, "GenuineIntel") == 0){
/* some random CPUs that support 8-byte watchpoints */
if(family == 15 && (model == 3 || model == 4 || model == 6)
|| family == 6 && (model == 15 || model == 23 || model == 28))
m->havewatchpt8 = 1;
/* Intel SDM claims amd64 support implies 8-byte watchpoint support */
cpuid(Highextfunc, regs);
if(regs[0] >= Procextfeat){
cpuid(Procextfeat, regs);
if((regs[3] & 1<<29) != 0)
m->havewatchpt8 = 1;
}
}
cputype = t;
return t->family;
@ -1229,3 +1249,61 @@ dumpmcregs(void)
iprint("\n");
}
}
void
setupwatchpts(Proc *pr, Watchpt *wp, int nwp)
{
int i;
u8int cfg;
Watchpt *p;
if(nwp > 4)
error("there are four watchpoints.");
if(nwp == 0){
memset(pr->dr, 0, sizeof(pr->dr));
return;
}
for(p = wp; p < wp + nwp; p++){
switch(p->type){
case WATCHRD|WATCHWR: case WATCHWR:
break;
case WATCHEX:
if(p->len != 1)
error("length must be 1 on breakpoints");
break;
default:
error("type must be rw-, -w- or --x");
}
switch(p->len){
case 1: case 2: case 4:
break;
case 8:
if(m->havewatchpt8) break;
default:
error(m->havewatchpt8 ? "length must be 1,2,4,8" : "length must be 1,2,4");
}
if((p->addr & p->len - 1) != 0)
error("address must be aligned according to length");
}
memset(pr->dr, 0, sizeof(pr->dr));
pr->dr[6] = 0xffff8ff0;
for(i = 0; i < nwp; i++){
pr->dr[i] = wp[i].addr;
switch(wp[i].type){
case WATCHRD|WATCHWR: cfg = 3; break;
case WATCHWR: cfg = 1; break;
case WATCHEX: cfg = 0; break;
default: continue;
}
switch(wp[i].len){
case 1: break;
case 2: cfg |= 4; break;
case 4: cfg |= 12; break;
case 8: cfg |= 8; break;
default: continue;
}
pr->dr[7] |= cfg << 16 + 4 * i;
pr->dr[7] |= 1 << 2 * i + 1;
}
}

View File

@ -51,6 +51,7 @@ ulong getcr0(void);
ulong getcr2(void);
ulong getcr3(void);
ulong getcr4(void);
u32int getdr6(void);
char* getconf(char*);
void guesscpuhz(int);
void halt(void);
@ -165,6 +166,9 @@ void procfork(Proc*);
void putcr0(ulong);
void putcr3(ulong);
void putcr4(ulong);
void putdr(u32int*);
void putdr6(u32int);
void putdr7(u32int);
void* rampage(void);
int rdmsr(int, vlong*);
void realmode(Ureg*);

View File

@ -4,6 +4,7 @@
#define X86FAMILY(x) ((((x)>>8) & 0x0F) | (((x)>>20) & 0xFF)<<4)
enum {
VectorDE = 1, /* debug exception */
VectorNMI = 2, /* non-maskable interrupt */
VectorBPT = 3, /* breakpoint */
VectorUD = 6, /* invalid opcode exception */

View File

@ -845,6 +845,38 @@ _rndbytes:
_rnddone:
RET
/* debug register access */
TEXT putdr(SB), $0
MOVL p+0(FP), SI
MOVL 28(SI), AX
MOVL AX, DR7
MOVL 0(SI), AX
MOVL AX, DR0
MOVL 4(SI), AX
MOVL AX, DR1
MOVL 8(SI), AX
MOVL AX, DR2
MOVL 12(SI), AX
MOVL AX, DR3
MOVL 24(SI), AX
MOVL AX, DR6
RET
TEXT getdr6(SB), $0
MOVL DR6, AX
RET
TEXT putdr6(SB), $0
MOVL p+0(FP), AX
MOVL AX, DR6
RET
TEXT putdr7(SB), $0
MOVL p+0(FP), AX
MOVL AX, DR7
RET
/*
* Used to get to the first process:
* set up an interrupt return frame and IRET to user level.

View File

@ -801,6 +801,8 @@ procsetup(Proc *p)
memset(p->gdt, 0, sizeof(p->gdt));
p->ldt = nil;
p->nldt = 0;
memset(p->dr, 0, sizeof(p->dr));
}
void
@ -831,6 +833,9 @@ procfork(Proc *p)
p->fpsave = up->fpsave;
p->fpstate = FPinactive;
}
/* clear debug registers */
memset(p->dr, 0, sizeof(p->dr));
splx(s);
}
@ -838,6 +843,9 @@ void
procrestore(Proc *p)
{
uvlong t;
if(p->dr[7] != 0)
putdr(p->dr);
if(p->kp)
return;
@ -854,6 +862,9 @@ void
procsave(Proc *p)
{
uvlong t;
if(p->dr[7] != 0)
putdr7(0);
cycles(&t);
p->kentry -= t;

View File

@ -13,6 +13,7 @@ static int trapinited;
void noted(Ureg*, ulong);
static void debugexc(Ureg*, void*);
static void debugbpt(Ureg*, void*);
static void fault386(Ureg*, void*);
static void doublefault(Ureg*, void*);
@ -222,6 +223,7 @@ trapinit(void)
* Special traps.
* Syscall() is called directly without going through trap().
*/
trapenable(VectorDE, debugexc, 0, "debugexc");
trapenable(VectorBPT, debugbpt, 0, "debugpt");
trapenable(VectorPF, fault386, 0, "fault386");
trapenable(Vector2F, doublefault, 0, "doublefault");
@ -626,6 +628,35 @@ dumpstack(void)
callwithureg(_dumpstack);
}
static void
debugexc(Ureg *, void *)
{
u32int dr6, m;
char buf[ERRMAX];
char *p, *e;
int i;
dr6 = getdr6();
if(up == nil)
panic("kernel debug exception dr6=%#.8ux", dr6);
putdr6(up->dr[6]);
m = up->dr[7];
m = (m >> 4 | m >> 3) & 8 | (m >> 3 | m >> 2) & 4 | (m >> 2 | m >> 1) & 2 | (m >> 1 | m) & 1;
m &= dr6;
if(m == 0){
sprint(buf, "sys: debug exception dr6=%#.8ux", dr6);
postnote(up, 1, buf, NDebug);
}else{
p = buf;
e = buf + sizeof(buf);
p = seprint(p, e, "sys: watchpoint ");
for(i = 0; i < 4; i++)
if((m & 1<<i) != 0)
p = seprint(p, e, "%d%s", i, (m >> i + 1 != 0) ? "," : "");
postnote(up, 1, buf, NDebug);
}
}
static void
debugbpt(Ureg* ureg, void*)
{

View File

@ -144,6 +144,8 @@ struct PMMU
ulong kmapcount;
ulong kmapindex;
ulong mmucount;
u64int dr[8];
};
/*
@ -219,6 +221,7 @@ struct Mach
char* cpuidtype;
int havetsc;
int havepge;
int havewatchpt8;
uvlong tscticks;
uintptr stack[1];

View File

@ -44,6 +44,7 @@ u64int getcr0(void);
u64int getcr2(void);
u64int getcr3(void);
u64int getcr4(void);
u64int getdr6(void);
char* getconf(char*);
void guesscpuhz(int);
void halt(void);
@ -158,6 +159,9 @@ void procfork(Proc*);
void putcr0(u64int);
void putcr3(u64int);
void putcr4(u64int);
void putdr(u64int*);
void putdr6(u64int);
void putdr7(u64int);
void* rampage(void);
int rdmsr(int, vlong*);
void realmode(Ureg*);

View File

@ -692,6 +692,35 @@ ones:
f3:
RET
/* debug register access */
TEXT putdr(SB), $0
MOVQ 56(BP), AX
MOVQ AX, DR7
MOVQ 0(BP), AX
MOVQ AX, DR0
MOVQ 8(BP), AX
MOVQ AX, DR1
MOVQ 16(BP), AX
MOVQ AX, DR2
MOVQ 24(BP), AX
MOVQ AX, DR3
MOVQ 48(BP), AX
MOVQ AX, DR6
RET
TEXT getdr6(SB), $0
MOVQ DR6, AX
RET
TEXT putdr6(SB), $0
MOVQ BP, DR6
RET
TEXT putdr7(SB), $0
MOVQ BP, DR7
RET
/*
*/
TEXT touser(SB), 1, $-4

View File

@ -797,6 +797,9 @@ void
procrestore(Proc *p)
{
uvlong t;
if(p->dr[7] != 0)
putdr(p->dr);
if(p->kp)
return;
@ -810,6 +813,9 @@ void
procsave(Proc *p)
{
uvlong t;
if(p->dr[7] != 0)
putdr7(0);
cycles(&t);
p->kentry -= t;

View File

@ -13,6 +13,7 @@ static int trapinited;
void noted(Ureg*, ulong);
static void debugexc(Ureg*, void*);
static void debugbpt(Ureg*, void*);
static void faultamd64(Ureg*, void*);
static void doublefault(Ureg*, void*);
@ -224,6 +225,7 @@ trapinit(void)
* Special traps.
* Syscall() is called directly without going through trap().
*/
trapenable(VectorDE, debugexc, 0, "debugexc");
trapenable(VectorBPT, debugbpt, 0, "debugpt");
trapenable(VectorPF, faultamd64, 0, "faultamd64");
trapenable(Vector2F, doublefault, 0, "doublefault");
@ -587,6 +589,35 @@ dumpstack(void)
callwithureg(_dumpstack);
}
static void
debugexc(Ureg *, void *)
{
u64int dr6, m;
char buf[ERRMAX];
char *p, *e;
int i;
dr6 = getdr6();
if(up == nil)
panic("kernel debug exception dr6=%#.8ullx", dr6);
putdr6(up->dr[6]);
m = up->dr[7];
m = (m >> 4 | m >> 3) & 8 | (m >> 3 | m >> 2) & 4 | (m >> 2 | m >> 1) & 2 | (m >> 1 | m) & 1;
m &= dr6;
if(m == 0){
sprint(buf, "sys: debug exception dr6=%#.8ullx", dr6);
postnote(up, 1, buf, NDebug);
}else{
p = buf;
e = buf + sizeof(buf);
p = seprint(p, e, "sys: watchpoint ");
for(i = 0; i < 4; i++)
if((m & 1<<i) != 0)
p = seprint(p, e, "%d%s", i, (m >> i + 1 != 0) ? "," : "");
postnote(up, 1, buf, NDebug);
}
}
static void
debugbpt(Ureg* ureg, void*)
{

View File

@ -34,6 +34,7 @@ enum
Qwait,
Qprofile,
Qsyscall,
Qwatchpt,
};
enum
@ -101,6 +102,7 @@ Dirtab procdir[] =
"wait", {Qwait}, 0, 0400,
"profile", {Qprofile}, 0, 0400,
"syscall", {Qsyscall}, 0, 0400,
"watchpt", {Qwatchpt}, 0, 0600,
};
static
@ -181,6 +183,8 @@ profclock(Ureg *ur, Timer *)
}
}
static int lenwatchpt(Proc *);
static int
procgen(Chan *c, char *name, Dirtab *tab, int, int s, Dir *dp)
{
@ -265,6 +269,11 @@ procgen(Chan *c, char *name, Dirtab *tab, int, int s, Dir *dp)
}
break;
}
switch(QID(tab->qid)){
case Qwatchpt:
len = lenwatchpt(p);
break;
}
mkqid(&qid, path|tab->qid.path, c->qid.vers, QTFILE);
devdir(c, qid, tab->name, len, p->user, perm, dp);
@ -334,19 +343,22 @@ nonone(Proc *p)
error(Eperm);
}
static void clearwatchpt(Proc *p);
static Chan*
procopen(Chan *c, int omode)
procopen(Chan *c, int omode0)
{
Proc *p;
Pgrp *pg;
Chan *tc;
int pid;
int omode;
if(c->qid.type & QTDIR)
return devopen(c, omode, 0, 0, procgen);
return devopen(c, omode0, 0, 0, procgen);
if(QID(c->qid) == Qtrace){
if (omode != OREAD)
if (omode0 != OREAD)
error(Eperm);
lock(&tlock);
if (waserror()){
@ -366,7 +378,7 @@ procopen(Chan *c, int omode)
unlock(&tlock);
poperror();
c->mode = openmode(omode);
c->mode = openmode(omode0);
c->flag |= COPEN;
c->offset = 0;
return c;
@ -382,7 +394,7 @@ procopen(Chan *c, int omode)
if(p->pid != pid)
error(Eprocdied);
omode = openmode(omode);
omode = openmode(omode0);
switch(QID(c->qid)){
case Qtext:
@ -425,6 +437,7 @@ procopen(Chan *c, int omode)
case Qfpregs:
case Qsyscall:
case Qppid:
case Qwatchpt:
nonone(p);
break;
@ -454,6 +467,19 @@ procopen(Chan *c, int omode)
error(Eprocdied);
tc = devopen(c, omode, 0, 0, procgen);
if(waserror()){
cclose(tc);
nexterror();
}
switch(QID(c->qid)){
case Qwatchpt:
if((omode0 & OTRUNC) != 0)
clearwatchpt(p);
break;
}
poperror();
qunlock(&p->debug);
poperror();
@ -700,6 +726,119 @@ readfd1(Chan *c, Proc *p, char *buf, int nbuf)
return n;
}
/*
* setupwatchpts(Proc *p, Watchpt *wp, int nwp) is defined for all arches separately.
* It tests whether wp is a valid set of watchpoints and errors out otherwise.
* If and only if they are valid, it sets up all watchpoints (clearing any preexisting ones).
* This is to make sure that failed writes to watchpt don't touch the existing watchpoints.
*/
static void
clearwatchpt(Proc *p)
{
setupwatchpts(p, nil, 0);
free(p->watchpt);
p->watchpt = nil;
p->nwatchpt = 0;
}
static int
lenwatchpt(Proc *pr)
{
/* careful, not holding debug lock */
return pr->nwatchpt * (10 + 4 * sizeof(uintptr));
}
static int
readwatchpt(Proc *pr, char *buf, int nbuf)
{
char *p, *e;
Watchpt *w;
p = buf;
e = buf + nbuf;
/* careful, length has to match lenwatchpt() */
for(w = pr->watchpt; w < pr->watchpt + pr->nwatchpt; w++)
p = seprint(p, e, sizeof(uintptr) == 8 ? "%c%c%c %#.16p %#.16p\n" : "%c%c%c %#.8p %#.8p\n",
(w->type & WATCHRD) != 0 ? 'r' : '-',
(w->type & WATCHWR) != 0 ? 'w' : '-',
(w->type & WATCHEX) != 0 ? 'x' : '-',
(void *) w->addr, (void *) w->len);
return p - buf;
}
static int
writewatchpt(Proc *pr, char *buf, int nbuf, uvlong offset)
{
char *p, *q, *e;
char line[256], *f[4];
Watchpt *wp, *wq;
int rc, nwp, nwp0;
uvlong x;
p = buf;
e = buf + nbuf;
if(offset != 0)
nwp0 = pr->nwatchpt;
else
nwp0 = 0;
nwp = 0;
for(q = p; q < e; q++)
nwp += *q == '\n';
if(nwp > 65536) error(Egreg);
wp = malloc((nwp0+nwp) * sizeof(Watchpt));
if(wp == nil) error(Enomem);
if(waserror()){
free(wp);
nexterror();
}
if(nwp0 > 0)
memmove(wp, pr->watchpt, sizeof(Watchpt) * nwp0);
for(wq = wp + nwp0;;){
q = memchr(p, '\n', e - p);
if(q == nil)
break;
if(q - p > sizeof(line) - 1)
error("line too long");
memmove(line, p, q - p);
line[q - p] = 0;
p = q + 1;
rc = tokenize(line, f, nelem(f));
if(rc == 0) continue;
if(rc != 3)
error("wrong number of fields");
for(q = f[0]; *q != 0; q++)
switch(*q){
case 'r': if((wq->type & WATCHRD) != 0) goto tinval; wq->type |= WATCHRD; break;
case 'w': if((wq->type & WATCHWR) != 0) goto tinval; wq->type |= WATCHWR; break;
case 'x': if((wq->type & WATCHEX) != 0) goto tinval; wq->type |= WATCHEX; break;
case '-': break;
default: tinval: error("invalid type");
}
x = strtoull(f[1], &q, 0);
if(f[1] == q || *q != 0 || x != (uintptr) x) error("invalid address");
wq->addr = x;
x = strtoull(f[2], &q, 0);
if(f[2] == q || *q != 0 || x != (uintptr) x) error("invalid length");
wq->len = x;
if(!okaddr(wq->addr, wq->len, 0)) error("bad address");
wq++;
}
nwp = wq - (wp + nwp0);
if(nwp == 0 && nwp0 == pr->nwatchpt){
poperror();
free(wp);
return p - buf;
}
setupwatchpts(pr, wp, nwp0 + nwp);
poperror();
free(pr->watchpt);
pr->watchpt = wp;
pr->nwatchpt = nwp0 + nwp;
return p - buf;
}
/*
* userspace can't pass negative file offset for a
* 64 bit kernel address, so we use 63 bit and sign
@ -1006,6 +1145,16 @@ procread(Chan *c, void *va, long n, vlong off)
case Qppid:
return readnum(offset, va, n, p->parentpid, NUMSIZE);
case Qwatchpt:
eqlock(&p->debug);
j = readwatchpt(p, statbuf, sizeof(statbuf));
qunlock(&p->debug);
if(offset >= j)
return 0;
if(offset+n > j)
n = j - offset;
goto statbufread;
}
error(Egreg);
@ -1125,6 +1274,9 @@ procwrite(Chan *c, void *va, long n, vlong off)
if(p->noteid != id)
error(Ebadarg);
break;
case Qwatchpt:
writewatchpt(p, va, n, off);
break;
default:
print("unknown qid in procwrite\n");
error(Egreg);

View File

@ -49,6 +49,7 @@ typedef struct Timers Timers;
typedef struct Uart Uart;
typedef struct Waitq Waitq;
typedef struct Walkqid Walkqid;
typedef struct Watchpt Watchpt;
typedef struct Watchdog Watchdog;
typedef int Devgen(Chan*, char*, Dirtab*, int, int, Dir*);
@ -772,6 +773,9 @@ struct Proc
PMMU;
char *syscalltrace; /* syscall trace */
Watchpt *watchpt; /* watchpoints */
int nwatchpt;
};
enum
@ -962,6 +966,16 @@ struct Watchdog
void (*stat)(char*, char*); /* watchdog statistics */
};
struct Watchpt
{
enum {
WATCHRD = 1,
WATCHWR = 2,
WATCHEX = 4,
} type;
uintptr addr, len;
};
/* queue state bits, Qmsg, Qcoalesce, and Qkick can be set in qopen */
enum

View File

@ -319,6 +319,7 @@ void setmalloctag(void*, uintptr);
void setrealloctag(void*, uintptr);
void setregisters(Ureg*, char*, char*, int);
void setswapchan(Chan*);
void setupwatchpts(Proc*, Watchpt*, int);
char* skipslash(char*);
void sleep(Rendez*, int(*)(void*), void*);
void* smalloc(ulong);

View File

@ -1209,6 +1209,10 @@ pexit(char *exitstr, int freemem)
free(up->syscalltrace);
up->syscalltrace = nil;
}
if(up->watchpt != nil){
free(up->watchpt);
up->watchpt = nil;
}
qunlock(&up->debug);
/* Sched must not loop for these locks */

View File

@ -497,3 +497,10 @@ cistrncmp(char *a, char *b, int n)
return 0;
}
void
setupwatchpts(Proc *, Watchpt *, int n)
{
if(n > 0)
error("no watchpoints");
}

View File

@ -497,3 +497,10 @@ confinit(void)
imagmem->maxsize = kpages;
// mainmem->flags |= POOL_PARANOIA;
}
void
setupwatchpts(Proc *, Watchpt *, int n)
{
if(n > 0)
error("no watchpoints");
}

View File

@ -907,3 +907,10 @@ wakewfi(void)
intrcpu(cpu);
#endif
}
void
setupwatchpts(Proc *, Watchpt *, int n)
{
if(n > 0)
error("no watchpoints");
}

View File

@ -427,3 +427,10 @@ main(void)
userinit();
schedinit();
}
void
setupwatchpts(Proc *, Watchpt *, int n)
{
if(n > 0)
error("no watchpoints");
}