|  | /* | 
|  | * Dwarf pc to source line conversion. | 
|  | * | 
|  | * Maybe should do the reverse here, but what should the interface look like? | 
|  | * One possibility is to use the Plan 9 line2addr interface: | 
|  | * | 
|  | *	long line2addr(ulong line, ulong basepc) | 
|  | * | 
|  | * which returns the smallest pc > basepc with line number line (ignoring file name). | 
|  | * | 
|  | * The encoding may be small, but it sure isn't simple! | 
|  | */ | 
|  |  | 
|  | #include <u.h> | 
|  | #include <libc.h> | 
|  | #include <bio.h> | 
|  | #include "elf.h" | 
|  | #include "dwarf.h" | 
|  |  | 
|  | #define trace 0 | 
|  |  | 
|  | enum | 
|  | { | 
|  | Isstmt = 1<<0, | 
|  | BasicDwarfBlock = 1<<1, | 
|  | EndSequence = 1<<2, | 
|  | PrologueEnd = 1<<3, | 
|  | EpilogueBegin = 1<<4 | 
|  | }; | 
|  |  | 
|  | typedef struct State State; | 
|  | struct State | 
|  | { | 
|  | ulong addr; | 
|  | ulong file; | 
|  | ulong line; | 
|  | ulong column; | 
|  | ulong flags; | 
|  | ulong isa; | 
|  | }; | 
|  |  | 
|  | int | 
|  | dwarfpctoline(Dwarf *d, ulong pc, char **cdir, char **dir, char **file, ulong *line, ulong *mtime, ulong *length) | 
|  | { | 
|  | uchar *prog, *opcount, *end; | 
|  | ulong off, unit, len, vers, x, start; | 
|  | int i, first, op, a, l, quantum, isstmt, linebase, linerange, opcodebase, nf; | 
|  | char *files, *dirs, *s; | 
|  | DwarfBuf b; | 
|  | DwarfSym sym; | 
|  | State emit, cur, reset; | 
|  | uchar **f, **newf; | 
|  |  | 
|  | f = nil; | 
|  |  | 
|  | if(dwarfaddrtounit(d, pc, &unit) < 0 | 
|  | || dwarflookuptag(d, unit, TagCompileUnit, &sym) < 0) | 
|  | return -1; | 
|  |  | 
|  | if(!sym.attrs.have.stmtlist){ | 
|  | werrstr("no line mapping information for 0x%lux", pc); | 
|  | return -1; | 
|  | } | 
|  | off = sym.attrs.stmtlist; | 
|  | if(off >= d->line.len){ | 
|  | fprint(2, "bad stmtlist\n"); | 
|  | goto bad; | 
|  | } | 
|  |  | 
|  | if(trace) fprint(2, "unit 0x%lux stmtlist 0x%lux\n", unit, sym.attrs.stmtlist); | 
|  |  | 
|  | memset(&b, 0, sizeof b); | 
|  | b.d = d; | 
|  | b.p = d->line.data + off; | 
|  | b.ep = b.p + d->line.len; | 
|  | b.addrsize = sym.b.addrsize;	/* should i get this from somewhere else? */ | 
|  |  | 
|  | len = dwarfget4(&b); | 
|  | if(b.p==nil || b.p+len > b.ep || b.p+len < b.p){ | 
|  | fprint(2, "bad len\n"); | 
|  | goto bad; | 
|  | } | 
|  |  | 
|  | b.ep = b.p+len; | 
|  | vers = dwarfget2(&b); | 
|  | if(vers != 2){ | 
|  | werrstr("bad dwarf version 0x%lux", vers); | 
|  | return -1; | 
|  | } | 
|  |  | 
|  | len = dwarfget4(&b); | 
|  | if(b.p==nil || b.p+len > b.ep || b.p+len < b.p){ | 
|  | fprint(2, "another bad len\n"); | 
|  | goto bad; | 
|  | } | 
|  | prog = b.p+len; | 
|  |  | 
|  | quantum = dwarfget1(&b); | 
|  | isstmt = dwarfget1(&b); | 
|  | linebase = (schar)dwarfget1(&b); | 
|  | linerange = (schar)dwarfget1(&b); | 
|  | opcodebase = dwarfget1(&b); | 
|  |  | 
|  | opcount = b.p-1; | 
|  | dwarfgetnref(&b, opcodebase-1); | 
|  | if(b.p == nil){ | 
|  | fprint(2, "bad opcode chart\n"); | 
|  | goto bad; | 
|  | } | 
|  |  | 
|  | /* just skip the files and dirs for now; we'll come back */ | 
|  | dirs = (char*)b.p; | 
|  | while(b.p!=nil && *b.p!=0) | 
|  | dwarfgetstring(&b); | 
|  | dwarfget1(&b); | 
|  |  | 
|  | files = (char*)b.p; | 
|  | while(b.p!=nil && *b.p!=0){ | 
|  | dwarfgetstring(&b); | 
|  | dwarfget128(&b); | 
|  | dwarfget128(&b); | 
|  | dwarfget128(&b); | 
|  | } | 
|  | dwarfget1(&b); | 
|  |  | 
|  | /* move on to the program */ | 
|  | if(b.p == nil || b.p > prog){ | 
|  | fprint(2, "bad header\n"); | 
|  | goto bad; | 
|  | } | 
|  | b.p = prog; | 
|  |  | 
|  | reset.addr = 0; | 
|  | reset.file = 1; | 
|  | reset.line = 1; | 
|  | reset.column = 0; | 
|  | reset.flags = isstmt ? Isstmt : 0; | 
|  | reset.isa = 0; | 
|  |  | 
|  | cur = reset; | 
|  | emit = reset; | 
|  | nf = 0; | 
|  | start = 0; | 
|  | if(trace) fprint(2, "program @ %lud ... %.*H opbase = %d\n", b.p - d->line.data, b.ep-b.p, b.p, opcodebase); | 
|  | first = 1; | 
|  | while(b.p != nil){ | 
|  | op = dwarfget1(&b); | 
|  | if(trace) fprint(2, "\tline %lud, addr 0x%lux, op %d %.10H", cur.line, cur.addr, op, b.p); | 
|  | if(op >= opcodebase){ | 
|  | a = (op - opcodebase) / linerange; | 
|  | l = (op - opcodebase) % linerange + linebase; | 
|  | cur.line += l; | 
|  | cur.addr += a * quantum; | 
|  | if(trace) fprint(2, " +%d,%d\n", a, l); | 
|  | emit: | 
|  | if(first){ | 
|  | if(cur.addr > pc){ | 
|  | werrstr("found wrong line mapping 0x%lux for pc 0x%lux", cur.addr, pc); | 
|  | goto out; | 
|  | } | 
|  | first = 0; | 
|  | start = cur.addr; | 
|  | } | 
|  | if(cur.addr > pc) | 
|  | break; | 
|  | if(b.p == nil){ | 
|  | werrstr("buffer underflow in line mapping"); | 
|  | goto out; | 
|  | } | 
|  | emit = cur; | 
|  | if(emit.flags & EndSequence){ | 
|  | werrstr("found wrong line mapping 0x%lux-0x%lux for pc 0x%lux", start, cur.addr, pc); | 
|  | goto out; | 
|  | } | 
|  | cur.flags &= ~(BasicDwarfBlock|PrologueEnd|EpilogueBegin); | 
|  | }else{ | 
|  | switch(op){ | 
|  | case 0:	/* extended op code */ | 
|  | if(trace) fprint(2, " ext"); | 
|  | len = dwarfget128(&b); | 
|  | end = b.p+len; | 
|  | if(b.p == nil || end > b.ep || end < b.p || len < 1) | 
|  | goto bad; | 
|  | switch(dwarfget1(&b)){ | 
|  | case 1:	/* end sequence */ | 
|  | if(trace) fprint(2, " end\n"); | 
|  | cur.flags |= EndSequence; | 
|  | goto emit; | 
|  | case 2:	/* set address */ | 
|  | cur.addr = dwarfgetaddr(&b); | 
|  | if(trace) fprint(2, " set pc 0x%lux\n", cur.addr); | 
|  | break; | 
|  | case 3:	/* define file */ | 
|  | newf = realloc(f, (nf+1)*sizeof(f[0])); | 
|  | if(newf == nil) | 
|  | goto out; | 
|  | f = newf; | 
|  | f[nf++] = b.p; | 
|  | s = dwarfgetstring(&b); | 
|  | dwarfget128(&b); | 
|  | dwarfget128(&b); | 
|  | dwarfget128(&b); | 
|  | if(trace) fprint(2, " def file %s\n", s); | 
|  | break; | 
|  | } | 
|  | if(b.p == nil || b.p > end) | 
|  | goto bad; | 
|  | b.p = end; | 
|  | break; | 
|  | case 1:	/* emit */ | 
|  | if(trace) fprint(2, " emit\n"); | 
|  | goto emit; | 
|  | case 2:	/* advance pc */ | 
|  | a = dwarfget128(&b); | 
|  | if(trace) fprint(2, " advance pc + %lud\n", a*quantum); | 
|  | cur.addr += a * quantum; | 
|  | break; | 
|  | case 3:	/* advance line */ | 
|  | l = dwarfget128s(&b); | 
|  | if(trace) fprint(2, " advance line + %ld\n", l); | 
|  | cur.line += l; | 
|  | break; | 
|  | case 4:	/* set file */ | 
|  | if(trace) fprint(2, " set file\n"); | 
|  | cur.file = dwarfget128s(&b); | 
|  | break; | 
|  | case 5:	/* set column */ | 
|  | if(trace) fprint(2, " set column\n"); | 
|  | cur.column = dwarfget128(&b); | 
|  | break; | 
|  | case 6:	/* negate stmt */ | 
|  | if(trace) fprint(2, " negate stmt\n"); | 
|  | cur.flags ^= Isstmt; | 
|  | break; | 
|  | case 7:	/* set basic block */ | 
|  | if(trace) fprint(2, " set basic block\n"); | 
|  | cur.flags |= BasicDwarfBlock; | 
|  | break; | 
|  | case 8:	/* const add pc */ | 
|  | a = (255 - opcodebase) / linerange * quantum; | 
|  | if(trace) fprint(2, " const add pc + %d\n", a); | 
|  | cur.addr += a; | 
|  | break; | 
|  | case 9:	/* fixed advance pc */ | 
|  | a = dwarfget2(&b); | 
|  | if(trace) fprint(2, " fixed advance pc + %d\n", a); | 
|  | cur.addr += a; | 
|  | break; | 
|  | case 10:	/* set prologue end */ | 
|  | if(trace) fprint(2, " set prologue end\n"); | 
|  | cur.flags |= PrologueEnd; | 
|  | break; | 
|  | case 11:	/* set epilogue begin */ | 
|  | if(trace) fprint(2, " set epilogue begin\n"); | 
|  | cur.flags |= EpilogueBegin; | 
|  | break; | 
|  | case 12:	/* set isa */ | 
|  | if(trace) fprint(2, " set isa\n"); | 
|  | cur.isa = dwarfget128(&b); | 
|  | break; | 
|  | default:	/* something new - skip it */ | 
|  | if(trace) fprint(2, " unknown %d\n", opcount[op]); | 
|  | for(i=0; i<opcount[op]; i++) | 
|  | dwarfget128(&b); | 
|  | break; | 
|  | } | 
|  | } | 
|  | } | 
|  | if(b.p == nil) | 
|  | goto bad; | 
|  |  | 
|  | /* finally!  the data we seek is in "emit" */ | 
|  |  | 
|  | if(emit.file == 0){ | 
|  | werrstr("invalid file index in mapping data"); | 
|  | goto out; | 
|  | } | 
|  | if(line) | 
|  | *line = emit.line; | 
|  |  | 
|  | /* skip over first emit.file-2 guys */ | 
|  | b.p = (uchar*)files; | 
|  | for(i=emit.file-1; i > 0 && b.p!=nil && *b.p!=0; i--){ | 
|  | dwarfgetstring(&b); | 
|  | dwarfget128(&b); | 
|  | dwarfget128(&b); | 
|  | dwarfget128(&b); | 
|  | } | 
|  | if(b.p == nil){ | 
|  | werrstr("problem parsing file data second time (cannot happen)"); | 
|  | goto bad; | 
|  | } | 
|  | if(*b.p == 0){ | 
|  | if(i >= nf){ | 
|  | werrstr("bad file index in mapping data"); | 
|  | goto bad; | 
|  | } | 
|  | b.p = f[i]; | 
|  | } | 
|  | s = dwarfgetstring(&b); | 
|  | if(file) | 
|  | *file = s; | 
|  | i = dwarfget128(&b);		/* directory */ | 
|  | x = dwarfget128(&b); | 
|  | if(mtime) | 
|  | *mtime = x; | 
|  | x = dwarfget128(&b); | 
|  | if(length) | 
|  | *length = x; | 
|  |  | 
|  | /* fetch dir name */ | 
|  | if(cdir) | 
|  | *cdir = sym.attrs.compdir; | 
|  |  | 
|  | if(dir){ | 
|  | if(i == 0) | 
|  | *dir = nil; | 
|  | else{ | 
|  | b.p = (uchar*)dirs; | 
|  | for(i--; i>0 && b.p!=nil && *b.p!=0; i--) | 
|  | dwarfgetstring(&b); | 
|  | if(b.p==nil || *b.p==0){ | 
|  | werrstr("bad directory reference in line mapping"); | 
|  | goto out;		/* can only happen with bad dir index */ | 
|  | } | 
|  | *dir = dwarfgetstring(&b); | 
|  | } | 
|  | } | 
|  |  | 
|  | /* free at last, free at last */ | 
|  | free(f); | 
|  | return 0; | 
|  |  | 
|  | bad: | 
|  | werrstr("corrupted line mapping for 0x%lux", pc); | 
|  | out: | 
|  | free(f); | 
|  | return -1; | 
|  | } |