Plan 9 from Bell Labs’s /usr/web/sources/contrib/quanstro/root/sys/src/9/port/devtrace.c

Copyright © 2021 Plan 9 Foundation.
Distributed under the MIT License.
Download the Plan 9 distribution.


#include "u.h"
#include "../port/lib.h"
#include "mem.h"
#include "dat.h"
#include "fns.h"
#include "../port/error.h"
#include "archtrace.h"

#pragma profile 0

enum {
	Qdir,
	Qctl,
	Qdata,

	TraceFree	= 0,
	TraceEntry, 
	TraceExit,

	Logsize		= 8192,
	Maxpidwatch	= 64,
	Printsize		= 121,
	Cacheline	= 64,
};

typedef struct Trace Trace;
typedef struct Tracelog Tracelog;
typedef struct Lockln Lockln;

struct Trace {
	Archtrace;
	Trace	*next;
	uchar	*start;
	uchar	*end;
	int	enabled;
	char	name[16];
};

/* This represents a trace "hit" or event */
struct Tracelog {
	uvlong	ticks;
	int	info;
	uintptr	pc;
	uintptr	dat[5];		/* depends on type */
};

/* bogus, non-portable experiment to reduce false sharing */
struct Lockln {
	union {
		Lock;
		uchar	pad[Cacheline];
	};
};

static	Rendez	tracesleep;
static	QLock	traceslock;
static	Trace	*traces;		/* all traces */
static	Lockln	loglk;
static	Tracelog	*tracelog;
	int	traceactive;
/*
 * trace indices. These are just ulongs. You mask them 
 * to get an index. This makes fifo empty/full etc. trivial. 
 */
static	uint	pw;
static	uint	pr;
static	int	tracesactive;
static	int	all;
static	long	traceinhits;
static	Lockln	traceinhitslk;
static	long	traceouthits;
static	Lockln	traceouthitslk;
static	ulong	logsize		= Logsize;
static	ulong	logmask		= Logsize - 1;
static	int	pidwatch[Maxpidwatch];
static	int	numpids;
	int	codesize;
static	char	hex[] 		= "0123456789abcdef";

static char eventname[] = {
[TraceEntry] 	'E',
[TraceExit] 	'X',
};

static Dirtab tracedir[]={
	".",		{Qdir, 0, QTDIR},	0,		DMDIR|0555,
	"tracectl",	{Qctl},			0,		0664,
	"trace",	{Qdata},		0,		0440,
};

extern	void	sfence(void);

char*
hex32(ulong l, char *c)
{
	int i;

	for(i = 7; i >= 0; i--){
		c[i] = hex[l&0xf];
		l >>= 4;
	}
	return c + 8;
}

char*
hex64(uvlong l, char *c)
{
	return hex32(l, hex32(l>>32, c));
}

static int
lognonempty(void)
{
	return pw - pr;
}

static int
logfull(void)
{
	return pw - pr == logsize;
}

static uvlong 
idx(uvlong f)
{
	return f & logmask;
}

/*
 * Check if the given trace overlaps any others
 * Returns 1 if there is overlap, 0 if clear.
 */
int
overlapping(Trace *p)
{
	Trace *t;

	for(t = traces; t != nil; t = t->next)
		if(p->start <= t->end && p->end >= t->start)
			return 1;
	return 0;
}

/*
 * Return 1 if pid is being watched or no pids are being watched.
 * Return 0 if pids are being watched and the argument is not
 * among them.
 */
int
watchingpid(int pid)
{
	int *tab, *m, n, i;
	
	if(numpids == 0)
		return 1;

	tab = pidwatch;
	n = numpids;
	while(n > 0){
		i = n/2;
		m = tab+i;
		if(*m == pid)
			return 1;
		if(*m < pid){
			tab += i+1;
			n -= i+1;
		}else
			n = i;
	}
	return 0;			
}

void
removetrace(Trace *p)
{
	Trace *prev, *t;

	prev = nil;
	for(t = traces; t != nil; prev = t, t = t->next)
		if(t == p){
			if(prev != nil)
				prev->next = t->next;
			if(t == traces)
				traces = nil;
			free(t);
		}
}

/* these next two functions assume tracelock is locked */
void
traceon(Trace *p)
{
	if(p->enabled != 1){
		p->enabled = 1;
		archtraceinstall(p);
		tracesactive++;
	}
}

void
traceoff(Trace *p)
{
	if(p->enabled == 1){
		p->enabled = 0;
		archtraceuninstall(p);
		tracesactive--;
	}
}

/*
 * Make a new tracelog (an event)
 */
static Tracelog*
newtracelog(void)
{
	Tracelog *t;

	t = nil;
	ilock(&loglk);
	if(!logfull())
		t = tracelog + idx(pw++);
 	iunlock(&loglk);

	return t;
}

