Skip to content

Commit

Permalink
cmd/internal/ld, runtime: abort on shared library ABI mismatch
Browse files Browse the repository at this point in the history
This:

1) Defines the ABI hash of a package (as the SHA1 of the __.PKGDEF)
2) Defines the ABI hash of a shared library (sort the packages by import
   path, concatenate the hashes of the packages and SHA1 that)
3) When building a shared library, compute the above value and define a
   global symbol that points to a go string that has the hash as its value.
4) When linking against a shared library, read the abi hash from the
   library and put both the value seen at link time and a reference
   to the global symbol into the moduledata.
5) During runtime initialization, check that the hash seen at link time
   still matches the hash the global symbol points to.

Change-Id: Iaa54c783790e6dde3057a2feadc35473d49614a5
Reviewed-on: https://go-review.googlesource.com/8773
Reviewed-by: Ian Lance Taylor <[email protected]>
Run-TryBot: Michael Hudson-Doyle <[email protected]>
  • Loading branch information
mwhudson authored and ianlancetaylor committed May 12, 2015
1 parent be0cb92 commit 77fc03f
Show file tree
Hide file tree
Showing 7 changed files with 186 additions and 42 deletions.
30 changes: 28 additions & 2 deletions misc/cgo/testshared/test.bash
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@

set -eu

export GOPATH="$(pwd)"

die () {
echo $@
Expand All @@ -23,8 +22,14 @@ rootdir="$(dirname $(go list -f '{{.Target}}' runtime))"
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

Expand Down Expand Up @@ -109,3 +114,24 @@ will_check_rebuilt $rootdir/libdep.so $rootdir/dep.a
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"
16 changes: 16 additions & 0 deletions src/cmd/internal/ld/data.go
Original file line number Diff line number Diff line change
Expand Up @@ -963,6 +963,22 @@ func Addstring(s *LSym, str string) int64 {
return int64(r)
}

// addgostring adds str, as a Go string value, to s. symname is the name of the
// symbol used to define the string data and must be unique per linked object.
func addgostring(s *LSym, symname, str string) {
sym := Linklookup(Ctxt, symname, 0)
if sym.Type != obj.Sxxx {
Diag("duplicate symname in addgostring: %s", symname)
}
sym.Reachable = true
sym.Local = true
sym.Type = obj.SRODATA
sym.Size = int64(len(str))
sym.P = []byte(str)
Addaddr(Ctxt, s, sym)
adduint(Ctxt, s, uint64(len(str)))
}

func addinitarrdata(s *LSym) {
p := s.Name + ".ptr"
sp := Linklookup(Ctxt, p, 0)
Expand Down
4 changes: 2 additions & 2 deletions src/cmd/internal/ld/ld.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,8 +109,8 @@ func addlibpath(ctxt *Link, srcref string, objref string, file string, pkg strin
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{})
l := &ctxt.Library[len(ctxt.Library)-1]
ctxt.Library = append(ctxt.Library, &Library{})
l := ctxt.Library[len(ctxt.Library)-1]
l.Objref = objref
l.Srcref = srcref
l.File = file
Expand Down
85 changes: 49 additions & 36 deletions src/cmd/internal/ld/lib.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import (
"bufio"
"bytes"
"cmd/internal/obj"
"crypto/sha1"
"debug/elf"
"fmt"
"io"
Expand Down Expand Up @@ -474,7 +475,7 @@ func loadlib() {
if Ctxt.Library[i].Shlib != "" {
ldshlibsyms(Ctxt.Library[i].Shlib)
} else {
objfile(Ctxt.Library[i].File, Ctxt.Library[i].Pkg)
objfile(Ctxt.Library[i])
}
}

Expand Down Expand Up @@ -520,7 +521,7 @@ func loadlib() {
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])
}
}
}
Expand Down Expand Up @@ -631,18 +632,18 @@ func nextar(bp *obj.Biobuf, off int64, a *ArHdr) int64 {
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))
Expand All @@ -651,7 +652,7 @@ func objfile(file string, pkg string) {
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
Expand All @@ -664,28 +665,37 @@ func objfile(file string, pkg string) {
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
}

if strings.HasPrefix(arhdr.name, symname) {
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)
}

/*
Expand All @@ -706,14 +716,14 @@ func objfile(file string, pkg string) {
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:
Expand Down Expand Up @@ -974,7 +984,7 @@ func hostlink() {

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)
Expand Down Expand Up @@ -1120,6 +1130,19 @@ func ldobj(f *obj.Biobuf, pkg string, length int64, pn string, file string, when
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 := ""
Expand All @@ -1134,8 +1157,8 @@ func ldshlibsyms(shlib string) {
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
}
}
Expand Down Expand Up @@ -1167,6 +1190,7 @@ func ldshlibsyms(shlib string) {
// 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
Expand All @@ -1178,15 +1202,10 @@ func ldshlibsyms(shlib string) {
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
Expand All @@ -1201,14 +1220,8 @@ func ldshlibsyms(shlib string) {
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)
Expand Down Expand Up @@ -1255,7 +1268,7 @@ func ldshlibsyms(shlib string) {
Ctxt.Etextp = last
}

Ctxt.Shlibs = append(Ctxt.Shlibs, libpath)
Ctxt.Shlibs = append(Ctxt.Shlibs, Shlib{Path: libpath, Hash: hash})
}

func mywhatsys() {
Expand Down
10 changes: 8 additions & 2 deletions src/cmd/internal/ld/link.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,11 @@ type Auto struct {
Gotype *LSym
}

type Shlib struct {
Path string
Hash []byte
}

type Link struct {
Thechar int32
Thestring string
Expand All @@ -122,8 +127,8 @@ type Link struct {
Nsymbol int32
Tlsg *LSym
Libdir []string
Library []Library
Shlibs []string
Library []*Library
Shlibs []Shlib
Tlsoffset int
Diag func(string, ...interface{})
Cursym *LSym
Expand All @@ -149,6 +154,7 @@ type Library struct {
File string
Pkg string
Shlib string
hash []byte
}

type Pcln struct {
Expand Down
Loading

0 comments on commit 77fc03f

Please sign in to comment.