| // 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/loader" |
| "cmd/link/internal/sym" |
| "encoding/binary" |
| "fmt" |
| "log" |
| "os" |
| "path/filepath" |
| "strings" |
| ) |
| |
| // pclnState holds state information used during pclntab generation. |
| // Here 'ldr' is just a pointer to the context's loader, 'container' |
| // is a bitmap holding whether a given symbol index is an outer or |
| // container symbol, 'deferReturnSym' is the index for the symbol |
| // "runtime.deferreturn", 'nameToOffset' is a helper function for |
| // capturing function names, 'numberedFiles' records the file number |
| // assigned to a given file symbol, 'filepaths' is a slice of |
| // expanded paths (indexed by file number). |
| type pclnState struct { |
| ldr *loader.Loader |
| container loader.Bitmap |
| deferReturnSym loader.Sym |
| nameToOffset func(name string) int32 |
| numberedFiles map[loader.Sym]int64 |
| filepaths []string |
| } |
| |
| func makepclnState(ctxt *Link) pclnState { |
| ldr := ctxt.loader |
| drs := ldr.Lookup("runtime.deferreturn", sym.SymVerABIInternal) |
| return pclnState{ |
| container: loader.MakeBitmap(ldr.NSym()), |
| ldr: ldr, |
| deferReturnSym: drs, |
| numberedFiles: make(map[loader.Sym]int64), |
| // NB: initial entry in filepaths below is to reserve the zero value, |
| // so that when we do a map lookup in numberedFiles fails, it will not |
| // return a value slot in filepaths. |
| filepaths: []string{""}, |
| } |
| } |
| |
| func (state *pclnState) ftabaddstring(ftab *loader.SymbolBuilder, s string) int32 { |
| start := len(ftab.Data()) |
| ftab.Grow(int64(start + len(s) + 1)) // make room for s plus trailing NUL |
| ftd := ftab.Data() |
| copy(ftd[start:], s) |
| return int32(start) |
| } |
| |
| // numberfile assigns a file number to the file if it hasn't been assigned already. |
| func (state *pclnState) numberfile(file loader.Sym) int64 { |
| if val, ok := state.numberedFiles[file]; ok { |
| return val |
| } |
| sn := state.ldr.SymName(file) |
| path := sn[len(src.FileSymPrefix):] |
| val := int64(len(state.filepaths)) |
| state.numberedFiles[file] = val |
| state.filepaths = append(state.filepaths, expandGoroot(path)) |
| return val |
| } |
| |
| func (state *pclnState) fileVal(file loader.Sym) int64 { |
| if val, ok := state.numberedFiles[file]; ok { |
| return val |
| } |
| panic("should have been numbered first") |
| } |
| |
| func (state *pclnState) renumberfiles(ctxt *Link, fi loader.FuncInfo, d *sym.Pcdata) { |
| // Give files numbers. |
| nf := fi.NumFile() |
| for i := uint32(0); i < nf; i++ { |
| state.numberfile(fi.File(int(i))) |
| } |
| |
| 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(nf) { |
| log.Fatalf("bad pcdata %d", oldval) |
| } |
| val = int32(state.fileVal(fi.File(int(oldval)))) |
| } |
| |
| 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 looks at a symbol's name to report whether this is a |
| // symbol that is referenced by C code |
| func onlycsymbol(sname string) bool { |
| switch sname { |
| case "_cgo_topofstack", "__cgo_topofstack", "_cgo_panic", "crosscall2": |
| return true |
| } |
| if strings.HasPrefix(sname, "_cgoexp_") { |
| return true |
| } |
| return false |
| } |
| |
| func emitPcln(ctxt *Link, s loader.Sym, container loader.Bitmap) bool { |
| if ctxt.BuildMode == BuildModePlugin && ctxt.HeadType == objabi.Hdarwin && onlycsymbol(ctxt.loader.SymName(s)) { |
| return false |
| } |
| // We want to generate func table entries only for the "lowest |
| // level" symbols, not containers of subsymbols. |
| return !container.Has(s) |
| } |
| |
| func (state *pclnState) computeDeferReturn(target *Target, s loader.Sym) uint32 { |
| deferreturn := uint32(0) |
| lastWasmAddr := uint32(0) |
| |
| relocs := state.ldr.Relocs(s) |
| for ri := 0; ri < relocs.Count(); ri++ { |
| r := relocs.At2(ri) |
| if target.IsWasm() && 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() == state.deferReturnSym || state.ldr.IsDeferReturnTramp(r.Sym())) { |
| if target.IsWasm() { |
| 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 target.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:", target.Arch.Family)) |
| } |
| } |
| break // only need one |
| } |
| } |
| return deferreturn |
| } |
| |
| // genInlTreeSym generates the InlTree sym for a function with the |
| // specified FuncInfo. |
| func (state *pclnState) genInlTreeSym(fi loader.FuncInfo, arch *sys.Arch) loader.Sym { |
| ldr := state.ldr |
| its := ldr.CreateExtSym("", 0) |
| inlTreeSym := ldr.MakeSymbolUpdater(its) |
| // Note: the generated symbol is given a type of sym.SGOFUNC, as a |
| // signal to the symtab() phase that it needs to be grouped in with |
| // other similar symbols (gcdata, etc); the dodata() phase will |
| // eventually switch the type back to SRODATA. |
| inlTreeSym.SetType(sym.SGOFUNC) |
| ldr.SetAttrReachable(its, true) |
| ninl := fi.NumInlTree() |
| for i := 0; i < int(ninl); i++ { |
| call := fi.InlTree(i) |
| // 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. |
| val := state.numberfile(call.File) |
| fn := ldr.SymName(call.Func) |
| nameoff := state.nameToOffset(fn) |
| |
| inlTreeSym.SetUint16(arch, int64(i*20+0), uint16(call.Parent)) |
| inlTreeSym.SetUint8(arch, int64(i*20+2), uint8(objabi.GetFuncID(fn, ""))) |
| // byte 3 is unused |
| inlTreeSym.SetUint32(arch, int64(i*20+4), uint32(val)) |
| inlTreeSym.SetUint32(arch, int64(i*20+8), uint32(call.Line)) |
| inlTreeSym.SetUint32(arch, int64(i*20+12), uint32(nameoff)) |
| inlTreeSym.SetUint32(arch, int64(i*20+16), uint32(call.ParentPC)) |
| } |
| return its |
| } |
| |
| // pclntab initializes the pclntab symbol with |
| // runtime function and file name information. |
| |
| // These variables are used to initialize runtime.firstmoduledata, see symtab.go:symtab. |
| var pclntabNfunc int32 |
| var pclntabFiletabOffset int32 |
| var pclntabPclntabOffset int32 |
| var pclntabFirstFunc loader.Sym |
| var pclntabLastFunc loader.Sym |
| |
| // pclntab generates the pcln table for the link output. Return value |
| // is a bitmap indexed by global symbol that marks 'container' text |
| // symbols, e.g. the set of all symbols X such that Outer(S) = X for |
| // some other text symbol S. |
| func (ctxt *Link) pclntab() loader.Bitmap { |
| funcdataBytes := int64(0) |
| ldr := ctxt.loader |
| ftabsym := ldr.LookupOrCreateSym("runtime.pclntab", 0) |
| ftab := ldr.MakeSymbolUpdater(ftabsym) |
| ftab.SetType(sym.SPCLNTAB) |
| ldr.SetAttrReachable(ftabsym, true) |
| |
| state := makepclnState(ctxt) |
| |
| // 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.Textp2 { |
| outer := ldr.OuterSym(s) |
| if outer != 0 { |
| state.container.Set(outer) |
| } |
| } |
| |
| // Gather some basic stats and info. |
| var nfunc int32 |
| prevSect := ldr.SymSect(ctxt.Textp2[0]) |
| for _, s := range ctxt.Textp2 { |
| if !emitPcln(ctxt, s, state.container) { |
| continue |
| } |
| nfunc++ |
| if pclntabFirstFunc == 0 { |
| pclntabFirstFunc = s |
| } |
| ss := ldr.SymSect(s) |
| if ss != 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 = ss |
| } |
| } |
| |
| 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) |
| |
| szHint := len(ctxt.Textp2) * 2 |
| funcnameoff := make(map[string]int32, szHint) |
| nameToOffset := func(name string) int32 { |
| nameoff, ok := funcnameoff[name] |
| if !ok { |
| nameoff = state.ftabaddstring(ftab, name) |
| funcnameoff[name] = nameoff |
| } |
| return nameoff |
| } |
| state.nameToOffset = nameToOffset |
| |
| pctaboff := make(map[string]uint32, szHint) |
| writepctab := func(off int32, p []byte) int32 { |
| start, ok := pctaboff[string(p)] |
| if !ok { |
| if len(p) > 0 { |
| start = uint32(len(ftab.Data())) |
| ftab.AddBytes(p) |
| } |
| pctaboff[string(p)] = start |
| } |
| newoff := int32(ftab.SetUint32(ctxt.Arch, int64(off), start)) |
| return newoff |
| } |
| |
| setAddr := (*loader.SymbolBuilder).SetAddrPlus |
| if ctxt.IsExe() && ctxt.IsInternal() && !ctxt.DynlinkingGo() { |
| // Internal linking static executable. At this point the function |
| // addresses are known, so we can just use them instead of emitting |
| // relocations. |
| // For other cases we are generating a relocatable binary so we |
| // still need to emit relocations. |
| // |
| // Also not do this optimization when using plugins (DynlinkingGo), |
| // as on darwin it does weird things with runtime.etext symbol. |
| // TODO: remove the weird thing and remove this condition. |
| setAddr = func(s *loader.SymbolBuilder, arch *sys.Arch, off int64, tgt loader.Sym, add int64) int64 { |
| if v := ldr.SymValue(tgt); v != 0 { |
| return s.SetUint(arch, off, uint64(v+add)) |
| } |
| return s.SetAddrPlus(arch, off, tgt, add) |
| } |
| } |
| |
| pcsp := sym.Pcdata{} |
| pcfile := sym.Pcdata{} |
| pcline := sym.Pcdata{} |
| pcdata := []sym.Pcdata{} |
| funcdata := []loader.Sym{} |
| funcdataoff := []int64{} |
| |
| nfunc = 0 // repurpose nfunc as a running index |
| prevFunc := ctxt.Textp2[0] |
| for _, s := range ctxt.Textp2 { |
| if !emitPcln(ctxt, s, state.container) { |
| continue |
| } |
| |
| thisSect := ldr.SymSect(s) |
| prevSect := ldr.SymSect(prevFunc) |
| if thisSect != prevSect { |
| // 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 |
| prevFuncSize := int64(ldr.SymSize(prevFunc)) |
| setAddr(ftab, ctxt.Arch, 8+int64(ctxt.Arch.PtrSize)+int64(nfunc)*2*int64(ctxt.Arch.PtrSize), prevFunc, prevFuncSize) |
| 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 |
| |
| pcsp.P = pcsp.P[:0] |
| pcline.P = pcline.P[:0] |
| pcfile.P = pcfile.P[:0] |
| pcdata = pcdata[:0] |
| funcdataoff = funcdataoff[:0] |
| funcdata = funcdata[:0] |
| fi := ldr.FuncInfo(s) |
| if fi.Valid() { |
| fi.Preload() |
| npc := fi.NumPcdata() |
| for i := uint32(0); i < npc; i++ { |
| pcdata = append(pcdata, sym.Pcdata{P: fi.Pcdata(int(i))}) |
| } |
| nfd := fi.NumFuncdataoff() |
| for i := uint32(0); i < nfd; i++ { |
| funcdataoff = append(funcdataoff, fi.Funcdataoff(int(i))) |
| } |
| funcdata = fi.Funcdata(funcdata) |
| } |
| |
| if fi.Valid() && fi.NumInlTree() > 0 { |
| |
| if len(pcdata) <= objabi.PCDATA_InlTreeIndex { |
| // Create inlining pcdata table. |
| newpcdata := make([]sym.Pcdata, objabi.PCDATA_InlTreeIndex+1) |
| copy(newpcdata, pcdata) |
| pcdata = newpcdata |
| } |
| |
| if len(funcdataoff) <= objabi.FUNCDATA_InlTree { |
| // Create inline tree funcdata. |
| newfuncdata := make([]loader.Sym, objabi.FUNCDATA_InlTree+1) |
| newfuncdataoff := make([]int64, objabi.FUNCDATA_InlTree+1) |
| copy(newfuncdata, funcdata) |
| copy(newfuncdataoff, funcdataoff) |
| funcdata = newfuncdata |
| funcdataoff = newfuncdataoff |
| } |
| } |
| |
| dSize := len(ftab.Data()) |
| funcstart := int32(dSize) |
| funcstart += int32(-dSize) & (int32(ctxt.Arch.PtrSize) - 1) // align to ptrsize |
| |
| setAddr(ftab, ctxt.Arch, 8+int64(ctxt.Arch.PtrSize)+int64(nfunc)*2*int64(ctxt.Arch.PtrSize), s, 0) |
| 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(pcdata))*4 + int32(len(funcdata))*int32(ctxt.Arch.PtrSize) |
| if len(funcdata) > 0 && (end&int32(ctxt.Arch.PtrSize-1) != 0) { |
| end += 4 |
| } |
| ftab.Grow(int64(end)) |
| |
| // entry uintptr |
| off = int32(setAddr(ftab, ctxt.Arch, int64(off), s, 0)) |
| |
| // name int32 |
| sn := ldr.SymName(s) |
| nameoff := nameToOffset(sn) |
| off = int32(ftab.SetUint32(ctxt.Arch, int64(off), uint32(nameoff))) |
| |
| // args int32 |
| // TODO: Move into funcinfo. |
| args := uint32(0) |
| if fi.Valid() { |
| args = uint32(fi.Args()) |
| } |
| off = int32(ftab.SetUint32(ctxt.Arch, int64(off), args)) |
| |
| // deferreturn |
| deferreturn := state.computeDeferReturn(&ctxt.Target, s) |
| off = int32(ftab.SetUint32(ctxt.Arch, int64(off), deferreturn)) |
| |
| if fi.Valid() { |
| pcsp = sym.Pcdata{P: fi.Pcsp()} |
| pcfile = sym.Pcdata{P: fi.Pcfile()} |
| pcline = sym.Pcdata{P: fi.Pcline()} |
| state.renumberfiles(ctxt, fi, &pcfile) |
| if false { |
| // Sanity check the new numbering |
| it := obj.NewPCIter(uint32(ctxt.Arch.MinLC)) |
| for it.Init(pcfile.P); !it.Done; it.Next() { |
| if it.Value < 1 || it.Value > int32(len(state.numberedFiles)) { |
| ctxt.Errorf(s, "bad file number in pcfile: %d not in range [1, %d]\n", it.Value, len(state.numberedFiles)) |
| errorexit() |
| } |
| } |
| } |
| } |
| |
| if fi.Valid() && fi.NumInlTree() > 0 { |
| its := state.genInlTreeSym(fi, ctxt.Arch) |
| funcdata[objabi.FUNCDATA_InlTree] = its |
| pcdata[objabi.PCDATA_InlTreeIndex] = sym.Pcdata{P: fi.Pcinline()} |
| } |
| |
| // pcdata |
| off = writepctab(off, pcsp.P) |
| off = writepctab(off, pcfile.P) |
| off = writepctab(off, pcline.P) |
| off = int32(ftab.SetUint32(ctxt.Arch, int64(off), uint32(len(pcdata)))) |
| |
| // funcID uint8 |
| var file string |
| if fi.Valid() && fi.NumFile() > 0 { |
| filesymname := ldr.SymName(fi.File(0)) |
| file = filesymname[len(src.FileSymPrefix):] |
| } |
| funcID := objabi.GetFuncID(sn, 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(funcdata)))) |
| for i := range pcdata { |
| off = writepctab(off, pcdata[i].P) |
| } |
| |
| // funcdata, must be pointer-aligned and we're only int32-aligned. |
| // Missing funcdata will be 0 (nil pointer). |
| if len(funcdata) > 0 { |
| if off&int32(ctxt.Arch.PtrSize-1) != 0 { |
| off += 4 |
| } |
| for i := range funcdata { |
| dataoff := int64(off) + int64(ctxt.Arch.PtrSize)*int64(i) |
| if funcdata[i] == 0 { |
| ftab.SetUint(ctxt.Arch, dataoff, uint64(funcdataoff[i])) |
| continue |
| } |
| // TODO: Dedup. |
| funcdataBytes += int64(len(ldr.Data(funcdata[i]))) |
| setAddr(ftab, ctxt.Arch, dataoff, funcdata[i], funcdataoff[i]) |
| } |
| off += int32(len(funcdata)) * int32(ctxt.Arch.PtrSize) |
| } |
| |
| if off != end { |
| ctxt.Errorf(s, "bad math in functab: funcstart=%d off=%d but end=%d (npcdata=%d nfuncdata=%d ptrsize=%d)", funcstart, off, end, len(pcdata), len(funcdata), ctxt.Arch.PtrSize) |
| errorexit() |
| } |
| |
| nfunc++ |
| } |
| |
| last := ctxt.Textp2[len(ctxt.Textp2)-1] |
| pclntabLastFunc = last |
| // Final entry of table is just end pc. |
| setAddr(ftab, ctxt.Arch, 8+int64(ctxt.Arch.PtrSize)+int64(nfunc)*2*int64(ctxt.Arch.PtrSize), last, ldr.SymSize(last)) |
| |
| // Start file table. |
| dSize := len(ftab.Data()) |
| start := int32(dSize) |
| start += int32(-dSize) & (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)) |
| |
| nf := len(state.numberedFiles) |
| ftab.Grow(int64(start) + int64((nf+1)*4)) |
| ftab.SetUint32(ctxt.Arch, int64(start), uint32(nf+1)) |
| for i := nf; i > 0; i-- { |
| path := state.filepaths[i] |
| val := int64(i) |
| ftab.SetUint32(ctxt.Arch, int64(start)+val*4, uint32(state.ftabaddstring(ftab, path))) |
| } |
| |
| ftab.SetSize(int64(len(ftab.Data()))) |
| |
| ctxt.NumFilesyms = len(state.numberedFiles) |
| |
| if ctxt.Debugvlog != 0 { |
| ctxt.Logf("pclntab=%d bytes, funcdata total %d bytes\n", ftab.Size(), funcdataBytes) |
| } |
| |
| return state.container |
| } |
| |
| 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. |
| // 'container' is a bitmap indexed by global symbol holding whether |
| // a given text symbols is a container (outer sym). |
| func (ctxt *Link) findfunctab(container loader.Bitmap) { |
| ldr := ctxt.loader |
| tsym := ldr.LookupOrCreateSym("runtime.findfunctab", 0) |
| t := ldr.MakeSymbolUpdater(tsym) |
| t.SetType(sym.SRODATA) |
| ldr.SetAttrReachable(tsym, true) |
| ldr.SetAttrLocal(tsym, true) |
| |
| // find min and max address |
| min := ldr.SymValue(ctxt.Textp2[0]) |
| lastp := ctxt.Textp2[len(ctxt.Textp2)-1] |
| max := ldr.SymValue(lastp) + ldr.SymSize(lastp) |
| |
| // 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.Textp2 { |
| if !emitPcln(ctxt, s, container) { |
| continue |
| } |
| p := ldr.SymValue(s) |
| var e loader.Sym |
| i++ |
| if i < len(ctxt.Textp2) { |
| e = ctxt.Textp2[i] |
| } |
| for e != 0 && !emitPcln(ctxt, e, container) && i < len(ctxt.Textp2) { |
| e = ctxt.Textp2[i] |
| i++ |
| } |
| q := max |
| if e != 0 { |
| q = ldr.SymValue(e) |
| } |
| |
| //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)) |
| } |
| } |
| } |