diff options
Diffstat (limited to 'src/cmd/cov/main.c')
-rw-r--r-- | src/cmd/cov/main.c | 484 |
1 files changed, 484 insertions, 0 deletions
diff --git a/src/cmd/cov/main.c b/src/cmd/cov/main.c new file mode 100644 index 000000000..33ef49e17 --- /dev/null +++ b/src/cmd/cov/main.c @@ -0,0 +1,484 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +/* + * code coverage + */ + +#include <u.h> +#include <libc.h> +#include <bio.h> +#include "tree.h" + +#include <ureg_amd64.h> +#include <mach.h> +typedef struct Ureg Ureg; + +void +usage(void) +{ + fprint(2, "usage: cov [-lsv] [-g substring] [-m minlines] [6.out args...]\n"); + fprint(2, "-g specifies pattern of interesting functions or files\n"); + exits("usage"); +} + +typedef struct Range Range; +struct Range +{ + uvlong pc; + uvlong epc; +}; + +int chatty; +int fd; +int longnames; +int pid; +int doshowsrc; +Map *mem; +Map *text; +Fhdr fhdr; +char *substring; +char cwd[1000]; +int ncwd; +int minlines = -1000; + +Tree breakpoints; // code ranges not run + +/* + * comparison for Range structures + * they are "equal" if they overlap, so + * that a search for [pc, pc+1) finds the + * Range containing pc. + */ +int +rangecmp(void *va, void *vb) +{ + Range *a = va, *b = vb; + if(a->epc <= b->pc) + return 1; + if(b->epc <= a->pc) + return -1; + return 0; +} + +/* + * remember that we ran the section of code [pc, epc). + */ +void +ran(uvlong pc, uvlong epc) +{ + Range key; + Range *r; + uvlong oldepc; + + if(chatty) + print("run %#llux-%#llux\n", pc, epc); + + key.pc = pc; + key.epc = pc+1; + r = treeget(&breakpoints, &key); + if(r == nil) + sysfatal("unchecked breakpoint at %#llux+%d", pc, (int)(epc-pc)); + + // Might be that the tail of the sequence + // was run already, so r->epc is before the end. + // Adjust len. + if(epc > r->epc) + epc = r->epc; + + if(r->pc == pc) { + r->pc = epc; + } else { + // Chop r to before pc; + // add new entry for after if needed. + // Changing r->epc does not affect r's position in the tree. + oldepc = r->epc; + r->epc = pc; + if(epc < oldepc) { + Range *n; + n = malloc(sizeof *n); + if(n == nil) + sysfatal("out of memory"); + n->pc = epc; + n->epc = oldepc; + treeput(&breakpoints, n, n); + } + } +} + +void +showsrc(char *file, int line1, int line2) +{ + Biobuf *b; + char *p; + int n, stop; + + if((b = Bopen(file, OREAD)) == nil) { + print("\topen %s: %r\n", file); + return; + } + + for(n=1; n<line1 && (p = Brdstr(b, '\n', 1)) != nil; n++) + free(p); + + // print up to five lines (this one and 4 more). + // if there are more than five lines, print 4 and "..." + stop = n+4; + if(stop > line2) + stop = line2; + if(stop < line2) + stop--; + for(; n<=stop && (p = Brdstr(b, '\n', 1)) != nil; n++) { + print(" %d %s\n", n, p); + free(p); + } + if(n < line2) + print(" ...\n"); + Bterm(b); +} + +/* + * if s is in the current directory or below, + * return the relative path. + */ +char* +shortname(char *s) +{ + if(!longnames && strlen(s) > ncwd && memcmp(s, cwd, ncwd) == 0 && s[ncwd] == '/') + return s+ncwd+1; + return s; +} + +/* + * we've decided that [pc, epc) did not run. + * do something about it. + */ +void +missing(uvlong pc, uvlong epc) +{ + char file[1000]; + int line1, line2; + char buf[100]; + Symbol s; + char *p; + uvlong uv; + + if(!findsym(pc, CTEXT, &s) || !fileline(file, sizeof file, pc)) { + notfound: + print("%#llux-%#llux\n", pc, epc); + return; + } + p = strrchr(file, ':'); + *p++ = 0; + line1 = atoi(p); + for(uv=pc; uv<epc; ) { + if(!fileline(file, sizeof file, epc-2)) + goto notfound; + uv += machdata->instsize(text, uv); + } + p = strrchr(file, ':'); + *p++ = 0; + line2 = atoi(p); + + if(line2+1-line2 < minlines) + return; + + if(pc == s.value) { + // never entered function + print("%s:%d %s never called (%#llux-%#llux)\n", shortname(file), line1, s.name, pc, epc); + return; + } + if(pc <= s.value+13) { + // probably stub for stack growth. + // check whether last instruction is call to morestack. + // the -5 below is the length of + // CALL sys.morestack. + buf[0] = 0; + machdata->das(text, epc-5, 0, buf, sizeof buf); + if(strstr(buf, "morestack")) + return; + } + + if(epc - pc == 5) { + // check for CALL sys.panicindex + buf[0] = 0; + machdata->das(text, pc, 0, buf, sizeof buf); + if(strstr(buf, "panicindex")) + return; + } + + if(epc - pc == 2 || epc -pc == 3) { + // check for XORL inside shift. + // (on x86 have to implement large left or unsigned right shift with explicit zeroing). + // f+90 0x00002c9f CMPL CX,$20 + // f+93 0x00002ca2 JCS f+97(SB) + // f+95 0x00002ca4 XORL AX,AX <<< + // f+97 0x00002ca6 SHLL CL,AX + // f+99 0x00002ca8 MOVL $1,CX + // + // f+c8 0x00002cd7 CMPL CX,$40 + // f+cb 0x00002cda JCS f+d0(SB) + // f+cd 0x00002cdc XORQ AX,AX <<< + // f+d0 0x00002cdf SHLQ CL,AX + // f+d3 0x00002ce2 MOVQ $1,CX + buf[0] = 0; + machdata->das(text, pc, 0, buf, sizeof buf); + if(strncmp(buf, "XOR", 3) == 0) { + machdata->das(text, epc, 0, buf, sizeof buf); + if(strncmp(buf, "SHL", 3) == 0 || strncmp(buf, "SHR", 3) == 0) + return; + } + } + + if(epc - pc == 3) { + // check for SAR inside shift. + // (on x86 have to implement large signed right shift as >>31). + // f+36 0x00016216 CMPL CX,$20 + // f+39 0x00016219 JCS f+3e(SB) + // f+3b 0x0001621b SARL $1f,AX <<< + // f+3e 0x0001621e SARL CL,AX + // f+40 0x00016220 XORL CX,CX + // f+42 0x00016222 CMPL CX,AX + buf[0] = 0; + machdata->das(text, pc, 0, buf, sizeof buf); + if(strncmp(buf, "SAR", 3) == 0) { + machdata->das(text, epc, 0, buf, sizeof buf); + if(strncmp(buf, "SAR", 3) == 0) + return; + } + } + + // show first instruction to make clear where we were. + machdata->das(text, pc, 0, buf, sizeof buf); + + if(line1 != line2) + print("%s:%d,%d %#llux-%#llux %s\n", + shortname(file), line1, line2, pc, epc, buf); + else + print("%s:%d %#llux-%#llux %s\n", + shortname(file), line1, pc, epc, buf); + if(doshowsrc) + showsrc(file, line1, line2); +} + +/* + * walk the tree, calling missing for each non-empty + * section of missing code. + */ +void +walktree(TreeNode *t) +{ + Range *n; + + if(t == nil) + return; + walktree(t->left); + n = t->key; + if(n->pc < n->epc) + missing(n->pc, n->epc); + walktree(t->right); +} + +/* + * set a breakpoint all over [pc, epc) + * and remember that we did. + */ +void +breakpoint(uvlong pc, uvlong epc) +{ + Range *r; + + r = malloc(sizeof *r); + if(r == nil) + sysfatal("out of memory"); + r->pc = pc; + r->epc = epc; + treeput(&breakpoints, r, r); + + for(; pc < epc; pc+=machdata->bpsize) + put1(mem, pc, machdata->bpinst, machdata->bpsize); +} + +/* + * install breakpoints over all text symbols + * that match the pattern. + */ +void +cover(void) +{ + Symbol s; + char *lastfn; + uvlong lastpc; + int i; + char buf[200]; + + lastfn = nil; + lastpc = 0; + for(i=0; textsym(&s, i); i++) { + switch(s.type) { + case 'T': + case 't': + if(lastpc != 0) { + breakpoint(lastpc, s.value); + lastpc = 0; + } + // Ignore second entry for a given name; + // that's the debugging blob. + if(lastfn && strcmp(s.name, lastfn) == 0) + break; + lastfn = s.name; + buf[0] = 0; + fileline(buf, sizeof buf, s.value); + if(substring == nil || strstr(buf, substring) || strstr(s.name, substring)) + lastpc = s.value; + } + } +} + +uvlong +rgetzero(Map *map, char *reg) +{ + USED(map); + USED(reg); + + return 0; +} + +/* + * remove the breakpoints at pc and successive instructions, + * up to and including the first jump or other control flow transfer. + */ +void +uncover(uvlong pc) +{ + uchar buf[1000]; + int n, n1, n2; + uvlong foll[2]; + + // Double-check that we stopped at a breakpoint. + if(get1(mem, pc, buf, machdata->bpsize) < 0) + sysfatal("read mem inst at %#llux: %r", pc); + if(memcmp(buf, machdata->bpinst, machdata->bpsize) != 0) + sysfatal("stopped at %#llux; not at breakpoint %d", pc, machdata->bpsize); + + // Figure out how many bytes of straight-line code + // there are in the text starting at pc. + n = 0; + while(n < sizeof buf) { + n1 = machdata->instsize(text, pc+n); + if(n+n1 > sizeof buf) + break; + n2 = machdata->foll(text, pc+n, rgetzero, foll); + n += n1; + if(n2 != 1 || foll[0] != pc+n) + break; + } + + // Record that this section of code ran. + ran(pc, pc+n); + + // Put original instructions back. + if(get1(text, pc, buf, n) < 0) + sysfatal("get1: %r"); + if(put1(mem, pc, buf, n) < 0) + sysfatal("put1: %r"); +} + +int +startprocess(char **argv) +{ + int pid; + + if((pid = fork()) < 0) + sysfatal("fork: %r"); + if(pid == 0) { + pid = getpid(); + if(ctlproc(pid, "hang") < 0) + sysfatal("ctlproc hang: %r"); + exec(argv[0], argv); + sysfatal("exec %s: %r", argv[0]); + } + if(ctlproc(pid, "attached") < 0 || ctlproc(pid, "waitstop") < 0) + sysfatal("attach %d %s: %r", pid, argv[0]); + return pid; +} + +int +go(void) +{ + uvlong pc; + char buf[100]; + int n; + + for(n = 0;; n++) { + ctlproc(pid, "startstop"); + if(get8(mem, offsetof(Ureg, ip), &pc) < 0) { + rerrstr(buf, sizeof buf); + if(strstr(buf, "exited") || strstr(buf, "No such process")) + return n; + sysfatal("cannot read pc: %r"); + } + pc--; + if(put8(mem, offsetof(Ureg, ip), pc) < 0) + sysfatal("cannot write pc: %r"); + uncover(pc); + } +} + +void +main(int argc, char **argv) +{ + int n; + + ARGBEGIN{ + case 'g': + substring = EARGF(usage()); + break; + case 'l': + longnames++; + break; + case 'n': + minlines = atoi(EARGF(usage())); + break; + case 's': + doshowsrc = 1; + break; + case 'v': + chatty++; + break; + default: + usage(); + }ARGEND + + getwd(cwd, sizeof cwd); + ncwd = strlen(cwd); + + if(argc == 0) { + *--argv = "6.out"; + } + fd = open(argv[0], OREAD); + if(fd < 0) + sysfatal("open %s: %r", argv[0]); + if(crackhdr(fd, &fhdr) <= 0) + sysfatal("crackhdr: %r"); + machbytype(fhdr.type); + if(syminit(fd, &fhdr) <= 0) + sysfatal("syminit: %r"); + text = loadmap(nil, fd, &fhdr); + if(text == nil) + sysfatal("loadmap: %r"); + pid = startprocess(argv); + mem = attachproc(pid, &fhdr); + if(mem == nil) + sysfatal("attachproc: %r"); + breakpoints.cmp = rangecmp; + cover(); + n = go(); + walktree(breakpoints.root); + if(chatty) + print("%d breakpoints\n", n); + detachproc(mem); + exits(0); +} + |