void
tracein(uintptr pc, uintptr a[4])
{	
	Tracelog *t;

//	_xinc(&traceinhits);
	ilock(&traceinhitslk);
	traceinhits++;
	iunlock(&traceinhitslk);
	if(!all)
		if(!up || !watchingpid(up->pid))
			return;
	t = newtracelog();
	if(!t)
		return;
	cycles(&t->ticks);
	t->pc = pc;
	t->dat[0] = -1;
	if(up)
		t->dat[0] = up->pid;
	t->dat[1] = a[0];
	t->dat[2] = a[1];
	t->dat[3] = a[2];
	t->dat[4] = a[3];
	sfence();
	t->info = TraceEntry;
}

void
traceout(uintptr pc, uintptr retval)
{
	Tracelog *t;

//	_xinc(&traceouthits);
	ilock(&traceouthitslk);
	traceouthits++;
	iunlock(&traceouthitslk);
	if(!all)
		if(!up || !watchingpid(up->pid))
			return;

	t = newtracelog();
	if(!t)
		return;
	cycles(&t->ticks);
	t->pc = (uintptr)pc;
	t->dat[0] = -1;
	if(up)
		t->dat[0] = up->pid;
	t->dat[1] = retval;
	t->dat[2] = 0;
	t->dat[3] = 0;
	sfence();
	t->info = TraceExit;
}

/* Create a new trace with the given range */
static Trace*
mktrace(uchar *start, uchar *end)
{
	Trace *p;

	p = malloc(sizeof *p);
	if(mkarchtrace(p, start, &end) == -1){
		free(p);
		return nil;
	}
	p->start = start;
	p->end = end;
	return p;
}

static Chan*
traceattach(char *spec)
{
	return devattach('T', spec);
}

static Walkqid*
tracewalk(Chan *c, Chan *nc, char **name, int nname)
{
	return devwalk(c, nc, name, nname, tracedir, nelem(tracedir), devgen);
}

static int
tracestat(Chan *c, uchar *db, int n)
{
	return devstat(c, db, n, tracedir, nelem(tracedir), devgen);
}

static Chan*
traceopen(Chan *c, int omode)
{
	if(tracelog == nil)
		tracelog = malloc(sizeof *tracelog * logsize);
	if(tracelog == nil)
		error(Enomem);
 	return devopen(c, omode, tracedir, nelem(tracedir), devgen);
}

static void
traceclose(Chan*)
{
}

static long
traceread(Chan *c, void *a, long n, vlong offset)
{
	char *buf, *e, *s, *s0;
	uint i, l, epr;
	Tracelog *pl;
	Trace *p;
	static QLock gate;

	if(c->qid.type == QTDIR)
		return devdirread(c, a, n, tracedir, nelem(tracedir), devgen);

	switch((int)c->qid.path){
	default:
		error("traceread: bad qid");
	case Qctl:
		buf = s = malloc(READSTR);
		e = buf + READSTR;
		s = seprint(s, e, "logsize %lud\n", logsize); 
		qlock(&traceslock);
		for(p = traces; p != nil; p = p->next){
			s = seprint(s, e, "trace %p %p new %s\n",
				p->start, p->end, p->name);
			s = archtracectlr(p, s, e);
		}
		for(p = traces; p != nil; p = p->next)
			s = seprint(s, e, "#trace %p traced? %d\n",
				p->start, p->enabled);
		for(p = traces; p != nil; p = p->next)
			if(p->enabled)
				s = seprint(s, e, "trace %s on\n", p->name);
		qunlock(&traceslock);
		for(i = 0; i < numpids; i++)
			s = seprint(s, e, "watch %d\n", pidwatch[i]);
		if(traceactive)
			s = seprint(s, e, "start\n"); 
		s = seprint(s, e, "#tracehits %d, in queue %d\n", pw, pw-pr); 
		s = seprint(s, e, "#tracelog %p\n", tracelog);
		s = seprint(s, e, "#traceactive %d\n", traceactive);
		s = seprint(s, e, "#traceinhits %lud\n", traceinhits);
		s = seprint(s, e, "#traceouthits %lud\n", traceouthits);
		USED(s);
		n = readstr(offset, a, n, buf);
		free(buf);
		break;
	case Qdata:
		/*
		 * print is avoided because it might be traced.
		 */
		s = s0 = a;
		qlock(&gate);
		for(epr = pr + n/Printsize; pr != epr && lognonempty(); pr++){
			pl = tracelog + idx(pr);
			if((l = eventname[pl->info]) == TraceFree)
				break;
			pl->info = TraceFree;
			/* simple format */
			*s++ = l;
			*s++ = ' ';
			s = hex64((uvlong)pl->pc, s);
			*s++ = ' ' ;
			s = hex64(pl->ticks, s);
			*s++ = ' ';
			for(i = 0; ; i++){
				s = hex64(pl->dat[i], s);
				if(i == 4)
					break;
				*s++ = ' ';
			}
			*s++ = '\n';
		}
		qunlock(&gate);
		n = s - s0;
		break;
	}
	return n;
}

static char notfound[] = "devtrace: trace not found";
static char badaddr[] = "devtrace: bad address";

