diff options
Diffstat (limited to 'src/pkg/runtime/symtab.c')
-rw-r--r-- | src/pkg/runtime/symtab.c | 466 |
1 files changed, 466 insertions, 0 deletions
diff --git a/src/pkg/runtime/symtab.c b/src/pkg/runtime/symtab.c new file mode 100644 index 000000000..d2ebf9b40 --- /dev/null +++ b/src/pkg/runtime/symtab.c @@ -0,0 +1,466 @@ +// 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. + +// Runtime symbol table access. Work in progress. +// The Plan 9 symbol table is not in a particularly convenient form. +// The routines here massage it into a more usable form; eventually +// we'll change 6l to do this for us, but it is easier to experiment +// here than to change 6l and all the other tools. +// +// The symbol table also needs to be better integrated with the type +// strings table in the future. This is just a quick way to get started +// and figure out exactly what we want. + +#include "runtime.h" +#include "defs.h" +#include "os.h" +#include "arch.h" + +extern byte pclntab[], epclntab[], symtab[], esymtab[]; + +typedef struct Sym Sym; +struct Sym +{ + uintptr value; + byte symtype; + byte *name; +// byte *gotype; +}; + +// Walk over symtab, calling fn(&s) for each symbol. +static void +walksymtab(void (*fn)(Sym*)) +{ + byte *p, *ep, *q; + Sym s; + + p = symtab; + ep = esymtab; + while(p < ep) { + if(p + 7 > ep) + break; + s.value = ((uint32)p[0]<<24) | ((uint32)p[1]<<16) | ((uint32)p[2]<<8) | ((uint32)p[3]); + + if(!(p[4]&0x80)) + break; + s.symtype = p[4] & ~0x80; + p += 5; + s.name = p; + if(s.symtype == 'z' || s.symtype == 'Z') { + // path reference string - skip first byte, + // then 2-byte pairs ending at two zeros. + q = p+1; + for(;;) { + if(q+2 > ep) + return; + if(q[0] == '\0' && q[1] == '\0') + break; + q += 2; + } + p = q+2; + }else{ + q = runtime·mchr(p, '\0', ep); + if(q == nil) + break; + p = q+1; + } + p += 4; // go type + fn(&s); + } +} + +// Symtab walker; accumulates info about functions. + +static Func *func; +static int32 nfunc; + +static byte **fname; +static int32 nfname; + +static uint32 funcinit; +static Lock funclock; + +static void +dofunc(Sym *sym) +{ + Func *f; + + switch(sym->symtype) { + case 't': + case 'T': + case 'l': + case 'L': + if(runtime·strcmp(sym->name, (byte*)"etext") == 0) + break; + if(func == nil) { + nfunc++; + break; + } + f = &func[nfunc++]; + f->name = runtime·gostringnocopy(sym->name); + f->entry = sym->value; + if(sym->symtype == 'L' || sym->symtype == 'l') + f->frame = -sizeof(uintptr); + break; + case 'm': + if(nfunc > 0 && func != nil) + func[nfunc-1].frame += sym->value; + break; + case 'p': + if(nfunc > 0 && func != nil) { + f = &func[nfunc-1]; + // args counts 32-bit words. + // sym->value is the arg's offset. + // don't know width of this arg, so assume it is 64 bits. + if(f->args < sym->value/4 + 2) + f->args = sym->value/4 + 2; + } + break; + case 'f': + if(fname == nil) { + if(sym->value >= nfname) { + if(sym->value >= 0x10000) { + runtime·printf("invalid symbol file index %p\n", sym->value); + runtime·throw("mangled symbol table"); + } + nfname = sym->value+1; + } + break; + } + fname[sym->value] = sym->name; + break; + } +} + +// put together the path name for a z entry. +// the f entries have been accumulated into fname already. +static void +makepath(byte *buf, int32 nbuf, byte *path) +{ + int32 n, len; + byte *p, *ep, *q; + + if(nbuf <= 0) + return; + + p = buf; + ep = buf + nbuf; + *p = '\0'; + for(;;) { + if(path[0] == 0 && path[1] == 0) + break; + n = (path[0]<<8) | path[1]; + path += 2; + if(n >= nfname) + break; + q = fname[n]; + len = runtime·findnull(q); + if(p+1+len >= ep) + break; + if(p > buf && p[-1] != '/') + *p++ = '/'; + runtime·memmove(p, q, len+1); + p += len; + } +} + +// walk symtab accumulating path names for use by pc/ln table. +// don't need the full generality of the z entry history stack because +// there are no includes in go (and only sensible includes in our c); +// assume code only appear in top-level files. +static void +dosrcline(Sym *sym) +{ + static byte srcbuf[1000]; + static struct { + String srcstring; + int32 aline; + int32 delta; + } files[200]; + static int32 incstart; + static int32 nfunc, nfile, nhist; + Func *f; + int32 i; + + switch(sym->symtype) { + case 't': + case 'T': + if(runtime·strcmp(sym->name, (byte*)"etext") == 0) + break; + f = &func[nfunc++]; + // find source file + for(i = 0; i < nfile - 1; i++) { + if (files[i+1].aline > f->ln0) + break; + } + f->src = files[i].srcstring; + f->ln0 -= files[i].delta; + break; + case 'z': + if(sym->value == 1) { + // entry for main source file for a new object. + makepath(srcbuf, sizeof srcbuf, sym->name+1); + nhist = 0; + nfile = 0; + if(nfile == nelem(files)) + return; + files[nfile].srcstring = runtime·gostring(srcbuf); + files[nfile].aline = 0; + files[nfile++].delta = 0; + } else { + // push or pop of included file. + makepath(srcbuf, sizeof srcbuf, sym->name+1); + if(srcbuf[0] != '\0') { + if(nhist++ == 0) + incstart = sym->value; + if(nhist == 0 && nfile < nelem(files)) { + // new top-level file + files[nfile].srcstring = runtime·gostring(srcbuf); + files[nfile].aline = sym->value; + // this is "line 0" + files[nfile++].delta = sym->value - 1; + } + }else{ + if(--nhist == 0) + files[nfile-1].delta += sym->value - incstart; + } + } + } +} + +// Interpret pc/ln table, saving the subpiece for each func. +static void +splitpcln(void) +{ + int32 line; + uintptr pc; + byte *p, *ep; + Func *f, *ef; + int32 pcquant; + + if(pclntab == epclntab || nfunc == 0) + return; + + switch(thechar) { + case '5': + pcquant = 4; + break; + default: // 6, 8 + pcquant = 1; + break; + } + + // pc/ln table bounds + p = pclntab; + ep = epclntab; + + f = func; + ef = func + nfunc; + pc = func[0].entry; // text base + f->pcln.array = p; + f->pc0 = pc; + line = 0; + for(;;) { + while(p < ep && *p > 128) + pc += pcquant * (*p++ - 128); + // runtime·printf("pc<%p targetpc=%p line=%d\n", pc, targetpc, line); + if(*p == 0) { + if(p+5 > ep) + break; + // 4 byte add to line + line += (p[1]<<24) | (p[2]<<16) | (p[3]<<8) | p[4]; + p += 5; + } else if(*p <= 64) + line += *p++; + else + line -= *p++ - 64; + + // pc, line now match. + // Because the state machine begins at pc==entry and line==0, + // it can happen - just at the beginning! - that the update may + // have updated line but left pc alone, to tell us the true line + // number for pc==entry. In that case, update f->ln0. + // Having the correct initial line number is important for choosing + // the correct file in dosrcline above. + if(f == func && pc == f->pc0) { + f->pcln.array = p; + f->pc0 = pc + pcquant; + f->ln0 = line; + } + + if(f < ef && pc >= (f+1)->entry) { + f->pcln.len = p - f->pcln.array; + f->pcln.cap = f->pcln.len; + do + f++; + while(f < ef && pc >= (f+1)->entry); + f->pcln.array = p; + // pc0 and ln0 are the starting values for + // the loop over f->pcln, so pc must be + // adjusted by the same pcquant update + // that we're going to do as we continue our loop. + f->pc0 = pc + pcquant; + f->ln0 = line; + } + + pc += pcquant; + } + if(f < ef) { + f->pcln.len = p - f->pcln.array; + f->pcln.cap = f->pcln.len; + } +} + + +// Return actual file line number for targetpc in func f. +// (Source file is f->src.) +// NOTE(rsc): If you edit this function, also edit extern.go:/FileLine +int32 +runtime·funcline(Func *f, uintptr targetpc) +{ + byte *p, *ep; + uintptr pc; + int32 line; + int32 pcquant; + + enum { + debug = 0 + }; + + switch(thechar) { + case '5': + pcquant = 4; + break; + default: // 6, 8 + pcquant = 1; + break; + } + + p = f->pcln.array; + ep = p + f->pcln.len; + pc = f->pc0; + line = f->ln0; + if(debug && !runtime·panicking) + runtime·printf("funcline start pc=%p targetpc=%p line=%d tab=%p+%d\n", + pc, targetpc, line, p, (int32)f->pcln.len); + for(;;) { + // Table is a sequence of updates. + + // Each update says first how to adjust the pc, + // in possibly multiple instructions... + while(p < ep && *p > 128) + pc += pcquant * (*p++ - 128); + + if(debug && !runtime·panicking) + runtime·printf("pc<%p targetpc=%p line=%d\n", pc, targetpc, line); + + // If the pc has advanced too far or we're out of data, + // stop and the last known line number. + if(pc > targetpc || p >= ep) + break; + + // ... and then how to adjust the line number, + // in a single instruction. + if(*p == 0) { + if(p+5 > ep) + break; + line += (p[1]<<24) | (p[2]<<16) | (p[3]<<8) | p[4]; + p += 5; + } else if(*p <= 64) + line += *p++; + else + line -= *p++ - 64; + // Now pc, line pair is consistent. + if(debug && !runtime·panicking) + runtime·printf("pc=%p targetpc=%p line=%d\n", pc, targetpc, line); + + // PC increments implicitly on each iteration. + pc += pcquant; + } + return line; +} + +static void +buildfuncs(void) +{ + extern byte etext[]; + + if(func != nil) + return; + + // Memory profiling uses this code; + // can deadlock if the profiler ends + // up back here. + m->nomemprof++; + + // count funcs, fnames + nfunc = 0; + nfname = 0; + walksymtab(dofunc); + + // initialize tables + func = runtime·mal((nfunc+1)*sizeof func[0]); + func[nfunc].entry = (uint64)etext; + fname = runtime·mal(nfname*sizeof fname[0]); + nfunc = 0; + walksymtab(dofunc); + + // split pc/ln table by func + splitpcln(); + + // record src file and line info for each func + walksymtab(dosrcline); + + m->nomemprof--; +} + +Func* +runtime·findfunc(uintptr addr) +{ + Func *f; + int32 nf, n; + + // Use atomic double-checked locking, + // because when called from pprof signal + // handler, findfunc must run without + // grabbing any locks. + // (Before enabling the signal handler, + // SetCPUProfileRate calls findfunc to trigger + // the initialization outside the handler.) + if(runtime·atomicload(&funcinit) == 0) { + runtime·lock(&funclock); + if(funcinit == 0) { + buildfuncs(); + runtime·atomicstore(&funcinit, 1); + } + runtime·unlock(&funclock); + } + + if(nfunc == 0) + return nil; + if(addr < func[0].entry || addr >= func[nfunc].entry) + return nil; + + // binary search to find func with entry <= addr. + f = func; + nf = nfunc; + while(nf > 0) { + n = nf/2; + if(f[n].entry <= addr && addr < f[n+1].entry) + return &f[n]; + else if(addr < f[n].entry) + nf = n; + else { + f += n+1; + nf -= n+1; + } + } + + // can't get here -- we already checked above + // that the address was in the table bounds. + // this can only happen if the table isn't sorted + // by address or if the binary search above is buggy. + runtime·prints("findfunc unreachable\n"); + return nil; +} |