code: 9ferno

ref: 58055b9e3a8c6df1cd128a3d4ed3667eab94e472
dir: /emu/port/devprog.c/

View raw version
#include	"dat.h"
#include	"fns.h"
#include	"error.h"
#include	<interp.h>
#include	<isa.h>
#include	"runt.h"

/*
 * Enable the heap device for environments that allow debugging =>
 * Must be 1 for a production environment.
 */
int	SECURE = 0;

enum
{
	Qdir,
	Qctl,
	Qdbgctl,
	Qheap,
	Qns,
	Qnsgrp,
	Qpgrp,
	Qstack,
	Qstatus,
	Qtext,
	Qwait,
	Qfd,
	Qexception,
};

/*
 * must be in same order as enum
 */
Dirtab progdir[] =
{
	"ctl",		{Qctl},		0,			0200,
	"dbgctl",	{Qdbgctl},	0,			0600,
	"heap",		{Qheap},	0,			0600,
	"ns",		{Qns},		0,			0400,
	"nsgrp",	{Qnsgrp},	0,			0444,
	"pgrp",		{Qpgrp},	0,			0444,
	"stack",	{Qstack},	0,			0400,
	"status",	{Qstatus},	0,			0444,
	"text",		{Qtext},	0,			0000,
	"wait",		{Qwait},	0,			0400,
	"fd",		{Qfd},		0,			0400,
	"exception",	{Qexception},	0,	0400,
};

enum
{
	CMkill,
	CMkillgrp,
	CMrestricted,
	CMexceptions,
	CMprivate
};

static
Cmdtab progcmd[] = {
	CMkill,	"kill",	1,
	CMkillgrp,	"killgrp",	1,
	CMrestricted, "restricted", 1,
	CMexceptions, "exceptions", 2,
	CMprivate, "private",	1,
};

enum
{
	CDstep,
	CDtoret,
	CDcont,
	CDstart,
	CDstop,
	CDunstop,
	CDmaim,
	CDbpt
};

static
Cmdtab progdbgcmd[] = {
	CDstep,	"step",	0,	/* known below to be first, to cope with stepN */
	CDtoret,	"toret",	1,
	CDcont,	"cont",	1,
	CDstart,	"start",	1,
	CDstop,	"stop",	1,
	CDunstop,	"unstop",	1,
	CDmaim,	"maim",	1,
	CDbpt,	"bpt",	4,
};

typedef struct Heapqry Heapqry;
struct Heapqry
{
	char	fmt;
	ulong	addr;
	ulong	module;
	int	count;
};

typedef struct Bpt	Bpt;

struct Bpt
{
	Bpt	*next;
	int	pc;
	char	*file;
	char	path[1];
};

typedef struct Progctl Progctl;
struct Progctl
{
	Rendez	r;
	int	ref;
	Proc	*debugger;	/* waiting for dbgxec */
	char	*msg;		/* reply from dbgxec */
	int	step;		/* instructions to try */
	int	stop;		/* stop running the program */
	Bpt*	bpts;		/* active breakpoints */
	Queue*	q;		/* status queue */
};

#define	QSHIFT		4		/* location in qid of pid */
#define	QID(q)		(((ulong)(q).path&0x0000000F)>>0)
#define QPID(pid)	(((pid)<<QSHIFT))
#define	PID(q)		((q).vers)
#define PATH(q)		((ulong)(q).path&~((1<<QSHIFT)-1))

static char *progstate[] =			/* must correspond to include/interp.h */
{
	"alt",				/* blocked in alt instruction */
	"send",				/* waiting to send */
	"recv",				/* waiting to recv */
	"debug",			/* debugged */
	"ready",			/* ready to be scheduled */
	"release",			/* interpreter released */
	"exiting",			/* exit because of kill or error */
	"broken",			/* thread crashed */
};

static	void	dbgstep(Progctl*, Prog*, int);
static	void	dbgstart(Prog*);
static	void	freebpts(Bpt*);
static	Bpt*	delbpt(Bpt*, char*, int);
static	Bpt*	setbpt(Bpt*, char*, int);
static	void	mntscan(Mntwalk*, Pgrp*);
extern	Type	*Trdchan;
extern	Type	*Twrchan;
extern	Module*	modules;
static  char 	Emisalign[] = "misaligned address";

