| // 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/obj" |
| "cmd/internal/objabi" |
| "cmd/internal/src" |
| "cmd/internal/sys" |
| "cmd/link/internal/sym" |
| "encoding/binary" |
| "fmt" |
| "log" |
| "os" |
| "path/filepath" |
| "strings" |
| ) |
| |
| func ftabaddstring(ftab *sym.Symbol, s string) int32 { |
| start := len(ftab.P) |
| ftab.Grow(int64(start + len(s) + 1)) // make room for s plus trailing NUL |
| copy(ftab.P[start:], s) |
| return int32(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) |
| } |
| |
| buf := make([]byte, binary.MaxVarintLen32) |
| newval := int32(-1) |
| var out sym.Pcdata |
| it := obj.NewPCIter(uint32(ctxt.Arch.MinLC)) |
| for it.Init(d.P); !it.Done; it.Next() { |
| // 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 |
| |
| // value |
| n := binary.PutVarint(buf, int64(dv)) |
| out.P = append(out.P, buf[:n]...) |
| |
| // pc delta |
| pc := (it.NextPC - it.PC) / it.PCScale |
| n = binary.PutUvarint(buf, uint64(pc)) |
| out.P = append(out.P, buf[:n]...) |
| } |
| |
| // terminating value delta |
| // we want to write varint-encoded 0, which is just 0 |
| out.P = append(out.P, 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_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. |
| return !s.Attr.Container() |
| } |
| |
| // 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] |
| |
| // Find container symbols and mark them as such. |
| for _, s := range ctxt.Textp { |
| if s.Outer != nil { |
| s.Outer.Attr |= sym.AttrContainer |
| } |
| } |
| |
| // Gather some basic stats and info. |
| var nfunc int32 |
| prevSect := ctxt.Textp[0].Sect |
| for _, s := range ctxt.Textp { |
| if !emitPcln(ctxt, s) { |
| continue |
| } |
| nfunc++ |
| if pclntabFirstFunc == nil { |
| pclntabFirstFunc = s |
| } |
| if s.Sect != prevSect { |
| // With multiple text sections, the external linker may insert functions |
| // between the sections, which are not known by Go. This leaves holes in |
| // the PC range covered by the func table. We need to generate an entry |
| // to mark the hole. |
| nfunc++ |
| prevSect = s.Sect |
| } |
| } |
| |
| 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(ftab, name) |
| funcnameoff[name] = nameoff |
| } |
| return nameoff |
| } |
| |
| pctaboff := make(map[string]uint32) |
| writepctab := func(off int32, p []byte) int32 { |
| start, ok := pctaboff[string(p)] |
| if !ok { |
| if len(p) > 0 { |
| start = uint32(len(ftab.P)) |
| ftab.AddBytes(p) |
| } |
| pctaboff[string(p)] = start |
| } |
| newoff := int32(ftab.SetUint32(ctxt.Arch, int64(off), start)) |
| return newoff |
| } |
| |
| nfunc = 0 // repurpose nfunc as a running index |
| prevFunc := ctxt.Textp[0] |
| for _, s := range ctxt.Textp { |
| if !emitPcln(ctxt, s) { |
| continue |
| } |
| |
| if s.Sect != prevFunc.Sect { |
| // With multiple text sections, there may be a hole here in the address |
| // space (see the comment above). We use an invalid funcoff value to |
| // mark the hole. |
| // See also runtime/symtab.go:findfunc |
| ftab.SetAddrPlus(ctxt.Arch, 8+int64(ctxt.Arch.PtrSize)+int64(nfunc)*2*int64(ctxt.Arch.PtrSize), prevFunc, prevFunc.Size) |
| ftab.SetUint(ctxt.Arch, 8+int64(ctxt.Arch.PtrSize)+int64(nfunc)*2*int64(ctxt.Arch.PtrSize)+int64(ctxt.Arch.PtrSize), ^uint64(0)) |
| nfunc++ |
| } |
| prevFunc = s |
| |
| pcln := s.FuncInfo |
| if pcln == nil { |
| pcln = &pclntabZpcln |
| } |
| |
| 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) // align to ptrsize |
| |
| 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.Type.IsDirectCall() && r.Sym != nil && r.Sym.Name == "runtime.deferreturn" { |
| if ctxt.Arch.Family == sys.Wasm { |
| deferreturn = lastWasmAddr - 1 |
| } 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) |
| switch ctxt.Arch.Family { |
| case sys.AMD64, sys.I386: |
| deferreturn-- |
| case sys.PPC64, sys.ARM, sys.ARM64, sys.MIPS, sys.MIPS64: |
| // no change |
| case sys.RISCV64: |
| // TODO(jsing): The JALR instruction is marked with |
| // R_CALLRISCV, whereas the actual reloc is currently |
| // one instruction earlier starting with the AUIPC. |
| deferreturn -= 4 |
| case sys.S390X: |
| deferreturn -= 2 |
| default: |
| panic(fmt.Sprint("Unhandled architecture:", ctxt.Arch.Family)) |
| } |
| } |
| 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 |
| it := obj.NewPCIter(uint32(ctxt.Arch.MinLC)) |
| for it.Init(pcln.Pcfile.P); !it.Done; it.Next() { |
| 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) |
| |
| inlTreeSym.SetUint16(ctxt.Arch, int64(i*20+0), uint16(call.Parent)) |
| inlTreeSym.SetUint8(ctxt.Arch, int64(i*20+2), uint8(objabi.GetFuncID(call.Func, ""))) |
| // 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 = writepctab(off, pcln.Pcsp.P) |
| off = writepctab(off, pcln.Pcfile.P) |
| off = writepctab(off, pcln.Pcline.P) |
| 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 = writepctab(off, pcln.Pcdata[i].P) |
| } |
| |
| // 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 { |
| dataoff := int64(off) + int64(ctxt.Arch.PtrSize)*int64(i) |
| if pcln.Funcdata[i] == nil { |
| ftab.SetUint(ctxt.Arch, dataoff, uint64(pcln.Funcdataoff[i])) |
| continue |
| } |
| // TODO: Dedup. |
| funcdataBytes += pcln.Funcdata[i].Size |
| ftab.SetAddrPlus(ctxt.Arch, dataoff, 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++ |
| } |
| |
| last := ctxt.Textp[len(ctxt.Textp)-1] |
| 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(ftab, s.Name))) |
| } |
| |
| ftab.Size = int64(len(ftab.P)) |
| |
| if ctxt.Debugvlog != 0 { |
| ctxt.Logf("pclntab=%d bytes, funcdata total %d bytes\n", 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)) |
| } |
| } |
| } |