cmd/interal/ld: darwin c-archive buildmode support

Uses ar to create an archive when -buildmode=c-archive.

A small example (that I hope to turn into a test in a later CL):

goarchive.go:
	package main

	import "fmt"

	import "C"

	func init() {
		fmt.Println("ran go init")
	}

	//export FuncInGo
	func FuncInGo() {
		fmt.Println("called a go function")
	}

	func main() {
		fmt.Println("in main")
	}

This can be compiled with:

	go build -ldflags=-buildmode=c-archive -o=libgo.a goarchive.go

main.c:

	#include <stdio.h>

	extern void FuncInGo();

	int main(void) {
		printf("c hello\n");
		FuncInGo();
		printf("c goodbye\n");
		return 0;
	}

Can be compiled with:

	cc main.c libgo.a

Apple provide a warning about the lack of PIE, but still produce a
binary which runs and outputs (on darwin/amd64):

	c hello
	ran go init
	called a go function
	c goodbye

Change-Id: I7611925f210a83afa6bd1e66a5601dd636a428c8
Reviewed-on: https://go-review.googlesource.com/8711
Reviewed-by: Ian Lance Taylor <iant@golang.org>
This commit is contained in:
David Crawshaw 2015-04-09 10:44:05 -04:00
parent 6e3a6c4d38
commit ced7ffe95b
5 changed files with 110 additions and 53 deletions

View file

