#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 new "); 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 remove"); if(p == nil) error(notfound); traceoff(p); removetrace(p); }else if(!strcmp(tok[2], "on")){ if(ntok != 3) error("devtrace: usage: trace on"); if(p == nil) error(notfound); traceon(p); }else if(!strcmp(tok[2], "off")){ if(ntok != 3) error("devtrace: usage: trace off"); if(p == nil) error(notfound); traceoff(p); }else error(Ebadarg); }else if(!strcmp(tok[0], "size")){ if(ntok != 2) error("devtrace: usage: size "); 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, };