static int
proggen(Chan *c, char *name, Dirtab *tab, int ntab, int s, Dir *dp)
{
	Qid qid;
	Prog *p;
	char *e;
	Osenv *o;
	ulong pid, path, perm, len;

	USED(ntab);

	if(s == DEVDOTDOT){
		mkqid(&qid, Qdir, 0, QTDIR);
		devdir(c, qid, "#p", 0, eve, DMDIR|0555, dp);
		return 1;
	}

	if((ulong)c->qid.path == Qdir) {
		if(name != nil){
			/* ignore s and use name to find pid */
			pid = strtoul(name, &e, 0);
			if(pid == 0 || *e != '\0')
				return -1;
			acquire();
			p = progpid(pid);
			if(p == nil){
				release();
				return -1;
			}
		}else{
			acquire();
			p = progn(s);
			if(p == nil) {
				release();
				return -1;
			}
			pid = p->pid;
		}
		o = p->osenv;
		sprint(up->genbuf, "%lud", pid);
		if(name != nil && strcmp(name, up->genbuf) != 0){
			release();
			return -1;
		}
		mkqid(&qid, pid<<QSHIFT, pid, QTDIR);
		devdir(c, qid, up->genbuf, 0, o->user, DMDIR|0555, dp);
		release();
		return 1;
	}

	if(s >= nelem(progdir))
		return -1;
	tab = &progdir[s];
	path = PATH(c->qid);

	acquire();
	p = progpid(PID(c->qid));
	if(p == nil) {
		release();
		return -1;
	}

	o = p->osenv;

	perm = tab->perm;
	if((perm & 7) == 0)
		perm = (perm|(perm>>3)|(perm>>6)) & o->pgrp->progmode;

	len = tab->length;
	mkqid(&qid, path|tab->qid.path, c->qid.vers, QTFILE);
	devdir(c, qid, tab->name, len, o->user, perm, dp);
	release();
	return 1;
}

static Chan*
progattach(char *spec)
{
	return devattach('p', spec);
}

static Walkqid*
progwalk(Chan *c, Chan *nc, char **name, int nname)
{
	return devwalk(c, nc, name, nname, 0, 0, proggen);
}

static int
progstat(Chan *c, uchar *db, int n)
{
	return devstat(c, db, n, 0, 0, proggen);
}

static Chan*
progopen(Chan *c, int omode)
{
	Prog *p;
	Osenv *o;
	Progctl *ctl;
	int perm;

	if(c->qid.type & QTDIR)
		return devopen(c, omode, 0, 0, proggen);

	acquire();
	if (waserror()) {
		release();
		nexterror();
	}
	p = progpid(PID(c->qid));
	if(p == nil)
		error(Ethread);
	o = p->osenv;
	perm = progdir[QID(c->qid)-1].perm;
	if((perm & 7) == 0)
		perm = (perm|(perm>>3)|(perm>>6)) & o->pgrp->progmode;
	devpermcheck(o->user, perm, omode);
	omode = openmode(omode);

	switch(QID(c->qid)){
	default:
		error(Egreg);
	case Qnsgrp:
	case Qpgrp:
	case Qtext:
	case Qstatus:
	case Qstack:
	case Qctl:
	case Qfd:
	case Qexception:
		break;
	case Qwait:
		c->aux = qopen(1024, Qmsg, nil, nil);
		if(c->aux == nil)
			error(Enomem);
		o->childq = c->aux;
		break;
	case Qns:
		c->aux = malloc(sizeof(Mntwalk));
		if(c->aux == nil)
			error(Enomem);
		break;
	case Qheap:
		if(SECURE || p->group->flags&Pprivatemem || omode != ORDWR)
			error(Eperm);
		c->aux = malloc(sizeof(Heapqry));
		if(c->aux == nil)
			error(Enomem);
		break;
	case Qdbgctl:
		if(SECURE || p->group->flags&Pprivatemem || omode != ORDWR)
			error(Eperm);
		ctl = malloc(sizeof(Progctl));
		if(ctl == nil)
			error(Enomem);
		ctl->q = qopen(1024, Qmsg, nil, nil);
		if(ctl->q == nil) {
			free(ctl);
			error(Enomem);
		}
		ctl->bpts = nil;
		ctl->ref = 1;
		c->aux = ctl;
		break;
	}
	if(p->state != Pexiting)
		c->qid.vers = p->pid;

	poperror();
	release();
	c->offset = 0;
	c->mode = omode;
	c->flag |= COPEN;
	return c;
}

static int
progwstat(Chan *c, uchar *db, int n)
{
	Dir d;
	Prog *p;
	char *u;
	Osenv *o;

	if(c->qid.type&QTDIR)
		error(Eperm);
	acquire();
	p = progpid(PID(c->qid));
	if(p == nil) {
		release();
		error(Ethread);
	}

	u = up->env->user;
	o = p->osenv;
	if(strcmp(u, o->user) != 0 && strcmp(u, eve) != 0) {
		release();
		error(Eperm);
	}

	n = convM2D(db, n, &d, nil);
	if(n == 0){
		release();
		error(Eshortstat);
	}
	if(d.mode != ~0UL)
		o->pgrp->progmode = d.mode&0777;
	release();
	return n;
}

