diff options
Diffstat (limited to 'src/cmd/ld/lib.c')
-rw-r--r-- | src/cmd/ld/lib.c | 843 |
1 files changed, 701 insertions, 142 deletions
diff --git a/src/cmd/ld/lib.c b/src/cmd/ld/lib.c index 0a6bd3e8f..da522dc0c 100644 --- a/src/cmd/ld/lib.c +++ b/src/cmd/ld/lib.c @@ -33,9 +33,17 @@ #include "lib.h" #include "../ld/elf.h" #include "../../pkg/runtime/stack.h" +#include "../../pkg/runtime/funcdata.h" #include <ar.h> +enum +{ + // Whether to assume that the external linker is "gold" + // (http://sourceware.org/ml/binutils/2008-03/msg00162.html). + AssumeGoldLinker = 0, +}; + int iconv(Fmt*); char symname[] = SYMDEF; @@ -45,6 +53,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; @@ -75,7 +91,7 @@ Lflag(char *arg) void libinit(void) { - char *race; + char *suffix, *suffixsep; fmtinstall('i', iconv); fmtinstall('Y', Yconv); @@ -85,10 +101,16 @@ libinit(void) print("goarch is not known: %s\n", goarch); // add goroot to the end of the libdir list. - race = ""; - if(flag_race) - race = "_race"; - Lflag(smprint("%s/pkg/%s_%s%s", goroot, goos, goarch, race)); + suffix = ""; + suffixsep = ""; + if(flag_installsuffix != nil) { + suffixsep = "_"; + suffix = flag_installsuffix; + } else if(flag_race) { + suffixsep = "_"; + suffix = "race"; + } + Lflag(smprint("%s/pkg/%s_%s%s%s", goroot, goos, goarch, suffixsep, suffix)); // Unix doesn't like it when we write to a running (or, sometimes, // recently run) binary, so remove the output file before writing it. @@ -103,17 +125,14 @@ libinit(void) } if(INITENTRY == nil) { - INITENTRY = mal(strlen(goarch)+strlen(goos)+10); - sprint(INITENTRY, "_rt0_%s_%s", goarch, goos); - } - lookup(INITENTRY, 0)->type = SXREF; - if(flag_shared) { - if(LIBINITENTRY == nil) { - LIBINITENTRY = mal(strlen(goarch)+strlen(goos)+20); - sprint(LIBINITENTRY, "_rt0_%s_%s_lib", goarch, goos); + INITENTRY = mal(strlen(goarch)+strlen(goos)+20); + if(!flag_shared) { + sprint(INITENTRY, "_rt0_%s_%s", goarch, goos); + } else { + sprint(INITENTRY, "_rt0_%s_%s_lib", goarch, goos); } - lookup(LIBINITENTRY, 0)->type = SXREF; } + lookup(INITENTRY, 0)->type = SXREF; } void @@ -215,7 +234,7 @@ addlib(char *src, char *obj) if(p != nil) *p = '\0'; - if(debug['v']) + if(debug['v'] > 1) Bprint(&bso, "%5.2f addlib: %s %s pulls in %s\n", cputime(), obj, src, pname); addlibpath(src, obj, pname, name); @@ -292,7 +311,13 @@ void loadlib(void) { int i, w, x; - Sym *s; + Sym *s, *gmsym; + + if(flag_shared) { + s = lookup("runtime.islibrary", 0); + s->dupok = 1; + adduint8(s, 1); + } loadinternal("runtime"); if(thechar == '5') @@ -314,7 +339,7 @@ loadlib(void) } for(i=0; i<libraryp; i++) { - if(debug['v']) + if(debug['v'] > 1) Bprint(&bso, "%5.2f autolib: %s (from %s)\n", cputime(), library[i].file, library[i].objref); iscgo |= strcmp(library[i].pkg, "runtime/cgo") == 0; objfile(library[i].file, library[i].pkg); @@ -343,6 +368,15 @@ loadlib(void) } } + gmsym = lookup("runtime.tlsgm", 0); + gmsym->type = STLSBSS; + gmsym->size = 2*PtrSize; + gmsym->hide = 1; + if(linkmode == LinkExternal && iself && HEADTYPE != Hopenbsd) + gmsym->reachable = 1; + else + gmsym->reachable = 0; + // Now that we know the link mode, trim the dynexp list. x = CgoExportDynamic; if(linkmode == LinkExternal) @@ -417,7 +451,7 @@ objfile(char *file, char *pkg) pkg = smprint("%i", pkg); - if(debug['v']) + if(debug['v'] > 1) Bprint(&bso, "%5.2f ldobj: %s (%s)\n", cputime(), file, pkg); Bflush(&bso); f = Bopen(file, 0); @@ -554,6 +588,16 @@ ldhostobj(void (*ld)(Biobuf*, char*, int64, char*), Biobuf *f, char *pkg, int64 } } + // DragonFly declares errno with __thread, which results in a symbol + // type of R_386_TLS_GD or R_X86_64_TLSGD. The Go linker does not + // currently know how to handle TLS relocations, hence we have to + // force external linking for any libraries that link in code that + // uses errno. This can be removed if the Go linker ever supports + // these relocation types. + if(HEADTYPE == Hdragonfly) + if(strcmp(pkg, "net") == 0 || strcmp(pkg, "os/user") == 0) + isinternal = 0; + if(!isinternal) externalobj = 1; @@ -653,7 +697,7 @@ hostlink(void) p = strchr(p + 1, ' '); } - argv = malloc((10+nhostobj+nldflag+c)*sizeof argv[0]); + argv = malloc((13+nhostobj+nldflag+c)*sizeof argv[0]); argc = 0; if(extld == nil) extld = "gcc"; @@ -665,11 +709,25 @@ hostlink(void) case '6': argv[argc++] = "-m64"; break; + case '5': + argv[argc++] = "-marm"; + break; } - if(!debug['s']) + if(!debug['s'] && !debug_s) { argv[argc++] = "-gdwarf-2"; + } else { + argv[argc++] = "-s"; + } if(HEADTYPE == Hdarwin) argv[argc++] = "-Wl,-no_pie,-pagezero_size,4000000"; + + if(iself && AssumeGoldLinker) + argv[argc++] = "-Wl,--rosegment"; + + if(flag_shared) { + argv[argc++] = "-Wl,-Bsymbolic"; + argv[argc++] = "-shared"; + } argv[argc++] = "-o"; argv[argc++] = outfile; @@ -759,10 +817,10 @@ ldobj(Biobuf *f, char *pkg, int64 len, char *pn, char *file, int whence) pn = estrdup(pn); - c1 = Bgetc(f); - c2 = Bgetc(f); - c3 = Bgetc(f); - c4 = Bgetc(f); + c1 = BGETC(f); + c2 = BGETC(f); + c3 = BGETC(f); + c4 = BGETC(f); Bungetc(f); Bungetc(f); Bungetc(f); @@ -840,12 +898,12 @@ ldobj(Biobuf *f, char *pkg, int64 len, char *pn, char *file, int whence) /* skip over exports and other info -- ends with \n!\n */ import0 = Boffset(f); c1 = '\n'; // the last line ended in \n - c2 = Bgetc(f); - c3 = Bgetc(f); + c2 = BGETC(f); + c3 = BGETC(f); while(c1 != '\n' || c2 != '!' || c3 != '\n') { c1 = c2; c2 = c3; - c3 = Bgetc(f); + c3 = BGETC(f); if(c3 == Beof) goto eof; } @@ -899,7 +957,7 @@ _lookup(char *symb, int v, int creat) { Sym *s; char *p; - int32 h; + uint32 h; int c; h = v; @@ -1179,16 +1237,6 @@ zerosig(char *sp) s->sig = 0; } -int32 -Bget4(Biobuf *f) -{ - uchar p[4]; - - if(Bread(f, p, 4) != 4) - return 0; - return p[0] | (p[1] << 8) | (p[2] << 16) | (p[3] << 24); -} - void mywhatsys(void) { @@ -1334,100 +1382,192 @@ addsection(Segment *seg, char *name, int rwx) } void -pclntab(void) +addvarint(Sym *s, uint32 val) { - vlong oldpc; + 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; - int32 oldlc, v, s; - Sym *sym; - uchar *bp; + + // 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; - sym = lookup("pclntab", 0); - sym->type = SPCLNTAB; - sym->reachable = 1; - if(debug['s']) - return; + 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", (vlong)p->pc, "", p); + continue; + } - oldpc = INITTEXT; - oldlc = 0; - for(cursym = textp; cursym != nil; cursym = cursym->next) { - for(p = cursym->text; p != P; p = p->link) { - if(p->line == oldlc || p->as == ATEXT || p->as == ANOP) { - if(debug['O']) - Bprint(&bso, "%6llux %P\n", - (vlong)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, "\t\t%6d", lcsize); - v = (p->pc - oldpc) / MINLC; - while(v) { - s = 127; - if(v < 127) - s = v; - symgrow(sym, lcsize+1); - bp = sym->p + lcsize; - *bp = s+128; /* 129-255 +pc */ - if(debug['O']) - Bprint(&bso, " pc+%d*%d(%d)", s, MINLC, s+128); - v -= s; - lcsize++; - } - s = p->line - oldlc; - oldlc = p->line; - oldpc = p->pc + MINLC; - if(s > 64 || s < -64) { - symgrow(sym, lcsize+5); - bp = sym->p + lcsize; - *bp++ = 0; /* 0 vv +lc */ - *bp++ = s>>24; - *bp++ = s>>16; - *bp++ = s>>8; - *bp = s; - if(debug['O']) { - if(s > 0) - Bprint(&bso, " lc+%d(%d,%d)\n", - s, 0, s); - else - Bprint(&bso, " lc%d(%d,%d)\n", - s, 0, s); - Bprint(&bso, "%6llux %P\n", - (vlong)p->pc, p); - } - lcsize += 5; - continue; - } - symgrow(sym, lcsize+1); - bp = sym->p + lcsize; - if(s > 0) { - *bp = 0+s; /* 1-64 +lc */ - if(debug['O']) { - Bprint(&bso, " lc+%d(%d)\n", s, 0+s); - Bprint(&bso, "%6llux %P\n", - (vlong)p->pc, p); - } - } else { - *bp = 64-s; /* 65-128 -lc */ - if(debug['O']) { - Bprint(&bso, " lc%d(%d)\n", s, 64-s); - Bprint(&bso, "%6llux %P\n", - (vlong)p->pc, p); - } - } - lcsize++; + Bprint(&bso, "%6llux %6s %P\n", (vlong)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", (vlong)p->pc, val, p); + + if(started) { + 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(lcsize & 1) { - symgrow(sym, lcsize+1); - sym->p[lcsize] = 129; - lcsize++; + + if(started) { + if(debug['O']) + Bprint(&bso, "%6llux done\n", (vlong)func->value+func->size); + addvarint(dst, (func->value+func->size - pc) / MINLC); + addvarint(dst, 0); // terminator } - sym->size = lcsize; - lcsize = 0; - if(debug['v'] || debug['O']) - Bprint(&bso, "lcsize = %d\n", lcsize); - Bflush(&bso); + 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); + USED(sym); + + if(oldval == -1) // starting + oldval = 0; + if(phase == 0) + return oldval; + if(oldval + p->spadj < -10000 || oldval + p->spadj > 1100000000) { + 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) +{ + USED(sym); + + 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 @@ -1479,7 +1619,7 @@ le16(uchar *b) uint32 le32(uchar *b) { - return b[0] | b[1]<<8 | b[2]<<16 | b[3]<<24; + return b[0] | b[1]<<8 | b[2]<<16 | (uint32)b[3]<<24; } uint64 @@ -1497,7 +1637,7 @@ be16(uchar *b) uint32 be32(uchar *b) { - return b[0]<<24 | b[1]<<16 | b[2]<<8 | b[3]; + return (uint32)b[0]<<24 | b[1]<<16 | b[2]<<8 | b[3]; } uint64 @@ -1900,24 +2040,8 @@ genasmsym(void (*put)(Sym*, char*, int, vlong, vlong, int, Sym*)) if(s->text == nil) continue; - /* filenames first */ - for(a=s->autom; a; a=a->link) - if(a->type == D_FILE) - put(nil, a->asym->name, 'z', a->aoffset, 0, 0, 0); - else - if(a->type == D_FILE1) - put(nil, a->asym->name, 'Z', a->aoffset, 0, 0, 0); - put(s, s->name, 'T', s->value, s->size, s->version, s->gotype); - /* frame, locals, args, auto and param after */ - put(nil, ".frame", 'm', (uint32)s->text->to.offset+PtrSize, 0, 0, 0); - put(nil, ".locals", 'm', s->locals, 0, 0, 0); - if(s->text->textflag & NOSPLIT) - put(nil, ".args", 'm', ArgsSizeUnknown, 0, 0, 0); - else - put(nil, ".args", 'm', s->args, 0, 0, 0); - for(a=s->autom; a; a=a->link) { // Emit a or p according to actual offset, even if label is wrong. // This avoids negative offsets, which cannot be encoded. @@ -1947,7 +2071,7 @@ genasmsym(void (*put)(Sym*, char*, int, vlong, vlong, int, Sym*)) } } if(debug['v'] || debug['n']) - Bprint(&bso, "symsize = %ud\n", symsize); + Bprint(&bso, "%5.2f symsize = %ud\n", cputime(), symsize); Bflush(&bso); } @@ -1974,3 +2098,438 @@ 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; + + // NOTE(rsc): We used to do the copyhistfrog first and this + // condition was if(tmp[0] != '\0') to check for an empty string, + // implying that histfrogp == 0, implying that this is a history pop. + // However, on Windows in the misc/cgo test, the linker is + // presented with an ANAME corresponding to an empty string, + // that ANAME ends up being the only histfrog, and thus we have + // a situation where histfrogp > 0 (not a pop) but the path we find + // is the empty string. Really that shouldn't happen, but it doesn't + // seem to be bothering anyone yet, and it's easier to fix the condition + // to test histfrogp than to track down where that empty string is + // coming from. Probably it is coming from go tool pack's P command. + if(histfrogp > 0) { + tmp[0] = '\0'; + copyhistfrog(tmp, sizeof tmp); + 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); +} + +static int32 +ftabaddstring(Sym *ftab, char *s) +{ + int32 n, start; + + n = strlen(s)+1; + start = ftab->np; + symgrow(ftab, start+n+1); + strcpy((char*)ftab->p + start, s); + return start; +} + +// pclntab initializes the pclntab symbol with +// runtime function and file name information. +void +pclntab(void) +{ + Prog *p; + int32 i, n, nfunc, start, funcstart; + uint32 *havepc, *havefunc; + Sym *ftab, *s; + int32 npcdata, nfuncdata, off, end; + int64 funcdata_bytes; + + funcdata_bytes = 0; + ftab = lookup("pclntab", 0); + ftab->type = SPCLNTAB; + ftab->reachable = 1; + + // See golang.org/s/go12symtab for the format. Briefly: + // 8-byte header + // nfunc [PtrSize bytes] + // function table, alternating PC and offset to func struct [each entry PtrSize bytes] + // end PC [PtrSize bytes] + // offset to file table [4 bytes] + nfunc = 0; + for(cursym = textp; cursym != nil; cursym = cursym->next) + nfunc++; + symgrow(ftab, 8+PtrSize+nfunc*2*PtrSize+PtrSize+4); + setuint32(ftab, 0, 0xfffffffb); + setuint8(ftab, 6, MINLC); + setuint8(ftab, 7, PtrSize); + setuintxx(ftab, 8, nfunc, PtrSize); + + nfunc = 0; + for(cursym = textp; cursym != nil; cursym = cursym->next, nfunc++) { + funcstart = ftab->np; + funcstart += -ftab->np & (PtrSize-1); + + setaddr(ftab, 8+PtrSize+nfunc*2*PtrSize, cursym); + setuintxx(ftab, 8+PtrSize+nfunc*2*PtrSize+PtrSize, funcstart, PtrSize); + + 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; + } + + // fixed size of struct, checked below + off = funcstart; + end = funcstart + PtrSize + 3*4 + 5*4 + npcdata*4 + nfuncdata*PtrSize; + if(nfuncdata > 0 && (end&(PtrSize-1))) + end += 4; + symgrow(ftab, end); + + // entry uintptr + off = setaddr(ftab, off, cursym); + + // name int32 + off = setuint32(ftab, off, ftabaddstring(ftab, cursym->name)); + + // args int32 + // TODO: Move into funcinfo. + if(cursym->text == nil) + off = setuint32(ftab, off, ArgsSizeUnknown); + else + off = setuint32(ftab, off, cursym->args); + + // 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(ftab, off, 0); + else + off = setuint32(ftab, off, (uint32)cursym->text->to.offset+PtrSize); + + // pcsp table (offset int32) + off = addpctab(ftab, off, cursym, "pctospadj", pctospadj, 0); + + // pcfile table (offset int32) + off = addpctab(ftab, off, cursym, "pctofileline file", pctofileline, 0); + + // pcln table (offset int32) + off = addpctab(ftab, off, cursym, "pctofileline line", pctofileline, 1); + + // npcdata int32 + off = setuint32(ftab, off, npcdata); + + // nfuncdata int32 + off = setuint32(ftab, 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", p->from.offset); + 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(ftab, off, 0); + continue; + } + off = addpctab(ftab, 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(ftab, off+PtrSize*i, p->to.offset, PtrSize); + else { + // TODO: Dedup. + funcdata_bytes += p->to.sym->size; + setaddrplus(ftab, off+PtrSize*i, p->to.sym, p->to.offset); + } + } + } + off += nfuncdata*PtrSize; + } + + if(off != end) { + diag("bad math in functab: funcstart=%d off=%d but end=%d (npcdata=%d nfuncdata=%d)", funcstart, off, end, npcdata, nfuncdata); + errorexit(); + } + + // Final entry of table is just end pc. + if(cursym->next == nil) + setaddrplus(ftab, 8+PtrSize+(nfunc+1)*2*PtrSize, cursym, cursym->size); + } + + // Start file table. + start = ftab->np; + start += -ftab->np & (PtrSize-1); + setuint32(ftab, 8+PtrSize+nfunc*2*PtrSize+PtrSize, start); + + symgrow(ftab, start+(nhistfile+1)*4); + setuint32(ftab, start, nhistfile); + for(s = filesyms; s != S; s = s->next) + setuint32(ftab, start + s->value*4, ftabaddstring(ftab, s->name)); + + ftab->size = ftab->np; + + if(debug['v']) + Bprint(&bso, "%5.2f pclntab=%lld bytes, funcdata total %lld bytes\n", cputime(), (vlong)ftab->size, (vlong)funcdata_bytes); +} |