set -eu
-export GOPATH="$(pwd)"
die () {
echo $@
template="${rootdir}_XXXXXXXX_dynlink"
std_install_dir=$(mktemp -d "$template")
+scratch_dir=$(mktemp -d)
+cp -a . $scratch_dir
+opwd="$(pwd)"
+cd $scratch_dir
+export GOPATH="$(pwd)"
+
cleanup () {
- rm -rf $std_install_dir ./bin/ ./pkg/
+ rm -rf $std_install_dir $scratch_dir
}
trap cleanup EXIT
go install -installsuffix="$mysuffix" -linkshared exe
assert_not_rebuilt $rootdir/dep.a
assert_rebuilt $rootdir/libdep.so
+
+# If we make an ABI-breaking change to dep and rebuild libp.so but not exe, exe will
+# abort with a complaint on startup.
+# This assumes adding an exported function breaks ABI, which is not true in some
+# senses but suffices for the narrow definition of ABI compatiblity the toolchain
+# uses today.
+echo "func ABIBreak() {}" >> src/dep/dep.go
+go install -installsuffix="$mysuffix" -buildmode=shared -linkshared dep
+output="$(./bin/exe 2>&1)" && die "exe succeeded after ABI break" || true
+msg="abi mismatch detected between the executable and libdep.so"
+{ echo "$output" | grep -q "$msg"; } || die "exe did not fail with expected message"
+
+# Rebuilding exe makes it work again.
+go install -installsuffix="$mysuffix" -linkshared exe
+./bin/exe || die "exe failed after rebuild"
+
+# If we make a change which does not break ABI (such as adding an
+# unexported function) and rebuild libdep.so, exe still works.
+echo "func noABIBreak() {}" >> src/dep/dep.go
+go install -installsuffix="$mysuffix" -buildmode=shared -linkshared dep
+./bin/exe || die "exe failed after non-ABI breaking change"
"bufio"
"bytes"
"cmd/internal/obj"
+ "crypto/sha1"
"debug/elf"
"fmt"
"io"
if Ctxt.Library[i].Shlib != "" {
ldshlibsyms(Ctxt.Library[i].Shlib)
} else {
- objfile(Ctxt.Library[i].File, Ctxt.Library[i].Pkg)
+ objfile(Ctxt.Library[i])
}
}
if DynlinkingGo() {
Exitf("cannot implicitly include runtime/cgo in a shared library")
}
- objfile(Ctxt.Library[i].File, Ctxt.Library[i].Pkg)
+ objfile(Ctxt.Library[i])
}
}
}
return int64(arsize) + SAR_HDR
}
-func objfile(file string, pkg string) {
- pkg = pathtoprefix(pkg)
+func objfile(lib *Library) {
+ pkg := pathtoprefix(lib.Pkg)
if Debug['v'] > 1 {
- fmt.Fprintf(&Bso, "%5.2f ldobj: %s (%s)\n", obj.Cputime(), file, pkg)
+ fmt.Fprintf(&Bso, "%5.2f ldobj: %s (%s)\n", obj.Cputime(), lib.File, pkg)
}
Bso.Flush()
var err error
var f *obj.Biobuf
- f, err = obj.Bopenr(file)
+ f, err = obj.Bopenr(lib.File)
if err != nil {
- Exitf("cannot open file %s: %v", file, err)
+ Exitf("cannot open file %s: %v", lib.File, err)
}
magbuf := make([]byte, len(ARMAG))
l := obj.Bseek(f, 0, 2)
obj.Bseek(f, 0, 0)
- ldobj(f, pkg, l, file, file, FileObj)
+ ldobj(f, pkg, l, lib.File, lib.File, FileObj)
obj.Bterm(f)
return
l := nextar(f, off, &arhdr)
var pname string
if l <= 0 {
- Diag("%s: short read on archive file symbol header", file)
+ Diag("%s: short read on archive file symbol header", lib.File)
goto out
}
off += l
l = nextar(f, off, &arhdr)
if l <= 0 {
- Diag("%s: short read on archive file symbol header", file)
+ Diag("%s: short read on archive file symbol header", lib.File)
goto out
}
}
if !strings.HasPrefix(arhdr.name, pkgname) {
- Diag("%s: cannot find package header", file)
+ Diag("%s: cannot find package header", lib.File)
goto out
}
+ if Buildmode == BuildmodeShared {
+ before := obj.Boffset(f)
+ pkgdefBytes := make([]byte, atolwhex(arhdr.size))
+ obj.Bread(f, pkgdefBytes)
+ hash := sha1.Sum(pkgdefBytes)
+ lib.hash = hash[:]
+ obj.Bseek(f, before, 0)
+ }
+
off += l
if Debug['u'] != 0 {
- ldpkg(f, pkg, atolwhex(arhdr.size), file, Pkgdef)
+ ldpkg(f, pkg, atolwhex(arhdr.size), lib.File, Pkgdef)
}
/*
break
}
if l < 0 {
- Exitf("%s: malformed archive", file)
+ Exitf("%s: malformed archive", lib.File)
}
off += l
- pname = fmt.Sprintf("%s(%s)", file, arhdr.name)
+ pname = fmt.Sprintf("%s(%s)", lib.File, arhdr.name)
l = atolwhex(arhdr.size)
- ldobj(f, pkg, l, pname, file, ArchiveObj)
+ ldobj(f, pkg, l, pname, lib.File, ArchiveObj)
}
out:
if Linkshared {
for _, shlib := range Ctxt.Shlibs {
- dir, base := filepath.Split(shlib)
+ dir, base := filepath.Split(shlib.Path)
argv = append(argv, "-L"+dir)
if !rpath.set {
argv = append(argv, "-Wl,-rpath="+dir)
ldobjfile(Ctxt, f, pkg, eof-obj.Boffset(f), pn)
}
+func readelfsymboldata(f *elf.File, sym *elf.Symbol) []byte {
+ data := make([]byte, sym.Size)
+ sect := f.Sections[sym.Section]
+ if sect.Type != elf.SHT_PROGBITS {
+ Diag("reading %s from non-PROGBITS section", sym.Name)
+ }
+ n, err := sect.ReadAt(data, int64(sym.Value-sect.Offset))
+ if uint64(n) != sym.Size {
+ Diag("reading contents of %s: %v", sym.Name, err)
+ }
+ return data
+}
+
func ldshlibsyms(shlib string) {
found := false
libpath := ""
Diag("cannot find shared library: %s", shlib)
return
}
- for _, processedname := range Ctxt.Shlibs {
- if processedname == libpath {
+ for _, processedlib := range Ctxt.Shlibs {
+ if processedlib.Path == libpath {
return
}
}
// table removed.
gcmasks := make(map[uint64][]byte)
types := []*LSym{}
+ var hash []byte
for _, s := range syms {
if elf.ST_TYPE(s.Info) == elf.STT_NOTYPE || elf.ST_TYPE(s.Info) == elf.STT_SECTION {
continue
continue
}
if strings.HasPrefix(s.Name, "runtime.gcbits.0x") {
- data := make([]byte, s.Size)
- sect := f.Sections[s.Section]
- if sect.Type == elf.SHT_PROGBITS {
- n, err := sect.ReadAt(data, int64(s.Value-sect.Offset))
- if uint64(n) != s.Size {
- Diag("Error reading contents of %s: %v", s.Name, err)
- }
- }
- gcmasks[s.Value] = data
+ gcmasks[s.Value] = readelfsymboldata(f, &s)
+ }
+ if s.Name == "go.link.abihashbytes" {
+ hash = readelfsymboldata(f, &s)
}
if elf.ST_BIND(s.Info) != elf.STB_GLOBAL {
continue
lsym.ElfType = elf.ST_TYPE(s.Info)
lsym.File = libpath
if strings.HasPrefix(lsym.Name, "type.") {
- data := make([]byte, s.Size)
- sect := f.Sections[s.Section]
- if sect.Type == elf.SHT_PROGBITS {
- n, err := sect.ReadAt(data, int64(s.Value-sect.Offset))
- if uint64(n) != s.Size {
- Diag("Error reading contents of %s: %v", s.Name, err)
- }
- lsym.P = data
+ if f.Sections[s.Section].Type == elf.SHT_PROGBITS {
+ lsym.P = readelfsymboldata(f, &s)
}
if !strings.HasPrefix(lsym.Name, "type..") {
types = append(types, lsym)
Ctxt.Etextp = last
}
- Ctxt.Shlibs = append(Ctxt.Shlibs, libpath)
+ Ctxt.Shlibs = append(Ctxt.Shlibs, Shlib{Path: libpath, Hash: hash})
}
func mywhatsys() {
import (
"cmd/internal/obj"
+ "crypto/sha1"
+ "fmt"
+ "path/filepath"
+ "sort"
"strings"
)
Lputl(uint32(v >> 32))
}
+type byPkg []*Library
+
+func (libs byPkg) Len() int {
+ return len(libs)
+}
+
+func (libs byPkg) Less(a, b int) bool {
+ return libs[a].Pkg < libs[b].Pkg
+}
+
+func (libs byPkg) Swap(a, b int) {
+ libs[a], libs[b] = libs[b], libs[a]
+}
+
func symtab() {
dosymtype()
}
}
+ if Buildmode == BuildmodeShared {
+ sort.Sort(byPkg(Ctxt.Library))
+ h := sha1.New()
+ for _, l := range Ctxt.Library {
+ h.Write(l.hash)
+ }
+ abihashgostr := Linklookup(Ctxt, "go.link.abihash."+filepath.Base(outfile), 0)
+ abihashgostr.Reachable = true
+ abihashgostr.Type = obj.SRODATA
+ var hashbytes []byte
+ addgostring(abihashgostr, "go.link.abihashbytes", string(h.Sum(hashbytes)))
+ }
+
// Information about the layout of the executable image for the
// runtime to use. Any changes here must be matched by changes to
// the definition of moduledata in runtime/symtab.go.
Addaddr(Ctxt, moduledata, Linklookup(Ctxt, "runtime.typelink", 0))
adduint(Ctxt, moduledata, uint64(ntypelinks))
adduint(Ctxt, moduledata, uint64(ntypelinks))
+ if len(Ctxt.Shlibs) > 0 {
+ thismodulename := filepath.Base(outfile)
+ if Buildmode == BuildmodeExe {
+ // When linking an executable, outfile is just "a.out". Make
+ // it something slightly more comprehensible.
+ thismodulename = "the executable"
+ }
+ addgostring(moduledata, "go.link.thismodulename", thismodulename)
+
+ modulehashes := Linklookup(Ctxt, "go.link.abihashes", 0)
+ modulehashes.Reachable = true
+ modulehashes.Local = true
+ modulehashes.Type = obj.SRODATA
+
+ for i, shlib := range Ctxt.Shlibs {
+ // modulehashes[i].modulename
+ modulename := filepath.Base(shlib.Path)
+ addgostring(modulehashes, fmt.Sprintf("go.link.libname.%d", i), modulename)
+
+ // modulehashes[i].linktimehash
+ addgostring(modulehashes, fmt.Sprintf("go.link.linkhash.%d", i), string(shlib.Hash))
+
+ // modulehashes[i].runtimehash
+ abihash := Linklookup(Ctxt, "go.link.abihash."+modulename, 0)
+ abihash.Reachable = true
+ Addaddr(Ctxt, modulehashes, abihash)
+ }
+
+ Addaddr(Ctxt, moduledata, modulehashes)
+ adduint(Ctxt, moduledata, uint64(len(Ctxt.Shlibs)))
+ adduint(Ctxt, moduledata, uint64(len(Ctxt.Shlibs)))
+ }
// The rest of moduledata is zero initialized.
// When linking an object that does not contain the runtime we are
// creating the moduledata from scratch and it does not have a