Skip to content

Commit

Permalink
cmd/link, cmd/go, cmd/dist: use copy of libgcc.a for internal linking
Browse files Browse the repository at this point in the history
Change the linker to use a copy of the C compiler support library,
libgcc.a, when doing internal linking.  This will be used to satisfy any
undefined symbols referenced by host objects.

Change the dist tool to copy the support library into a new directory
tree under GOROOT/pkg/libgcc.  This ensures that libgcc is available
even when building Go programs on a system that has no C compiler.  The
C compiler is required when building the Go installation in the first
place, but is not required thereafter.

Change the go tool to not link libgcc into cgo objects.

Correct the linker handling of a weak symbol in an ELF input object to
not always create a new symbol, but to use an existing symbol if there
is one; this is necessary on freebsd-amd64, where libgcc contains a weak
definition of compilerrt_abort_impl.

Fixes #9510.

Change-Id: I1ab28182263238d9bcaf6a42804e5da2a87d8778
Reviewed-on: https://go-review.googlesource.com/16741
Reviewed-by: Russ Cox <rsc@golang.org>
  • Loading branch information
ianlancetaylor committed Nov 14, 2015
1 parent 5a0d9ef commit 754f707
Show file tree
Hide file tree
Showing 9 changed files with 272 additions and 71 deletions.
1 change: 1 addition & 0 deletions misc/cgo/test/cgo_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ func Test8811(t *testing.T) { test8811(t) }
func TestReturnAfterGrow(t *testing.T) { testReturnAfterGrow(t) }
func TestReturnAfterGrowFromGo(t *testing.T) { testReturnAfterGrowFromGo(t) }
func Test9026(t *testing.T) { test9026(t) }
func Test9510(t *testing.T) { test9510(t) }
func Test9557(t *testing.T) { test9557(t) }
func Test10303(t *testing.T) { test10303(t, 10) }
func Test11925(t *testing.T) { test11925(t) }
Expand Down
24 changes: 24 additions & 0 deletions misc/cgo/test/issue9510.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// Copyright 2015 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.

// Test that we can link together two different cgo packages that both
// use the same libgcc function.

package cgotest

import (
"runtime"
"testing"

"./issue9510a"
"./issue9510b"
)

func test9510(t *testing.T) {
if runtime.GOARCH == "arm" {
t.Skip("skipping because libgcc may be a Thumb library")
}
issue9510a.F(1, 1)
issue9510b.F(1, 1)
}
15 changes: 15 additions & 0 deletions misc/cgo/test/issue9510a/a.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package issue9510a

/*
static double csquare(double a, double b) {
__complex__ double d;
__real__ d = a;
__imag__ d = b;
return __real__ (d * d);
}
*/
import "C"

func F(a, b float64) float64 {
return float64(C.csquare(C.double(a), C.double(b)))
}
15 changes: 15 additions & 0 deletions misc/cgo/test/issue9510b/b.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package issue9510b

/*
static double csquare(double a, double b) {
__complex__ double d;
__real__ d = a;
__imag__ d = b;
return __real__ (d * d);
}
*/
import "C"

func F(a, b float64) float64 {
return float64(C.csquare(C.double(a), C.double(b)))
}
49 changes: 49 additions & 0 deletions src/cmd/dist/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"bytes"
"flag"
"fmt"
"io"
"os"
"os/exec"
"path/filepath"
Expand Down Expand Up @@ -1002,6 +1003,7 @@ func cmdbootstrap() {
setup()

checkCC()
copyLibgcc()
bootstrapBuildTools()

// For the main bootstrap, building for host os/arch.
Expand Down Expand Up @@ -1110,6 +1112,53 @@ func checkCC() {
}
}