static void
closedbgctl(Progctl *ctl, Prog *p)
{
	Osenv *o;

	if(ctl->ref-- > 1)
		return;
	freebpts(ctl->bpts);
	if(p != nil){
		o = p->osenv;
		if(o->debug == ctl){
			o->debug = nil;
			p->xec = xec;
		}
	}
	qfree(ctl->q);
	free(ctl);
}

static void
progclose(Chan *c)
{
	int i;
	Prog *f;
	Osenv *o;
	Progctl *ctl;

	switch(QID(c->qid)) {
	case Qns:
	case Qheap:
		free(c->aux);
		break;
	case Qdbgctl:
		if((c->flag & COPEN) == 0)
			return;
		ctl = c->aux;
		acquire();
		closedbgctl(ctl, progpid(PID(c->qid)));
		release();
		break;
	case Qwait:
		acquire();
		i = 0;
		for(;;) {
			f = progn(i++);
			if(f == nil)
				break;
			o = f->osenv;
			if(o->waitq == c->aux)
				o->waitq = nil;
			if(o->childq == c->aux)
				o->childq = nil;
		}
		release();
		qfree(c->aux);
	}
}

static int
progsize(Prog *p)
{
	int size;
	Frame *f;
	uchar *fp;
	Modlink *m;

	m = p->R.M;
	size = 0;
	if(m->MP != H)
		size += hmsize(D2H(m->MP));
	if(m->prog != nil)
		size += msize(m->prog);

	fp = p->R.FP;
	while(fp != nil) {
		f = (Frame*)fp;
		fp = f->fp;
		if(f->mr != nil) {
			if(f->mr->MP != H)
				size += hmsize(D2H(f->mr->MP));
			if(f->mr->prog != nil)
				size += msize(f->mr->prog);
		}
		if(f->t == nil)
			size += msize(SEXTYPE(f));
	}
	return size/1024;
}

static long
progoffset(long offset, char *va, int *np)
{
	if(offset > 0) {
		offset -= *np;
		if(offset < 0) {
			memmove(va, va+*np+offset, -offset);
			*np = -offset;
		}
		else
			*np = 0;
	}
	return offset;
}

static int
progqidwidth(Chan *c)
{
	char buf[32];

	return sprint(buf, "%lud", c->qid.vers);
}

int
progfdprint(Chan *c, int fd, int w, char *s, int ns)
{
	int n;

	if(w == 0)
		w = progqidwidth(c);
	n = snprint(s, ns, "%3d %.2s %C %4ld (%.16llux %*lud %.2ux) %5ld %8lld %s\n",
		fd,
		&"r w rw"[(c->mode&3)<<1],
		devtab[c->type]->dc, c->dev,
		c->qid.path, w, c->qid.vers, c->qid.type,
		c->iounit, c->offset, c->name->s);
	return n;
}

static int
progfds(Osenv *o, char *va, int count, long offset)
{
	Fgrp *f;
	Chan *c;
	int n, i, w, ww;

	f = o->fgrp;	/* f is not locked because we've acquired */
	n = readstr(0, va, count, o->pgrp->dot->name->s);
	n += snprint(va+n, count-n, "\n");
	offset = progoffset(offset, va, &n);
	/* compute width of qid.path */
	w = 0;
	for(i = 0; i <= f->maxfd; i++) {
		c = f->fd[i];
		if(c == nil)
			continue;
		ww = progqidwidth(c);
		if(ww > w)
			w = ww;
	}
	for(i = 0; i <= f->maxfd; i++) {
		c = f->fd[i];
		if(c == nil)
			continue;
		n += progfdprint(c, i, w, va+n, count-n);
		offset = progoffset(offset, va, &n);
	}
	return n;
}

Inst *
pc2dispc(Inst *pc, Module *mod)
{
	uintptr l, u, m, v;
	uintptr *tab = mod->pctab;

	v = (uintptr)pc - (uintptr)mod->prog;
	l = 0;
	u = mod->nprog-1;
	while(l < u){
		m = (l+u+1)/2;
		if(tab[m] < v)
			l = m;
		else if(tab[m] > v)
			u = m-1;
		else
			l = u = m;
	}
	if(l == u && tab[u] <= v && u != mod->nprog-1 && tab[u+1] > v)
		return &mod->prog[u];
	return 0;
}

