#include "u.h" #include "../port/lib.h" #include "mem.h" #include "dat.h" #include "fns.h" #include "../port/error.h" #include "netif.h" /* * NB: To be used with 6l -e so tracein/out are called upon * function entry and exit. * There's no trace(3) man page, look at write source to see the * commands. */ #pragma profile 0 typedef struct Trace Trace; /* This is a trace--a segment of memory to watch for entries and exits */ struct Trace { struct Trace *next; void *func; void *start; void *end; int enabled; char name[16]; }; enum { Qdir, Qctl, Qdata, }; enum { TraceEntry = 1, TraceExit, }; /* fix me make this programmable */ enum { defaultlogsize = 8192, }; /* This represents a trace "hit" or event */ typedef struct Tracelog Tracelog; struct Tracelog { uint64_t ticks; int info; uintptr_t pc; /* these are different depending on type */ uintptr_t dat[5]; int machno; }; //static Rendez tracesleep; // not used static QLock traceslock; /* this will contain as many entries as there are valid pc values */ static Trace **tracemap; static Trace *traces; /* This stores all the traces */ //static Lock loglk; // not used static Tracelog *tracelog = nil; int traceactive = 0; /* trace indices. These are just unsigned longs. You mask them * to get an index. This makes fifo empty/full etc. trivial. */ static uint32_t pw = 0, pr = 0; static int tracesactive = 0; static int all = 0; static int watching = 0; static int slothits = 0; static unsigned int traceinhits = 0; static unsigned int newplfail = 0; static unsigned long logsize = defaultlogsize, logmask = defaultlogsize - 1; static int printsize = 0; //The length of a line being printed /* These are for observing a single process */ static int *pidwatch = nil; static int numpids = 0; static const int PIDWATCHSIZE = 32; /* The number of PIDS that can be watched. Pretty arbitrary. */ int codesize = 0; //static uint64_t lastestamp; /* last entry timestamp */ // not used //static uint64_t lastxstamp; /* last exit timestamp */ // not used /* Trace events can be either Entries or Exits */ static char eventname[] = { [TraceEntry] = 'E', [TraceExit] = 'X', }; static Dirtab tracedir[]={ ".", {Qdir, 0, QTDIR}, 0, DMDIR|0555, "tracectl", {Qctl}, 0, 0664, "trace", {Qdata}, 0, 0440, }; char hex[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', }; /* big-endian ... */ void hex8(uint32_t l, char *c) { int i; for(i = 2; i; i--){ c[i-1] = hex[l&0xf]; l >>= 4; } } void hex16(uint32_t l, char *c) { int i; for(i = 4; i; i--){ c[i-1] = hex[l&0xf]; l >>= 4; } } void hex32(uint32_t l, char *c) { int i; for(i = 8; i; i--){ c[i-1] = hex[l&0xf]; l >>= 4; } } void hex64(uint64_t l, char *c) { hex32(l>>32, c); hex32(l, &c[8]); } static int lognonempty(void * _1) { return pw - pr; } #if 0 static int logfull(void) { return (pw - pr) >= logsize; } #endif static uint64_t idx(uint64_t 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 *curr; curr = traces; if (!curr) return 0; do { if ((curr->start < p->start && p->start < curr->end) || (curr->start < p->end && p->end < curr->end)) return 1; curr = curr->next; } while (curr != nil); return 0; } /* Make sure a PC is valid and traced; if so, return its Trace */ /* if dopanic == 1, the kernel will panic on an invalid PC */ struct Trace ** traceslot(void *pc, int dopanic) { int index; struct Trace **p; if (pc > (void *)etext) { if (dopanic) panic("Bad PC %p", pc); jehanne_print("Invalid PC %p\n", pc); return nil; } index = (int)((uintptr_t)pc - KTZERO); if (index > codesize){ if (dopanic) { panic("Bad PC %p", pc); while(1); } jehanne_print("Invalid PC %p\n", pc); return nil; } p = &tracemap[index]; if (tracemap[index]) ainc(&slothits); return p; } /* Check if the given PC is traced and return a Trace if so */ struct Trace * traced(void *pc, int dopanic) { struct Trace **p; p = traceslot(pc, dopanic); if (p == nil) return nil; return *p; } /* * 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 i; if (pidwatch[0] == 0) return 1; for (i = 0; i < numpids; i++) { if (pidwatch[i] == pid) return 1; } return 0; } /* * Remove a trace. */ void removetrace(Trace *p) { uint8_t *cp; struct Trace *prev; struct Trace *curr; struct Trace **slot; slot = traceslot(p->start, 0); for(cp = p->start; cp <= (uint8_t *)p->end; slot++, cp++) *slot = nil; curr = traces; if (curr == p) { if (curr->next) { traces = curr->next; } else { traces = nil; //this seems to work fine } jehanne_free(curr); return; } prev = curr; curr = curr->next; do { if (curr == p) { prev->next = curr->next; return; } prev = curr; curr = curr->next; } while (curr != nil); } /* it is recommended that you call these with something sane. */ /* these next two functions assume you locked tracelock */ /* Turn on a trace */ void traceon(struct Trace *p) { uint8_t *cp; struct Trace **slot; slot = traceslot(p->start, 0); for(cp = p->start; cp <= (uint8_t *)p->end; slot++, cp++) *slot = p; p->enabled = 1; tracesactive++; } /* Turn off a trace */ void traceoff(struct Trace *p) { uint8_t *cp; struct Trace **slot; slot = traceslot(p->start, 0); for(cp = p->start; cp <= (uint8_t *)p->end; slot++, cp++) *slot = nil; p->enabled = 0; tracesactive--; } /* Make a new tracelog (an event) */ /* can return NULL, meaning, no record for you */ static struct Tracelog * newpl(void) { uint32_t index; index = ainc((int *)&pw); return &tracelog[idx(index)]; } /* Called every time a (traced) function starts */ /* this is not really smp safe. FIX */ void tracein(void* pc, uintptr_t a1, uintptr_t a2, uintptr_t a3, uintptr_t a4) { struct Tracelog *pl; /* if we are here, tracing is active. Turn it off. */ traceactive = 0; if (! traced(pc, 1)){ traceactive = 1; return; } ainc((int *)&traceinhits); /* Continue if we are watching this pid or we're not watching any */ if (!all) if (!up || !watchingpid(up->pid)){ traceactive = 1; return; } pl = newpl(); if (! pl) { ainc((int *)&newplfail); traceactive = 1; return; } cycles(&pl->ticks); pl->pc = (uintptr_t)pc; if (up) pl->dat[0] = up->pid; else pl->dat[0] = (unsigned long)-1; pl->dat[1] = a1; pl->dat[2] = a2; pl->dat[3] = a3; pl->dat[4] = a4; pl->info = TraceEntry; pl->machno = m->machno; traceactive = 1; } /* Called every time a traced function exits */ void traceout(void* pc, uintptr_t retval) { struct Tracelog *pl; /* if we are here, tracing is active. Turn it off. */ traceactive = 0; if (! traced(pc, 1)){ traceactive = 1; return; } if (!all) if (!up || !watchingpid(up->pid)){ traceactive = 1; return; } pl = newpl(); if (! pl){ traceactive = 1; return; } cycles(&pl->ticks); pl->pc = (uintptr_t)pc; if (up) pl->dat[0] = up->pid; else pl->dat[0] = (unsigned long)-1; pl->dat[1] = retval; pl->dat[2] = 0; pl->dat[3] = 0; pl->info = TraceExit; pl->machno = m->machno; traceactive = 1; } /* Create a new trace with the given range */ static Trace * mktrace(void *func, void *start, void *end) { Trace *p; p = jehanne_mallocz(sizeof p[0], 1); p->func = func; p->start = start; p->end = end; return p; } #if 0 /* Get rid of an old trace */ static void freetrace(Trace *p) { jehanne_free(p); } #endif static Chan* traceattach(Chan *c, Chan *ac, char *spec, int flags) { 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 long tracestat(Chan *c, uint8_t *db, long n) { return devstat(c, db, n, tracedir, nelem(tracedir), devgen); } static Chan* traceopen(Chan *c, unsigned long omode) { /* if there is no tracelog, allocate one. Open always fails * if the basic alloc fails. You can resize it later. */ codesize = (uintptr_t)etext - (uintptr_t)KTZERO; if (! tracemap) //tracemap = jehanne_mallocz(sizeof(struct tracemap *)*codesize, 1); tracemap = jehanne_mallocz(sizeof(struct Trace *)*codesize, 1); if (! tracemap) error("tracemap malloc failed"); if (! tracelog) tracelog = jehanne_mallocz(sizeof(*tracelog)*logsize, 1); /* I guess malloc doesn't toss an error */ if (! tracelog) error("tracelog malloc failed"); if (! pidwatch) pidwatch = jehanne_mallocz(sizeof(int)*PIDWATCHSIZE, 1); if (! pidwatch) error("pidwatch malloc failed"); c = devopen(c, omode, tracedir, nelem(tracedir), devgen); return c; } static void traceclose(Chan * _1) { } /* * Reading from the device, either the data or control files. * The data reading involves deep rminnich magic so we don't have * to call jehanne_print(), which is traced. */ static long traceread(Chan *c, void *a, long n, int64_t offset) { char *buf; char *cp = a; struct Tracelog *pl; Trace *p; int i, j; int saveactive = traceactive; traceactive = 0; // static QLock gate; // not used if (waserror()) { traceactive = saveactive; nexterror(); } if(c->qid.type == QTDIR) { long l = devdirread(c, a, n, tracedir, nelem(tracedir), devgen); poperror(); traceactive = saveactive; return l; } switch((int) c->qid.path){ default: error("traceread: bad qid"); case Qctl: i = 0; qlock(&traceslock); buf = jehanne_malloc(READSTR); i += jehanne_snprint(buf + i, READSTR - i, "logsize %lud\n", logsize); for(p = traces; p != nil; p = p->next) i += jehanne_snprint(buf + i, READSTR - i, "trace %p %p new %s\n", p->start, p->end, p->name); for(p = traces; p != nil; p = p->next) i += jehanne_snprint(buf + i, READSTR - i, "#trace %p traced? %p\n", p->func, traced(p->func, 0)); for(p = traces; p != nil; p = p->next) if (p->enabled) i += jehanne_snprint(buf + i, READSTR - i, "trace %s on\n", p->name); i += jehanne_snprint(buf + i, READSTR - i, "#tracehits %d, in queue %d\n", pw, pw-pr); i += jehanne_snprint(buf + i, READSTR - i, "#tracelog %p\n", tracelog); i += jehanne_snprint(buf + i, READSTR - i, "#traceactive %d\n", saveactive); i += jehanne_snprint(buf + i, READSTR - i, "#slothits %d\n", slothits); i += jehanne_snprint(buf + i, READSTR - i, "#traceinhits %d\n", traceinhits); for (j = 0; j < numpids - 1; j++) i += jehanne_snprint(buf + i, READSTR - i, "watch %d\n", pidwatch[j]); jehanne_snprint(buf + i, READSTR - i, "watch %d\n", pidwatch[numpids - 1]); n = readstr(offset, a, n, buf); jehanne_free(buf); qunlock(&traceslock); break; case Qdata: // Set the printsize /* 32-bit E PCPCPCPC TIMETIMETIMETIME PID# CR XXARG1XX XXARG2XX XXARG3XX XXARG4XX\n */ if (sizeof(uintptr_t) == 4) { printsize = 73; // 32-bit format } else { printsize = 121; // must be 64-bit } i = 0; while(lognonempty((void *)0)){ int j; if ((pw - pr) > logsize) pr = pw - logsize; pl = tracelog + idx(pr); if ((i + printsize) > n) break; /* simple format */ if (sizeof(uintptr_t) == 4) { cp[0] = eventname[pl->info]; cp ++; *cp++ = ' '; hex32((uint32_t)pl->pc, cp); cp[8] = ' '; cp += 9; hex64(pl->ticks, cp); cp[16] = ' '; cp += 17; hex16(pl->dat[0], cp); cp += 4; cp[0] = ' '; cp++; hex8(pl->machno, cp); cp += 2; cp[0] = ' '; cp++; for(j = 1; j < 4; j++){ hex32(pl->dat[j], cp); cp[8] = ' '; cp += 9; } /* adjust for extra skip above */ cp--; *cp++ = '\n'; pr++; i += printsize; } else { cp[0] = eventname[pl->info]; cp ++; *cp++ = ' '; hex64((uint64_t)pl->pc, cp); cp[16] = ' '; cp += 17; hex64(pl->ticks, cp); cp[16] = ' '; cp += 17; hex32(pl->dat[0], cp); cp += 8; cp[0] = ' '; cp++; cp[0] = ' '; cp++; cp[0] = ' '; cp++; cp[0] = ' '; cp++; hex8(pl->machno, cp); cp += 4; for (j = 1; j < 5; j++) { hex64(pl->dat[j], cp); cp[16] = ' '; cp += 17; } cp--; *cp++ = '\n'; pr++; i += printsize; } } n = i; break; } poperror(); traceactive = saveactive; return n; } /* * Process commands sent to the ctl file. */ static long tracewrite(Chan *c, void *a, long n, int64_t _1) { char *tok[6]; //changed this so "tracein" works with the new 4th arg char *ep, *s = nil; Trace *p, **pp, *foo; int ntok; int saveactive = traceactive; traceactive = 0; qlock(&traceslock); if(waserror()){ qunlock(&traceslock); if(s != nil) jehanne_free(s); traceactive = saveactive; nexterror(); } switch((uintptr_t)c->qid.path){ default: error("tracewrite: bad qid"); case Qctl: s = jehanne_malloc(n + 1); jehanne_memmove(s, a, n); s[n] = 0; ntok = jehanne_tokenize(s, tok, nelem(tok)); if(!jehanne_strcmp(tok[0], "trace")){ /* 'trace' ktextaddr 'on'|'off'|'mk'|'del' [name] */ if(ntok < 3) { error("devtrace: usage: 'trace' [ktextaddr|name] 'on'|'off'|'mk'|'del' [name]"); } for(pp = &traces; *pp != nil; pp = &(*pp)->next){ if(!jehanne_strcmp(tok[1], (*pp)->name)) break; } p = *pp; if((ntok > 3) && (!jehanne_strcmp(tok[3], "new"))){ uintptr_t addr; void *start, *end, *func; if (ntok != 5) { error("devtrace: usage: trace new "); } addr = (uintptr_t)jehanne_strtoul(tok[1], &ep, 16); if (addr < KTZERO) addr |= KTZERO; func = start = (void *)addr; if(*ep) { error("devtrace: start address not in recognized format"); } addr = (uintptr_t)jehanne_strtoul(tok[2], &ep, 16); if (addr < KTZERO) addr |= KTZERO; end = (void *)addr; if(*ep) { error("devtrace: end address not in recognized format"); } if (start > (void *)end || start > (void *)etext || end > (void *)etext) error("devtrace: invalid address range"); /* What do we do here? start and end are weird * if((addr < (uintptr_t)start) || (addr > (uintptr_t)end) error("devtrace: address out of bounds"); */ if(p) { error("devtrace: trace already exists"); } p = mktrace(func, start, end); for (foo = traces; foo != nil; foo = foo->next) { if (!jehanne_strcmp(tok[4], foo->name)) error("devtrace: trace with that name already exists"); } if (!overlapping(p)) { p->next = traces; if(ntok < 5) jehanne_snprint(p->name, sizeof p->name, "%p", func); else jehanne_strncpy(p->name, tok[4], sizeof p->name); traces = p; } else { error("devtrace: given range overlaps with existing trace"); } } else if(!jehanne_strcmp(tok[2], "remove")){ if (ntok != 3) error("devtrace: usage: trace remove"); if (p == nil) { error("devtrace: trace not found"); } removetrace(p); } else if(!jehanne_strcmp(tok[2], "on")){ if (ntok != 3) error("devtrace: usage: trace on"); if(p == nil) { error("devtrace: trace not found"); } if (! traced(p->func, 0)){ traceon(p); } } else if(!jehanne_strcmp(tok[2], "off")){ if (ntok != 3) error("devtrace: usage: trace off"); if(p == nil) { error("devtrace: trace not found"); } if(traced(p->func, 0)){ traceoff(p); } } } else if(!jehanne_strcmp(tok[0], "query")){ /* See if addr is being traced */ Trace* p; uintptr_t addr; if (ntok != 2) { error("devtrace: usage: query "); } addr = (uintptr_t)jehanne_strtoul(tok[1], &ep, 16); if (addr < KTZERO) addr |= KTZERO; p = traced((void *)addr, 0); if (p) { jehanne_print("Probing is enabled\n"); } else { jehanne_print("Probing is disabled\n"); } } else if(!jehanne_strcmp(tok[0], "size")){ int l, size; struct Tracelog *newtracelog; if (ntok != 2) error("devtrace: usage: size "); l = jehanne_strtoul(tok[1], &ep, 0); if(*ep) { error("devtrace: size not in recognized format"); } size = 1 << l; /* sort of foolish. Alloc new trace first, then free old. */ /* and too bad if there are unread traces */ newtracelog = jehanne_mallocz(sizeof(*newtracelog)*size, 1); /* does malloc throw waserror? I don't know */ if (newtracelog){ jehanne_free(tracelog); tracelog = newtracelog; logsize = size; logmask = size - 1; pr = pw = 0; } else { error("devtrace: can't allocate that much"); } } else if (!jehanne_strcmp(tok[0], "testtracein")) { /* Manually jump to a certain bit of traced code */ uintptr_t pc, a1, a2, a3, a4; int x; if (ntok != 6) error("devtrace: usage: testtracein "); pc = (uintptr_t)jehanne_strtoul(tok[1], &ep, 16); if (pc < KTZERO) pc |= KTZERO; a1 = (uintptr_t)jehanne_strtoul(tok[2], &ep, 16); a2 = (uintptr_t)jehanne_strtoul(tok[3], &ep, 16); a3 = (uintptr_t)jehanne_strtoul(tok[4], &ep, 16); a4 = (uintptr_t)jehanne_strtoul(tok[5], &ep, 16); if (traced((void *)pc, 0)) { x = splhi(); watching = 1; tracein((void *)pc, a1, a2, a3, a4); watching = 0; splx(x); } } else if (!jehanne_strcmp(tok[0], "watch")) { /* Watch a certain PID */ int pid; if (ntok != 2) { error("devtrace: usage: watch [0|]"); } pid = jehanne_atoi(tok[1]); if (pid == 0) { pidwatch = jehanne_mallocz(sizeof(int)*PIDWATCHSIZE, 1); numpids = 0; } else if (pid < 0) { error("PID must be greater than zero."); } else if (numpids < PIDWATCHSIZE) { pidwatch[numpids] = pid; ainc(&numpids); } else { error("pidwatch array full!"); } } else if (!jehanne_strcmp(tok[0], "start")) { if (ntok != 1) error("devtrace: usage: start"); saveactive = 1; } else if (!jehanne_strcmp(tok[0], "stop")) { if (ntok != 1) error("devtrace: usage: stop"); saveactive = 0; all = 0; } else if (!jehanne_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)"); } jehanne_free(s); break; } poperror(); qunlock(&traceslock); traceactive = saveactive; return n; } Dev tracedevtab = { 'T', "trace", devreset, devinit, devshutdown, traceattach, tracewalk, tracestat, traceopen, devcreate, traceclose, traceread, devbread, tracewrite, devbwrite, devremove, devwstat, };