1
0
mirror of https://github.com/golang/go synced 2024-11-22 07:14:40 -07:00

cmd/ld, runtime: new in-memory symbol table format

Design at http://golang.org/s/go12symtab.

This enables some cleanup of the garbage collector metadata
that will be done in future CLs.

This CL does not move the old symtab and pclntab back into
an unmapped section of the file. That's a bit tricky and will be
done separately.

Fixes #4020.

R=golang-dev, dave, cshapiro, iant, r
CC=golang-dev, nigeltao
https://golang.org/cl/11085043
This commit is contained in:
Russ Cox 2013-07-16 09:41:38 -04:00
parent 63e0ddc7bf
commit 5d363c6357
27 changed files with 969 additions and 668 deletions

View File

@ -197,8 +197,12 @@ enum as
AMULAWB,
AUSEFIELD,
ALOCALS,
ATYPE,
AFUNCDATA,
APCDATA,
// TODO: Remove these.
ALOCALS,
ANPTRS,
APTRS,

View File

@ -172,6 +172,7 @@ struct Sym
char* dynimplib;
char* dynimpvers;
struct Section* sect;
struct Hist* hist;
// STEXT
Auto* autom;
@ -282,7 +283,7 @@ enum
MINSIZ = 64,
NENT = 100,
MAXIO = 8192,
MAXHIST = 20, /* limit of path elements for history symbols */
MAXHIST = 40, /* limit of path elements for history symbols */
MINLC = 4,
};
@ -319,7 +320,6 @@ EXTERN char* rpath;
EXTERN uint32 stroffset;
EXTERN int32 symsize;
EXTERN Sym* textp;
EXTERN int version;
EXTERN char xcmp[C_GOK+1][C_GOK+1];
EXTERN Prog zprg;
EXTERN int dtype;

View File

@ -278,6 +278,7 @@ main(int argc, char *argv[])
span();
addexport();
// textaddress() functionality is handled in span()
functab();
pclntab();
symtab();
dodata();
@ -549,6 +550,7 @@ loop:
addhist(p->line, D_FILE); /* 'z' */
if(p->to.offset)
addhist(p->to.offset, D_FILE1); /* 'Z' */
savehist(p->line, p->to.offset);
histfrogp = 0;
goto loop;
@ -699,6 +701,7 @@ loop:
p->to.offset = autosize;
autosize += 4;
s->type = STEXT;
s->hist = gethist();
s->text = p;
s->value = pc;
s->args = p->to.offset2;

View File

@ -759,15 +759,18 @@ enum as
AAESKEYGENASSIST,
APSHUFD,
APCLMULQDQ,
AUSEFIELD,
ALOCALS,
ATYPE,
AFUNCDATA,
APCDATA,
// TODO: Remove these.
ALOCALS,
ANPTRS,
APTRS,
APCLMULQDQ,
ALAST
};

View File

@ -177,6 +177,7 @@ struct Sym
char* dynimplib;
char* dynimpvers;
struct Section* sect;
struct Hist* hist; // for ATEXT
// STEXT
Auto* autom;
@ -213,7 +214,7 @@ enum
STRINGSZ = 200,
MINLC = 1,
MAXIO = 8192,
MAXHIST = 20, /* limit of path elements for history symbols */
MAXHIST = 40, /* limit of path elements for history symbols */
Yxxx = 0,
Ynone,
@ -359,7 +360,6 @@ EXTERN int32 spsize;
EXTERN Sym* symlist;
EXTERN int32 symsize;
EXTERN int tlsoffset;
EXTERN int version;
EXTERN Prog zprg;
EXTERN int dtype;
EXTERN char* paramspace;

View File

@ -289,6 +289,7 @@ main(int argc, char *argv[])
dope();
addexport();
textaddress();
functab();
pclntab();
symtab();
dodata();
@ -542,6 +543,7 @@ loop:
addhist(p->line, D_FILE); /* 'z' */
if(p->to.offset)
addhist(p->to.offset, D_FILE1); /* 'Z' */
savehist(p->line, p->to.offset);
histfrogp = 0;
goto loop;
@ -690,6 +692,7 @@ loop:
s->gotype = fromgotype;
}
s->type = STEXT;
s->hist = gethist();
s->value = pc;
s->args = p->to.offset >> 32;
s->nptrs = -1;

View File

@ -1339,14 +1339,11 @@ Optab optab[] =
{ AAESKEYGENASSIST, yaes2, Pq, 0x3a,0xdf,(0) },
{ APSHUFD, yaes2, Pq, 0x70,(0) },
{ APCLMULQDQ, yxshuf, Pq, 0x3a,0x44,0 },
{ AUSEFIELD, ynop, Px, 0,0 },
{ ALOCALS },
{ ATYPE },
{ ANPTRS },
{ APTRS },
{ APCLMULQDQ, yxshuf, Pq, 0x3a,0x44,0 },
{ AFUNCDATA, ynop, Px, 0,0 },
{ APCDATA, ynop, Px, 0,0 },
{ AEND },
0

View File

@ -578,8 +578,12 @@ enum as
APSHUFB,
AUSEFIELD,
ALOCALS,
ATYPE,
AFUNCDATA,
APCDATA,
// TODO: Remove these.
ALOCALS,
ANPTRS,
APTRS,

View File

@ -159,6 +159,7 @@ struct Sym
char* dynimplib;
char* dynimpvers;
struct Section* sect;
struct Hist* hist; // for ATEXT
// STEXT
Auto* autom;
@ -187,7 +188,7 @@ enum
STRINGSZ = 200,
MINLC = 1,
MAXIO = 8192,
MAXHIST = 20, /* limit of path elements for history symbols */
MAXHIST = 40, /* limit of path elements for history symbols */
Yxxx = 0,
Ynone,
@ -313,7 +314,6 @@ EXTERN Sym* symlist;
EXTERN int32 symsize;
EXTERN Sym* textp;
EXTERN int32 textsize;
EXTERN int version;
EXTERN Prog zprg;
EXTERN int dtype;
EXTERN int tlsoffset;

View File

@ -316,6 +316,7 @@ main(int argc, char *argv[])
span();
addexport();
textaddress();
functab();
pclntab();
symtab();
dodata();
@ -552,6 +553,7 @@ loop:
addhist(p->line, D_FILE); /* 'z' */
if(p->to.offset)
addhist(p->to.offset, D_FILE1); /* 'Z' */
savehist(p->line, p->to.offset);
histfrogp = 0;
goto loop;
@ -695,6 +697,7 @@ loop:
diag("%s: redefinition: %s\n%P", pn, s->name, p);
}
s->type = STEXT;
s->hist = gethist();
s->value = pc;
s->args = p->to.offset2;
s->nptrs = -1;

View File

@ -1001,10 +1001,9 @@ Optab optab[] =
{ APSHUFB, ymshufb,Pq, 0x38, 0x00 },
{ AUSEFIELD, ynop, Px, 0,0 },
{ ALOCALS },
{ ATYPE },
{ ANPTRS },
{ APTRS },
{ AFUNCDATA, ynop, Px, 0,0 },
{ APCDATA, ynop, Px, 0,0 },
0
};

View File

@ -415,6 +415,8 @@ Zconv(Fmt *fp)
s = sp->s;
se = s + sp->len;
// NOTE: Keep in sync with ../ld/go.c:/^Zconv.
while(s < se) {
n = chartorune(&r, s);
s += n;

View File

@ -763,7 +763,7 @@ setuintxx(Sym *s, vlong off, uint64 v, vlong wid)
s->p[off+i] = cast[inuxi8[i]];
break;
}
return off;
return off+wid;
}
vlong
@ -800,28 +800,28 @@ adduint64(Sym *s, uint64 v)
return adduintxx(s, v, 8);
}
void
vlong
setuint8(Sym *s, vlong r, uint8 v)
{
setuintxx(s, r, v, 1);
return setuintxx(s, r, v, 1);
}
void
vlong
setuint16(Sym *s, vlong r, uint16 v)
{
setuintxx(s, r, v, 2);
return setuintxx(s, r, v, 2);
}
void
vlong
setuint32(Sym *s, vlong r, uint32 v)
{
setuintxx(s, r, v, 4);
return setuintxx(s, r, v, 4);
}
void
vlong
setuint64(Sym *s, vlong r, uint64 v)
{
setuintxx(s, r, v, 8);
return setuintxx(s, r, v, 8);
}
vlong
@ -842,7 +842,7 @@ addaddrplus(Sym *s, Sym *t, vlong add)
r->siz = PtrSize;
r->type = D_ADDR;
r->add = add;
return i;
return i + r->siz;
}
static vlong
@ -863,7 +863,7 @@ addaddrplus4(Sym *s, Sym *t, vlong add)
r->siz = 4;
r->type = D_ADDR;
r->add = add;
return i;
return i + r->siz;
}
vlong
@ -884,7 +884,7 @@ addpcrelplus(Sym *s, Sym *t, vlong add)
r->add = add;
r->type = D_PCREL;
r->siz = 4;
return i;
return i + r->siz;
}
vlong
@ -911,7 +911,7 @@ setaddrplus(Sym *s, vlong off, Sym *t, vlong add)
r->siz = PtrSize;
r->type = D_ADDR;
r->add = add;
return off;
return off + r->siz;
}
vlong
@ -937,7 +937,7 @@ addsize(Sym *s, Sym *t)
r->off = i;
r->siz = PtrSize;
r->type = D_SIZE;
return i;
return i + r->siz;
}
void