static int
progstack(REG *reg, int state, char *va, int count, long offset)
{
	int n;
	Frame *f;
	Inst *pc;
	uchar *fp;
	Modlink *m;

	n = 0;
	m = reg->M;
	fp = reg->FP;
	pc = reg->PC;

	/*
	 * all states other than debug and ready block,
	 * but interp has already advanced the PC
	 */
	if(!m->compiled && state != Pready && state != Pdebug && pc > m->prog)
		pc--;
	if(m->compiled && m->m->pctab != nil)
		pc = pc2dispc(pc, m->m);

	while(fp != nil) {
		f = (Frame*)fp;
		n += snprint(va+n, count-n, "%.8zx %.8zx %.8zx %.8zx %d %s\n",
				(uintptr)f,		/* FP */
				(uintptr)(pc - m->prog),	/* PC in dis instructions */
				(uintptr)m->MP,		/* MP */
				(uintptr)m->prog,	/* Code for module */
				m->compiled && m->m->pctab == nil,	/* True if native assembler: fool stack utility for now */
				m->m->path);	/* File system path */

		if(offset > 0) {
			offset -= n;
			if(offset < 0) {
				memmove(va, va+n+offset, -offset);
				n = -offset;
			}
			else
				n = 0;
		}

		pc = f->lr;
		fp = f->fp;
		if(f->mr != nil)
			m = f->mr;
		if(!m->compiled)
			pc--;
		else if(m->m->pctab != nil)
			pc = pc2dispc(pc, m->m)-1;
	}
	return n;
}

static int
calldepth(REG *reg)
{
	int n;
	uchar *fp;

	n = 0;
	for(fp = reg->FP; fp != nil; fp = ((Frame*)fp)->fp)
		n++;
	return n;
}

static int
progheap(Heapqry *hq, char *va, int count, ulong offset)
{
	WORD *w;
	void *p;
	List *hd;
	Array *a;
	char *fmt, *str;
	Module *m;
	Modlink *ml;
	Channel *c;
	ulong addr;
	String *ss;
	union { REAL r; LONG l; WORD w[2]; } rock;
	int i, s, n, len, signed_off;
	Type *t;

	n = 0;
	s = 0;
	signed_off = offset;
	addr = hq->addr;
	for(i = 0; i < hq->count; i++) {
		switch(hq->fmt) {
		case 'W':
			if(addr & 3)
				return -1;
			n += snprint(va+n, count-n, "%zd\n", *(WORD*)addr);
			s = sizeof(WORD);
			break;
		case 'B':
			n += snprint(va+n, count-n, "%d\n", *(BYTE*)addr);
			s = sizeof(BYTE);
			break;
		case 'V':
			if(addr & 3)
				return -1;
			w = (WORD*)addr;
			rock.w[0] = w[0];
			rock.w[1] = w[1];
			n += snprint(va+n, count-n, "%lld\n", rock.l);
			s = sizeof(LONG);
			break;
		case 'R':
			if(addr & 3)
				return -1;
			w = (WORD*)addr;
			rock.w[0] = w[0];
			rock.w[1] = w[1];
			n += snprint(va+n, count-n, "%g\n", rock.r);
			s = sizeof(REAL);
			break;
		case 'I':
			if(addr & 3)
				return -1;
			for(m = modules; m != nil; m = m->link)
				if(m == (Module*)hq->module)
					break;
			if(m == nil)
				error(Ebadctl);
			addr = (uintptr)(m->prog+addr);
			n += snprint(va+n, count-n, "%D\n", (Inst*)addr);
			s = sizeof(Inst);
			break;
		case 'P':
			if(addr & 3)
				return -1;
			p = *(void**)addr;
			fmt = "nil\n";
			if(p != H)
				fmt = "%lux\n";
			n += snprint(va+n, count-n, fmt, p);
			s = sizeof(WORD);
			break;
		case 'L':
			if(addr & 3)
				return -1;
			hd = *(List**)addr;
			if(hd == H || D2H(hd)->t != &Tlist)
				return -1;
			n += snprint(va+n, count-n, "%zx.%zx\n", (uintptr)&hd->tail, (uintptr)hd->data);
			s = sizeof(WORD);
			break;
		case 'A':
			if(addr & 3)
				return -1;
			a = *(Array**)addr;
			if(a == H)
				n += snprint(va+n, count-n, "nil\n");
			else {
				if(D2H(a)->t != &Tarray)
					return -1;
				n += snprint(va+n, count-n, "%zd.%zx\n", a->len, (uintptr)a->data);
			}
			s = sizeof(WORD);
			break;
		case 'C':
			if(addr & 3)
				return -1;
			ss = *(String**)addr;
			if(ss == H)
				ss = &snil;
			else
			if(D2H(ss)->t != &Tstring)
				return -1;
			n += snprint(va+n, count-n, "%d.", abs(ss->len));
			str = string2c(ss);
			len = strlen(str);
			if(count-n < len)
				len = count-n;
			if(len > 0) {
				memmove(va+n, str, len);
				n += len;
			}
			break;
		case 'M':
			if(addr & 3)
				return -1;
			ml = *(Modlink**)addr;
			fmt = ml == H ? "nil\n" : "%lux\n";
			n += snprint(va+n, count-n, fmt, ml->MP);
			s = sizeof(WORD);
			break;
		case 'c':
			if(addr & 3)
				return -1;
			c = *(Channel**)addr;
			if(c == H)
				n += snprint(va+n, count-n, "nil\n");
			else{
				t = D2H(c)->t;
				if(t != &Tchannel && t != Trdchan && t != Twrchan)
					return -1;
				if(c->buf == H)
					n += snprint(va+n, count-n, "0.%zx\n", (uintptr)c);
				else
					n += snprint(va+n, count-n, "%zd.%zx.%d.%d\n", c->buf->len, (uintptr)c->buf->data, c->front, c->size);
			}
			break;
			
		}
		addr += s;
		if(signed_off > 0) {
			signed_off -= n;
			if(signed_off < 0) {
				memmove(va, va+n+signed_off, -signed_off);
				n = -signed_off;
			}
			else
				n = 0;
		}
	}
	return n;
}

