// Copyright 2009 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. // go-specific code shared across loaders (5l, 6l, 8l). package ld import ( "bytes" "cmd/internal/obj" "fmt" "os" "strconv" "strings" ) // go-specific code shared across loaders (5l, 6l, 8l). // replace all "". with pkg. func expandpkg(t0 string, pkg string) string { return strings.Replace(t0, `"".`, pkg+".", -1) } // accumulate all type information from .6 files. // check for inconsistencies. // TODO: // generate debugging section in binary. // once the dust settles, try to move some code to // libmach, so that other linkers and ar can share. /* * package import data */ type Import struct { prefix string // "type", "var", "func", "const" name string def string file string } // importmap records type information about imported symbols to detect inconsistencies. // Entries are keyed by qualified symbol name (e.g., "runtime.Callers" or "net/url.Error"). var importmap = map[string]*Import{} func lookupImport(name string) *Import { if x, ok := importmap[name]; ok { return x } x := &Import{name: name} importmap[name] = x return x } func ldpkg(f *obj.Biobuf, pkg string, length int64, filename string, whence int) { var p0, p1 int if Debug['g'] != 0 { return } if int64(int(length)) != length { fmt.Fprintf(os.Stderr, "%s: too much pkg data in %s\n", os.Args[0], filename) if Debug['u'] != 0 { errorexit() } return } bdata := make([]byte, length) if int64(obj.Bread(f, bdata)) != length { fmt.Fprintf(os.Stderr, "%s: short pkg read %s\n", os.Args[0], filename) if Debug['u'] != 0 { errorexit() } return } data := string(bdata) // first \n$$ marks beginning of exports - skip rest of line p0 = strings.Index(data, "\n$$") if p0 < 0 { if Debug['u'] != 0 && whence != ArchiveObj { Exitf("cannot find export data in %s", filename) } return } p0 += 3 for p0 < len(data) && data[p0] != '\n' { p0++ } // second marks end of exports / beginning of local data p1 = strings.Index(data[p0:], "\n$$") if p1 < 0 { fmt.Fprintf(os.Stderr, "%s: cannot find end of exports in %s\n", os.Args[0], filename) if Debug['u'] != 0 { errorexit() } return } p1 += p0 for p0 < p1 && (data[p0] == ' ' || data[p0] == '\t' || data[p0] == '\n') { p0++ } if p0 < p1 { if !strings.HasPrefix(data[p0:], "package ") { fmt.Fprintf(os.Stderr, "%s: bad package section in %s - %.20s\n", os.Args[0], filename, data[p0:]) if Debug['u'] != 0 { errorexit() } return } p0 += 8 for p0 < p1 && (data[p0] == ' ' || data[p0] == '\t' || data[p0] == '\n') { p0++ } pname := p0 for p0 < p1 && data[p0] != ' ' && data[p0] != '\t' && data[p0] != '\n' { p0++ } if Debug['u'] != 0 && whence != ArchiveObj && (p0+6 > p1 || !strings.HasPrefix(data[p0:], " safe\n")) { Exitf("load of unsafe package %s", filename) } name := data[pname:p0] for p0 < p1 && data[p0] != '\n' { p0++ } if p0 < p1 { p0++ } if pkg == "main" && name != "main" { Exitf("%s: not package main (package %s)", filename, name) } loadpkgdata(filename, pkg, data[p0:p1]) } // __.PKGDEF has no cgo section - those are in the C compiler-generated object files. if whence == Pkgdef { return } // look for cgo section p0 = strings.Index(data[p1:], "\n$$ // cgo") if p0 >= 0 { p0 += p1 i := strings.IndexByte(data[p0+1:], '\n') if i < 0 { fmt.Fprintf(os.Stderr, "%s: found $$ // cgo but no newline in %s\n", os.Args[0], filename) if Debug['u'] != 0 { errorexit() } return } p0 += 1 + i p1 = strings.Index(data[p0:], "\n$$") if p1 < 0 { p1 = strings.Index(data[p0:], "\n!\n") } if p1 < 0 { fmt.Fprintf(os.Stderr, "%s: cannot find end of // cgo section in %s\n", os.Args[0], filename) if Debug['u'] != 0 { errorexit() } return } p1 += p0 loadcgo(filename, pkg, data[p0:p1]) } } func loadpkgdata(file string, pkg string, data string) { var prefix string var name string var def string p := data for parsepkgdata(file, pkg, &p, &prefix, &name, &def) > 0 { x := lookupImport(name) if x.prefix == "" { x.prefix = prefix x.def = def x.file = file } else if x.prefix != prefix { fmt.Fprintf(os.Stderr, "%s: conflicting definitions for %s\n", os.Args[0], name) fmt.Fprintf(os.Stderr, "%s:\t%s %s ...\n", x.file, x.prefix, name) fmt.Fprintf(os.Stderr, "%s:\t%s %s ...\n", file, prefix, name) nerrors++ } else if x.def != def { fmt.Fprintf(os.Stderr, "%s: conflicting definitions for %s\n", os.Args[0], name) fmt.Fprintf(os.Stderr, "%s:\t%s %s %s\n", x.file, x.prefix, name, x.def) fmt.Fprintf(os.Stderr, "%s:\t%s %s %s\n", file, prefix, name, def) nerrors++ } } } func parsepkgdata(file string, pkg string, pp *string, prefixp *string, namep *string, defp *string) int { // skip white space p := *pp loop: for len(p) > 0 && (p[0] == ' ' || p[0] == '\t' || p[0] == '\n') { p = p[1:] } if len(p) == 0 || strings.HasPrefix(p, "$$\n") { return 0 } // prefix: (var|type|func|const) prefix := p if len(p) < 7 { return -1 } if strings.HasPrefix(p, "var ") { p = p[4:] } else if strings.HasPrefix(p, "type ") { p = p[5:] } else if strings.HasPrefix(p, "func ") { p = p[5:] } else if strings.HasPrefix(p, "const ") { p = p[6:] } else if strings.HasPrefix(p, "import ") { p = p[7:] for len(p) > 0 && p[0] != ' ' { p = p[1:] } p = p[1:] line := p for len(p) > 0 && p[0] != '\n' { p = p[1:] } if len(p) == 0 { fmt.Fprintf(os.Stderr, "%s: %s: confused in import line\n", os.Args[0], file) nerrors++ return -1 } line = line[:len(line)-len(p)] line = strings.TrimSuffix(line, " // indirect") path, err := strconv.Unquote(line) if err != nil { fmt.Fprintf(os.Stderr, "%s: %s: confused in import path: %q\n", os.Args[0], file, line) nerrors++ return -1 } p = p[1:] imported(pkg, path) goto loop } else { fmt.Fprintf(os.Stderr, "%s: %s: confused in pkg data near <<%.40s>>\n", os.Args[0], file, prefix) nerrors++ return -1 } prefix = prefix[:len(prefix)-len(p)-1] // name: a.b followed by space name := p inquote := false for len(p) > 0 { if p[0] == ' ' && !inquote { break } if p[0] == '\\' { p = p[1:] } else if p[0] == '"' { inquote = !inquote } p = p[1:] } if len(p) == 0 { return -1 } name = name[:len(name)-len(p)] p = p[1:] // def: free form to new line def := p for len(p) > 0 && p[0] != '\n' { p = p[1:] } if len(p) == 0 { return -1 } def = def[:len(def)-len(p)] var defbuf *bytes.Buffer p = p[1:] // include methods on successive lines in def of named type var meth string for parsemethod(&p, &meth) > 0 { if defbuf == nil { defbuf = new(bytes.Buffer) defbuf.WriteString(def) } defbuf.WriteString("\n\t") defbuf.WriteString(meth) } if defbuf != nil { def = defbuf.String() } name = expandpkg(name, pkg) def = expandpkg(def, pkg) // done *pp = p *prefixp = prefix *namep = name *defp = def return 1 } func parsemethod(pp *string, methp *string) int { // skip white space p := *pp for len(p) > 0 && (p[0] == ' ' || p[0] == '\t') { p = p[1:] } if len(p) == 0 { return 0 } // might be a comment about the method if strings.HasPrefix(p, "//") { goto useline } // if it says "func (", it's a method if strings.HasPrefix(p, "func (") { goto useline } return 0 // definition to end of line useline: *methp = p for len(p) > 0 && p[0] != '\n' { p = p[1:] } if len(p) == 0 { fmt.Fprintf(os.Stderr, "%s: lost end of line in method definition\n", os.Args[0]) *pp = "" return -1 } *methp = (*methp)[:len(*methp)-len(p)] *pp = p[1:] return 1 } func loadcgo(file string, pkg string, p string) { var next string var q string var f []string var local string var remote string var lib string var s *LSym p0 := "" for ; p != ""; p = next { if i := strings.Index(p, "\n"); i >= 0 { p, next = p[:i], p[i+1:] } else { next = "" } p0 = p // save for error message f = tokenize(p) if len(f) == 0 { continue } if f[0] == "cgo_import_dynamic" { if len(f) < 2 || len(f) > 4 { goto err } local = f[1] remote = local if len(f) > 2 { remote = f[2] } lib = "" if len(f) > 3 { lib = f[3] } if Debug['d'] != 0 { fmt.Fprintf(os.Stderr, "%s: %s: cannot use dynamic imports with -d flag\n", os.Args[0], file) nerrors++ return } if local == "_" && remote == "_" { // allow #pragma dynimport _ _ "foo.so" // to force a link of foo.so. havedynamic = 1 if HEADTYPE == obj.Hdarwin { Machoadddynlib(lib) } else { dynlib = append(dynlib, lib) } continue } local = expandpkg(local, pkg) q = "" if i := strings.Index(remote, "#"); i >= 0 { remote, q = remote[:i], remote[i+1:] } s = Linklookup(Ctxt, local, 0) if local != f[1] { } if s.Type == 0 || s.Type == obj.SXREF || s.Type == obj.SHOSTOBJ { s.Dynimplib = lib s.Extname = remote s.Dynimpvers = q if s.Type != obj.SHOSTOBJ { s.Type = obj.SDYNIMPORT } havedynamic = 1 } continue } if f[0] == "cgo_import_static" { if len(f) != 2 { goto err } local = f[1] s = Linklookup(Ctxt, local, 0) s.Type = obj.SHOSTOBJ s.Size = 0 continue } if f[0] == "cgo_export_static" || f[0] == "cgo_export_dynamic" { if len(f) < 2 || len(f) > 3 { goto err } local = f[1] if len(f) > 2 { remote = f[2] } else { remote = local } local = expandpkg(local, pkg) s = Linklookup(Ctxt, local, 0) switch Buildmode { case BuildmodeCShared, BuildmodeCArchive: if s == Linklookup(Ctxt, "main", 0) { continue } } // export overrides import, for openbsd/cgo. // see issue 4878. if s.Dynimplib != "" { s.Dynimplib = "" s.Extname = "" s.Dynimpvers = "" s.Type = 0 } if s.Cgoexport == 0 { s.Extname = remote dynexp = append(dynexp, s) } else if s.Extname != remote { fmt.Fprintf(os.Stderr, "%s: conflicting cgo_export directives: %s as %s and %s\n", os.Args[0], s.Name, s.Extname, remote) nerrors++ return } if f[0] == "cgo_export_static" { s.Cgoexport |= CgoExportStatic } else { s.Cgoexport |= CgoExportDynamic } if local != f[1] { } continue } if f[0] == "cgo_dynamic_linker" { if len(f) != 2 { goto err } if Debug['I'] == 0 { if interpreter != "" && interpreter != f[1] { fmt.Fprintf(os.Stderr, "%s: conflict dynlinker: %s and %s\n", os.Args[0], interpreter, f[1]) nerrors++ return } interpreter = f[1] } continue } if f[0] == "cgo_ldflag" { if len(f) != 2 { goto err } ldflag = append(ldflag, f[1]) continue } } return err: fmt.Fprintf(os.Stderr, "%s: %s: invalid dynimport line: %s\n", os.Args[0], file, p0) nerrors++ } var seenlib = make(map[string]bool) func adddynlib(lib string) { if seenlib[lib] || Linkmode == LinkExternal { return } seenlib[lib] = true if Iself { s := Linklookup(Ctxt, ".dynstr", 0) if s.Size == 0 { Addstring(s, "") } Elfwritedynent(Linklookup(Ctxt, ".dynamic", 0), DT_NEEDED, uint64(Addstring(s, lib))) } else { Diag("adddynlib: unsupported binary format") } } func Adddynsym(ctxt *Link, s *LSym) { if s.Dynid >= 0 || Linkmode == LinkExternal { return } if Iself { Elfadddynsym(ctxt, s) } else if HEADTYPE == obj.Hdarwin { Diag("adddynsym: missed symbol %s (%s)", s.Name, s.Extname) } else if HEADTYPE == obj.Hwindows { // already taken care of } else { Diag("adddynsym: unsupported binary format") } } var markq *LSym var emarkq *LSym func mark1(s *LSym, parent *LSym) { if s == nil || s.Reachable { return } if strings.HasPrefix(s.Name, "go.weak.") { return } s.Reachable = true s.Reachparent = parent if markq == nil { markq = s } else { emarkq.Queue = s } emarkq = s } func mark(s *LSym) { mark1(s, nil) } func markflood() { var a *Auto var i int for s := markq; s != nil; s = s.Queue { if s.Type == obj.STEXT { if Debug['v'] > 1 { fmt.Fprintf(&Bso, "marktext %s\n", s.Name) } for a = s.Autom; a != nil; a = a.Link { mark1(a.Gotype, s) } } for i = 0; i < len(s.R); i++ { mark1(s.R[i].Sym, s) } if s.Pcln != nil { for i = 0; i < s.Pcln.Nfuncdata; i++ { mark1(s.Pcln.Funcdata[i], s) } } mark1(s.Gotype, s) mark1(s.Sub, s) mark1(s.Outer, s) } } var markextra = []string{ "runtime.morestack", "runtime.morestackx", "runtime.morestack00", "runtime.morestack10", "runtime.morestack01", "runtime.morestack11", "runtime.morestack8", "runtime.morestack16", "runtime.morestack24", "runtime.morestack32", "runtime.morestack40", "runtime.morestack48", // on arm, lock in the div/mod helpers too "_div", "_divu", "_mod", "_modu", } func deadcode() { if Debug['v'] != 0 { fmt.Fprintf(&Bso, "%5.2f deadcode\n", obj.Cputime()) } if Buildmode == BuildmodeShared { // Mark all symbols defined in this library as reachable when // building a shared library. for s := Ctxt.Allsym; s != nil; s = s.Allsym { if s.Type != 0 && s.Type != obj.SDYNIMPORT { mark(s) } } markflood() } else { mark(Linklookup(Ctxt, INITENTRY, 0)) if Linkshared && Buildmode == BuildmodeExe { mark(Linkrlookup(Ctxt, "main.main", 0)) mark(Linkrlookup(Ctxt, "main.init", 0)) } for i := 0; i < len(markextra); i++ { mark(Linklookup(Ctxt, markextra[i], 0)) } for i := 0; i < len(dynexp); i++ { mark(dynexp[i]) } markflood() // keep each beginning with 'typelink.' if the symbol it points at is being kept. for s := Ctxt.Allsym; s != nil; s = s.Allsym { if strings.HasPrefix(s.Name, "go.typelink.") { s.Reachable = len(s.R) == 1 && s.R[0].Sym.Reachable } } // remove dead text but keep file information (z symbols). var last *LSym for s := Ctxt.Textp; s != nil; s = s.Next { if !s.Reachable { continue } // NOTE: Removing s from old textp and adding to new, shorter textp. if last == nil { Ctxt.Textp = s } else { last.Next = s } last = s } if last == nil { Ctxt.Textp = nil Ctxt.Etextp = nil } else { last.Next = nil Ctxt.Etextp = last } } for s := Ctxt.Allsym; s != nil; s = s.Allsym { if strings.HasPrefix(s.Name, "go.weak.") { s.Special = 1 // do not lay out in data segment s.Reachable = true s.Hide = 1 } } // record field tracking references var buf bytes.Buffer var p *LSym for s := Ctxt.Allsym; s != nil; s = s.Allsym { if strings.HasPrefix(s.Name, "go.track.") { s.Special = 1 // do not lay out in data segment s.Hide = 1 if s.Reachable { buf.WriteString(s.Name[9:]) for p = s.Reachparent; p != nil; p = p.Reachparent { buf.WriteString("\t") buf.WriteString(p.Name) } buf.WriteString("\n") } s.Type = obj.SCONST s.Value = 0 } } if tracksym == "" { return } s := Linklookup(Ctxt, tracksym, 0) if !s.Reachable { return } addstrdata(tracksym, buf.String()) } func doweak() { var t *LSym // resolve weak references only if // target symbol will be in binary anyway. for s := Ctxt.Allsym; s != nil; s = s.Allsym { if strings.HasPrefix(s.Name, "go.weak.") { t = Linkrlookup(Ctxt, s.Name[8:], int(s.Version)) if t != nil && t.Type != 0 && t.Reachable { s.Value = t.Value s.Type = t.Type s.Outer = t } else { s.Type = obj.SCONST s.Value = 0 } continue } } } func addexport() { if HEADTYPE == obj.Hdarwin { return } for _, exp := range dynexp { Adddynsym(Ctxt, exp) } for _, lib := range dynlib { adddynlib(lib) } } type Pkg struct { mark bool checked bool path string impby []*Pkg } var ( // pkgmap records the imported-by relationship between packages. // Entries are keyed by package path (e.g., "runtime" or "net/url"). pkgmap = map[string]*Pkg{} pkgall []*Pkg ) func lookupPkg(path string) *Pkg { if p, ok := pkgmap[path]; ok { return p } p := &Pkg{path: path} pkgmap[path] = p pkgall = append(pkgall, p) return p } // imported records that package pkg imports package imp. func imported(pkg, imp string) { // everyone imports runtime, even runtime. if imp == "runtime" { return } p := lookupPkg(pkg) i := lookupPkg(imp) i.impby = append(i.impby, p) } func (p *Pkg) cycle() *Pkg { if p.checked { return nil } if p.mark { nerrors++ fmt.Printf("import cycle:\n") fmt.Printf("\t%s\n", p.path) return p } p.mark = true for _, q := range p.impby { if bad := q.cycle(); bad != nil { p.mark = false p.checked = true fmt.Printf("\timports %s\n", p.path) if bad == p { return nil } return bad } } p.checked = true p.mark = false return nil } func importcycles() { for _, p := range pkgall { p.cycle() } } func setlinkmode(arg string) { if arg == "internal" { Linkmode = LinkInternal } else if arg == "external" { Linkmode = LinkExternal } else if arg == "auto" { Linkmode = LinkAuto } else { Exitf("unknown link mode -linkmode %s", arg) } }