cmd/6l: support -linkshared

Change-Id: Id469165b1acd383837b1f4e1e6f961e10dfa5d61
Reviewed-on: https://go-review.googlesource.com/8332
Reviewed-by: Ian Lance Taylor <iant@golang.org>
Run-TryBot: Ian Lance Taylor <iant@golang.org>
diff --git a/src/cmd/internal/ld/go.go b/src/cmd/internal/ld/go.go
index 55c9bb2..1d83081 100644
--- a/src/cmd/internal/ld/go.go
+++ b/src/cmd/internal/ld/go.go
@@ -631,6 +631,10 @@
 		mark(Linkrlookup(Ctxt, "main.init", 0))
 	} 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))
 		}
diff --git a/src/cmd/internal/ld/ld.go b/src/cmd/internal/ld/ld.go
index a0f1f32..7242301 100644
--- a/src/cmd/internal/ld/ld.go
+++ b/src/cmd/internal/ld/ld.go
@@ -34,6 +34,7 @@
 import (
 	"cmd/internal/obj"
 	"fmt"
+	"io/ioutil"
 	"os"
 	"path"
 	"strconv"
@@ -57,11 +58,19 @@
 	}
 
 	var pname string
+	isshlib := false
 	if (ctxt.Windows == 0 && strings.HasPrefix(name, "/")) || (ctxt.Windows != 0 && len(name) >= 2 && name[1] == ':') {
 		pname = name
 	} else {
 		// try dot, -L "libdir", and then goroot.
 		for _, dir := range ctxt.Libdir {
+			if Linkshared {
+				pname = dir + "/" + pkg + ".shlibname"
+				if _, err := os.Stat(pname); err == nil {
+					isshlib = true
+					break
+				}
+			}
 			pname = dir + "/" + name
 			if _, err := os.Stat(pname); err == nil {
 				break
@@ -72,10 +81,14 @@
 	pname = path.Clean(pname)
 
 	if ctxt.Debugvlog > 1 && ctxt.Bso != nil {
-		fmt.Fprintf(ctxt.Bso, "%5.2f addlib: %s %s pulls in %s\n", elapsed(), obj, src, pname)
+		fmt.Fprintf(ctxt.Bso, "%5.2f addlib: %s %s pulls in %s isshlib %v\n", elapsed(), obj, src, pname, isshlib)
 	}
 
-	addlibpath(ctxt, src, obj, pname, pkg)
+	if isshlib {
+		addlibpath(ctxt, src, obj, "", pkg, pname)
+	} else {
+		addlibpath(ctxt, src, obj, pname, pkg, "")
+	}
 }
 
 /*
@@ -85,15 +98,15 @@
  *	file: object file, e.g., /home/rsc/go/pkg/container/vector.a
  *	pkg: package import path, e.g. container/vector
  */
-func addlibpath(ctxt *Link, srcref string, objref string, file string, pkg string) {
+func addlibpath(ctxt *Link, srcref string, objref string, file string, pkg string, shlibnamefile string) {
 	for i := 0; i < len(ctxt.Library); i++ {
-		if file == ctxt.Library[i].File {
+		if pkg == ctxt.Library[i].Pkg {
 			return
 		}
 	}
 
 	if ctxt.Debugvlog > 1 && ctxt.Bso != nil {
-		fmt.Fprintf(ctxt.Bso, "%5.2f addlibpath: srcref: %s objref: %s file: %s pkg: %s\n", obj.Cputime(), srcref, objref, file, pkg)
+		fmt.Fprintf(ctxt.Bso, "%5.2f addlibpath: srcref: %s objref: %s file: %s pkg: %s shlibnamefile: %s\n", obj.Cputime(), srcref, objref, file, pkg, shlibnamefile)
 	}
 
 	ctxt.Library = append(ctxt.Library, Library{})
@@ -102,6 +115,13 @@
 	l.Srcref = srcref
 	l.File = file
 	l.Pkg = pkg
+	if shlibnamefile != "" {
+		shlibbytes, err := ioutil.ReadFile(shlibnamefile)
+		if err != nil {
+			Diag("cannot read %s: %v", shlibnamefile, err)
+		}
+		l.Shlib = strings.TrimSpace(string(shlibbytes))
+	}
 }
 
 func atolwhex(s string) int64 {
diff --git a/src/cmd/internal/ld/lib.go b/src/cmd/internal/ld/lib.go
index ed54ce8..02d93af 100644
--- a/src/cmd/internal/ld/lib.go
+++ b/src/cmd/internal/ld/lib.go
@@ -33,6 +33,7 @@
 import (
 	"bytes"
 	"cmd/internal/obj"
+	"debug/elf"
 	"errors"
 	"fmt"
 	"io"
@@ -40,6 +41,7 @@
 	"log"
 	"os"
 	"os/exec"
+	"path/filepath"
 	"strings"
 )
 
@@ -153,9 +155,7 @@
 // DynlinkingGo returns whether we are producing Go code that can live
 // in separate shared libraries linked together at runtime.
 func DynlinkingGo() bool {
-	// TODO(mwhudson): This is a bit silly for now, but it will need to have
-	// "|| Linkshared" appended when a subsequent change adds that flag.
-	return Buildmode == BuildmodeShared
+	return Buildmode == BuildmodeShared || Linkshared
 }
 
 var (
@@ -171,6 +171,7 @@
 	flag_installsuffix string
 	flag_race          int
 	Buildmode          BuildMode
+	Linkshared         bool
 	tracksym           string
 	interpreter        string
 	tmpdir             string
@@ -371,16 +372,25 @@
 }
 
 func loadinternal(name string) {
-	var pname string
-
 	found := 0
 	for i := 0; i < len(Ctxt.Libdir); i++ {
-		pname = fmt.Sprintf("%s/%s.a", Ctxt.Libdir[i], name)
+		if Linkshared {
+			shlibname := fmt.Sprintf("%s/%s.shlibname", Ctxt.Libdir[i], name)
+			if Debug['v'] != 0 {
+				fmt.Fprintf(&Bso, "searching for %s.a in %s\n", name, shlibname)
+			}
+			if obj.Access(shlibname, obj.AEXIST) >= 0 {
+				addlibpath(Ctxt, "internal", "internal", "", name, shlibname)
+				found = 1
+				break
+			}
+		}
+		pname := fmt.Sprintf("%s/%s.a", Ctxt.Libdir[i], name)
 		if Debug['v'] != 0 {
 			fmt.Fprintf(&Bso, "searching for %s.a in %s\n", name, pname)
 		}
 		if obj.Access(pname, obj.AEXIST) >= 0 {
-			addlibpath(Ctxt, "internal", "internal", pname, name)
+			addlibpath(Ctxt, "internal", "internal", pname, name, "")
 			found = 1
 			break
 		}
@@ -412,7 +422,11 @@
 			fmt.Fprintf(&Bso, "%5.2f autolib: %s (from %s)\n", obj.Cputime(), Ctxt.Library[i].File, Ctxt.Library[i].Objref)
 		}
 		iscgo = iscgo || Ctxt.Library[i].Pkg == "runtime/cgo"
-		objfile(Ctxt.Library[i].File, Ctxt.Library[i].Pkg)
+		if Ctxt.Library[i].Shlib != "" {
+			ldshlibsyms(Ctxt.Library[i].Shlib)
+		} else {
+			objfile(Ctxt.Library[i].File, Ctxt.Library[i].Pkg)
+		}
 	}
 
 	if Linkmode == LinkAuto {
@@ -451,7 +465,11 @@
 		loadinternal("runtime/cgo")
 
 		if i < len(Ctxt.Library) {
-			objfile(Ctxt.Library[i].File, Ctxt.Library[i].Pkg)
+			if Ctxt.Library[i].Shlib != "" {
+				ldshlibsyms(Ctxt.Library[i].Shlib)
+			} else {
+				objfile(Ctxt.Library[i].File, Ctxt.Library[i].Pkg)
+			}
 		}
 	}
 
@@ -482,7 +500,7 @@
 	// TODO(crawshaw): android should require leaving the tlsg->type
 	// alone (as the runtime-provided SNOPTRBSS) just like darwin/arm.
 	// But some other part of the linker is expecting STLSBSS.
-	if goos != "darwin" || Thearch.Thechar != '5' {
+	if tlsg.Type != SDYNIMPORT && (goos != "darwin" || Thearch.Thechar != '5') {
 		tlsg.Type = STLSBSS
 	}
 	tlsg.Size = int64(Thearch.Ptrsize)
@@ -827,6 +845,13 @@
 		argv = append(argv, "-shared")
 	}
 
+	if Linkshared && Iself {
+		// We force all symbol resolution to be done at program startup
+		// because lazy PLT resolution can use large amounts of stack at
+		// times we cannot allow it to do so.
+		argv = append(argv, "-znow")
+	}
+
 	argv = append(argv, "-o")
 	argv = append(argv, outfile)
 
@@ -879,6 +904,18 @@
 	}
 
 	argv = append(argv, fmt.Sprintf("%s/go.o", tmpdir))
+
+	if Linkshared {
+		for _, shlib := range Ctxt.Shlibs {
+			dir, base := filepath.Split(shlib)
+			argv = append(argv, "-L"+dir)
+			argv = append(argv, "-Wl,-rpath="+dir)
+			base = strings.TrimSuffix(base, ".so")
+			base = strings.TrimPrefix(base, "lib")
+			argv = append(argv, "-l"+base)
+		}
+	}
+
 	argv = append(argv, ldflag...)
 
 	for _, p := range strings.Fields(extldflags) {
@@ -1029,6 +1066,91 @@
 	Diag("truncated object file: %s", pn)
 }
 
+func ldshlibsyms(shlib string) {
+	found := false
+	libpath := ""
+	for _, libdir := range Ctxt.Libdir {
+		libpath = filepath.Join(libdir, shlib)
+		if _, err := os.Stat(libpath); err == nil {
+			found = true
+			break
+		}
+	}
+	if !found {
+		Diag("cannot find shared library: %s", shlib)
+		return
+	}
+	for _, processedname := range Ctxt.Shlibs {
+		if processedname == libpath {
+			return
+		}
+	}
+	if Ctxt.Debugvlog > 1 && Ctxt.Bso != nil {
+		fmt.Fprintf(Ctxt.Bso, "%5.2f ldshlibsyms: found library with name %s at %s\n", obj.Cputime(), shlib, libpath)
+		Bflush(Ctxt.Bso)
+	}
+
+	f, err := elf.Open(libpath)
+	if err != nil {
+		Diag("cannot open shared library: %s", libpath)
+		return
+	}
+	defer f.Close()
+	syms, err := f.DynamicSymbols()
+	if err != nil {
+		Diag("cannot read symbols from shared library: %s", libpath)
+		return
+	}
+	for _, s := range syms {
+		if elf.ST_TYPE(s.Info) == elf.STT_NOTYPE || elf.ST_TYPE(s.Info) == elf.STT_SECTION {
+			continue
+		}
+		if s.Section == elf.SHN_UNDEF {
+			continue
+		}
+		if strings.HasPrefix(s.Name, "_") {
+			continue
+		}
+		lsym := Linklookup(Ctxt, s.Name, 0)
+		if lsym.Type != 0 && lsym.Dupok == 0 {
+			Diag(
+				"Found duplicate symbol %s reading from %s, first found in %s",
+				s.Name, shlib, lsym.File)
+		}
+		lsym.Type = SDYNIMPORT
+		lsym.File = libpath
+	}
+
+	// We might have overwritten some functions above (this tends to happen for the
+	// autogenerated type equality/hashing functions) and we don't want to generated
+	// pcln table entries for these any more so unstitch them from the Textp linked
+	// list.
+	var last *LSym
+
+	for s := Ctxt.Textp; s != nil; s = s.Next {
+		if s.Type == SDYNIMPORT {
+			continue
+		}
+
+		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
+	}
+
+	Ctxt.Shlibs = append(Ctxt.Shlibs, libpath)
+}
+
 func mywhatsys() {
 	goroot = obj.Getgoroot()
 	goos = obj.Getgoos()
diff --git a/src/cmd/internal/ld/link.go b/src/cmd/internal/ld/link.go
index 83cfe28..0eca045 100644
--- a/src/cmd/internal/ld/link.go
+++ b/src/cmd/internal/ld/link.go
@@ -114,6 +114,7 @@
 	Tlsg      *LSym
 	Libdir    []string
 	Library   []Library
+	Shlibs    []string
 	Tlsoffset int
 	Diag      func(string, ...interface{})
 	Cursym    *LSym
@@ -138,6 +139,7 @@
 	Srcref string
 	File   string
 	Pkg    string
+	Shlib  string
 }
 
 type Pcln struct {
diff --git a/src/cmd/internal/ld/pobj.go b/src/cmd/internal/ld/pobj.go
index 539e3d3..c4e779d 100644
--- a/src/cmd/internal/ld/pobj.go
+++ b/src/cmd/internal/ld/pobj.go
@@ -112,6 +112,7 @@
 	obj.Flagstr("installsuffix", "suffix: pkg directory suffix", &flag_installsuffix)
 	obj.Flagstr("k", "sym: set field tracking symbol", &tracksym)
 	obj.Flagfn1("linkmode", "mode: set link mode (internal, external, auto)", setlinkmode)
+	flag.BoolVar(&Linkshared, "linkshared", false, "link against installed Go shared libraries")
 	obj.Flagcount("n", "dump symbol table", &Debug['n'])
 	obj.Flagstr("o", "outfile: set output file", &outfile)
 	obj.Flagstr("r", "dir1:dir2:...: set ELF dynamic linker search path", &rpath)
@@ -176,6 +177,11 @@
 
 	Thearch.Archinit()
 
+	if Linkshared && !Iself {
+		Diag("-linkshared can only be used on elf systems")
+		Errorexit()
+	}
+
 	if Debug['v'] != 0 {
 		fmt.Fprintf(&Bso, "HEADER = -H%d -T0x%x -D0x%x -R0x%x\n", HEADTYPE, uint64(INITTEXT), uint64(INITDAT), uint32(INITRND))
 	}
@@ -191,10 +197,10 @@
 			} else {
 				pkgpath, file = parts[0], parts[1]
 			}
-			addlibpath(Ctxt, "command line", "command line", file, pkgpath)
+			addlibpath(Ctxt, "command line", "command line", file, pkgpath, "")
 		}
 	} else {
-		addlibpath(Ctxt, "command line", "command line", flag.Arg(0), "main")
+		addlibpath(Ctxt, "command line", "command line", flag.Arg(0), "main", "")
 	}
 	loadlib()
 
diff --git a/src/cmd/internal/ld/symtab.go b/src/cmd/internal/ld/symtab.go
index c31f70a..1898a9b 100644
--- a/src/cmd/internal/ld/symtab.go
+++ b/src/cmd/internal/ld/symtab.go
@@ -104,6 +104,9 @@
 
 	case 'U':
 		type_ = STT_NOTYPE
+		if x == Ctxt.Tlsg {
+			type_ = STT_TLS
+		}
 
 	case 't':
 		type_ = STT_TLS