WORD
modstatus(REG *r, char *ptr, int len)
{
	Inst *PC;
	Frame *f;

	if(r->M->m->name[0] == '$') {
		f = (Frame*)r->FP;
		snprint(ptr, len, "%s[%s]", f->mr->m->name, r->M->m->name);
		if(f->mr->compiled)
			return (WORD)f->lr;
		return f->lr - f->mr->prog;
	}
	memmove(ptr, r->M->m->name, len);
	if(r->M->compiled)
		return (WORD)r->PC;
	PC = r->PC;
	/* should really check for blocked states */
	if(PC > r->M->prog)
		PC--;
	return PC - r->M->prog;
}

static void
int2flag(int flag, char *s)
{
	if(flag == 0){
		*s = '\0';
		return;
	}
	*s++ = '-';
	if(flag & MAFTER)
		*s++ = 'a';
	if(flag & MBEFORE)
		*s++ = 'b';
	if(flag & MCREATE)
		*s++ = 'c';
	if(flag & MCACHE)
		*s++ = 'C';
	*s = '\0';
}

static char*
progtime(ulong msec, char *buf, char *ebuf)
{
	int tenths, sec;

	tenths = msec/100;
	sec = tenths/10;
	seprint(buf, ebuf, "%4d:%2.2d.%d", sec/60, sec%60, tenths%10);
	return buf;
}

static long
progread(Chan *c, void *va, long n, vlong offset)
{
	int i;
	Prog *p;
	Osenv *o;
	Mntwalk *mw;
	ulong grpid;
	char *a = va;
	Progctl *ctl;
	char mbuf[64], timebuf[12];
	char flag[10];

	if(c->qid.type & QTDIR)
		return devdirread(c, a, n, 0, 0, proggen);

	switch(QID(c->qid)){
	case Qdbgctl:
		ctl = c->aux;
		return qread(ctl->q, va, n);
	case Qstatus:
		acquire();
		p = progpid(PID(c->qid));
		if(p == nil || p->state == Pexiting || p->R.M == H) {
			release();
			snprint(up->genbuf, sizeof(up->genbuf), "%8lud %8d %10s %s %10s %5dK %s",
				PID(c->qid),
				0,
				eve,
				progtime(0, timebuf, timebuf+sizeof(timebuf)),
				progstate[Pexiting],
				0,
				"[$Sys]");
			return readstr(offset, va, n, up->genbuf);
		}
		modstatus(&p->R, mbuf, sizeof(mbuf));
		o = p->osenv;
		snprint(up->genbuf, sizeof(up->genbuf), "%8d %8d %10s %s %10s %5dK %s",
			p->pid,
			p->group!=nil? p->group->id: 0,
			o->user,
			progtime(p->ticks, timebuf, timebuf+sizeof(timebuf)),
			progstate[p->state],
			progsize(p),
			mbuf);
		release();
		return readstr(offset, va, n, up->genbuf);
	case Qwait:
		return qread(c->aux, va, n);
	case Qns:
		acquire();
		if(waserror()){
			release();
			nexterror();
		}
		p = progpid(PID(c->qid));
		if(p == nil)
			error(Ethread);
		mw = c->aux;
		if(mw->cddone){
			poperror();
			release();
			return 0;
		}
		o = p->osenv;
		mntscan(mw, o->pgrp);
		if(mw->mh == 0) {
			mw->cddone = 1;
			i = snprint(a, n, "cd %s\n", o->pgrp->dot->name->s);
			poperror();
			release();
			return i;
		}
		int2flag(mw->cm->mflag, flag);
		if(strcmp(mw->cm->to->name->s, "#M") == 0){
			i = snprint(a, n, "mount %s %s %s %s\n", flag,
				mw->cm->to->mchan->name->s,
				mw->mh->from->name->s, mw->cm->spec? mw->cm->spec : "");
		}else
			i = snprint(a, n, "bind %s %s %s\n", flag,
				mw->cm->to->name->s, mw->mh->from->name->s);
		poperror();
		release();
		return i;
	case Qnsgrp:
		acquire();
		p = progpid(PID(c->qid));
		if(p == nil) {
			release();
			error(Ethread);
		}
		grpid = ((Osenv *)p->osenv)->pgrp->pgrpid;
		release();
		return readnum(offset, va, n, grpid, NUMSIZE);
	case Qpgrp:
		acquire();
		p = progpid(PID(c->qid));
		if(p == nil) {
			release();
			error(Ethread);
		}
		grpid = p->group!=nil? p->group->id: 0;
		release();
		return readnum(offset, va, n, grpid, NUMSIZE);
	case Qstack:
		acquire();
		p = progpid(PID(c->qid));
		if(p == nil || p->state == Pexiting) {
			release();
			error(Ethread);
		}
		if(p->state == Pready) {
			release();
			error(Estopped);
		}
		n = progstack(&p->R, p->state, va, n, offset);
		release();
		return n;
	case Qheap:
		acquire();
		if(waserror()){
			release();
			nexterror();
		}
		n = progheap(c->aux, va, n, offset);
		if(n == -1)
			error(Emisalign);
		poperror();
		release();
		return n;
	case Qfd:
		acquire();
		if(waserror()) {
			release();
			nexterror();
		}
		p = progpid(PID(c->qid));
		if(p == nil)
			error(Ethread);
		o = p->osenv;
		n = progfds(o, va, n, offset);
		poperror();
		release();
		return n;
	case Qexception:
		acquire();
		p = progpid(PID(c->qid));
		if(p == nil) {
			release();
			error(Ethread);
		}
		if(p->exstr == nil)
			up->genbuf[0] = 0;
		else
			snprint(up->genbuf, sizeof(up->genbuf), p->exstr);
		release();
		return readstr(offset, va, n, up->genbuf);
	}
	error(Egreg);
	return 0;
}

