// 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))
}
}
}