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