static void
mntscan(Mntwalk *mw, Pgrp *pg)
{
	Mount *t;
	Mhead *f;
	int nxt, i;
	ulong last, bestmid;

	rlock(&pg->ns);

	nxt = 0;
	bestmid = ~0;

	last = 0;
	if(mw->mh)
		last = mw->cm->mountid;

	for(i = 0; i < MNTHASH; i++) {
		for(f = pg->mnthash[i]; f; f = f->hash) {
			for(t = f->mount; t; t = t->next) {
				if(mw->mh == 0 ||
				  (t->mountid > last && t->mountid < bestmid)) {
					mw->cm = t;
					mw->mh = f;
					bestmid = mw->cm->mountid;
					nxt = 1;
				}
			}
		}
	}
	if(nxt == 0)
		mw->mh = 0;

	runlock(&pg->ns);
}

static long
progwrite(Chan *c, void *va, long n, vlong offset)
{
	Prog *p, *f;
	Heapqry *hq;
	char buf[512];
	Progctl *ctl;
	char *b;
	int i, pc;
	Cmdbuf *cb;
	Cmdtab *ct;

	USED(offset);
	USED(va);

	if(c->qid.type & QTDIR)
		error(Eisdir);

	acquire();
	if(waserror()) {
		release();
		nexterror();
	}
	p = progpid(PID(c->qid));
	if(p == nil)
		error(Ethread);

	switch(QID(c->qid)){
	case Qctl:
		cb = parsecmd(va, n);
		if(waserror()){
			free(cb);
			nexterror();
		}
		ct = lookupcmd(cb, progcmd, nelem(progcmd));
		switch(ct->index){
		case CMkillgrp:
			killgrp(p, "killed");
			break;
		case CMkill:
			killprog(p, "killed");
			break;
		case CMrestricted:
			p->flags |= Prestrict;
			break;
		case CMexceptions:
			if(p->group->id != p->pid)
				error(Eperm);
			if(strcmp(cb->f[1], "propagate") == 0)
				p->flags |= Ppropagate;
			else if(strcmp(cb->f[1], "notifyleader") == 0)
				p->flags |= Pnotifyleader;
			else
				error(Ebadctl);
			break;
		case CMprivate:
			p->group->flags |= Pprivatemem;
			break;
		}
		poperror();
		free(cb);
		break;
	case Qdbgctl:
		cb = parsecmd(va, n);
		if(waserror()){
			free(cb);
			nexterror();
		}
		if(cb->nf == 1 && strncmp(cb->f[0], "step", 4) == 0)
			ct = progdbgcmd;
		else
			ct = lookupcmd(cb, progdbgcmd, nelem(progdbgcmd));
		switch(ct->index){
		case CDstep:
			if(cb->nf == 1)
				i = strtoul(cb->f[0]+4, nil, 0);
			else
				i = strtoul(cb->f[1], nil, 0);
			dbgstep(c->aux, p, i);
			break;
		case CDtoret:
			f = currun();
			i = calldepth(&p->R);
			while(f->kill == nil) {
				dbgstep(c->aux, p, 1024);
				if(i > calldepth(&p->R))
					break;
			}
			break;
		case CDcont:
			f = currun();
			while(f->kill == nil)
				dbgstep(c->aux, p, 1024);
			break;
		case CDstart:
			dbgstart(p);
			break;
		case CDstop:
			ctl = c->aux;
			ctl->stop = 1;
			break;
		case CDunstop:
			ctl = c->aux;
			ctl->stop = 0;
			break;
		case CDbpt:
			pc = strtoul(cb->f[3], nil, 10);
			ctl = c->aux;
			if(strcmp(cb->f[1], "set") == 0)
				ctl->bpts = setbpt(ctl->bpts, cb->f[2], pc);
			else if(strcmp(cb->f[1], "del") == 0)
				ctl->bpts = delbpt(ctl->bpts, cb->f[2], pc);
			else
				error(Ebadctl);
			break;
		case CDmaim:
			p->kill = "maim";
			break;
		}
		poperror();
		free(cb);
		break;
	case Qheap:
		/*
		 * Heap query:
		 *	addr.Fn
		 *	pc+module.In
		 */
		i = n;
		if(i > sizeof(buf)-1)
			i = sizeof(buf)-1;
		memmove(buf, va, i);
		buf[i] = '\0';
		hq = c->aux;
		hq->addr = strtoul(buf, &b, 0);
		if(*b == '+')
			hq->module = strtoul(b, &b, 0);
		if(*b++ != '.')
			error(Ebadctl);
		hq->fmt = *b++;
		hq->count = strtoul(b, nil, 0);
		break;
	default:
		print("unknown qid in procwrite\n");
		error(Egreg);
	}
	poperror();
	release();
	return n;
}