View File

@ -613,7 +613,7 @@ markflood(void)
}
static char*
morename[] =
markextra[] =
{
"runtime.morestack",
"runtime.morestackx",
@ -629,6 +629,12 @@ morename[] =
"runtime.morestack32",
"runtime.morestack40",
"runtime.morestack48",
// on arm, lock in the div/mod helpers too
"_div",
"_divu",
"_mod",
"_modu",
};
static int
@ -676,8 +682,8 @@ deadcode(void)
mark(lookup(INITENTRY, 0));
if(flag_shared)
mark(lookup(LIBINITENTRY, 0));
for(i=0; i<nelem(morename); i++)
mark(lookup(morename[i], 0));
for(i=0; i<nelem(markextra); i++)
mark(lookup(markextra[i], 0));
for(i=0; i<ndynexp; i++)
mark(dynexp[i]);
@ -794,6 +800,8 @@ Zconv(Fmt *fp)
return fmtstrcpy(fp, "<nil>");
se = s + strlen(s);
// NOTE: Keep in sync with ../gc/go.c:/^Zconv.
while(s < se) {
n = chartorune(&r, s);
s += n;
@ -822,6 +830,9 @@ Zconv(Fmt *fp)
fmtrune(fp, '\\');
fmtrune(fp, r);
break;
case 0xFEFF: // BOM, basically disallowed in source code
fmtstrcpy(fp, "\\uFEFF");
break;
}
}
return 0;

View File

