// 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 #include #include #include #include #include "tree.h" #include #include 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 %#lux+%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); 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 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; uvinstsize(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.throwindex buf[0] = 0; machdata->das(text, pc, 0, buf, sizeof buf); if(strstr(buf, "throwindex")) 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); 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) { 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"); execv(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"; argc++; } 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); }