static Bpt*
setbpt(Bpt *bpts, char *path, int pc)
{
	int n;
	Bpt *b;

	n = strlen(path);
	b = mallocz(sizeof *b + n, 0);
	if(b == nil)
		return bpts;
	b->pc = pc;
	memmove(b->path, path, n+1);
	b->file = b->path;
	path = strrchr(b->path, '/');
	if(path != nil)
		b->file = path + 1;
	b->next = bpts;
	return b;
}

static Bpt*
delbpt(Bpt *bpts, char *path, int pc)
{
	Bpt *b, **last;

	last = &bpts;
	for(b = bpts; b != nil; b = b->next){
		if(b->pc == pc && strcmp(b->path, path) == 0) {
			*last = b->next;
			free(b);
			break;
		}
		last = &b->next;
	}
	return bpts;
}

static void
freebpts(Bpt *b)
{
	Bpt *next;

	for(; b != nil; b = next){
		next = b->next;
		free(b);
	}
}

static void
telldbg(Progctl *ctl, char *msg)
{
	kstrcpy(ctl->msg, msg, ERRMAX);
	ctl->debugger = nil;
	Wakeup(&ctl->r);
}

static void
dbgstart(Prog *p)
{
	Osenv *o;
	Progctl *ctl;

	o = p->osenv;
	ctl = o->debug;
	if(ctl != nil && ctl->debugger != nil)
		error("prog debugged");
	if(p->state == Pdebug)
		addrun(p);
	o->debug = nil;
	p->xec = xec;
}

static int
xecdone(void *vc)
{
	Progctl *ctl = vc;

	return ctl->debugger == nil;
}

static void
dbgstep(Progctl *vctl, Prog *p, int n)
{
	Osenv * volatile o;
	Progctl * volatile ctl;
	char buf[ERRMAX+20], *msg;

	if(p == currun())
		error("cannot step yourself");

	if(p->state == Pbroken)
		error("prog broken");

	ctl = vctl;
	if(ctl->debugger != nil)
		error("prog already debugged");

	o = p->osenv;
	if(o->debug == nil) {
		o->debug = ctl;
		p->xec = dbgxec;
	}else if(o->debug != ctl)
		error("prog already debugged");

	ctl->step = n;
	if(p->state == Pdebug)
		addrun(p);
	ctl->debugger = up;
	strcpy(buf, "child: ");
	msg = buf+7;
	ctl->msg = msg;

	/*
	 * wait for reply from dbgxec; release is okay because prog is now
	 * debugged, cannot exit.
	 */
	if(waserror()){
		acquire();
		ctl->debugger = nil;
		ctl->msg = nil;
		o->debug = nil;
		p->xec = xec;
		nexterror();
	}
	release();
	Sleep(&ctl->r, xecdone, ctl);
	poperror();
	acquire();
	if(msg[0] != '\0')
		error(buf);
}