@ -967,8 +967,11 @@ func dosymtype() {
} }
// Create a new entry in the .init_array section that points to the // Create a new entry in the .init_array section that points to the
// library initializer function. // library initializer function.
if Buildmode == BuildmodeCShared && s.Name == INITENTRY { switch Buildmode {
addinitarrdata(s) case BuildmodeCArchive, BuildmodeCShared:
if s.Name == INITENTRY {
addinitarrdata(s)
}
} }
} }
} }
@ -1329,7 +1332,9 @@ func dodata() {
sect.Length = uint64(datsize) - sect.Vaddr sect.Length = uint64(datsize) - sect.Vaddr
/* shared library initializer */ /* shared library initializer */
if Buildmode == BuildmodeCShared || DynlinkingGo() { switch Buildmode {
case BuildmodeCArchive, BuildmodeCShared, BuildmodeShared:
// TODO(mwhudson): switch on Linkshared
sect := addsection(&Segdata, ".init_array", 06) sect := addsection(&Segdata, ".init_array", 06)
sect.Align = maxalign(s, SINITARR) sect.Align = maxalign(s, SINITARR)
datsize = Rnd(datsize, int64(sect.Align)) datsize = Rnd(datsize, int64(sect.Align))

View file

@ -1658,7 +1658,9 @@ func doelf() {
Addstring(shstrtab, ".note.GNU-stack") Addstring(shstrtab, ".note.GNU-stack")
} }
if Buildmode == BuildmodeCShared || DynlinkingGo() { switch Buildmode {
case BuildmodeCArchive, BuildmodeCShared, BuildmodeShared:
// TODO(mwhudson): switch on Linkshared
Addstring(shstrtab, ".init_array") Addstring(shstrtab, ".init_array")
switch Thearch.Thechar { switch Thearch.Thechar {
case '6', '7', '9': case '6', '7', '9':

View file

@ -474,8 +474,11 @@ func loadcgo(file string, pkg string, p string) {
local = expandpkg(local, pkg) local = expandpkg(local, pkg)
s = Linklookup(Ctxt, local, 0) s = Linklookup(Ctxt, local, 0)
if Buildmode == BuildmodeCShared && s == Linklookup(Ctxt, "main", 0) { switch Buildmode {
continue case BuildmodeCShared, BuildmodeCArchive:
if s == Linklookup(Ctxt, "main", 0) {
continue
}
} }
// export overrides import, for openbsd/cgo. // export overrides import, for openbsd/cgo.
@ -619,7 +622,7 @@ func deadcode() {
fmt.Fprintf(&Bso, "%5.2f deadcode\n", obj.Cputime()) fmt.Fprintf(&Bso, "%5.2f deadcode\n", obj.Cputime())
} }
if Buildmode == BuildmodeShared { if Buildmode == BuildmodeShared || Buildmode == BuildmodeCArchive {
// Mark all symbols as reachable when building a // Mark all symbols as reachable when building a
// shared library. // shared library.
for s := Ctxt.Allsym; s != nil; s = s.Allsym { for s := Ctxt.Allsym; s != nil; s = s.Allsym {

View file

@ -34,7 +34,6 @@ import (
"bytes" "bytes"
"cmd/internal/obj" "cmd/internal/obj"
"debug/elf" "debug/elf"
"errors"
"fmt" "fmt"
"io" "io"
"io/ioutil" "io/ioutil"
@ -257,27 +256,35 @@ type BuildMode uint8
const ( const (
BuildmodeExe BuildMode = iota BuildmodeExe BuildMode = iota
BuildmodeCArchive
BuildmodeCShared BuildmodeCShared
BuildmodeShared BuildmodeShared
) )
func (mode *BuildMode) Set(s string) error { func (mode *BuildMode) Set(s string) error {
goos := obj.Getgoos()
goarch := obj.Getgoarch()
badmode := func() error {
return fmt.Errorf("buildmode %s not supported on %s/%s", s, goos, goarch)
}
switch s { switch s {
default: default:
return errors.New("invalid mode") return fmt.Errorf("invalid buildmode: %q", s)
case "exe": case "exe":
*mode = BuildmodeExe *mode = BuildmodeExe
case "c-archive":
if goos != "darwin" {
return badmode()
}
*mode = BuildmodeCArchive
case "c-shared": case "c-shared":
goarch := obj.Getgoarch()
if goarch != "amd64" && goarch != "arm" { if goarch != "amd64" && goarch != "arm" {
return fmt.Errorf("not supported on %s", goarch) return badmode()
} }
*mode = BuildmodeCShared *mode = BuildmodeCShared
case "shared": case "shared":
goos := obj.Getgoos()
goarch := obj.Getgoarch()
if goos != "linux" || goarch != "amd64" { if goos != "linux" || goarch != "amd64" {
return fmt.Errorf("not supported on %s/%s", goos, goarch) return badmode()
} }
*mode = BuildmodeShared *mode = BuildmodeShared
} }
@ -288,6 +295,8 @@ func (mode *BuildMode) String() string {
switch *mode { switch *mode {
case BuildmodeExe: case BuildmodeExe:
return "exe" return "exe"
case BuildmodeCArchive:
return "c-archive"
case BuildmodeCShared: case BuildmodeCShared:
return "c-shared" return "c-shared"
case BuildmodeShared: case BuildmodeShared:
@ -339,7 +348,7 @@ func libinit() {
if INITENTRY == "" { if INITENTRY == "" {
switch Buildmode { switch Buildmode {
case BuildmodeCShared: case BuildmodeCShared, BuildmodeCArchive:
INITENTRY = fmt.Sprintf("_rt0_%s_%s_lib", goarch, goos) INITENTRY = fmt.Sprintf("_rt0_%s_%s_lib", goarch, goos)
case BuildmodeExe: case BuildmodeExe:
INITENTRY = fmt.Sprintf("_rt0_%s_%s", goarch, goos) INITENTRY = fmt.Sprintf("_rt0_%s_%s", goarch, goos)
@ -402,10 +411,15 @@ func loadinternal(name string) {
} }
func loadlib() { func loadlib() {
if Buildmode == BuildmodeCShared { switch Buildmode {
case BuildmodeCShared:
s := Linklookup(Ctxt, "runtime.islibrary", 0) s := Linklookup(Ctxt, "runtime.islibrary", 0)
s.Dupok = 1 s.Dupok = 1
Adduint8(Ctxt, s, 1) Adduint8(Ctxt, s, 1)
case BuildmodeCArchive:
s := Linklookup(Ctxt, "runtime.isarchive", 0)
s.Dupok = 1
Adduint8(Ctxt, s, 1)
} }
loadinternal("runtime") loadinternal("runtime")
@ -782,14 +796,79 @@ func hostlinksetup() {
coutbuf = *Binitw(cout) coutbuf = *Binitw(cout)
} }
// hostobjCopy creates a copy of the object files in hostobj in a
// temporary directory.
func hostobjCopy() (paths []string) {
for i, h := range hostobj {
f, err := os.Open(h.file)
if err != nil {
Ctxt.Cursym = nil
Diag("cannot reopen %s: %v", h.pn, err)
Errorexit()
}
if _, err := f.Seek(h.off, 0); err != nil {
Ctxt.Cursym = nil
Diag("cannot seek %s: %v", h.pn, err)
Errorexit()
}
p := fmt.Sprintf("%s/%06d.o", tmpdir, i)
paths = append(paths, p)
w, err := os.Create(p)
if err != nil {
Ctxt.Cursym = nil
Diag("cannot create %s: %v", p, err)
Errorexit()
}
if _, err := io.CopyN(w, f, h.length); err != nil {
Ctxt.Cursym = nil
Diag("cannot write %s: %v", p, err)
Errorexit()
}
if err := w.Close(); err != nil {
Ctxt.Cursym = nil
Diag("cannot close %s: %v", p, err)
Errorexit()
}
}
return paths
}
// archive builds a .a archive from the hostobj object files.
func archive() {
if Buildmode != BuildmodeCArchive {
return
}
os.Remove(outfile)
argv := []string{"ar", "-q", "-c", outfile}
argv = append(argv, hostobjCopy()...)
argv = append(argv, fmt.Sprintf("%s/go.o", tmpdir))
if Debug['v'] != 0 {
fmt.Fprintf(&Bso, "archive: %s\n", strings.Join(argv, " "))
Bflush(&Bso)
}
if out, err := exec.Command(argv[0], argv[1:]...).CombinedOutput(); err != nil {
Ctxt.Cursym = nil
Diag("%s: running %s failed: %v\n%s", os.Args[0], argv[0], err, out)
Errorexit()
}
}
func hostlink() { func hostlink() {
if Linkmode != LinkExternal || nerrors > 0 { if Linkmode != LinkExternal || nerrors > 0 {
return return
} }
if Buildmode == BuildmodeCArchive {
return
}
if extld == "" { if extld == "" {
extld = "gcc" extld = "gcc"
} }
var argv []string var argv []string
argv = append(argv, extld) argv = append(argv, extld)
switch Thearch.Thechar { switch Thearch.Thechar {
@ -830,10 +909,11 @@ func hostlink() {
argv = append(argv, "-Wl,--rosegment") argv = append(argv, "-Wl,--rosegment")
} }
if Buildmode == BuildmodeCShared { switch Buildmode {
case BuildmodeCShared:
argv = append(argv, "-Wl,-Bsymbolic") argv = append(argv, "-Wl,-Bsymbolic")
argv = append(argv, "-shared") argv = append(argv, "-shared")
} else if Buildmode == BuildmodeShared { case BuildmodeShared:
// TODO(mwhudson): unless you do this, dynamic relocations fill // TODO(mwhudson): unless you do this, dynamic relocations fill
// out the findfunctab table and for some reason shared libraries // out the findfunctab table and for some reason shared libraries
// and the executable both define a main function and putting the // and the executable both define a main function and putting the
@ -868,41 +948,7 @@ func hostlink() {
argv = append(argv, "-Qunused-arguments") argv = append(argv, "-Qunused-arguments")
} }
// already wrote main object file argv = append(argv, hostobjCopy()...)
// copy host objects to temporary directory
for i, h := range hostobj {
f, err := os.Open(h.file)
if err != nil {
Ctxt.Cursym = nil
Diag("cannot reopen %s: %v", h.pn, err)
Errorexit()
}
if _, err := f.Seek(h.off, 0); err != nil {
Ctxt.Cursym = nil
Diag("cannot seek %s: %v", h.pn, err)
Errorexit()
}
p := fmt.Sprintf("%s/%06d.o", tmpdir, i)
argv = append(argv, p)
w, err := os.Create(p)
if err != nil {
Ctxt.Cursym = nil
Diag("cannot create %s: %v", p, err)
Errorexit()
}
if _, err := io.CopyN(w, f, h.length); err != nil {
Ctxt.Cursym = nil
Diag("cannot write %s: %v", p, err)
Errorexit()
}
if err := w.Close(); err != nil {
Ctxt.Cursym = nil
Diag("cannot close %s: %v", p, err)
Errorexit()
}
}
argv = append(argv, fmt.Sprintf("%s/go.o", tmpdir)) argv = append(argv, fmt.Sprintf("%s/go.o", tmpdir))
if Linkshared { if Linkshared {

View file

@ -237,6 +237,7 @@ func Ldmain() {
Thearch.Asmb() Thearch.Asmb()
undef() undef()
hostlink() hostlink()
archive()
if Debug['v'] != 0 { if Debug['v'] != 0 {
fmt.Fprintf(&Bso, "%5.2f cpu time\n", obj.Cputime()) fmt.Fprintf(&Bso, "%5.2f cpu time\n", obj.Cputime())
fmt.Fprintf(&Bso, "%d symbols\n", Ctxt.Nsymbol) fmt.Fprintf(&Bso, "%d symbols\n", Ctxt.Nsymbol)