@ -52,6 +52,14 @@ int nlibdir = 0;
static int maxlibdir = 0;
static int cout = -1;
// symbol version, incremented each time a file is loaded.
// version==1 is reserved for savehist.
enum
{
HistVersion = 1,
};
int version = HistVersion;
// Set if we see an object compiled by the host compiler that is not
// from a package that is known to support internal linking mode.
static int externalobj = 0;
@ -1447,6 +1455,194 @@ pclntab(void)
Bflush(&bso);
}
void
addvarint(Sym *s, uint32 val)
{
int32 n;
uint32 v;
uchar *p;
n = 0;
for(v = val; v >= 0x80; v >>= 7)
n++;
n++;
symgrow(s, s->np+n);
p = s->p + s->np - n;
for(v = val; v >= 0x80; v >>= 7)
*p++ = v | 0x80;
*p++ = v;
}
// funcpctab appends to dst a pc-value table mapping the code in func to the values
// returned by valfunc parameterized by arg. The invocation of valfunc to update the
// current value is, for each p,
//
// val = valfunc(func, val, p, 0, arg);
// record val as value at p->pc;
// val = valfunc(func, val, p, 1, arg);
//
// where func is the function, val is the current value, p is the instruction being
// considered, and arg can be used to further parameterize valfunc.
void
funcpctab(Sym *dst, Sym *func, char *desc, int32 (*valfunc)(Sym*, int32, Prog*, int32, int32), int32 arg)
{
int dbg, i, start;
int32 oldval, val, started;
uint32 delta;
vlong pc;
Prog *p;
// To debug a specific function, uncomment second line and change name.
dbg = 0;
//dbg = strcmp(func->name, "main.main") == 0;
debug['O'] += dbg;
start = dst->np;
if(debug['O'])
Bprint(&bso, "funcpctab %s -> %s [valfunc=%s]\n", func->name, dst->name, desc);
val = -1;
oldval = val;
pc = func->value;
if(debug['O'])
Bprint(&bso, "%6llux %6d %P\n", pc, val, func->text);
started = 0;
for(p=func->text; p != P; p = p->link) {
// Update val. If it's not changing, keep going.
val = valfunc(func, val, p, 0, arg);
if(val == oldval && started) {
val = valfunc(func, val, p, 1, arg);
if(debug['O'])
Bprint(&bso, "%6llux %6s %P\n", p->pc, "", p);
continue;
}
// If the pc of the next instruction is the same as the
// pc of this instruction, this instruction is not a real
// instruction. Keep going, so that we only emit a delta
// for a true instruction boundary in the program.
if(p->link && p->link->pc == p->pc) {
val = valfunc(func, val, p, 1, arg);
if(debug['O'])
Bprint(&bso, "%6llux %6s %P\n", p->pc, "", p);
continue;
}
// The table is a sequence of (value, pc) pairs, where each
// pair states that the given value is in effect from the current position
// up to the given pc, which becomes the new current position.
// To generate the table as we scan over the program instructions,
// we emit a "(value" when pc == func->value, and then
// each time we observe a change in value we emit ", pc) (value".
// When the scan is over, we emit the closing ", pc)".
//
// The table is delta-encoded. The value deltas are signed and
// transmitted in zig-zag form, where a complement bit is placed in bit 0,
// and the pc deltas are unsigned. Both kinds of deltas are sent
// as variable-length little-endian base-128 integers,
// where the 0x80 bit indicates that the integer continues.
if(debug['O'])
Bprint(&bso, "%6llux %6d %P\n", p->pc, val, p);
if(!started)
started = 1;
else {
addvarint(dst, (p->pc - pc) / MINLC);
pc = p->pc;
}
delta = val - oldval;
if(delta>>31)
delta = 1 | ~(delta<<1);
else
delta <<= 1;
addvarint(dst, delta);
oldval = val;
started = 1;
val = valfunc(func, val, p, 1, arg);
}
if(started) {
if(debug['O'])
Bprint(&bso, "%6llux done\n", func->value+func->size);
addvarint(dst, (func->value+func->size - pc) / MINLC);
addvarint(dst, 0); // terminator
}
if(debug['O']) {
Bprint(&bso, "wrote %d bytes\n", dst->np - start);
for(i=start; i<dst->np; i++)
Bprint(&bso, " %02ux", dst->p[i]);
Bprint(&bso, "\n");
}
debug['O'] -= dbg;
}
// pctofileline computes either the file number (arg == 0)
// or the line number (arg == 1) to use at p.
// Because p->lineno applies to p, phase == 0 (before p)
// takes care of the update.
static int32
pctofileline(Sym *sym, int32 oldval, Prog *p, int32 phase, int32 arg)
{
int32 f, l;
if(p->as == ATEXT || p->as == ANOP || p->as == AUSEFIELD || p->line == 0 || phase == 1)
return oldval;
getline(sym->hist, p->line, &f, &l);
if(f == 0) {
// print("getline failed for %s %P\n", cursym->name, p);
return oldval;
}
if(arg == 0)
return f;
return l;
}
// pctospadj computes the sp adjustment in effect.
// It is oldval plus any adjustment made by p itself.
// The adjustment by p takes effect only after p, so we
// apply the change during phase == 1.
static int32
pctospadj(Sym *sym, int32 oldval, Prog *p, int32 phase, int32 arg)
{
USED(arg);
if(oldval == -1) // starting
oldval = 0;
if(phase == 0)
return oldval;
if(oldval + p->spadj < -10000 || oldval + p->spadj > 1000000000) {
diag("overflow in spadj: %d + %d = %d", oldval, p->spadj, oldval + p->spadj);
errorexit();
}
return oldval + p->spadj;
}
// pctopcdata computes the pcdata value in effect at p.
// A PCDATA instruction sets the value in effect at future
// non-PCDATA instructions.
// Since PCDATA instructions have no width in the final code,
// it does not matter which phase we use for the update.
static int32
pctopcdata(Sym *sym, int32 oldval, Prog *p, int32 phase, int32 arg)
{
if(phase == 0 || p->as != APCDATA || p->from.offset != arg)
return oldval;
if((int32)p->to.offset != p->to.offset) {
diag("overflow in PCDATA instruction: %P", p);
errorexit();
}
return p->to.offset;
}
#define LOG 5
void
mkfwd(void)
@ -2001,3 +2197,421 @@ erealloc(void *p, long n)
}
return p;
}
// Saved history stacks encountered while reading archives.
// Keeping them allows us to answer virtual lineno -> file:line
// queries.
//
// The history stack is a complex data structure, described best at the
// bottom of http://plan9.bell-labs.com/magic/man2html/6/a.out.
// One of the key benefits of interpreting it here is that the runtime
// does not have to. Perhaps some day the compilers could generate
// a simpler linker input too.
struct Hist
{
int32 line;
int32 off;
Sym *file;
};
static Hist *histcopy;
static Hist *hist;
static int32 nhist;
static int32 maxhist;
static int32 histdepth;
static int32 nhistfile;
static Sym *filesyms;
// savehist processes a single line, off history directive
// found in the input object file.
void
savehist(int32 line, int32 off)
{
char tmp[1024];
Sym *file;
Hist *h;
tmp[0] = '\0';
copyhistfrog(tmp, sizeof tmp);
if(tmp[0]) {
file = lookup(tmp, HistVersion);
if(file->type != SFILEPATH) {
file->value = ++nhistfile;
file->type = SFILEPATH;
file->next = filesyms;
filesyms = file;
}
} else
file = nil;
if(file != nil && line == 1 && off == 0) {
// start of new stack
if(histdepth != 0) {
diag("history stack phase error: unexpected start of new stack depth=%d file=%s", histdepth, tmp);
errorexit();
}
nhist = 0;
histcopy = nil;
}
if(nhist >= maxhist) {
if(maxhist == 0)
maxhist = 1;
maxhist *= 2;
hist = erealloc(hist, maxhist*sizeof hist[0]);
}
h = &hist[nhist++];
h->line = line;
h->off = off;
h->file = file;
if(file != nil) {
if(off == 0)
histdepth++;
} else {
if(off != 0) {
diag("history stack phase error: bad offset in pop");
errorexit();
}
histdepth--;
}
}
// gethist returns the history stack currently in effect.
// The result is valid indefinitely.
Hist*
gethist(void)
{
if(histcopy == nil) {
if(nhist == 0)
return nil;
histcopy = mal((nhist+1)*sizeof hist[0]);
memmove(histcopy, hist, nhist*sizeof hist[0]);
histcopy[nhist].line = -1;
}
return histcopy;
}
typedef struct Hstack Hstack;
struct Hstack
{
Hist *h;
int delta;
};
// getline sets *f to the file number and *l to the line number
// of the virtual line number line according to the history stack h.
void
getline(Hist *h, int32 line, int32 *f, int32 *l)
{
Hstack stk[100];
int nstk, start;
Hist *top, *h0;
static Hist *lasth;
static int32 laststart, lastend, lastdelta, lastfile;
h0 = h;
*f = 0;
*l = 0;
start = 0;
if(h == nil || line == 0) {
print("%s: getline: h=%p line=%d\n", cursym->name, h, line);
return;
}
// Cache span used during last lookup, so that sequential
// translation of line numbers in compiled code is efficient.
if(!debug['O'] && lasth == h && laststart <= line && line < lastend) {
*f = lastfile;
*l = line - lastdelta;
return;
}
if(debug['O'])
print("getline %d laststart=%d lastend=%d\n", line, laststart, lastend);
nstk = 0;
for(; h->line != -1; h++) {
if(debug['O'])
print("\t%s %d %d\n", h->file ? h->file->name : "?", h->line, h->off);
if(h->line > line) {
if(nstk == 0) {
diag("history stack phase error: empty stack at line %d", (int)line);
errorexit();
}
top = stk[nstk-1].h;
lasth = h;
lastfile = top->file->value;
laststart = start;
lastend = h->line;
lastdelta = stk[nstk-1].delta;
*f = lastfile;
*l = line - lastdelta;
if(debug['O'])
print("\tgot %d %d [%d %d %d]\n", *f, *l, laststart, lastend, lastdelta);
return;
}
if(h->file == nil) {
// pop included file
if(nstk == 0) {
diag("history stack phase error: stack underflow");
errorexit();
}
nstk--;
if(nstk > 0)
stk[nstk-1].delta += h->line - stk[nstk].h->line;
start = h->line;
} else if(h->off == 0) {
// push included file
if(nstk >= nelem(stk)) {
diag("history stack phase error: stack overflow");
errorexit();
}
start = h->line;
stk[nstk].h = h;
stk[nstk].delta = h->line - 1;
nstk++;
} else {
// #line directive
if(nstk == 0) {
diag("history stack phase error: stack underflow");
errorexit();
}
stk[nstk-1].h = h;
stk[nstk-1].delta = h->line - h->off;
start = h->line;
}
if(debug['O'])
print("\t\tnstk=%d delta=%d\n", nstk, stk[nstk].delta);
}
diag("history stack phase error: cannot find line for %d", line);
nstk = 0;
for(h = h0; h->line != -1; h++) {
print("\t%d %d %s\n", h->line, h->off, h->file ? h->file->name : "");
if(h->file == nil)
nstk--;
else if(h->off == 0)
nstk++;
}
}
// defgostring returns a symbol for the Go string containing text.
Sym*
defgostring(char *text)
{
char *p;
Sym *s;
int32 n;
n = strlen(text);
p = smprint("go.string.\"%Z\"", text);
s = lookup(p, 0);
if(s->size == 0) {
s->type = SGOSTRING;
s->reachable = 1;
s->size = 2*PtrSize+n;
symgrow(s, 2*PtrSize+n);
setaddrplus(s, 0, s, 2*PtrSize);
setuintxx(s, PtrSize, n, PtrSize);
memmove(s->p+2*PtrSize, text, n);
}
s->reachable = 1;
return s;
}
// addpctab appends to f a pc-value table, storing its offset at off.
// The pc-value table is for func and reports the value of valfunc
// parameterized by arg.
static int32
addpctab(Sym *f, int32 off, Sym *func, char *desc, int32 (*valfunc)(Sym*, int32, Prog*, int32, int32), int32 arg)
{
int32 start;
start = f->np;
funcpctab(f, func, desc, valfunc, arg);
if(start == f->np) {
// no table
return setuint32(f, off, 0);
}
if((int32)start > (int32)f->np) {
diag("overflow adding pc-table: symbol too large");
errorexit();
}
return setuint32(f, off, start);
}
// functab initializes the functab and filetab symbols with
// runtime function and file name information.
void
functab(void)
{
Prog *p;
int32 i, n, start;
uint32 *havepc, *havefunc;
Sym *ftab, *f;
int32 npcdata, nfuncdata, off, end;
char *q;
ftab = lookup("functab", 0);
ftab->type = SRODATA;
ftab->reachable = 1;
if(debug['s'])
return;
adduintxx(ftab, 0, PtrSize);
for(cursym = textp; cursym != nil; cursym = cursym->next) {
q = smprint("go.func.%s", cursym->name);
f = lookup(q, cursym->version);
f->type = SRODATA;
f->reachable = 1;
free(q);
addaddrplus(ftab, cursym, 0);
addaddrplus(ftab, f, 0);
npcdata = 0;
nfuncdata = 0;
for(p = cursym->text; p != P; p = p->link) {
if(p->as == APCDATA && p->from.offset >= npcdata)
npcdata = p->from.offset+1;
if(p->as == AFUNCDATA && p->from.offset >= nfuncdata)
nfuncdata = p->from.offset+1;
}
off = 0;
// fixed size of struct, checked below
end = 2*PtrSize + 5*4 + 5*4 + npcdata*4 + nfuncdata*PtrSize;
if(nfuncdata > 0 && (end&(PtrSize-1)))
end += 4;
symgrow(f, end);
// name *string
off = setaddr(f, off, defgostring(cursym->name));
// entry uintptr
off = setaddr(f, off, cursym);
// args int32
// TODO: Move into funcinfo.
if(cursym->text == nil || (cursym->text->textflag & NOSPLIT) && cursym->args == 0 && cursym->nptrs < 0) {
// This might be a vararg function and have no
// predetermined argument size. This check is
// approximate and will also match 0 argument
// nosplit functions compiled by 6c.
off = setuint32(f, off, ArgsSizeUnknown);
} else
off = setuint32(f, off, cursym->args);
// locals int32
// TODO: Move into funcinfo.
off = setuint32(f, off, cursym->locals);
// frame int32
// TODO: Remove entirely. The pcsp table is more precise.
// This is only used by a fallback case during stack walking
// when a called function doesn't have argument information.
// We need to make sure everything has argument information
// and then remove this.
if(cursym->text == nil)
off = setuint32(f, off, 0);
else
off = setuint32(f, off, (uint32)cursym->text->to.offset+PtrSize);
// TODO: Move into funcinfo.
// ptrsoff, ptrslen int32
start = f->np;
for(i = 0; i < cursym->nptrs; i += 32)
adduint32(f, cursym->ptrs[i/32]);
off = setuint32(f, off, start);
off = setuint32(f, off, (f->np - start)/4);
// pcsp table (offset int32)
off = addpctab(f, off, cursym, "pctospadj", pctospadj, 0);
// pcfile table (offset int32)
off = addpctab(f, off, cursym, "pctofileline file", pctofileline, 0);
// pcln table (offset int32)
off = addpctab(f, off, cursym, "pctofileline line", pctofileline, 1);
// npcdata int32
off = setuint32(f, off, npcdata);
// nfuncdata int32
off = setuint32(f, off, nfuncdata);
// tabulate which pc and func data we have.
n = ((npcdata+31)/32 + (nfuncdata+31)/32)*4;
havepc = mal(n);
havefunc = havepc + (npcdata+31)/32;
for(p = cursym->text; p != P; p = p->link) {
if(p->as == AFUNCDATA) {
if((havefunc[p->from.offset/32]>>(p->from.offset%32))&1)
diag("multiple definitions for FUNCDATA $%d", i);
havefunc[p->from.offset/32] |= 1<<(p->from.offset%32);
}
if(p->as == APCDATA)
havepc[p->from.offset/32] |= 1<<(p->from.offset%32);
}
// pcdata.
for(i=0; i<npcdata; i++) {
if(!(havepc[i/32]>>(i%32))&1) {
off = setuint32(f, off, 0);
continue;
}
off = addpctab(f, off, cursym, "pctopcdata", pctopcdata, i);
}
unmal(havepc, n);
// funcdata, must be pointer-aligned and we're only int32-aligned.
// Unlike pcdata, can gather in a single pass.
// Missing funcdata will be 0 (nil pointer).
if(nfuncdata > 0) {
if(off&(PtrSize-1))
off += 4;
for(p = cursym->text; p != P; p = p->link) {
if(p->as == AFUNCDATA) {
i = p->from.offset;
if(p->to.type == D_CONST)
setuintxx(f, off+PtrSize*i, p->to.offset, PtrSize);
else
setaddrplus(f, off+PtrSize*i, p->to.sym, p->to.offset);
}
}
off += nfuncdata*PtrSize;
}
if(off != end) {
diag("bad math in functab: off=%d but end=%d (npcdata=%d nfuncdata=%d)", off, end, npcdata, nfuncdata);
errorexit();
}
f->size = f->np;
// Final entry of table is just end pc.
if(cursym->next == nil) {
addaddrplus(ftab, cursym, cursym->size);
adduintxx(ftab, 0, PtrSize);
}
}
setuintxx(ftab, 0, (ftab->np-PtrSize)/(2*PtrSize) - 1, PtrSize);
ftab->size = ftab->np;
ftab = lookup("filetab", 0);
ftab->type = SRODATA;
ftab->reachable = 1;
symgrow(ftab, (nhistfile+1)*PtrSize);
setuintxx(ftab, 0, nhistfile+1, PtrSize);
for(f = filesyms; f != S; f = f->next)
setaddr(ftab, f->value*PtrSize, defgostring(f->name));
ftab->size = ftab->np;
}