// copyLibgcc copies the C compiler's libgcc into the pkg directory.
func copyLibgcc() {
if !needCC() {
return
}
var args []string
switch goarch {
case "386":
args = []string{"-m32"}
case "amd64", "amd64p32":
args = []string{"-m64"}
case "arm":
args = []string{"-marm"}
}
args = append(args, "--print-libgcc-file-name")
output, err := exec.Command(defaultcctarget, args...).Output()
if err != nil {
fatal("cannot find libgcc file name: %v", err)
}
libgcc := strings.TrimSpace(string(output))
if len(libgcc) == 0 {
return
}
in, err := os.Open(libgcc)
if err != nil {
if os.IsNotExist(err) {
return
}
fatal("cannot open libgcc for copying: %v", err)
}
defer in.Close()
outdir := filepath.Join(goroot, "pkg", "libgcc", goos+"_"+goarch)
if err := os.MkdirAll(outdir, 0777); err != nil {
fatal("cannot create libgcc.a directory: %v", err)
}
out, err := os.Create(filepath.Join(outdir, "libgcc"))
if err != nil {
fatal("cannot create libgcc.a for copying: %v", err)
}
if _, err := io.Copy(out, in); err != nil {
fatal("error copying libgcc: %v", err)
}
if err := out.Close(); err != nil {
fatal("error closing new libgcc: %v", err)
}
}

