// Copyright 2013 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package ld import ( "cmd/internal/objabi" "cmd/internal/src" "cmd/internal/sys" "cmd/link/internal/sym" "log" "os" "path/filepath" "strings" ) // iteration over encoded pcdata tables. func getvarint(pp *[]byte) uint32 { v := uint32(0) p := *pp for shift := 0; ; shift += 7 { v |= uint32(p[0]&0x7F) << uint(shift) tmp4 := p p = p[1:] if tmp4[0]&0x80 == 0 { break } } *pp = p return v } func pciternext(it *Pciter) { it.pc = it.nextpc if it.done != 0 { return } if -cap(it.p) >= -cap(it.d.P[len(it.d.P):]) { it.done = 1 return } // value delta v := getvarint(&it.p) if v == 0 && it.start == 0 { it.done = 1 return } it.start = 0 dv := int32(v>>1) ^ (int32(v<<31) >> 31) it.value += dv // pc delta v = getvarint(&it.p) it.nextpc = it.pc + v*it.pcscale } func pciterinit(ctxt *Link, it *Pciter, d *sym.Pcdata) { it.d = *d it.p = it.d.P it.pc = 0 it.nextpc = 0 it.value = -1 it.start = 1 it.done = 0 it.pcscale = uint32(ctxt.Arch.MinLC) pciternext(it) } func addvarint(d *sym.Pcdata, val uint32) { n := int32(0) for v := val; v >= 0x80; v >>= 7 { n++ } n++ old := len(d.P) for cap(d.P) < len(d.P)+int(n) { d.P = append(d.P[:cap(d.P)], 0) } d.P = d.P[:old+int(n)] p := d.P[old:] var v uint32 for v = val; v >= 0x80; v >>= 7 { p[0] = byte(v | 0x80) p = p[1:] } p[0] = byte(v) } func addpctab(ctxt *Link, ftab *sym.Symbol, off int32, d *sym.Pcdata) int32 { var start int32 if len(d.P) > 0 { start = int32(len(ftab.P)) ftab.AddBytes(d.P) } return int32(ftab.SetUint32(ctxt.Arch, int64(off), uint32(start))) } func ftabaddstring(ctxt *Link, ftab *sym.Symbol, s string) int32 { n := int32(len(s)) + 1 start := int32(len(ftab.P)) ftab.Grow(int64(start) + int64(n) + 1) copy(ftab.P[start:], s) return start } // numberfile assigns a file number to the file if it hasn't been assigned already. func numberfile(ctxt *Link, file *sym.Symbol) { if file.Type != sym.SFILEPATH { ctxt.Filesyms = append(ctxt.Filesyms, file) file.Value = int64(len(ctxt.Filesyms)) file.Type = sym.SFILEPATH path := file.Name[len(src.FileSymPrefix):] file.Name = expandGoroot(path) } } func renumberfiles(ctxt *Link, files []*sym.Symbol, d *sym.Pcdata) { // Give files numbers. for _, f := range files { numberfile(ctxt, f) } newval := int32(-1) var out sym.Pcdata var it Pciter for pciterinit(ctxt, &it, d); it.done == 0; pciternext(&it) { // value delta oldval := it.value var val int32 if oldval == -1 { val = -1 } else { if oldval < 0 || oldval >= int32(len(files)) { log.Fatalf("bad pcdata %d", oldval) } val = int32(files[oldval].Value) } dv := val - newval newval = val v := (uint32(dv) << 1) ^ uint32(dv>>31) addvarint(&out, v) // pc delta addvarint(&out, (it.nextpc-it.pc)/it.pcscale) } // terminating value delta addvarint(&out, 0) *d = out } // onlycsymbol reports whether this is a symbol that is referenced by C code. func onlycsymbol(s *sym.Symbol) bool { switch s.Name { case "_cgo_topofstack", "_cgo_panic", "crosscall2": return true } if strings.HasPrefix(s.Name, "_cgoexp_") { return true } return false } func emitPcln(ctxt *Link, s *sym.Symbol) bool { if s == nil { return true } if ctxt.BuildMode == BuildModePlugin && ctxt.HeadType == objabi.Hdarwin && onlycsymbol(s) { return false } // We want to generate func table entries only for the "lowest level" symbols, // not containers of subsymbols. if s.Attr.Container() { return true } return true } // pclntab initializes the pclntab symbol with // runtime function and file name information. var pclntabZpcln sym.FuncInfo // These variables are used to initialize runtime.firstmoduledata, see symtab.go:symtab. var pclntabNfunc int32 var pclntabFiletabOffset int32 var pclntabPclntabOffset int32 var pclntabFirstFunc *sym.Symbol var pclntabLastFunc *sym.Symbol func (ctxt *Link) pclntab() { funcdataBytes := int64(0) ftab := ctxt.Syms.Lookup("runtime.pclntab", 0) ftab.Type = sym.SPCLNTAB ftab.Attr |= sym.AttrReachable // See golang.org/s/go12symtab for the format. Briefly: // 8-byte header // nfunc [thearch.ptrsize bytes] // function table, alternating PC and offset to func struct [each entry thearch.ptrsize bytes] // end PC [thearch.ptrsize bytes] // offset to file table [4 bytes] nfunc := int32(0) // Find container symbols and mark them as such. for _, s := range ctxt.Textp { if s.Outer != nil { s.Outer.Attr |= sym.AttrContainer } } for _, s := range ctxt.Textp { if emitPcln(ctxt, s) { nfunc++ } } pclntabNfunc = nfunc ftab.Grow(8 + int64(ctxt.Arch.PtrSize) + int64(nfunc)*2*int64(ctxt.Arch.PtrSize) + int64(ctxt.Arch.PtrSize) + 4) ftab.SetUint32(ctxt.Arch, 0, 0xfffffffb) ftab.SetUint8(ctxt.Arch, 6, uint8(ctxt.Arch.MinLC)) ftab.SetUint8(ctxt.Arch, 7, uint8(ctxt.Arch.PtrSize)) ftab.SetUint(ctxt.Arch, 8, uint64(nfunc)) pclntabPclntabOffset = int32(8 + ctxt.Arch.PtrSize) funcnameoff := make(map[string]int32) nameToOffset := func(name string) int32 { nameoff, ok := funcnameoff[name] if !ok { nameoff = ftabaddstring(ctxt, ftab, name) funcnameoff[name] = nameoff } return nameoff } nfunc = 0 var last *sym.Symbol for _, s := range ctxt.Textp { last = s if !emitPcln(ctxt, s) { continue } pcln := s.FuncInfo if pcln == nil { pcln = &pclntabZpcln } if pclntabFirstFunc == nil { pclntabFirstFunc = s } if len(pcln.InlTree) > 0 { if len(pcln.Pcdata) <= objabi.PCDATA_InlTreeIndex { // Create inlining pcdata table. pcdata := make([]sym.Pcdata, objabi.PCDATA_InlTreeIndex+1) copy(pcdata, pcln.Pcdata) pcln.Pcdata = pcdata } if len(pcln.Funcdataoff) <= objabi.FUNCDATA_InlTree { // Create inline tree funcdata. funcdata := make([]*sym.Symbol, objabi.FUNCDATA_InlTree+1) funcdataoff := make([]int64, objabi.FUNCDATA_InlTree+1) copy(funcdata, pcln.Funcdata) copy(funcdataoff, pcln.Funcdataoff) pcln.Funcdata = funcdata pcln.Funcdataoff = funcdataoff } } funcstart := int32(len(ftab.P)) funcstart += int32(-len(ftab.P)) & (int32(ctxt.Arch.PtrSize) - 1) ftab.SetAddr(ctxt.Arch, 8+int64(ctxt.Arch.PtrSize)+int64(nfunc)*2*int64(ctxt.Arch.PtrSize), s) ftab.SetUint(ctxt.Arch, 8+int64(ctxt.Arch.PtrSize)+int64(nfunc)*2*int64(ctxt.Arch.PtrSize)+int64(ctxt.Arch.PtrSize), uint64(funcstart)) // Write runtime._func. Keep in sync with ../../../../runtime/runtime2.go:/_func // and package debug/gosym. // fixed size of struct, checked below off := funcstart end := funcstart + int32(ctxt.Arch.PtrSize) + 3*4 + 5*4 + int32(len(pcln.Pcdata))*4 + int32(len(pcln.Funcdata))*int32(ctxt.Arch.PtrSize) if len(pcln.Funcdata) > 0 && (end&int32(ctxt.Arch.PtrSize-1) != 0) { end += 4 } ftab.Grow(int64(end)) // entry uintptr off = int32(ftab.SetAddr(ctxt.Arch, int64(off), s)) // name int32 nameoff := nameToOffset(s.Name) off = int32(ftab.SetUint32(ctxt.Arch, int64(off), uint32(nameoff))) // args int32 // TODO: Move into funcinfo. args := uint32(0) if s.FuncInfo != nil { args = uint32(s.FuncInfo.Args) } off = int32(ftab.SetUint32(ctxt.Arch, int64(off), args)) // deferreturn deferreturn := uint32(0) lastWasmAddr := uint32(0) for _, r := range s.R { if ctxt.Arch.Family == sys.Wasm && r.Type == objabi.R_ADDR { // Wasm does not have a live variable set at the deferreturn // call itself. Instead it has one identified by the // resumption point immediately preceding the deferreturn. // The wasm code has a R_ADDR relocation which is used to // set the resumption point to PC_B. lastWasmAddr = uint32(r.Add) } if r.Sym != nil && r.Sym.Name == "runtime.deferreturn" && r.Add == 0 { if ctxt.Arch.Family == sys.Wasm { deferreturn = lastWasmAddr } else { // Note: the relocation target is in the call instruction, but // is not necessarily the whole instruction (for instance, on // x86 the relocation applies to bytes [1:5] of the 5 byte call // instruction). deferreturn = uint32(r.Off) } break // only need one } } off = int32(ftab.SetUint32(ctxt.Arch, int64(off), deferreturn)) if pcln != &pclntabZpcln { renumberfiles(ctxt, pcln.File, &pcln.Pcfile) if false { // Sanity check the new numbering var it Pciter for pciterinit(ctxt, &it, &pcln.Pcfile); it.done == 0; pciternext(&it) { if it.value < 1 || it.value > int32(len(ctxt.Filesyms)) { Errorf(s, "bad file number in pcfile: %d not in range [1, %d]\n", it.value, len(ctxt.Filesyms)) errorexit() } } } } if len(pcln.InlTree) > 0 { inlTreeSym := ctxt.Syms.Lookup("inltree."+s.Name, 0) inlTreeSym.Type = sym.SRODATA inlTreeSym.Attr |= sym.AttrReachable | sym.AttrDuplicateOK for i, call := range pcln.InlTree { // Usually, call.File is already numbered since the file // shows up in the Pcfile table. However, two inlined calls // might overlap exactly so that only the innermost file // appears in the Pcfile table. In that case, this assigns // the outer file a number. numberfile(ctxt, call.File) nameoff := nameToOffset(call.Func.Name) inlTreeSym.SetUint16(ctxt.Arch, int64(i*20+0), uint16(call.Parent)) inlTreeSym.SetUint8(ctxt.Arch, int64(i*20+2), uint8(objabi.GetFuncID(call.Func.Name, call.Func.File))) // byte 3 is unused inlTreeSym.SetUint32(ctxt.Arch, int64(i*20+4), uint32(call.File.Value)) inlTreeSym.SetUint32(ctxt.Arch, int64(i*20+8), uint32(call.Line)) inlTreeSym.SetUint32(ctxt.Arch, int64(i*20+12), uint32(nameoff)) inlTreeSym.SetUint32(ctxt.Arch, int64(i*20+16), uint32(call.ParentPC)) } pcln.Funcdata[objabi.FUNCDATA_InlTree] = inlTreeSym pcln.Pcdata[objabi.PCDATA_InlTreeIndex] = pcln.Pcinline } // pcdata off = addpctab(ctxt, ftab, off, &pcln.Pcsp) off = addpctab(ctxt, ftab, off, &pcln.Pcfile) off = addpctab(ctxt, ftab, off, &pcln.Pcline) off = int32(ftab.SetUint32(ctxt.Arch, int64(off), uint32(len(pcln.Pcdata)))) // funcID uint8 var file string if s.FuncInfo != nil && len(s.FuncInfo.File) > 0 { file = s.FuncInfo.File[0].Name } funcID := objabi.GetFuncID(s.Name, file) off = int32(ftab.SetUint8(ctxt.Arch, int64(off), uint8(funcID))) // unused off += 2 // nfuncdata must be the final entry. off = int32(ftab.SetUint8(ctxt.Arch, int64(off), uint8(len(pcln.Funcdata)))) for i := range pcln.Pcdata { off = addpctab(ctxt, ftab, off, &pcln.Pcdata[i]) } // funcdata, must be pointer-aligned and we're only int32-aligned. // Missing funcdata will be 0 (nil pointer). if len(pcln.Funcdata) > 0 { if off&int32(ctxt.Arch.PtrSize-1) != 0 { off += 4 } for i := range pcln.Funcdata { if pcln.Funcdata[i] == nil { ftab.SetUint(ctxt.Arch, int64(off)+int64(ctxt.Arch.PtrSize)*int64(i), uint64(pcln.Funcdataoff[i])) } else { // TODO: Dedup. funcdataBytes += pcln.Funcdata[i].Size ftab.SetAddrPlus(ctxt.Arch, int64(off)+int64(ctxt.Arch.PtrSize)*int64(i), pcln.Funcdata[i], pcln.Funcdataoff[i]) } } off += int32(len(pcln.Funcdata)) * int32(ctxt.Arch.PtrSize) } if off != end { Errorf(s, "bad math in functab: funcstart=%d off=%d but end=%d (npcdata=%d nfuncdata=%d ptrsize=%d)", funcstart, off, end, len(pcln.Pcdata), len(pcln.Funcdata), ctxt.Arch.PtrSize) errorexit() } nfunc++ } pclntabLastFunc = last // Final entry of table is just end pc. ftab.SetAddrPlus(ctxt.Arch, 8+int64(ctxt.Arch.PtrSize)+int64(nfunc)*2*int64(ctxt.Arch.PtrSize), last, last.Size) // Start file table. start := int32(len(ftab.P)) start += int32(-len(ftab.P)) & (int32(ctxt.Arch.PtrSize) - 1) pclntabFiletabOffset = start ftab.SetUint32(ctxt.Arch, 8+int64(ctxt.Arch.PtrSize)+int64(nfunc)*2*int64(ctxt.Arch.PtrSize)+int64(ctxt.Arch.PtrSize), uint32(start)) ftab.Grow(int64(start) + (int64(len(ctxt.Filesyms))+1)*4) ftab.SetUint32(ctxt.Arch, int64(start), uint32(len(ctxt.Filesyms)+1)) for i := len(ctxt.Filesyms) - 1; i >= 0; i-- { s := ctxt.Filesyms[i] ftab.SetUint32(ctxt.Arch, int64(start)+s.Value*4, uint32(ftabaddstring(ctxt, ftab, s.Name))) } ftab.Size = int64(len(ftab.P)) if ctxt.Debugvlog != 0 { ctxt.Logf("%5.2f pclntab=%d bytes, funcdata total %d bytes\n", Cputime(), ftab.Size, funcdataBytes) } } func gorootFinal() string { root := objabi.GOROOT if final := os.Getenv("GOROOT_FINAL"); final != "" { root = final } return root } func expandGoroot(s string) string { const n = len("$GOROOT") if len(s) >= n+1 && s[:n] == "$GOROOT" && (s[n] == '/' || s[n] == '\\') { return filepath.ToSlash(filepath.Join(gorootFinal(), s[n:])) } return s } const ( BUCKETSIZE = 256 * MINFUNC SUBBUCKETS = 16 SUBBUCKETSIZE = BUCKETSIZE / SUBBUCKETS NOIDX = 0x7fffffff ) // findfunctab generates a lookup table to quickly find the containing // function for a pc. See src/runtime/symtab.go:findfunc for details. func (ctxt *Link) findfunctab() { t := ctxt.Syms.Lookup("runtime.findfunctab", 0) t.Type = sym.SRODATA t.Attr |= sym.AttrReachable t.Attr |= sym.AttrLocal // find min and max address min := ctxt.Textp[0].Value lastp := ctxt.Textp[len(ctxt.Textp)-1] max := lastp.Value + lastp.Size // for each subbucket, compute the minimum of all symbol indexes // that map to that subbucket. n := int32((max - min + SUBBUCKETSIZE - 1) / SUBBUCKETSIZE) indexes := make([]int32, n) for i := int32(0); i < n; i++ { indexes[i] = NOIDX } idx := int32(0) for i, s := range ctxt.Textp { if !emitPcln(ctxt, s) { continue } p := s.Value var e *sym.Symbol i++ if i < len(ctxt.Textp) { e = ctxt.Textp[i] } for !emitPcln(ctxt, e) && i < len(ctxt.Textp) { e = ctxt.Textp[i] i++ } q := max if e != nil { q = e.Value } //print("%d: [%lld %lld] %s\n", idx, p, q, s->name); for ; p < q; p += SUBBUCKETSIZE { i = int((p - min) / SUBBUCKETSIZE) if indexes[i] > idx { indexes[i] = idx } } i = int((q - 1 - min) / SUBBUCKETSIZE) if indexes[i] > idx { indexes[i] = idx } idx++ } // allocate table nbuckets := int32((max - min + BUCKETSIZE - 1) / BUCKETSIZE) t.Grow(4*int64(nbuckets) + int64(n)) // fill in table for i := int32(0); i < nbuckets; i++ { base := indexes[i*SUBBUCKETS] if base == NOIDX { Errorf(nil, "hole in findfunctab") } t.SetUint32(ctxt.Arch, int64(i)*(4+SUBBUCKETS), uint32(base)) for j := int32(0); j < SUBBUCKETS && i*SUBBUCKETS+j < n; j++ { idx = indexes[i*SUBBUCKETS+j] if idx == NOIDX { Errorf(nil, "hole in findfunctab") } if idx-base >= 256 { Errorf(nil, "too many functions in a findfunc bucket! %d/%d %d %d", i, nbuckets, j, idx-base) } t.SetUint8(ctxt.Arch, int64(i)*(4+SUBBUCKETS)+4+int64(j), uint8(idx-base)) } } }