void
dbgexit(Prog *kid, int broken, char *estr)
{
	int n;
	Osenv *e;
	Progctl *ctl;
	char buf[ERRMAX+20];

	e = kid->osenv;
	ctl = e->debug;
	e->debug = nil;
	kid->xec = xec;

	if(broken)
		n = snprint(buf, sizeof(buf), "broken: %s", estr);
	else
		n = snprint(buf, sizeof(buf), "exited");
	if(ctl->debugger)
		telldbg(ctl, buf);
	qproduce(ctl->q, buf, n);
}

static void
dbgaddrun(Prog *p)
{
	Osenv *o;

	p->state = Pdebug;
	p->addrun = nil;
	o = p->osenv;
	if(o->rend != nil)
		Wakeup(o->rend);
	o->rend = nil;
}

static int
bdone(void *vp)
{
	Prog *p = vp;

	return p->addrun == nil;
}

static void
dbgblock(Prog *p)
{
	Osenv *o;
	Progctl *ctl;

	o = p->osenv;
	ctl = o->debug;
	qproduce(ctl->q, progstate[p->state], strlen(progstate[p->state]));
	pushrun(p);
	p->addrun = dbgaddrun;
	o->rend = &up->sleep;

	/*
	 * bdone(p) is safe after release because p is being debugged,
	 * so cannot exit.
	 */
	if(waserror()){
		acquire();
		nexterror();
	}
	release();
	if(o->rend != nil)
		Sleep(o->rend, bdone, p);
	poperror();
	acquire();
	if(p->kill != nil)
		error(p->kill);
	ctl = o->debug;
	if(ctl != nil)
		qproduce(ctl->q, "run", 3);
}

void
dbgxec(Prog *p)
{
	Bpt *b;
	Prog *kid;
	Osenv *env;
	Progctl *ctl;
	int op, pc, n;
	char buf[ERRMAX+10];
	extern void (*dec[])(void);

	env = p->osenv;
	ctl = env->debug;
	ctl->ref++;
	if(waserror()){
		closedbgctl(ctl, p);
		nexterror();
	}

	R = p->R;
	R.MP = R.M->MP;

	R.IC = p->quanta;
	if((ulong)R.IC > ctl->step)
		R.IC = ctl->step;
	if(ctl->stop)
		R.IC = 0;


	buf[0] = '\0';

	if(R.IC != 0 && R.M->compiled) {
		comvec();
		if(p != currun())
			dbgblock(p);
		goto save;
	}

	while(R.IC != 0) {
		if(0)
			print("step: %zx: %s %4zd %D\n",
				(uintptr)p, R.M->m->name, R.PC-R.M->prog, R.PC);

		dec[R.PC->add]();
		op = R.PC->op;
		R.PC++;
		optab[op]();

		/*
		 * check notification about new progs
		 */
		if(op == ISPAWN || op == IMSPAWN) {
			/* pick up the kid from the end of the run queue */
			kid = delruntail(Pdebug);
			n = snprint(buf, sizeof buf, "new %d", kid->pid);
			qproduce(ctl->q, buf, n);
			buf[0] = '\0';
		}
		if(op == ILOAD) {
			n = snprint(buf, sizeof buf, "load %s", string2c(*(String**)R.s));
			qproduce(ctl->q, buf, n);
			buf[0] = '\0';
		}

		/*
		 * check for returns at big steps
		 */
		if(op == IRET)
			R.IC = 1;

		/*
		 * check for blocked progs
		 */
		if(R.IC == 1 && p != currun())
			dbgblock(p);
		if(ctl->stop)
			R.IC = 1;
		R.IC--;

		if(ctl->bpts == nil)
			continue;

		pc = R.PC - R.M->prog;
		for(b = ctl->bpts; b != nil; b = b->next) {
			if(pc == b->pc &&
			  (strcmp(R.M->m->path, b->path) == 0 ||
			   strcmp(R.M->m->path, b->file) == 0)) {
				strcpy(buf, "breakpoint");
				goto save;
			}
		}
	}
save:
	if(ctl->stop)
		strcpy(buf, "stopped");

	p->R = R;

	if(env->debug == nil) {
		poperror();
		return;
	}

	if(p == currun())
		delrun(Pdebug);

	telldbg(env->debug, buf);
	poperror();
	closedbgctl(env->debug, p);
}

Dev progdevtab = {
	'p',
	"prog",

	devinit,
	progattach,
	progwalk,
	progstat,
	progopen,
	devcreate,
	progclose,
	progread,
	devbread,
	progwrite,
	devbwrite,
	devremove,
	progwstat
};