func defaulttarg() string {
// xgetwd might return a path with symlinks fully resolved, and if
// there happens to be symlinks in goroot, then the hasprefix test
Expand Down
57 changes: 2 additions & 55 deletions src/cmd/go/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -2753,43 +2753,6 @@ func gccgoCleanPkgpath(p *Package) string {
return strings.Map(clean, gccgoPkgpath(p))
}

// libgcc returns the filename for libgcc, as determined by invoking gcc with
// the -print-libgcc-file-name option.
func (b *builder) libgcc(p *Package) (string, error) {
var buf bytes.Buffer

gccCmd := b.gccCmd(p.Dir)

prev := b.print
if buildN {
// In -n mode we temporarily swap out the builder's
// print function to capture the command-line. This
// let's us assign it to $LIBGCC and produce a valid
// buildscript for cgo packages.
b.print = func(a ...interface{}) (int, error) {
return fmt.Fprint(&buf, a...)
}
}
f, err := b.runOut(p.Dir, p.ImportPath, nil, gccCmd, "-print-libgcc-file-name")
if err != nil {
return "", fmt.Errorf("gcc -print-libgcc-file-name: %v (%s)", err, f)
}
if buildN {
s := fmt.Sprintf("LIBGCC=$(%s)\n", buf.Next(buf.Len()-1))
b.print = prev
b.print(s)
return "$LIBGCC", nil
}

// The compiler might not be able to find libgcc, and in that case,
// it will simply return "libgcc.a", which is of no use to us.
if !filepath.IsAbs(string(f)) {
return "", nil
}

return strings.Trim(string(f), "\r\n"), nil
}

// gcc runs the gcc C compiler to create an object from a single C file.
func (b *builder) gcc(p *Package, out string, flags []string, cfile string) error {
return b.ccompile(p, out, flags, cfile, b.gccCmd(p.Dir))
Expand Down Expand Up @@ -2915,12 +2878,6 @@ func (b *builder) cflags(p *Package, def bool) (cppflags, cflags, cxxflags, ldfl

var cgoRe = regexp.MustCompile(`[/\\:]`)

var (
cgoLibGccFile string
cgoLibGccErr error
cgoLibGccFileOnce sync.Once
)

func (b *builder) cgo(p *Package, cgoExe, obj string, pcCFLAGS, pcLDFLAGS, cgofiles, gccfiles, gxxfiles, mfiles []string) (outGo, outObj []string, err error) {
cgoCPPFLAGS, cgoCFLAGS, cgoCXXFLAGS, cgoLDFLAGS := b.cflags(p, true)
_, cgoexeCFLAGS, _, _ := b.cflags(p, false)
Expand Down Expand Up @@ -3049,22 +3006,12 @@ func (b *builder) cgo(p *Package, cgoExe, obj string, pcCFLAGS, pcLDFLAGS, cgofi
}
}

cgoLibGccFileOnce.Do(func() {
cgoLibGccFile, cgoLibGccErr = b.libgcc(p)
})
if cgoLibGccFile == "" && cgoLibGccErr != nil {
return nil, nil, err
}

var staticLibs []string
if goos == "windows" {
// libmingw32 and libmingwex might also use libgcc, so libgcc must come last,
// and they also have some inter-dependencies, so must use linker groups.
// libmingw32 and libmingwex have some inter-dependencies,
// so must use linker groups.
staticLibs = []string{"-Wl,--start-group", "-lmingwex", "-lmingw32", "-Wl,--end-group"}
}
if cgoLibGccFile != "" {
staticLibs = append(staticLibs, cgoLibGccFile)
}

cflags := stringList(cgoCPPFLAGS, cgoCFLAGS)
for _, cfile := range cfiles {
Expand Down
132 changes: 132 additions & 0 deletions src/cmd/link/internal/ld/ar.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,13 @@

package ld

import (
"cmd/internal/obj"
"encoding/binary"
"fmt"
"os"
)

const (
SARMAG = 8
SAR_HDR = 16 + 44
Expand All @@ -48,3 +55,128 @@ type ArHdr struct {
size string
fmag string
}

// hostArchive reads an archive file holding host objects and links in
// required objects. The general format is the same as a Go archive
// file, but it has an armap listing symbols and the objects that
// define them. This is used for the compiler support library
// libgcc.a.
func hostArchive(name string) {
f, err := obj.Bopenr(name)
if err != nil {
if os.IsNotExist(err) {
// It's OK if we don't have a libgcc file at all.
return
}
Exitf("cannot open file %s: %v", name, err)
}
defer obj.Bterm(f)

magbuf := make([]byte, len(ARMAG))
if obj.Bread(f, magbuf) != len(magbuf) {
Exitf("file %s too short", name)
}

var arhdr ArHdr
l := nextar(f, obj.Boffset(f), &arhdr)
if l <= 0 {
Exitf("%s missing armap", name)
}

var armap archiveMap
if arhdr.name == "/" || arhdr.name == "/SYM64/" {
armap = readArmap(name, f, arhdr)
} else {
Exitf("%s missing armap", name)
}

loaded := make(map[uint64]bool)
any := true
for any {
var load []uint64
for s := Ctxt.Allsym; s != nil; s = s.Allsym {
for _, r := range s.R {
if r.Sym != nil && r.Sym.Type&obj.SMASK == obj.SXREF {
if off := armap[r.Sym.Name]; off != 0 && !loaded[off] {
load = append(load, off)
loaded[off] = true
}
}
}
}

for _, off := range load {
l := nextar(f, int64(off), &arhdr)
if l <= 0 {
Exitf("%s missing archive entry at offset %d", name, off)
}
pname := fmt.Sprintf("%s(%s)", name, arhdr.name)
l = atolwhex(arhdr.size)

h := ldobj(f, "libgcc", l, pname, name, ArchiveObj)
obj.Bseek(f, h.off, 0)
h.ld(f, h.pkg, h.length, h.pn)
}

any = len(load) > 0
}
}

// archiveMap is an archive symbol map: a mapping from symbol name to
// offset within the archive file.
type archiveMap map[string]uint64

// readArmap reads the archive symbol map.
func readArmap(filename string, f *obj.Biobuf, arhdr ArHdr) archiveMap {
is64 := arhdr.name == "/SYM64/"
wordSize := 4
if is64 {
wordSize = 8
}

l := atolwhex(arhdr.size)
contents := make([]byte, l)
if obj.Bread(f, contents) != int(l) {
Exitf("short read from %s", filename)
}

var c uint64
if is64 {
c = binary.BigEndian.Uint64(contents)
} else {
c = uint64(binary.BigEndian.Uint32(contents))
}
contents = contents[wordSize:]

ret := make(archiveMap)

names := contents[c*uint64(wordSize):]
for i := uint64(0); i < c; i++ {
n := 0
for names[n] != 0 {
n++
}
name := string(names[:n])
names = names[n+1:]

// For Mach-O and PE/386 files we strip a leading
// underscore from the symbol name.
if goos == "darwin" || (goos == "windows" && goarch == "386") {
if name[0] == '_' && len(name) > 1 {
name = name[1:]
}
}

var off uint64
if is64 {
off = binary.BigEndian.Uint64(contents)
} else {
off = uint64(binary.BigEndian.Uint32(contents))
}
contents = contents[wordSize:]

ret[name] = off
}

return ret
}
2 changes: 1 addition & 1 deletion src/cmd/link/internal/ld/ldelf.go
Original file line number Diff line number Diff line change
Expand Up @@ -1076,7 +1076,7 @@ func readelfsym(elfobj *ElfObj, i int, sym *ElfSym, needSym int) (err error) {

case ElfSymBindWeak:
if needSym != 0 {
s = linknewsym(Ctxt, sym.name, 0)
s = Linklookup(Ctxt, sym.name, 0)
if sym.other == 2 {
s.Type |= obj.SHIDDEN
}
Expand Down
Loading

0 comments on commit 754f707

Please sign in to comment.