blob: 5a35f635b792fd7abc83d98e5a88bfe1422c5cd1 [file] [log] [blame]
// 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 int32 symdat[];
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*))
{
int32 *v;
byte *p, *ep, *q;
Sym s;
if(symdat == nil)
return;
#ifdef __WINDOWS__
v = get_symdat_addr();
p = (byte*)v+8;
#else
v = symdat;
p = (byte*)(symdat+2);
#endif
ep = p + v[0];
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 = 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 Lock funclock;
static void
dofunc(Sym *sym)
{
Func *f;
switch(sym->symtype) {
case 't':
case 'T':
case 'l':
case 'L':
if(strcmp(sym->name, (byte*)"etext") == 0)
break;
if(func == nil) {
nfunc++;
break;
}
f = &func[nfunc++];
f->name = 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)
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 = findnull(q);
if(p+1+len >= ep)
break;
if(p > buf && p[-1] != '/')
*p++ = '/';
mcpy(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(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 = 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 = 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 *v;
int32 pcquant;
switch(thechar) {
case '5':
pcquant = 4;
break;
default: // 6, 8
pcquant = 1;
break;
}
if(symdat == nil)
return;
// pc/ln table bounds
#ifdef __WINDOWS__
v = get_symdat_addr();
p = (byte*)v+8;
#else
v = symdat;
p = (byte*)(symdat+2);
#endif
p += v[0];
ep = p+v[1];
f = func;
ef = func + nfunc;
pc = func[0].entry; // text base
f->pcln.array = p;
f->pc0 = pc - pcquant;
line = 0;
for(; p < ep; p++) {
if(f < ef && pc > (f+1)->entry) {
f->pcln.len = p - f->pcln.array;
f->pcln.cap = f->pcln.len;
f++;
f->pcln.array = p;
f->pc0 = pc;
f->ln0 = line;
}
if(*p == 0) {
// 4 byte add to line
line += (p[1]<<24) | (p[2]<<16) | (p[3]<<8) | p[4];
p += 4;
} else if(*p <= 64) {
line += *p;
} else if(*p <= 128) {
line -= *p - 64;
} else {
pc += pcquant*(*p - 129);
}
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
funcline(Func *f, uint64 targetpc)
{
byte *p, *ep;
uintptr pc;
int32 line;
int32 pcquant;
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;
for(; p < ep && pc <= targetpc; p++) {
if(*p == 0) {
line += (p[1]<<24) | (p[2]<<16) | (p[3]<<8) | p[4];
p += 4;
} else if(*p <= 64) {
line += *p;
} else if(*p <= 128) {
line -= *p - 64;
} else {
pc += pcquant*(*p - 129);
}
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 = mal((nfunc+1)*sizeof func[0]);
func[nfunc].entry = (uint64)etext;
fname = 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*
findfunc(uintptr addr)
{
Func *f;
int32 nf, n;
lock(&funclock);
if(func == nil)
buildfuncs();
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.
prints("findfunc unreachable\n");
return nil;
}