mirror of
https://github.com/golang/go.git
synced 2025-12-08 06:10:04 +00:00
[dev.link] cmd/link: implement live method tracking in deadcode2
This essentially replicates the logic of live method tracking and type symbol decoding, rewritten to operate on indices instead of Symbols. TODO: the special handling of reflect.Type.Method has not been implemented. TODO: the symbol name is used too much. It ought to be a better way to do it. Change-Id: I860ee7a506c00833902e4870d15aea698a705dd9 Reviewed-on: https://go-review.googlesource.com/c/go/+/199078 Reviewed-by: Than McIntosh <thanm@google.com>
This commit is contained in:
parent
cc21e4d130
commit
7d6f79e617
2 changed files with 268 additions and 16 deletions
|
|
@ -5,26 +5,29 @@
|
||||||
package ld
|
package ld
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"cmd/internal/objabi"
|
"cmd/internal/objabi"
|
||||||
"cmd/internal/sys"
|
"cmd/internal/sys"
|
||||||
"cmd/link/internal/objfile"
|
"cmd/link/internal/objfile"
|
||||||
"cmd/link/internal/sym"
|
"cmd/link/internal/sym"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
"unicode"
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ = fmt.Print
|
var _ = fmt.Print
|
||||||
|
|
||||||
// TODO:
|
// TODO:
|
||||||
// - Live method tracking:
|
// - Live method tracking:
|
||||||
// Prune methods that are not directly called and cannot
|
// The special handling of reflect.Type.Method has not
|
||||||
// be potentially called by interface or reflect call.
|
// been implemented.
|
||||||
// For now, all the methods from reachable type are alive.
|
|
||||||
// - Shared object support:
|
// - Shared object support:
|
||||||
// It basically marks everything. We could consider using
|
// It basically marks everything. We could consider using
|
||||||
// a different mechanism to represent it.
|
// a different mechanism to represent it.
|
||||||
// - Field tracking support:
|
// - Field tracking support:
|
||||||
// It needs to record from where the symbol is referenced.
|
// It needs to record from where the symbol is referenced.
|
||||||
|
// - Debug output:
|
||||||
|
// Emit messages about which symbols are kept or deleted.
|
||||||
|
|
||||||
type workQueue []objfile.Sym
|
type workQueue []objfile.Sym
|
||||||
|
|
||||||
|
|
@ -36,10 +39,15 @@ type deadcodePass2 struct {
|
||||||
ctxt *Link
|
ctxt *Link
|
||||||
loader *objfile.Loader
|
loader *objfile.Loader
|
||||||
wq workQueue
|
wq workQueue
|
||||||
|
|
||||||
|
ifaceMethod map[methodsig]bool // methods declared in reached interfaces
|
||||||
|
markableMethods []methodref2 // methods of reached types
|
||||||
|
reflectMethod bool // TODO: this is not set for now
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *deadcodePass2) init() {
|
func (d *deadcodePass2) init() {
|
||||||
d.loader.InitReachable()
|
d.loader.InitReachable()
|
||||||
|
d.ifaceMethod = make(map[methodsig]bool)
|
||||||
|
|
||||||
var names []string
|
var names []string
|
||||||
|
|
||||||
|
|
@ -86,6 +94,21 @@ func (d *deadcodePass2) init() {
|
||||||
func (d *deadcodePass2) flood() {
|
func (d *deadcodePass2) flood() {
|
||||||
for !d.wq.empty() {
|
for !d.wq.empty() {
|
||||||
symIdx := d.wq.pop()
|
symIdx := d.wq.pop()
|
||||||
|
|
||||||
|
name := d.loader.RawSymName(symIdx)
|
||||||
|
if strings.HasPrefix(name, "type.") && name[5] != '.' { // TODO: use an attribute instead of checking name
|
||||||
|
p := d.loader.Data(symIdx)
|
||||||
|
if len(p) != 0 && decodetypeKind(d.ctxt.Arch, p)&kindMask == kindInterface {
|
||||||
|
for _, sig := range decodeIfaceMethods2(d.loader, d.ctxt.Arch, symIdx) {
|
||||||
|
if d.ctxt.Debugvlog > 1 {
|
||||||
|
d.ctxt.Logf("reached iface method: %s\n", sig)
|
||||||
|
}
|
||||||
|
d.ifaceMethod[sig] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var methods []methodref2
|
||||||
relocs := d.loader.Relocs(symIdx)
|
relocs := d.loader.Relocs(symIdx)
|
||||||
for i := 0; i < relocs.Count; i++ {
|
for i := 0; i < relocs.Count; i++ {
|
||||||
r := relocs.At(i)
|
r := relocs.At(i)
|
||||||
|
|
@ -93,8 +116,12 @@ func (d *deadcodePass2) flood() {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if r.Type == objabi.R_METHODOFF {
|
if r.Type == objabi.R_METHODOFF {
|
||||||
// TODO: we should do something about it
|
if i+2 >= relocs.Count {
|
||||||
// For now, all the methods are considered live
|
panic("expect three consecutive R_METHODOFF relocs")
|
||||||
|
}
|
||||||
|
methods = append(methods, methodref2{src: symIdx, r: i})
|
||||||
|
i += 2
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
d.mark(r.Sym)
|
d.mark(r.Sym)
|
||||||
}
|
}
|
||||||
|
|
@ -102,6 +129,20 @@ func (d *deadcodePass2) flood() {
|
||||||
for i := 0; i < naux; i++ {
|
for i := 0; i < naux; i++ {
|
||||||
d.mark(d.loader.AuxSym(symIdx, i))
|
d.mark(d.loader.AuxSym(symIdx, i))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(methods) != 0 {
|
||||||
|
// Decode runtime type information for type methods
|
||||||
|
// to help work out which methods can be called
|
||||||
|
// dynamically via interfaces.
|
||||||
|
methodsigs := decodetypeMethods2(d.loader, d.ctxt.Arch, symIdx)
|
||||||
|
if len(methods) != len(methodsigs) {
|
||||||
|
panic(fmt.Sprintf("%q has %d method relocations for %d methods", d.loader.SymName(symIdx), len(methods), len(methodsigs)))
|
||||||
|
}
|
||||||
|
for i, m := range methodsigs {
|
||||||
|
methods[i].m = m
|
||||||
|
}
|
||||||
|
d.markableMethods = append(d.markableMethods, methods...)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -112,19 +153,67 @@ func (d *deadcodePass2) mark(symIdx objfile.Sym) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d *deadcodePass2) markMethod(m methodref2) {
|
||||||
|
relocs := d.loader.Relocs(m.src)
|
||||||
|
d.mark(relocs.At(m.r).Sym)
|
||||||
|
d.mark(relocs.At(m.r + 1).Sym)
|
||||||
|
d.mark(relocs.At(m.r + 2).Sym)
|
||||||
|
}
|
||||||
|
|
||||||
func deadcode2(ctxt *Link) {
|
func deadcode2(ctxt *Link) {
|
||||||
loader := ctxt.loader
|
loader := ctxt.loader
|
||||||
d := deadcodePass2{ctxt: ctxt, loader: loader}
|
d := deadcodePass2{ctxt: ctxt, loader: loader}
|
||||||
d.init()
|
d.init()
|
||||||
d.flood()
|
d.flood()
|
||||||
|
|
||||||
|
callSym := loader.Lookup("reflect.Value.Call", sym.SymVerABIInternal)
|
||||||
|
methSym := loader.Lookup("reflect.Value.Method", sym.SymVerABIInternal)
|
||||||
|
reflectSeen := false
|
||||||
|
|
||||||
|
if ctxt.DynlinkingGo() {
|
||||||
|
// Exported methods may satisfy interfaces we don't know
|
||||||
|
// about yet when dynamically linking.
|
||||||
|
reflectSeen = true
|
||||||
|
}
|
||||||
|
|
||||||
|
for {
|
||||||
|
if !reflectSeen {
|
||||||
|
if d.reflectMethod || (callSym != 0 && loader.Reachable.Has(callSym)) || (methSym != 0 && loader.Reachable.Has(methSym)) {
|
||||||
|
// Methods might be called via reflection. Give up on
|
||||||
|
// static analysis, mark all exported methods of
|
||||||
|
// all reachable types as reachable.
|
||||||
|
reflectSeen = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mark all methods that could satisfy a discovered
|
||||||
|
// interface as reachable. We recheck old marked interfaces
|
||||||
|
// as new types (with new methods) may have been discovered
|
||||||
|
// in the last pass.
|
||||||
|
rem := d.markableMethods[:0]
|
||||||
|
for _, m := range d.markableMethods {
|
||||||
|
if (reflectSeen && m.isExported()) || d.ifaceMethod[m.m] {
|
||||||
|
d.markMethod(m)
|
||||||
|
} else {
|
||||||
|
rem = append(rem, m)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
d.markableMethods = rem
|
||||||
|
|
||||||
|
if d.wq.empty() {
|
||||||
|
// No new work was discovered. Done.
|
||||||
|
break
|
||||||
|
}
|
||||||
|
d.flood()
|
||||||
|
}
|
||||||
|
|
||||||
n := loader.NSym()
|
n := loader.NSym()
|
||||||
if ctxt.BuildMode != BuildModeShared {
|
if ctxt.BuildMode != BuildModeShared {
|
||||||
// Keep a itablink if the symbol it points at is being kept.
|
// Keep a itablink if the symbol it points at is being kept.
|
||||||
// (When BuildModeShared, always keep itablinks.)
|
// (When BuildModeShared, always keep itablinks.)
|
||||||
for i := 1; i < n; i++ {
|
for i := 1; i < n; i++ {
|
||||||
s := objfile.Sym(i)
|
s := objfile.Sym(i)
|
||||||
if strings.HasPrefix(loader.RawSymName(s), "go.itablink.") {
|
if strings.HasPrefix(loader.RawSymName(s), "go.itablink.") { // TODO: use an attribute instread of checking name
|
||||||
relocs := loader.Relocs(s)
|
relocs := loader.Relocs(s)
|
||||||
if relocs.Count > 0 && loader.Reachable.Has(relocs.At(0).Sym) {
|
if relocs.Count > 0 && loader.Reachable.Has(relocs.At(0).Sym) {
|
||||||
loader.Reachable.Set(s)
|
loader.Reachable.Set(s)
|
||||||
|
|
@ -143,3 +232,154 @@ func deadcode2(ctxt *Link) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// methodref2 holds the relocations from a receiver type symbol to its
|
||||||
|
// method. There are three relocations, one for each of the fields in
|
||||||
|
// the reflect.method struct: mtyp, ifn, and tfn.
|
||||||
|
type methodref2 struct {
|
||||||
|
m methodsig
|
||||||
|
src objfile.Sym // receiver type symbol
|
||||||
|
r int // the index of R_METHODOFF relocations
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m methodref2) isExported() bool {
|
||||||
|
for _, r := range m.m {
|
||||||
|
return unicode.IsUpper(r)
|
||||||
|
}
|
||||||
|
panic("methodref has no signature")
|
||||||
|
}
|
||||||
|
|
||||||
|
// decodeMethodSig2 decodes an array of method signature information.
|
||||||
|
// Each element of the array is size bytes. The first 4 bytes is a
|
||||||
|
// nameOff for the method name, and the next 4 bytes is a typeOff for
|
||||||
|
// the function type.
|
||||||
|
//
|
||||||
|
// Conveniently this is the layout of both runtime.method and runtime.imethod.
|
||||||
|
func decodeMethodSig2(loader *objfile.Loader, arch *sys.Arch, symIdx objfile.Sym, off, size, count int) []methodsig {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
var methods []methodsig
|
||||||
|
for i := 0; i < count; i++ {
|
||||||
|
buf.WriteString(decodetypeName2(loader, symIdx, off))
|
||||||
|
mtypSym := decodeRelocSym2(loader, symIdx, int32(off+4))
|
||||||
|
mp := loader.Data(mtypSym)
|
||||||
|
|
||||||
|
buf.WriteRune('(')
|
||||||
|
inCount := decodetypeFuncInCount(arch, mp)
|
||||||
|
for i := 0; i < inCount; i++ {
|
||||||
|
if i > 0 {
|
||||||
|
buf.WriteString(", ")
|
||||||
|
}
|
||||||
|
a := decodetypeFuncInType2(loader, arch, mtypSym, i)
|
||||||
|
buf.WriteString(loader.SymName(a))
|
||||||
|
}
|
||||||
|
buf.WriteString(") (")
|
||||||
|
outCount := decodetypeFuncOutCount(arch, mp)
|
||||||
|
for i := 0; i < outCount; i++ {
|
||||||
|
if i > 0 {
|
||||||
|
buf.WriteString(", ")
|
||||||
|
}
|
||||||
|
a := decodetypeFuncOutType2(loader, arch, mtypSym, i)
|
||||||
|
buf.WriteString(loader.SymName(a))
|
||||||
|
}
|
||||||
|
buf.WriteRune(')')
|
||||||
|
|
||||||
|
off += size
|
||||||
|
methods = append(methods, methodsig(buf.String()))
|
||||||
|
buf.Reset()
|
||||||
|
}
|
||||||
|
return methods
|
||||||
|
}
|
||||||
|
|
||||||
|
func decodeIfaceMethods2(loader *objfile.Loader, arch *sys.Arch, symIdx objfile.Sym) []methodsig {
|
||||||
|
p := loader.Data(symIdx)
|
||||||
|
if decodetypeKind(arch, p)&kindMask != kindInterface {
|
||||||
|
panic(fmt.Sprintf("symbol %q is not an interface", loader.SymName(symIdx)))
|
||||||
|
}
|
||||||
|
rel := decodeReloc2(loader, symIdx, int32(commonsize(arch)+arch.PtrSize))
|
||||||
|
if rel.Sym == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if rel.Sym != symIdx {
|
||||||
|
panic(fmt.Sprintf("imethod slice pointer in %q leads to a different symbol", loader.SymName(symIdx)))
|
||||||
|
}
|
||||||
|
off := int(rel.Add) // array of reflect.imethod values
|
||||||
|
numMethods := int(decodetypeIfaceMethodCount(arch, p))
|
||||||
|
sizeofIMethod := 4 + 4
|
||||||
|
return decodeMethodSig2(loader, arch, symIdx, off, sizeofIMethod, numMethods)
|
||||||
|
}
|
||||||
|
|
||||||
|
func decodetypeMethods2(loader *objfile.Loader, arch *sys.Arch, symIdx objfile.Sym) []methodsig {
|
||||||
|
p := loader.Data(symIdx)
|
||||||
|
if !decodetypeHasUncommon(arch, p) {
|
||||||
|
panic(fmt.Sprintf("no methods on %q", loader.SymName(symIdx)))
|
||||||
|
}
|
||||||
|
off := commonsize(arch) // reflect.rtype
|
||||||
|
switch decodetypeKind(arch, p) & kindMask {
|
||||||
|
case kindStruct: // reflect.structType
|
||||||
|
off += 4 * arch.PtrSize
|
||||||
|
case kindPtr: // reflect.ptrType
|
||||||
|
off += arch.PtrSize
|
||||||
|
case kindFunc: // reflect.funcType
|
||||||
|
off += arch.PtrSize // 4 bytes, pointer aligned
|
||||||
|
case kindSlice: // reflect.sliceType
|
||||||
|
off += arch.PtrSize
|
||||||
|
case kindArray: // reflect.arrayType
|
||||||
|
off += 3 * arch.PtrSize
|
||||||
|
case kindChan: // reflect.chanType
|
||||||
|
off += 2 * arch.PtrSize
|
||||||
|
case kindMap: // reflect.mapType
|
||||||
|
off += 4*arch.PtrSize + 8
|
||||||
|
case kindInterface: // reflect.interfaceType
|
||||||
|
off += 3 * arch.PtrSize
|
||||||
|
default:
|
||||||
|
// just Sizeof(rtype)
|
||||||
|
}
|
||||||
|
|
||||||
|
mcount := int(decodeInuxi(arch, p[off+4:], 2))
|
||||||
|
moff := int(decodeInuxi(arch, p[off+4+2+2:], 4))
|
||||||
|
off += moff // offset to array of reflect.method values
|
||||||
|
const sizeofMethod = 4 * 4 // sizeof reflect.method in program
|
||||||
|
return decodeMethodSig2(loader, arch, symIdx, off, sizeofMethod, mcount)
|
||||||
|
}
|
||||||
|
|
||||||
|
func decodeReloc2(loader *objfile.Loader, symIdx objfile.Sym, off int32) objfile.Reloc {
|
||||||
|
relocs := loader.Relocs(symIdx)
|
||||||
|
for j := 0; j < relocs.Count; j++ {
|
||||||
|
rel := relocs.At(j)
|
||||||
|
if rel.Off == off {
|
||||||
|
return rel
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return objfile.Reloc{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func decodeRelocSym2(loader *objfile.Loader, symIdx objfile.Sym, off int32) objfile.Sym {
|
||||||
|
return decodeReloc2(loader, symIdx, off).Sym
|
||||||
|
}
|
||||||
|
|
||||||
|
// decodetypeName2 decodes the name from a reflect.name.
|
||||||
|
func decodetypeName2(loader *objfile.Loader, symIdx objfile.Sym, off int) string {
|
||||||
|
r := decodeRelocSym2(loader, symIdx, int32(off))
|
||||||
|
if r == 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
data := loader.Data(r)
|
||||||
|
namelen := int(uint16(data[1])<<8 | uint16(data[2]))
|
||||||
|
return string(data[3 : 3+namelen])
|
||||||
|
}
|
||||||
|
|
||||||
|
func decodetypeFuncInType2(loader *objfile.Loader, arch *sys.Arch, symIdx objfile.Sym, i int) objfile.Sym {
|
||||||
|
uadd := commonsize(arch) + 4
|
||||||
|
if arch.PtrSize == 8 {
|
||||||
|
uadd += 4
|
||||||
|
}
|
||||||
|
if decodetypeHasUncommon(arch, loader.Data(symIdx)) {
|
||||||
|
uadd += uncommonSize()
|
||||||
|
}
|
||||||
|
return decodeRelocSym2(loader, symIdx, int32(uadd+i*arch.PtrSize))
|
||||||
|
}
|
||||||
|
|
||||||
|
func decodetypeFuncOutType2(loader *objfile.Loader, arch *sys.Arch, symIdx objfile.Sym, i int) objfile.Sym {
|
||||||
|
return decodetypeFuncInType2(loader, arch, symIdx, i+decodetypeFuncInCount(arch, loader.Data(symIdx)))
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -235,10 +235,10 @@ func (l *Loader) NSym() int {
|
||||||
|
|
||||||
// Returns the raw (unpatched) name of the i-th symbol.
|
// Returns the raw (unpatched) name of the i-th symbol.
|
||||||
func (l *Loader) RawSymName(i Sym) string {
|
func (l *Loader) RawSymName(i Sym) string {
|
||||||
r, li := l.ToLocal(i)
|
if l.extStart != 0 && i >= l.extStart {
|
||||||
if r == nil {
|
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
r, li := l.ToLocal(i)
|
||||||
osym := goobj2.Sym{}
|
osym := goobj2.Sym{}
|
||||||
osym.Read(r.Reader, r.SymOff(li))
|
osym.Read(r.Reader, r.SymOff(li))
|
||||||
return osym.Name
|
return osym.Name
|
||||||
|
|
@ -246,10 +246,10 @@ func (l *Loader) RawSymName(i Sym) string {
|
||||||
|
|
||||||
// Returns the (patched) name of the i-th symbol.
|
// Returns the (patched) name of the i-th symbol.
|
||||||
func (l *Loader) SymName(i Sym) string {
|
func (l *Loader) SymName(i Sym) string {
|
||||||
r, li := l.ToLocal(i)
|
if l.extStart != 0 && i >= l.extStart {
|
||||||
if r == nil {
|
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
r, li := l.ToLocal(i)
|
||||||
osym := goobj2.Sym{}
|
osym := goobj2.Sym{}
|
||||||
osym.Read(r.Reader, r.SymOff(li))
|
osym.Read(r.Reader, r.SymOff(li))
|
||||||
return strings.Replace(osym.Name, "\"\".", r.pkgprefix, -1)
|
return strings.Replace(osym.Name, "\"\".", r.pkgprefix, -1)
|
||||||
|
|
@ -257,27 +257,39 @@ func (l *Loader) SymName(i Sym) string {
|
||||||
|
|
||||||
// Returns the type of the i-th symbol.
|
// Returns the type of the i-th symbol.
|
||||||
func (l *Loader) SymType(i Sym) sym.SymKind {
|
func (l *Loader) SymType(i Sym) sym.SymKind {
|
||||||
r, li := l.ToLocal(i)
|
if l.extStart != 0 && i >= l.extStart {
|
||||||
if r == nil {
|
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
r, li := l.ToLocal(i)
|
||||||
osym := goobj2.Sym{}
|
osym := goobj2.Sym{}
|
||||||
osym.Read(r.Reader, r.SymOff(li))
|
osym.Read(r.Reader, r.SymOff(li))
|
||||||
return sym.AbiSymKindToSymKind[objabi.SymKind(osym.Type)]
|
return sym.AbiSymKindToSymKind[objabi.SymKind(osym.Type)]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Returns the symbol content of the i-th symbol. i is global index.
|
||||||
|
func (l *Loader) Data(i Sym) []byte {
|
||||||
|
if l.extStart != 0 && i >= l.extStart {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
r, li := l.ToLocal(i)
|
||||||
|
return r.Data(li)
|
||||||
|
}
|
||||||
|
|
||||||
// Returns the number of aux symbols given a global index.
|
// Returns the number of aux symbols given a global index.
|
||||||
func (l *Loader) NAux(i Sym) int {
|
func (l *Loader) NAux(i Sym) int {
|
||||||
r, li := l.ToLocal(i)
|
if l.extStart != 0 && i >= l.extStart {
|
||||||
if r == nil {
|
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
r, li := l.ToLocal(i)
|
||||||
return r.NAux(li)
|
return r.NAux(li)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns the referred symbol of the j-th aux symbol of the i-th
|
// Returns the referred symbol of the j-th aux symbol of the i-th
|
||||||
// symbol.
|
// symbol.
|
||||||
func (l *Loader) AuxSym(i Sym, j int) Sym {
|
func (l *Loader) AuxSym(i Sym, j int) Sym {
|
||||||
|
if l.extStart != 0 && i >= l.extStart {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
r, li := l.ToLocal(i)
|
r, li := l.ToLocal(i)
|
||||||
a := goobj2.Aux{}
|
a := goobj2.Aux{}
|
||||||
a.Read(r.Reader, r.AuxOff(li, j))
|
a.Read(r.Reader, r.AuxOff(li, j))
|
||||||
|
|
@ -305,10 +317,10 @@ func (relocs *Relocs) At(j int) Reloc {
|
||||||
|
|
||||||
// Relocs returns a Relocs object for the given global sym.
|
// Relocs returns a Relocs object for the given global sym.
|
||||||
func (l *Loader) Relocs(i Sym) Relocs {
|
func (l *Loader) Relocs(i Sym) Relocs {
|
||||||
r, li := l.ToLocal(i)
|
if l.extStart != 0 && i >= l.extStart {
|
||||||
if r == nil {
|
|
||||||
return Relocs{}
|
return Relocs{}
|
||||||
}
|
}
|
||||||
|
r, li := l.ToLocal(i)
|
||||||
return l.relocs(r, li)
|
return l.relocs(r, li)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue