ref: 773be02aa18095e857c6659416d84951ceb60d41
parent: 1cfa405d0a272cbd7df22d4b9767eb57e21cc21f
author: aiju <devnull@localhost>
date: Mon Jun 12 15:03:07 EDT 2017
kernel: add support for hardware watchpoints
--- a/sys/src/9/bcm/main.c
+++ b/sys/src/9/bcm/main.c
@@ -584,3 +584,10 @@
{
return cas32(addr, old, new);
}
+
+void
+setupwatchpts(Proc *, Watchpt *, int n)
+{
+ if(n > 0)
+ error("no watchpoints");
+}
--- a/sys/src/9/kw/main.c
+++ b/sys/src/9/kw/main.c
@@ -638,3 +638,10 @@
{
return cas32(addr, old, new);
}
+
+void
+setupwatchpts(Proc *, Watchpt *, int n)
+{
+ if(n > 0)
+ error("no watchpoints");
+}
--- a/sys/src/9/mtx/main.c
+++ b/sys/src/9/mtx/main.c
@@ -436,3 +436,10 @@
return 0;
}
+
+void
+setupwatchpts(Proc *, Watchpt *, int n)
+{
+ if(n > 0)
+ error("no watchpoints");
+}
--- a/sys/src/9/omap/main.c
+++ b/sys/src/9/omap/main.c
@@ -654,3 +654,10 @@
{
return cas32(addr, old, new);
}
+
+void
+setupwatchpts(Proc *, Watchpt *, int n)
+{
+ if(n > 0)
+ error("no watchpoints");
+}
--- a/sys/src/9/pc/dat.h
+++ b/sys/src/9/pc/dat.h
@@ -159,6 +159,8 @@
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 @@
char* cpuidtype;
int havetsc;
int havepge;
+ int havewatchpt8;
uvlong tscticks;
int pdballoc;
int pdbfree;
--- a/sys/src/9/pc/devarch.c
+++ b/sys/src/9/pc/devarch.c
@@ -49,6 +49,9 @@
Procsig,
Proctlbcache,
Procserial,
+
+ Highextfunc = 0x80000000,
+ Procextfeat,
};
typedef long Rdwrfn(Chan*, void*, long, vlong);
@@ -874,6 +877,23 @@
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;
@@ -1227,5 +1247,63 @@
iprint(" MISC %.16llux", w);
}
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;
}
}
--- a/sys/src/9/pc/fns.h
+++ b/sys/src/9/pc/fns.h
@@ -51,6 +51,7 @@
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 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*);
--- a/sys/src/9/pc/io.h
+++ b/sys/src/9/pc/io.h
@@ -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 */
--- a/sys/src/9/pc/l.s
+++ b/sys/src/9/pc/l.s
@@ -845,6 +845,38 @@
_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.
--- a/sys/src/9/pc/main.c
+++ b/sys/src/9/pc/main.c
@@ -801,6 +801,8 @@
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 @@
p->fpsave = up->fpsave;
p->fpstate = FPinactive;
}
+
+ /* clear debug registers */
+ memset(p->dr, 0, sizeof(p->dr));
splx(s);
}
@@ -838,6 +843,9 @@
procrestore(Proc *p)
{
uvlong t;
+
+ if(p->dr[7] != 0)
+ putdr(p->dr);
if(p->kp)
return;
@@ -854,6 +862,9 @@
procsave(Proc *p)
{
uvlong t;
+
+ if(p->dr[7] != 0)
+ putdr7(0);
cycles(&t);
p->kentry -= t;
--- a/sys/src/9/pc/trap.c
+++ b/sys/src/9/pc/trap.c
@@ -13,6 +13,7 @@
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 @@
* 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");
@@ -624,6 +626,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
--- a/sys/src/9/pc64/dat.h
+++ b/sys/src/9/pc64/dat.h
@@ -144,6 +144,8 @@
ulong kmapcount;
ulong kmapindex;
ulong mmucount;
+
+ u64int dr[8];
};
/*
@@ -219,6 +221,7 @@
char* cpuidtype;
int havetsc;
int havepge;
+ int havewatchpt8;
uvlong tscticks;
uintptr stack[1];
--- a/sys/src/9/pc64/fns.h
+++ b/sys/src/9/pc64/fns.h
@@ -44,6 +44,7 @@
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 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*);
--- a/sys/src/9/pc64/l.s
+++ b/sys/src/9/pc64/l.s
@@ -692,6 +692,35 @@
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
--- a/sys/src/9/pc64/main.c
+++ b/sys/src/9/pc64/main.c
@@ -797,6 +797,9 @@
procrestore(Proc *p)
{
uvlong t;
+
+ if(p->dr[7] != 0)
+ putdr(p->dr);
if(p->kp)
return;
@@ -810,6 +813,9 @@
procsave(Proc *p)
{
uvlong t;
+
+ if(p->dr[7] != 0)
+ putdr7(0);
cycles(&t);
p->kentry -= t;
--- a/sys/src/9/pc64/trap.c
+++ b/sys/src/9/pc64/trap.c
@@ -13,6 +13,7 @@
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 @@
* 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 @@
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*)
{
--- a/sys/src/9/port/devproc.c
+++ b/sys/src/9/port/devproc.c
@@ -34,6 +34,7 @@
Qwait,
Qprofile,
Qsyscall,
+ Qwatchpt,
};
enum
@@ -101,6 +102,7 @@
"wait", {Qwait}, 0, 0400,
"profile", {Qprofile}, 0, 0400,
"syscall", {Qsyscall}, 0, 0400,
+ "watchpt", {Qwatchpt}, 0, 0600,
};
static
@@ -181,6 +183,8 @@
}
}
+static int lenwatchpt(Proc *);
+
static int
procgen(Chan *c, char *name, Dirtab *tab, int, int s, Dir *dp)
{
@@ -265,6 +269,11 @@
}
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 @@
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 @@
unlock(&tlock);
poperror();
- c->mode = openmode(omode);
+ c->mode = openmode(omode0);
c->flag |= COPEN;
c->offset = 0;
return c;
@@ -382,7 +394,7 @@
if(p->pid != pid)
error(Eprocdied);
- omode = openmode(omode);
+ omode = openmode(omode0);
switch(QID(c->qid)){
case Qtext:
@@ -425,6 +437,7 @@
case Qfpregs:
case Qsyscall:
case Qppid:
+ case Qwatchpt:
nonone(p);
break;
@@ -454,6 +467,19 @@
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();
@@ -701,6 +727,119 @@
}
/*
+ * 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
* extend to 64 bit.
@@ -1006,6 +1145,16 @@
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);
@@ -1124,6 +1273,9 @@
}
if(p->noteid != id)
error(Ebadarg);
+ break;
+ case Qwatchpt:
+ writewatchpt(p, va, n, off);
break;
default:
print("unknown qid in procwrite\n");
--- a/sys/src/9/port/portdat.h
+++ b/sys/src/9/port/portdat.h
@@ -49,6 +49,7 @@
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 @@
PMMU;
char *syscalltrace; /* syscall trace */
+
+ Watchpt *watchpt; /* watchpoints */
+ int nwatchpt;
};
enum
@@ -960,6 +964,16 @@
void (*disable)(void); /* watchdog disable */
void (*restart)(void); /* watchdog restart */
void (*stat)(char*, char*); /* watchdog statistics */
+};
+
+struct Watchpt
+{
+ enum {
+ WATCHRD = 1,
+ WATCHWR = 2,
+ WATCHEX = 4,
+ } type;
+ uintptr addr, len;
};
--- a/sys/src/9/port/portfns.h
+++ b/sys/src/9/port/portfns.h
@@ -319,6 +319,7 @@
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);
--- a/sys/src/9/port/proc.c
+++ b/sys/src/9/port/proc.c
@@ -1209,6 +1209,10 @@
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 */
--- a/sys/src/9/ppc/main.c
+++ b/sys/src/9/ppc/main.c
@@ -497,3 +497,10 @@
return 0;
}
+
+void
+setupwatchpts(Proc *, Watchpt *, int n)
+{
+ if(n > 0)
+ error("no watchpoints");
+}
--- a/sys/src/9/sgi/main.c
+++ b/sys/src/9/sgi/main.c
@@ -497,3 +497,10 @@
imagmem->maxsize = kpages;
// mainmem->flags |= POOL_PARANOIA;
}
+
+void
+setupwatchpts(Proc *, Watchpt *, int n)
+{
+ if(n > 0)
+ error("no watchpoints");
+}
--- a/sys/src/9/teg2/main.c
+++ b/sys/src/9/teg2/main.c
@@ -907,3 +907,10 @@
intrcpu(cpu);
#endif
}
+
+void
+setupwatchpts(Proc *, Watchpt *, int n)
+{
+ if(n > 0)
+ error("no watchpoints");
+}
--- a/sys/src/9/zynq/main.c
+++ b/sys/src/9/zynq/main.c
@@ -427,3 +427,10 @@
userinit();
schedinit();
}
+
+void
+setupwatchpts(Proc *, Watchpt *, int n)
+{
+ if(n > 0)
+ error("no watchpoints");
+}