| // 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 <time.h> | |
| #include <libc.h> | |
| #include <bio.h> | |
| #include <ctype.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 %#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<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); | |
| 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); | |
| } | |