Previous CL introduced index fingerprint in the object files.
This CL implements the second part: checking fingerprint
consistency in the linker when packages are loaded.
Change-Id: I05dd4c4045a65adfd95e77b625d6c75a7a70e4f1
Reviewed-on: https://go-review.googlesource.com/c/go/+/229618
Run-TryBot: Cherry Zhang <cherryyz@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Than McIntosh <thanm@google.com>
Reviewed-by: Jeremy Faller <jeremy@golang.org>
type FingerprintType [8]byte
+func (fp FingerprintType) IsZero() bool { return fp == FingerprintType{} }
+
// Package Index.
const (
PkgIdxNone = (1<<31 - 1) - iota // Non-package symbols
package ld
import (
+ "cmd/internal/goobj2"
"cmd/link/internal/loader"
"cmd/link/internal/sym"
"io/ioutil"
return pname, isshlib
}
-func addlib(ctxt *Link, src string, obj string, lib string) *sym.Library {
+func addlib(ctxt *Link, src, obj, lib string, fingerprint goobj2.FingerprintType) *sym.Library {
pkg := pkgname(ctxt, lib)
// already loaded?
if l := ctxt.LibraryByPkg[pkg]; l != nil {
+ checkFingerprint(l, l.Fingerprint, src, fingerprint)
return l
}
}
if isshlib {
- return addlibpath(ctxt, src, obj, "", pkg, pname)
+ return addlibpath(ctxt, src, obj, "", pkg, pname, fingerprint)
}
- return addlibpath(ctxt, src, obj, pname, pkg, "")
+ return addlibpath(ctxt, src, obj, pname, pkg, "", fingerprint)
}
/*
* file: object file, e.g., /home/rsc/go/pkg/container/vector.a
* pkg: package import path, e.g. container/vector
* shlib: path to shared library, or .shlibname file holding path
+ * fingerprint: if not 0, expected fingerprint for import from srcref
+ * fingerprint is 0 if the library is not imported (e.g. main)
*/
-func addlibpath(ctxt *Link, srcref string, objref string, file string, pkg string, shlib string) *sym.Library {
+func addlibpath(ctxt *Link, srcref, objref, file, pkg, shlib string, fingerprint goobj2.FingerprintType) *sym.Library {
if l := ctxt.LibraryByPkg[pkg]; l != nil {
return l
}
if ctxt.Debugvlog > 1 {
- ctxt.Logf("addlibpath: srcref: %s objref: %s file: %s pkg: %s shlib: %s\n", srcref, objref, file, pkg, shlib)
+ ctxt.Logf("addlibpath: srcref: %s objref: %s file: %s pkg: %s shlib: %s fingerprint: %x\n", srcref, objref, file, pkg, shlib, fingerprint)
}
l := &sym.Library{}
l.Srcref = srcref
l.File = file
l.Pkg = pkg
+ l.Fingerprint = fingerprint
if shlib != "" {
if strings.HasSuffix(shlib, ".shlibname") {
data, err := ioutil.ReadFile(shlib)
import (
"bytes"
"cmd/internal/bio"
+ "cmd/internal/goobj2"
"cmd/internal/obj"
"cmd/internal/objabi"
"cmd/internal/sys"
}
func loadinternal(ctxt *Link, name string) *sym.Library {
+ zerofp := goobj2.FingerprintType{}
if ctxt.linkShared && ctxt.PackageShlib != nil {
if shlib := ctxt.PackageShlib[name]; shlib != "" {
- return addlibpath(ctxt, "internal", "internal", "", name, shlib)
+ return addlibpath(ctxt, "internal", "internal", "", name, shlib, zerofp)
}
}
if ctxt.PackageFile != nil {
if pname := ctxt.PackageFile[name]; pname != "" {
- return addlibpath(ctxt, "internal", "internal", pname, name, "")
+ return addlibpath(ctxt, "internal", "internal", pname, name, "", zerofp)
}
ctxt.Logf("loadinternal: cannot find %s\n", name)
return nil
ctxt.Logf("searching for %s.a in %s\n", name, shlibname)
}
if _, err := os.Stat(shlibname); err == nil {
- return addlibpath(ctxt, "internal", "internal", "", name, shlibname)
+ return addlibpath(ctxt, "internal", "internal", "", name, shlibname, zerofp)
}
}
pname := filepath.Join(libdir, name+".a")
ctxt.Logf("searching for %s.a in %s\n", name, pname)
}
if _, err := os.Stat(pname); err == nil {
- return addlibpath(ctxt, "internal", "internal", pname, name, "")
+ return addlibpath(ctxt, "internal", "internal", pname, name, "", zerofp)
}
}
ldpkg(ctxt, f, lib, import1-import0-2, pn) // -2 for !\n
f.MustSeek(import1, 0)
- ctxt.loader.Preload(ctxt.Syms, f, lib, unit, eof-f.Offset(), 0)
+ fingerprint := ctxt.loader.Preload(ctxt.Syms, f, lib, unit, eof-f.Offset())
+ if !fingerprint.IsZero() { // Assembly objects don't have fingerprints. Ignore them.
+ // Check fingerprint, to ensure the importing and imported packages
+ // have consistent view of symbol indices.
+ // Normally the go command should ensure this. But in case something
+ // goes wrong, it could lead to obscure bugs like run-time crash.
+ // Check it here to be sure.
+ if lib.Fingerprint.IsZero() { // Not yet imported. Update its fingerprint.
+ lib.Fingerprint = fingerprint
+ }
+ checkFingerprint(lib, fingerprint, lib.Srcref, lib.Fingerprint)
+ }
+
addImports(ctxt, lib, pn)
return nil
}
+func checkFingerprint(lib *sym.Library, libfp goobj2.FingerprintType, src string, srcfp goobj2.FingerprintType) {
+ if libfp != srcfp {
+ Exitf("fingerprint mismatch: %s has %x, import from %s expecting %x", lib, libfp, src, srcfp)
+ }
+}
+
func readelfsymboldata(ctxt *Link, f *elf.File, sym *elf.Symbol) []byte {
data := make([]byte, sym.Size)
sect := f.Sections[sym.Section]
func addImports(ctxt *Link, l *sym.Library, pn string) {
pkg := objabi.PathToPrefix(l.Pkg)
- for _, importStr := range l.ImportStrings {
- lib := addlib(ctxt, pkg, pn, importStr)
+ for _, imp := range l.Autolib {
+ lib := addlib(ctxt, pkg, pn, imp.Pkg, imp.Fingerprint)
if lib != nil {
l.Imports = append(l.Imports, lib)
}
}
- l.ImportStrings = nil
+ l.Autolib = nil
}
import (
"bufio"
+ "cmd/internal/goobj2"
"cmd/internal/objabi"
"cmd/internal/sys"
"cmd/link/internal/benchmark"
ctxt.Logf("HEADER = -H%d -T0x%x -R0x%x\n", ctxt.HeadType, uint64(*FlagTextAddr), uint32(*FlagRound))
}
+ zerofp := goobj2.FingerprintType{}
switch ctxt.BuildMode {
case BuildModeShared:
for i := 0; i < flag.NArg(); i++ {
}
pkglistfornote = append(pkglistfornote, pkgpath...)
pkglistfornote = append(pkglistfornote, '\n')
- addlibpath(ctxt, "command line", "command line", file, pkgpath, "")
+ addlibpath(ctxt, "command line", "command line", file, pkgpath, "", zerofp)
}
case BuildModePlugin:
- addlibpath(ctxt, "command line", "command line", flag.Arg(0), *flagPluginPath, "")
+ addlibpath(ctxt, "command line", "command line", flag.Arg(0), *flagPluginPath, "", zerofp)
default:
- addlibpath(ctxt, "command line", "command line", flag.Arg(0), "main", "")
+ addlibpath(ctxt, "command line", "command line", flag.Arg(0), "main", "", zerofp)
}
bench.Start("loadlib")
ctxt.loadlib()
// Preload a package: add autolibs, add defined package symbols to the symbol table.
// Does not add non-package symbols yet, which will be done in LoadNonpkgSyms.
// Does not read symbol data.
-func (l *Loader) Preload(syms *sym.Symbols, f *bio.Reader, lib *sym.Library, unit *sym.CompilationUnit, length int64, flags int) {
+// Returns the fingerprint of the object.
+func (l *Loader) Preload(syms *sym.Symbols, f *bio.Reader, lib *sym.Library, unit *sym.CompilationUnit, length int64) goobj2.FingerprintType {
roObject, readonly, err := f.Slice(uint64(length))
if err != nil {
log.Fatal("cannot read object file:", err)
or := &oReader{r, unit, localSymVersion, r.Flags(), pkgprefix, make([]Sym, ndef+nnonpkgdef+r.NNonpkgref()), ndef, uint32(len(l.objs))}
// Autolib
- autolib := r.Autolib()
- for _, p := range autolib {
- lib.ImportStrings = append(lib.ImportStrings, p.Pkg)
- // TODO: fingerprint is ignored for now
- }
+ lib.Autolib = append(lib.Autolib, r.Autolib()...)
// DWARF file table
nfile := r.NDwarfFile()
// The caller expects us consuming all the data
f.MustSeek(length, os.SEEK_CUR)
+
+ return r.Fingerprint()
}
// Preload symbols of given kind from an object.
package sym
+import "cmd/internal/goobj2"
+
type Library struct {
- Objref string
- Srcref string
- File string
- Pkg string
- Shlib string
- Hash string
- ImportStrings []string
- Imports []*Library
- Main bool
- Safe bool
- Units []*CompilationUnit
+ Objref string
+ Srcref string
+ File string
+ Pkg string
+ Shlib string
+ Hash string
+ Fingerprint goobj2.FingerprintType
+ Autolib []goobj2.ImportedPkg
+ Imports []*Library
+ Main bool
+ Safe bool
+ Units []*CompilationUnit
Textp2 []LoaderSym // text syms defined in this library
DupTextSyms2 []LoaderSym // dupok text syms defined in this library
t.Errorf("unexpected output:\n%s", out)
}
}
+
+func TestIndexMismatch(t *testing.T) {
+ // Test that index mismatch will cause a link-time error (not run-time error).
+ // This shouldn't happen with "go build". We invoke the compiler and the linker
+ // manually, and try to "trick" the linker with an inconsistent object file.
+ testenv.MustHaveGoBuild(t)
+
+ tmpdir, err := ioutil.TempDir("", "TestIndexMismatch")
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer os.RemoveAll(tmpdir)
+
+ aSrc := filepath.Join("testdata", "testIndexMismatch", "a.go")
+ bSrc := filepath.Join("testdata", "testIndexMismatch", "b.go")
+ mSrc := filepath.Join("testdata", "testIndexMismatch", "main.go")
+ aObj := filepath.Join(tmpdir, "a.o")
+ mObj := filepath.Join(tmpdir, "main.o")
+ exe := filepath.Join(tmpdir, "main.exe")
+
+ // Build a program with main package importing package a.
+ cmd := exec.Command(testenv.GoToolPath(t), "tool", "compile", "-o", aObj, aSrc)
+ t.Log(cmd)
+ out, err := cmd.CombinedOutput()
+ if err != nil {
+ t.Fatalf("compiling a.go failed: %v\n%s", err, out)
+ }
+ cmd = exec.Command(testenv.GoToolPath(t), "tool", "compile", "-I", tmpdir, "-o", mObj, mSrc)
+ t.Log(cmd)
+ out, err = cmd.CombinedOutput()
+ if err != nil {
+ t.Fatalf("compiling main.go failed: %v\n%s", err, out)
+ }
+ cmd = exec.Command(testenv.GoToolPath(t), "tool", "link", "-L", tmpdir, "-o", exe, mObj)
+ t.Log(cmd)
+ out, err = cmd.CombinedOutput()
+ if err != nil {
+ t.Errorf("linking failed: %v\n%s", err, out)
+ }
+
+ // Now, overwrite a.o with the object of b.go. This should
+ // result in an index mismatch.
+ cmd = exec.Command(testenv.GoToolPath(t), "tool", "compile", "-o", aObj, bSrc)
+ t.Log(cmd)
+ out, err = cmd.CombinedOutput()
+ if err != nil {
+ t.Fatalf("compiling a.go failed: %v\n%s", err, out)
+ }
+ cmd = exec.Command(testenv.GoToolPath(t), "tool", "link", "-L", tmpdir, "-o", exe, mObj)
+ t.Log(cmd)
+ out, err = cmd.CombinedOutput()
+ if err == nil {
+ t.Fatalf("linking didn't fail")
+ }
+ if !bytes.Contains(out, []byte("fingerprint mismatch")) {
+ t.Errorf("did not see expected error message. out:\n%s", out)
+ }
+}
--- /dev/null
+// Copyright 2020 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.
+
+package a
+
+//go:noinline
+func A() { println("A") }
--- /dev/null
+// Copyright 2020 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.
+
+package a
+
+//go:noinline
+func B() { println("B") }
--- /dev/null
+// Copyright 2020 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.
+
+package main
+
+import "a"
+
+func main() { a.A() }