summaryrefslogtreecommitdiff
path: root/src/cmd/ld/lib.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/cmd/ld/lib.c')
-rw-r--r--src/cmd/ld/lib.c614
1 files changed, 614 insertions, 0 deletions
diff --git a/src/cmd/ld/lib.c b/src/cmd/ld/lib.c
index 18943d5f3..1447f4d65 100644
--- a/src/cmd/ld/lib.c
+++ b/src/cmd/ld/lib.c
@@ -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;
+}