View File

@ -41,10 +41,12 @@ enum
STYPE,
SSTRING,
SGOSTRING,
SGOFUNC,
SRODATA,
SFUNCTAB,
STYPELINK,
SSYMTAB,
SPCLNTAB,
SSYMTAB, // TODO: move to unmapped section
SPCLNTAB, // TODO: move to unmapped section
SELFROSECT,
/* writable, non-executable */
@ -67,6 +69,7 @@ enum
SMACHOINDIRECTPLT,
SMACHOINDIRECTGOT,
SFILE,
SFILEPATH,
SCONST,
SDYNIMPORT,
SHOSTOBJ,
@ -129,9 +132,14 @@ struct Section
uvlong rellen;
};
typedef struct Hist Hist;
#pragma incomplete struct Hist
extern char symname[];
extern char **libdir;
extern int nlibdir;
extern int version;
EXTERN char* INITENTRY;
EXTERN char* thestring;
@ -194,6 +202,9 @@ void addlibpath(char *srcref, char *objref, char *file, char *pkg);
Section* addsection(Segment*, char*, int);
void copyhistfrog(char *buf, int nbuf);
void addhist(int32 line, int type);
void savehist(int32 line, int32 off);
Hist* gethist(void);
void getline(Hist*, int32 line, int32 *f, int32 *l);
void asmlc(void);
void histtoauto(void);
void collapsefrog(Sym *s);
@ -216,6 +227,7 @@ void objfile(char *file, char *pkg);
void libinit(void);
void pclntab(void);
void symtab(void);
void functab(void);
void Lflag(char *arg);
void usage(void);
void adddynrel(Sym*, Reloc*);
@ -251,10 +263,11 @@ vlong addpcrelplus(Sym*, Sym*, vlong);
vlong addsize(Sym*, Sym*);
vlong setaddrplus(Sym*, vlong, Sym*, vlong);
vlong setaddr(Sym*, vlong, Sym*);
void setuint8(Sym*, vlong, uint8);
void setuint16(Sym*, vlong, uint16);
void setuint32(Sym*, vlong, uint32);
void setuint64(Sym*, vlong, uint64);
vlong setuint8(Sym*, vlong, uint8);
vlong setuint16(Sym*, vlong, uint16);
vlong setuint32(Sym*, vlong, uint32);
vlong setuint64(Sym*, vlong, uint64);
vlong setuintxx(Sym*, vlong, uint64, vlong);
void asmsym(void);
void asmelfsym(void);
void asmplan9sym(void);
@ -284,6 +297,7 @@ void hostobjs(void);
void hostlink(void);
char* estrdup(char*);
void* erealloc(void*, long);
Sym* defgostring(char*);
int pathchar(void);
void* mal(uint32);

View File

@ -466,7 +466,8 @@ putsymb(Sym *s, char *name, int t, vlong v, vlong size, int ver, Sym *typ)
void
symtab(void)
{
Sym *s, *symtype, *symtypelink, *symgostring;
Sym *s, *symtype, *symtypelink, *symgostring, *symgofunc;
dosymtype();
// Define these so that they'll get put into the symbol table.
@ -519,6 +520,12 @@ symtab(void)
s->reachable = 1;
symgostring = s;
s = lookup("go.func.*", 0);
s->type = SGOFUNC;
s->size = 0;
s->reachable = 1;
symgofunc = s;
symtypelink = lookup("typelink", 0);
symt = lookup("symtab", 0);
@ -548,6 +555,11 @@ symtab(void)
s->hide = 1;
s->outer = symgostring;
}
if(strncmp(s->name, "go.func.", 8) == 0) {
s->type = SGOFUNC;
s->hide = 1;
s->outer = symgofunc;
}
}
if(debug['s'])

View File

@ -573,9 +573,9 @@ havem:
MOVL BP, 0(DI)
// Push arguments to cgocallbackg.
// Frame size here must match the frame size above
// Frame size here must match the frame size above plus the pushes
// to trick traceback routines into doing the right thing.
SUBL $12, DI
SUBL $20, DI
MOVL AX, 0(DI)
MOVL BX, 4(DI)
MOVL DX, 8(DI)
@ -587,9 +587,9 @@ havem:
// Restore g->sched (== m->curg->sched) from saved values.
get_tls(CX)
MOVL g(CX), SI
MOVL 12(SP), BP
MOVL 20(SP), BP
MOVL BP, (g_sched+gobuf_pc)(SI)
LEAL (12+4)(SP), DI
LEAL (20+4)(SP), DI
MOVL DI, (g_sched+gobuf_sp)(SI)
// Switch back to m->g0's stack and restore m->g0->sched.sp.

View File

@ -609,9 +609,9 @@ havem:
MOVQ BP, 0(DI)
// Push arguments to cgocallbackg.
// Frame size here must match the frame size above
// Frame size here must match the frame size above plus the pushes
// to trick traceback routines into doing the right thing.
SUBQ $24, DI
SUBQ $40, DI
MOVQ AX, 0(DI)
MOVQ BX, 8(DI)
MOVQ DX, 16(DI)
@ -623,9 +623,9 @@ havem:
// Restore g->sched (== m->curg->sched) from saved values.
get_tls(CX)
MOVQ g(CX), SI
MOVQ 24(SP), BP
MOVQ 40(SP), BP
MOVQ BP, (g_sched+gobuf_pc)(SI)
LEAQ (24+8)(SP), DI
LEAQ (40+8)(SP), DI
MOVQ DI, (g_sched+gobuf_sp)(SI)
// Switch back to m->g0's stack and restore m->g0->sched.sp.

View File

@ -368,12 +368,12 @@ havem:
MOVW (g_sched+gobuf_sp)(g), R4 // prepare stack as R4
// Push gobuf.pc
// Frame size here must match the frame size above plus the push
// to trick traceback routines into doing the right thing.
MOVW (g_sched+gobuf_pc)(g), R5
MOVW.W R5, -16(R4)
MOVW.W R5, -20(R4)
// Push arguments to cgocallbackg.
// Frame size here must match the frame size above
// to trick traceback routines into doing the right thing.
MOVW R0, 4(R4)
MOVW R1, 8(R4)
MOVW R2, 12(R4)
@ -385,7 +385,7 @@ havem:
// Restore g->sched (== m->curg->sched) from saved values.
MOVW 0(R13), R5
MOVW R5, (g_sched+gobuf_pc)(g)
ADD $(12+4), R13, R4
ADD $(16+4), R13, R4
MOVW R4, (g_sched+gobuf_sp)(g)
// Switch back to m->g0's stack and restore m->g0->sched.sp.