static uchar*
getaddr(char *s)
{
	char *e;
	uvlong u;

	u = strtoull(s, &e, 16);
	if(*e)
		error(badaddr);
	if(u < KTZERO)
		u |= KTZERO;
	if((char*)u > etext)
		error(badaddr);
	return (uchar*)u;
}
	
static long
tracewrite(Chan *c, void *a, long n, vlong)
{
	char *tok[6], *ep, *s;
	uchar *start, *end;
	int *w, l, ntok, pid, saveactive;
	Trace *p, *t;
	Tracelog *lg;

	saveactive = traceactive;
	traceactive = 0;
	mfence();
	s = nil;
	qlock(&traceslock);
	if(waserror()){
		qunlock(&traceslock);
		if(s != nil)
			free(s);
		traceactive = saveactive;
		nexterror();
	}
	switch((uintptr)c->qid.path){
	default:
		error("tracewrite: bad qid");
	case Qctl:
		s = malloc(n + 1);
		memmove(s, a, n);
		s[n] = 0;
		ntok = tokenize(s, tok, nelem(tok));
		if(!strcmp(tok[0], "trace")){
			for(p = traces; p != nil; p = p->next)
				if(strcmp(tok[1], p->name) == 0)
					break;
			if(ntok > 3 && !strcmp(tok[3], "new")){
				if(ntok != 5)
					error("devtrace: usage: trace <ktextstart> <ktextend> new <name>");
				start = getaddr(tok[1]);
				end = getaddr(tok[2]);
				if(start > end)
					error("devtrace: invalid address range");
				if(p)
					error("devtrace: trace already exists");

				if((p = mktrace(start, end)) == nil)
					error(Egreg);
				for(t = traces; t != nil; t = t->next)
					if(strcmp(tok[4], t->name) == 0)
						error("devtrace: trace with that name already exists");
				if(overlapping(p))
					error("devtrace: given range overlaps with existing trace");
				if(ntok < 5)
					snprint(p->name, sizeof p->name, "%p", start);
				else
					strncpy(p->name, tok[4], sizeof p->name);
				p->next = traces;
				traces = p;
			}else if(!strcmp(tok[2], "remove")){
				if(ntok != 3)
					error("devtrace: usage: trace <name> remove");
				if(p == nil)
					error(notfound);
				traceoff(p);
				removetrace(p);
			}else if(!strcmp(tok[2], "on")){
				if(ntok != 3)
					error("devtrace: usage: trace <name> on");
				if(p == nil)
					error(notfound);
				traceon(p);
			}else if(!strcmp(tok[2], "off")){
				if(ntok != 3) 
					error("devtrace: usage: trace <name> off");
				if(p == nil)
					error(notfound);
				traceoff(p);
			}else
				error(Ebadarg);
		}else if(!strcmp(tok[0], "size")){
			if(ntok != 2)
				error("devtrace: usage: size <exponent>");	
			l = 1 << strtoul(tok[1], &ep, 0);
			if(*ep || l < 0x10000000)
				error(badaddr);
			lg = malloc(sizeof *lg * l);
			if(lg == nil)
				error(Enomem);
			free(tracelog);
			tracelog = lg;
			logsize = l;
			logmask = l - 1;
			pr = pw = 0;
		}else if(!strcmp(tok[0], "watch")){
			if(ntok != 2)
				error("devtrace: usage: watch [0|pid]");
			pid = atoi(tok[1]);
			if(pid == 0)
				numpids = 0;
			else if(numpids == Maxpidwatch)
				error("pidwatch array full!");
			else{
				for(w=pidwatch+numpids; w > pidwatch && *(w-1) > pid; w--)
					*w = *(w-1);
				*w = pid;
				numpids++;
			}
		}else if(!strcmp(tok[0], "start")){
			if(ntok != 1)
				error("devtrace: usage: start");
			saveactive = 1;
		}else if(!strcmp(tok[0], "stop")){
			if(ntok != 1)
				error("devtrace: usage: stop");
			saveactive = 0;
			all = 0;
		}else if(!strcmp(tok[0], "all")){
			if(ntok != 1)
				error("devtrace: usage: all");
			saveactive = 1;
			all = 1;
		}else
			error("devtrace:  usage: 'trace' [ktextaddr|name] 'on'|'off'|'mk'|'del' [name] or:  'size' buffersize (power of 2)");
		break;
	}
	poperror();
	qunlock(&traceslock);
	if(s != nil)
		free(s);
	traceactive = saveactive;
	return n;
}

Dev tracedevtab = {
	'T',
	"trace",
	devreset,
	devinit,
	devshutdown,
	traceattach,
	tracewalk,
	tracestat,
	traceopen,
	devcreate,
	traceclose,
	traceread,
	devbread,
	tracewrite,
	devbwrite,
	devremove,
	devwstat,
};

Bell Labs OSI certified Powered by Plan 9

(Return to Plan 9 Home Page)

Copyright © 2021 Plan 9 Foundation. All Rights Reserved.
Comments to [email protected].