shithub: riscv

Download patch

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");
+}