View File

@ -78,18 +78,8 @@ func Caller(skip int) (pc uintptr, file string, line int, ok bool)
// It returns the number of entries written to pc.
func Callers(skip int, pc []uintptr) int
type Func struct { // Keep in sync with runtime.h:struct Func
name string
typ string // go type string
src string // src file name
pcln []byte // pc/ln tab for this func
entry uintptr // entry pc
pc0 uintptr // starting pc, ln for table
ln0 int32
frame int32 // stack frame size
args int32 // in/out args size
locals int32 // locals size
ptrs []int32 // pointer map
type Func struct {
opaque struct{} // unexported field to disallow conversions
}
// FuncForPC returns a *Func describing the function that contains the
@ -97,10 +87,14 @@ type Func struct { // Keep in sync with runtime.h:struct Func
func FuncForPC(pc uintptr) *Func
// Name returns the name of the function.
func (f *Func) Name() string { return f.name }
func (f *Func) Name() string {
return funcname_go(f)
}
// Entry returns the entry address of the function.
func (f *Func) Entry() uintptr { return f.entry }
func (f *Func) Entry() uintptr {
return funcentry_go(f)
}
// FileLine returns the file name and line number of the
// source code corresponding to the program counter pc.
@ -112,6 +106,8 @@ func (f *Func) FileLine(pc uintptr) (file string, line int) {
// implemented in symtab.c
func funcline_go(*Func, uintptr) (string, int)
func funcname_go(*Func) string
func funcentry_go(*Func) uintptr
// SetFinalizer sets the finalizer associated with x to f.
// When the garbage collector finds an unreachable block

View File

@ -1390,7 +1390,7 @@ addframeroots(Stkframe *frame, void*)
Func *f;
byte *ap;
int32 i, j, nuintptr;
uint32 w, b;
uint32 w, b, *ptrs;
// Scan local variables if stack frame has been allocated.
if(frame->varlen > 0)
@ -1399,11 +1399,12 @@ addframeroots(Stkframe *frame, void*)
// Scan arguments.
// Use pointer information if known.
f = frame->fn;
if(f->args > 0 && f->ptrs.array != nil) {
if(f->args > 0 && f->ptrslen > 0) {
ap = frame->argp;
nuintptr = f->args / sizeof(uintptr);
for(i = 0; i < f->ptrs.len; i++) {
w = ((uint32*)f->ptrs.array)[i];
ptrs = (uint32*)((byte*)f + f->ptrsoff);
for(i = 0; i < f->ptrslen; i++) {
w = ptrs[i];
b = 1;
j = nuintptr;
if(j > 32)

View File

@ -294,12 +294,11 @@ runtime·Caller(intgo skip, uintptr retpc, String retfile, intgo retline, bool r
retbool = true; // have retpc at least
} else {
retpc = rpc[1];
retfile = f->src;
pc = retpc;
g = runtime·findfunc(rpc[0]);
if(pc > f->entry && (g == nil || g->entry != (uintptr)runtime·sigpanic))
pc--;
retline = runtime·funcline(f, pc);
retline = runtime·funcline(f, pc, &retfile);
retbool = true;
}
FLUSH(&retpc);

View File

@ -401,21 +401,25 @@ enum
SigIgnored = 1<<6, // the signal was ignored before we registered for it
};
// NOTE(rsc): keep in sync with extern.go:/type.Func.
// Eventually, the loaded symbol table should be closer to this form.
// layout of in-memory per-function information prepared by linker
// See http://golang.org/s/go12symtab.
struct Func
{
String name;
String type; // go type string
String src; // src file name
Slice pcln; // pc/ln tab for this func
uintptr entry; // entry pc
uintptr pc0; // starting pc, ln for table
int32 ln0;
int32 frame; // stack frame size
String *name; // function name
uintptr entry; // start pc
// TODO: Remove these fields.
int32 args; // in/out args size
int32 locals; // locals size
Slice ptrs; // pointer map
int32 frame; // legacy frame size; use pcsp if possible
int32 ptrsoff;
int32 ptrslen;
int32 pcsp;
int32 pcfile;
int32 pcln;
int32 npcdata;
int32 nfuncdata;
};
// layout of Itab known to compilers
@ -790,7 +794,9 @@ void runtime·unminit(void);
void runtime·signalstack(byte*, int32);
void runtime·symtabinit(void);
Func* runtime·findfunc(uintptr);
int32 runtime·funcline(Func*, uintptr);
int32 runtime·funcline(Func*, uintptr, String*);
int32 runtime·funcarglen(Func*, uintptr);
int32 runtime·funcspdelta(Func*, uintptr);
void* runtime·stackalloc(uint32);
void runtime·stackfree(void*, uintptr);
MCache* runtime·allocmcache(void);

View File

@ -3,52 +3,7 @@
// license that can be found in the LICENSE file.
// Runtime symbol table parsing.
//
// The Go tools use a symbol table derived from the Plan 9 symbol table
// format. The symbol table is kept in its own section treated as
// read-only memory when the binary is running: the binary consults the
// table.
//
// The format used by Go 1.0 was basically the Plan 9 format. Each entry
// is variable sized but had this format:
//
// 4-byte value, big endian
// 1-byte type ([A-Za-z] + 0x80)
// name, NUL terminated (or for 'z' and 'Z' entries, double-NUL terminated)
// 4-byte Go type address, big endian (new in Go)
//
// In order to support greater interoperation with standard toolchains,
// Go 1.1 uses a more flexible yet smaller encoding of the entries.
// The overall structure is unchanged from Go 1.0 and, for that matter,
// from Plan 9.
//
// The Go 1.1 table is a re-encoding of the data in a Go 1.0 table.
// To identify a new table as new, it begins one of two eight-byte
// sequences:
//
// FF FF FF FD 00 00 00 xx - big endian new table
// FD FF FF FF 00 00 00 xx - little endian new table
//
// This sequence was chosen because old tables stop at an entry with type
// 0, so old code reading a new table will see only an empty table. The
// first four bytes are the target-endian encoding of 0xfffffffd. The
// final xx gives AddrSize, the width of a full-width address.
//
// After that header, each entry is encoded as follows.
//
// 1-byte type (0-51 + two flag bits)
// AddrSize-byte value, host-endian OR varint-encoded value
// AddrSize-byte Go type address OR nothing
// [n] name, terminated as before
//
// The type byte comes first, but 'A' encodes as 0 and 'a' as 26, so that
// the type itself is only in the low 6 bits. The upper two bits specify
// the format of the next two fields. If the 0x40 bit is set, the value
// is encoded as an full-width 4- or 8-byte target-endian word. Otherwise
// the value is a varint-encoded number. If the 0x80 bit is set, the Go
// type is present, again as a 4- or 8-byte target-endian word. If not,
// there is no Go type in this entry. The NUL-terminated name ends the
// entry.
// See http://golang.org/s/go12symtab for an overview.
#include "runtime.h"
#include "defs_GOOS_GOARCH.h"
@ -56,423 +11,82 @@
#include "arch_GOARCH.h"
#include "malloc.h"
extern byte pclntab[], epclntab[], symtab[], esymtab[];
typedef struct Sym Sym;
struct Sym
typedef struct Ftab Ftab;
struct Ftab
{
uintptr value;
byte symtype;
byte *name;
// byte *gotype;
uintptr entry;
Func *func;
};
static uintptr mainoffset;
extern uintptr functab[];
extern void main·main(void);
static Ftab *ftab;
static uintptr nftab;
extern String *filetab[];
static uintptr nfiletab;
static String end = { (uint8*)"end", 3 };
void
runtime·symtabinit(void)
{
int32 i, j;
ftab = (Ftab*)(functab+1);
nftab = functab[0];
for(i=0; i<nftab; i++) {
// NOTE: ftab[nftab].entry is legal; it is the address beyond the final function.
if(ftab[i].entry > ftab[i+1].entry) {
runtime·printf("function symbol table not sorted by program counter: %p %S > %p %S", ftab[i].entry, *ftab[i].func->name, ftab[i+1].entry, i+1 == nftab ? end : *ftab[i+1].func->name);
for(j=0; j<=i; j++)
runtime·printf("\t%p %S\n", ftab[j].entry, *ftab[j].func->name);
runtime·throw("invalid runtime symbol table");
}
}
nfiletab = (uintptr)filetab[0];
}
static uint32
readvarint(byte **pp)
{
byte *p;
uint32 v;
int32 shift;
v = 0;
p = *pp;
for(shift = 0;; shift += 7) {
v |= (*p & 0x7F) << shift;
if(!(*p++ & 0x80))
break;
}
*pp = p;
return v;
}
static uintptr
readword(byte **pp, byte *ep)
funcdata(Func *f, int32 i)
{
byte *p;
byte *p;
p = *pp;
if(ep - p < sizeof(void*)) {
*pp = ep;
if(i < 0 || i >= f->nfuncdata)
return 0;
}
*pp = p + sizeof(void*);
// Hairy, but only one of these four cases gets compiled.
if(sizeof(void*) == 8) {
if(BigEndian) {
return ((uint64)p[0]<<56) | ((uint64)p[1]<<48) | ((uint64)p[2]<<40) | ((uint64)p[3]<<32) |
((uint64)p[4]<<24) | ((uint64)p[5]<<16) | ((uint64)p[6]<<8) | ((uint64)p[7]);
}
return ((uint64)p[7]<<56) | ((uint64)p[6]<<48) | ((uint64)p[5]<<40) | ((uint64)p[4]<<32) |
((uint64)p[3]<<24) | ((uint64)p[2]<<16) | ((uint64)p[1]<<8) | ((uint64)p[0]);
}
if(BigEndian) {
return ((uint32)p[0]<<24) | ((uint32)p[1]<<16) | ((uint32)p[2]<<8) | ((uint32)p[3]);
}
return ((uint32)p[3]<<24) | ((uint32)p[2]<<16) | ((uint32)p[1]<<8) | ((uint32)p[0]);
p = (byte*)&f->nfuncdata + 4 + f->npcdata*4;
if(sizeof(void*) == 8 && ((uintptr)p & 4))
p += 4;
return ((uintptr*)p)[i];
}
// Walk over symtab, calling fn(&s) for each symbol.
static void
walksymtab(void (*fn)(Sym*))
{
byte *p, *ep, *q;
Sym s;
int32 widevalue, havetype, shift;
p = symtab;
ep = esymtab;
// Table must begin with correct magic number.
if(ep - p < 8 || p[4] != 0x00 || p[5] != 0x00 || p[6] != 0x00 || p[7] != sizeof(void*))
return;
if(BigEndian) {
if(p[0] != 0xff || p[1] != 0xff || p[2] != 0xff || p[3] != 0xfd)
return;
} else {
if(p[0] != 0xfd || p[1] != 0xff || p[2] != 0xff || p[3] != 0xff)
return;
}
p += 8;
while(p < ep) {
s.symtype = p[0]&0x3F;
widevalue = p[0]&0x40;
havetype = p[0]&0x80;
if(s.symtype < 26)
s.symtype += 'A';
else
s.symtype += 'a' - 26;
p++;
// Value, either full-width or varint-encoded.
if(widevalue) {
s.value = readword(&p, ep);
} else {
s.value = 0;
shift = 0;
while(p < ep && (p[0]&0x80) != 0) {
s.value |= (uintptr)(p[0]&0x7F)<<shift;
shift += 7;
p++;
}
if(p >= ep)
break;
s.value |= (uintptr)p[0]<<shift;
p++;
}
// Go type, if present. Ignored but must skip over.
if(havetype)
readword(&p, ep);
// Name.
if(ep - p < 2)
break;
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;
}
fn(&s);
}
}
// Symtab walker; accumulates info about functions.
static Func *func;
static int32 nfunc;
static byte **fname;
static int32 nfname;
static uintptr lastvalue;
static void
dofunc(Sym *sym)
{
Func *f;
uintgo cap;
switch(sym->symtype) {
case 't':
case 'T':
case 'l':
case 'L':
if(runtime·strcmp(sym->name, (byte*)"etext") == 0)
break;
if(sym->value < lastvalue) {
runtime·printf("runtime: symbols out of order: %p before %p\n", lastvalue, sym->value);
runtime·throw("malformed symbol table");
}
lastvalue = sym->value;
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)
break;
if(runtime·strcmp(sym->name, (byte*)".frame") == 0)
func[nfunc-1].frame = sym->value;
else if(runtime·strcmp(sym->name, (byte*)".locals") == 0)
func[nfunc-1].locals = sym->value;
else if(runtime·strcmp(sym->name, (byte*)".args") == 0)
func[nfunc-1].args = sym->value;
else if(runtime·strcmp(sym->name, (byte*)".nptrs") == 0) {
if(sym->value != func[nfunc-1].args/sizeof(uintptr)) {
runtime·printf("runtime: pointer map size and argument size disagree\n");
runtime·throw("mangled symbol table");
}
cap = ROUND(sym->value, 32) / 32;
func[nfunc-1].ptrs.array = runtime·persistentalloc(cap*sizeof(uint32), sizeof(uint32));
func[nfunc-1].ptrs.len = 0;
func[nfunc-1].ptrs.cap = cap;
} else if(runtime·strcmp(sym->name, (byte*)".ptrs") == 0) {
if(func[nfunc-1].ptrs.len >= func[nfunc-1].ptrs.cap) {
runtime·printf("runtime: more pointer map entries read than argument words\n");
runtime·throw("mangled symbol table");
}
((uint32*)func[nfunc-1].ptrs.array)[func[nfunc-1].ptrs.len++] = sym->value;
} else {
runtime·printf("runtime: invalid '%c' symbol named '%s'\n", (int8)sym->symtype, sym->name);
runtime·throw("mangled symbol table");
}
break;
case 'f':
if(fname == nil) {
if(sym->value >= nfname) {
if(sym->value >= 0x10000) {
runtime·printf("runtime: 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.
// returns the length of the path name.
static int32
makepath(byte *buf, int32 nbuf, byte *path)
{
int32 n, len;
byte *p, *ep, *q;
if(nbuf <= 0)
return 0;
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;
}
return p - buf;
}
static String
gostringn(byte *p, int32 l)
{
String s;
if(l == 0)
return runtime·emptystring;
s.len = l;
s.str = runtime·persistentalloc(l, 1);
runtime·memmove(s.str, p, l);
return s;
}
static struct
{
String srcstring;
int32 aline;
int32 delta;
} *files;
enum { maxfiles = 200 };
// 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)
{
#pragma dataflag 16 // no pointers
static byte srcbuf[1000];
static int32 incstart;
static int32 nfunc, nfile, nhist;
Func *f;
int32 i, l;
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.
l = makepath(srcbuf, sizeof srcbuf, sym->name+1);
nhist = 0;
nfile = 0;
if(nfile == maxfiles)
return;
files[nfile].srcstring = gostringn(srcbuf, l);
files[nfile].aline = 0;
files[nfile++].delta = 0;
} else {
// push or pop of included file.
l = makepath(srcbuf, sizeof srcbuf, sym->name+1);
if(srcbuf[0] != '\0') {
if(nhist++ == 0)
incstart = sym->value;
if(nhist == 0 && nfile < maxfiles) {
// new top-level file
files[nfile].srcstring = gostringn(srcbuf, l);
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.
// Return associated data value 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)
static int32
pcvalue(Func *f, int32 off, uintptr targetpc)
{
byte *p, *ep;
byte *p;
uintptr pc;
int32 line;
int32 pcquant;
int32 value, vdelta, pcshift;
uint32 uvdelta, pcdelta;
enum {
debug = 0
@ -480,125 +94,138 @@ runtime·funcline(Func *f, uintptr targetpc)
switch(thechar) {
case '5':
pcquant = 4;
pcshift = 2;
break;
default: // 6, 8
pcquant = 1;
pcshift = 0;
break;
}
p = f->pcln.array;
ep = p + f->pcln.len;
pc = f->pc0;
line = f->ln0;
// The table is a delta-encoded sequence of (value, pc) pairs.
// Each pair states the given value is in effect up to pc.
// The value deltas are signed, zig-zag encoded.
// The pc deltas are unsigned.
// The starting value is -1, the starting pc is the function entry.
// The table ends at a value delta of 0 except in the first pair.
if(off == 0)
return -1;
p = (byte*)f + off;
pc = f->entry;
value = -1;
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);
runtime·printf("pcvalue start f=%S [%p] pc=%p targetpc=%p value=%d tab=%p\n",
*f->name, f, pc, targetpc, value, p);
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)
uvdelta = readvarint(&p);
if(uvdelta == 0 && pc != f->entry)
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++;
if(uvdelta&1)
uvdelta = ~(uvdelta>>1);
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;
uvdelta >>= 1;
vdelta = (int32)uvdelta;
pcdelta = readvarint(&p) << pcshift;
value += vdelta;
pc += pcdelta;
if(debug)
runtime·printf("\tvalue=%d until pc=%p\n", value, pc);
if(targetpc < pc)
return value;
}
// If there was a table, it should have covered all program counters.
// If not, something is wrong.
runtime·printf("runtime: invalid pc-encoded table f=%S pc=%p targetpc=%p tab=%p\n",
*f->name, pc, targetpc, p);
runtime·throw("invalid runtime symbol table");
return -1;
}
static String unknown = { (uint8*)"?", 1 };
int32
runtime·funcline(Func *f, uintptr targetpc, String *file)
{
int32 line;
int32 fileno;
*file = unknown;
fileno = pcvalue(f, f->pcfile, targetpc);
line = pcvalue(f, f->pcln, targetpc);
if(fileno == -1 || line == -1 || fileno >= nfiletab) {
// runtime·printf("looking for %p in %S got file=%d line=%d\n", targetpc, *f->name, fileno, line);
return 0;
}
*file = *filetab[fileno];
return line;
}
int32
runtime·funcspdelta(Func *f, uintptr targetpc)
{
int32 x;
x = pcvalue(f, f->pcsp, targetpc);
if(x&(sizeof(void*)-1))
runtime·printf("invalid spdelta %d %d\n", f->pcsp, x);
return x;
}
static int32
pcdatavalue(Func *f, int32 table, uintptr targetpc)
{
if(table < 0 || table >= f->npcdata)
return -1;
return pcvalue(f, (&f->nfuncdata)[1+table], targetpc);
}
int32
runtime·funcarglen(Func *f, uintptr targetpc)
{
return pcdatavalue(f, 0, targetpc);
}
void
runtime·funcline_go(Func *f, uintptr targetpc, String retfile, intgo retline)
{
retfile = f->src;
retline = runtime·funcline(f, targetpc);
FLUSH(&retfile);
retline = runtime·funcline(f, targetpc, &retfile);
FLUSH(&retline);
}
void
runtime·symtabinit(void)
runtime·funcname_go(Func *f, String ret)
{
extern byte etext[];
ret = *f->name;
FLUSH(&ret);
}
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;
lastvalue = 0;
walksymtab(dofunc);
// Initialize tables.
// Memory obtained from runtime·persistentalloc() is not scanned by GC,
// this is fine because all pointers either point into sections of the executable
// or also obtained from persistentalloc().
func = runtime·persistentalloc((nfunc+1)*sizeof func[0], 0);
func[nfunc].entry = (uint64)etext;
fname = runtime·persistentalloc(nfname*sizeof fname[0], 0);
nfunc = 0;
lastvalue = 0;
walksymtab(dofunc);
// split pc/ln table by func
splitpcln();
// record src file and line info for each func
files = runtime·malloc(maxfiles * sizeof(files[0]));
walksymtab(dosrcline);
files = nil;
m->nomemprof--;
void
runtime·funcentry_go(Func *f, uintptr ret)
{
ret = f->entry;
FLUSH(&ret);
}
Func*
runtime·findfunc(uintptr addr)
{
Func *f;
Ftab *f;
int32 nf, n;
if(nfunc == 0)
if(nftab == 0)
return nil;
if(addr < func[0].entry || addr >= func[nfunc].entry)
if(addr < ftab[0].entry || addr >= ftab[nftab].entry)
return nil;
// binary search to find func with entry <= addr.
f = func;
nf = nfunc;
f = ftab;
nf = nftab;
while(nf > 0) {
n = nf/2;
if(f[n].entry <= addr && addr < f[n+1].entry)
return &f[n];
return f[n].func;
else if(addr < f[n].entry)
nf = n;
else {
@ -654,5 +281,5 @@ runtime·showframe(Func *f, G *gp)
return 1;
if(traceback < 0)
traceback = runtime·gotraceback(nil);
return traceback > 1 || f != nil && contains(f->name, ".") && !hasprefix(f->name, "runtime.");
return traceback > 1 || f != nil && contains(*f->name, ".") && !hasprefix(*f->name, "runtime.");
}

View File

@ -15,15 +15,18 @@ void _mod(void);
void _divu(void);
void _modu(void);
static String unknown = { (uint8*)"?", 1 };
int32
runtime·gentraceback(uintptr pc0, uintptr sp0, uintptr lr0, G *gp, int32 skip, uintptr *pcbuf, int32 max, void (*callback)(Stkframe*, void*), void *v, bool printall)
{
int32 i, n, nprint, skip0;
int32 i, n, nprint, skip0, line;
uintptr x, tracepc;
bool waspanic, printing;
Func *f, *f2;
Stkframe frame;
Stktop *stk;
String file;
skip0 = skip;
@ -76,11 +79,8 @@ runtime·gentraceback(uintptr pc0, uintptr sp0, uintptr lr0, G *gp, int32 skip,
// Derive frame pointer and link register.
if(frame.lr == 0)
frame.lr = *(uintptr*)frame.sp;
if(frame.fp == 0) {
frame.fp = frame.sp;
if(frame.pc > f->entry && f->frame >= sizeof(uintptr))
frame.fp += f->frame;
}
if(frame.fp == 0)
frame.fp = frame.sp + runtime·funcspdelta(f, frame.pc);
// Derive size of arguments.
frame.argp = (byte*)frame.fp + sizeof(uintptr);
@ -96,7 +96,7 @@ runtime·gentraceback(uintptr pc0, uintptr sp0, uintptr lr0, G *gp, int32 skip,
else if((f2 = runtime·findfunc(frame.lr)) != nil && f2->frame >= sizeof(uintptr))
frame.arglen = f2->frame; // conservative overestimate
else {
runtime·printf("runtime: unknown argument frame size for %S\n", f->name);
runtime·printf("runtime: unknown argument frame size for %S\n", *f->name);
if(!printing)
runtime·throw("invalid stack");
}
@ -113,7 +113,7 @@ runtime·gentraceback(uintptr pc0, uintptr sp0, uintptr lr0, G *gp, int32 skip,
frame.varlen = frame.fp - frame.sp;
} else {
if(f->locals > frame.fp - frame.sp) {
runtime·printf("runtime: inconsistent locals=%p frame=%p fp=%p sp=%p for %S\n", (uintptr)f->locals, (uintptr)f->frame, frame.fp, frame.sp, f->name);
runtime·printf("runtime: inconsistent locals=%p frame=%p fp=%p sp=%p for %S\n", (uintptr)f->locals, (uintptr)f->frame, frame.fp, frame.sp, *f->name);
runtime·throw("invalid stack");
}
frame.varp = (byte*)frame.fp - f->locals;
@ -138,7 +138,7 @@ runtime·gentraceback(uintptr pc0, uintptr sp0, uintptr lr0, G *gp, int32 skip,
tracepc = frame.pc; // back up to CALL instruction for funcline.
if(n > 0 && frame.pc > f->entry && !waspanic)
tracepc -= sizeof(uintptr);
runtime·printf("%S(", f->name);
runtime·printf("%S(", *f->name);
for(i = 0; i < frame.arglen/sizeof(uintptr); i++) {
if(i >= 5) {
runtime·prints(", ...");
@ -149,7 +149,8 @@ runtime·gentraceback(uintptr pc0, uintptr sp0, uintptr lr0, G *gp, int32 skip,
runtime·printhex(((uintptr*)frame.argp)[i]);
}
runtime·prints(")\n");
runtime·printf("\t%S:%d", f->src, runtime·funcline(f, tracepc));
line = runtime·funcline(f, tracepc, &file);
runtime·printf("\t%S:%d", file, line);
if(frame.pc > f->entry)
runtime·printf(" +%p", (uintptr)(frame.pc - f->entry));
if(m->throwing && gp == m->curg)
@ -164,7 +165,7 @@ runtime·gentraceback(uintptr pc0, uintptr sp0, uintptr lr0, G *gp, int32 skip,
waspanic = f->entry == (uintptr)runtime·sigpanic;
// Do not unwind past the bottom of the stack.
if(f->entry == (uintptr)runtime·goexit || f->entry == (uintptr)runtime·mstart || f->entry == (uintptr)_rt0_go)
if(f->entry == (uintptr)runtime·goexit || f->entry == (uintptr)runtime·mstart || f->entry == (uintptr)runtime·mcall || f->entry == (uintptr)_rt0_go)
break;
// Unwind to next frame.
@ -172,16 +173,7 @@ runtime·gentraceback(uintptr pc0, uintptr sp0, uintptr lr0, G *gp, int32 skip,
frame.lr = 0;
frame.sp = frame.fp;
frame.fp = 0;
// If this was div or divu or mod or modu, the caller had
// an extra 8 bytes on its stack. Adjust sp.
if(f->entry == (uintptr)_div || f->entry == (uintptr)_divu || f->entry == (uintptr)_mod || f->entry == (uintptr)_modu)
frame.sp += 8;
// If this was deferproc or newproc, the caller had an extra 12.
if(f->entry == (uintptr)runtime·deferproc || f->entry == (uintptr)runtime·newproc)
frame.sp += 12;
// sighandler saves the lr on stack before faking a call to sigpanic
if(waspanic) {
x = *(uintptr*)frame.sp;
@ -203,16 +195,19 @@ runtime·gentraceback(uintptr pc0, uintptr sp0, uintptr lr0, G *gp, int32 skip,
static void
printcreatedby(G *gp)
{
int32 line;
uintptr pc, tracepc;
Func *f;
String file;
if((pc = gp->gopc) != 0 && (f = runtime·findfunc(pc)) != nil
&& runtime·showframe(f, gp) && gp->goid != 1) {
runtime·printf("created by %S\n", f->name);
runtime·printf("created by %S\n", *f->name);
tracepc = pc; // back up to CALL instruction for funcline.
if(pc > f->entry)
tracepc -= sizeof(uintptr);
runtime·printf("\t%S:%d", f->src, runtime·funcline(f, tracepc));
line = runtime·funcline(f, tracepc, &file);
runtime·printf("\t%S:%d", file, line);
if(pc > f->entry)
runtime·printf(" +%p", (uintptr)(pc - f->entry));
runtime·printf("\n");

View File

@ -16,6 +16,8 @@ void runtime·sigpanic(void);
// This code is also used for the 386 tracebacks.
// Use uintptr for an appropriate word-sized integer.
static String unknown = { (uint8*)"?", 1 };
// Generic traceback. Handles runtime stack prints (pcbuf == nil),
// the runtime.Callers function (pcbuf != nil), as well as the garbage
// collector (callback != nil). A little clunky to merge these, but avoids
@ -23,12 +25,13 @@ void runtime·sigpanic(void);
int32
runtime·gentraceback(uintptr pc0, uintptr sp0, uintptr lr0, G *gp, int32 skip, uintptr *pcbuf, int32 max, void (*callback)(Stkframe*, void*), void *v, bool printall)
{
int32 i, n, nprint;
int32 i, n, nprint, line;
uintptr tracepc;
bool waspanic, printing;
Func *f, *f2;
Func *f, *flr;
Stkframe frame;
Stktop *stk;
String file;
USED(lr0);
@ -62,33 +65,36 @@ runtime·gentraceback(uintptr pc0, uintptr sp0, uintptr lr0, G *gp, int32 skip,
frame.sp = stk->gobuf.sp;
frame.lr = 0;
frame.fp = 0;
frame.fn = nil;
if(printing && runtime·showframe(nil, gp))
runtime·printf("----- stack segment boundary -----\n");
stk = (Stktop*)stk->stackbase;
continue;
}
if(frame.pc <= 0x1000 || (frame.fn = f = runtime·findfunc(frame.pc)) == nil) {
if(callback != nil)
f = frame.fn;
if(f == nil && (frame.pc <= 0x1000 || (frame.fn = f = runtime·findfunc(frame.pc)) == nil)) {
if(callback != nil) {
runtime·printf("unknown pc %p\n", frame.pc);
runtime·throw("unknown pc");
}
break;
}
// Found an actual function.
// Derive frame pointer and link register.
if(frame.fp == 0) {
frame.fp = frame.sp;
if(frame.pc > f->entry && f->frame >= sizeof(uintptr))
frame.fp += f->frame;
else
frame.fp += sizeof(uintptr);
frame.fp = frame.sp + runtime·funcspdelta(f, frame.pc);
frame.fp += sizeof(uintptr); // caller PC
}
if(frame.lr == 0)
frame.lr = ((uintptr*)frame.fp)[-1];
flr = runtime·findfunc(frame.lr);
// Derive size of arguments.
frame.argp = (byte*)frame.fp;
frame.arglen = 0;
if(f->args != ArgsSizeUnknown)
if(flr != nil && (i = runtime·funcarglen(flr, frame.lr)) >= 0)
frame.arglen = i;
else if(f->args != ArgsSizeUnknown)
frame.arglen = f->args;
else if(runtime·haszeroargs(f->entry))
frame.arglen = 0;
@ -96,10 +102,10 @@ runtime·gentraceback(uintptr pc0, uintptr sp0, uintptr lr0, G *gp, int32 skip,
frame.arglen = stk->argsize;
else if(f->entry == (uintptr)runtime·deferproc || f->entry == (uintptr)runtime·newproc)
frame.arglen = 2*sizeof(uintptr) + *(int32*)frame.argp;
else if((f2 = runtime·findfunc(frame.lr)) != nil && f2->frame >= sizeof(uintptr))
frame.arglen = f2->frame; // conservative overestimate
else if(flr != nil && flr->frame >= sizeof(uintptr))
frame.arglen = flr->frame; // conservative overestimate
else {
runtime·printf("runtime: unknown argument frame size for %S\n", f->name);
runtime·printf("runtime: unknown argument frame size for %S called from %p [%S]\n", *f->name, frame.lr, flr ? *flr->name : unknown);
if(!printing)
runtime·throw("invalid stack");
}
@ -116,7 +122,7 @@ runtime·gentraceback(uintptr pc0, uintptr sp0, uintptr lr0, G *gp, int32 skip,
frame.varlen = frame.fp - sizeof(uintptr) - frame.sp;
} else {
if(f->locals > frame.fp - sizeof(uintptr) - frame.sp) {
runtime·printf("runtime: inconsistent locals=%p frame=%p fp=%p sp=%p for %S\n", (uintptr)f->locals, (uintptr)f->frame, frame.fp, frame.sp, f->name);
runtime·printf("runtime: inconsistent locals=%p frame=%p fp=%p sp=%p for %S\n", (uintptr)f->locals, (uintptr)f->frame, frame.fp, frame.sp, *f->name);
runtime·throw("invalid stack");
}
frame.varp = (byte*)frame.fp - sizeof(uintptr) - f->locals;
@ -141,7 +147,7 @@ runtime·gentraceback(uintptr pc0, uintptr sp0, uintptr lr0, G *gp, int32 skip,
tracepc = frame.pc; // back up to CALL instruction for funcline.
if(n > 0 && frame.pc > f->entry && !waspanic)
tracepc--;
runtime·printf("%S(", f->name);
runtime·printf("%S(", *f->name);
for(i = 0; i < frame.arglen/sizeof(uintptr); i++) {
if(i >= 5) {
runtime·prints(", ...");
@ -152,7 +158,8 @@ runtime·gentraceback(uintptr pc0, uintptr sp0, uintptr lr0, G *gp, int32 skip,
runtime·printhex(((uintptr*)frame.argp)[i]);
}
runtime·prints(")\n");
runtime·printf("\t%S:%d", f->src, runtime·funcline(f, tracepc));
line = runtime·funcline(f, tracepc, &file);
runtime·printf("\t%S:%d", file, line);
if(frame.pc > f->entry)
runtime·printf(" +%p", (uintptr)(frame.pc - f->entry));
if(m->throwing && gp == m->curg)
@ -166,14 +173,12 @@ runtime·gentraceback(uintptr pc0, uintptr sp0, uintptr lr0, G *gp, int32 skip,
skipped:
waspanic = f->entry == (uintptr)runtime·sigpanic;
if(f->entry == (uintptr)runtime·deferproc || f->entry == (uintptr)runtime·newproc)
frame.fp += 2*sizeof(uintptr);
// Do not unwind past the bottom of the stack.
if(f->entry == (uintptr)runtime·goexit || f->entry == (uintptr)runtime·mstart || f->entry == (uintptr)_rt0_go)
if(f->entry == (uintptr)runtime·goexit || f->entry == (uintptr)runtime·mstart || f->entry == (uintptr)runtime·mcall || f->entry == (uintptr)_rt0_go)
break;
// Unwind to next frame.
frame.fn = flr;
frame.pc = frame.lr;
frame.lr = 0;
frame.sp = frame.fp;
@ -189,16 +194,19 @@ runtime·gentraceback(uintptr pc0, uintptr sp0, uintptr lr0, G *gp, int32 skip,
static void
printcreatedby(G *gp)
{
int32 line;
String file;
uintptr pc, tracepc;
Func *f;
// Show what created goroutine, except main goroutine (goid 1).
if((pc = gp->gopc) != 0 && (f = runtime·findfunc(pc)) != nil && gp->goid != 1) {
runtime·printf("created by %S\n", f->name);
runtime·printf("created by %S\n", *f->name);
tracepc = pc; // back up to CALL instruction for funcline.
if(pc > f->entry)
tracepc--;
runtime·printf("\t%S:%d", f->src, runtime·funcline(f, tracepc));
line = runtime·funcline(f, tracepc, &file);
runtime·printf("\t%S:%d", file, line);
if(pc > f->entry)
runtime·printf(" +%p", (uintptr)(pc - f->entry));
runtime·printf("\n");