Also add HasCGO() to internal/testenv for tests.
Updates #21706
Change-Id: I938188047024052bdb42b3ac1a77708f3c2a6dbb
Reviewed-on: https://go-review.googlesource.com/62591
Run-TryBot: Ian Lance Taylor <iant@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Ian Lance Taylor <iant@golang.org>
package goobj
import (
+ "debug/elf"
+ "debug/macho"
+ "debug/pe"
"fmt"
"internal/testenv"
+ "io"
"io/ioutil"
"os"
"os/exec"
)
var (
- buildDir string
- go1obj string
- go2obj string
- goarchive string
+ buildDir string
+ go1obj string
+ go2obj string
+ goarchive string
+ cgoarchive string
)
func TestMain(m *testing.M) {
os.Exit(exit)
}
+func copyDir(dst, src string) error {
+ err := os.MkdirAll(dst, 0777)
+ if err != nil {
+ return err
+ }
+ fis, err := ioutil.ReadDir(src)
+ if err != nil {
+ return err
+ }
+ for _, fi := range fis {
+ err = copyFile(filepath.Join(dst, fi.Name()), filepath.Join(src, fi.Name()))
+ if err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+func copyFile(dst, src string) (err error) {
+ var s, d *os.File
+ s, err = os.Open(src)
+ if err != nil {
+ return err
+ }
+ defer s.Close()
+ d, err = os.Create(dst)
+ if err != nil {
+ return err
+ }
+ defer func() {
+ e := d.Close()
+ if err == nil {
+ err = e
+ }
+ }()
+ _, err = io.Copy(d, s)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
func buildGoobj() error {
var err error
return fmt.Errorf("go tool pack c %s %s %s: %v\n%s", goarchive, go1obj, go2obj, err, out)
}
+ if testenv.HasCGO() {
+ gopath := filepath.Join(buildDir, "gopath")
+ err = copyDir(filepath.Join(gopath, "src", "mycgo"), filepath.Join("testdata", "mycgo"))
+ if err != nil {
+ return err
+ }
+ cmd := exec.Command(gotool, "install", "mycgo")
+ cmd.Env = append(os.Environ(), "GOPATH="+gopath)
+ out, err = cmd.CombinedOutput()
+ if err != nil {
+ return fmt.Errorf("go install mycgo: %v\n%s", err, out)
+ }
+ pat := filepath.Join(gopath, "pkg", "*", "mycgo.a")
+ ms, err := filepath.Glob(pat)
+ if err != nil {
+ return err
+ }
+ if len(ms) == 0 {
+ return fmt.Errorf("cannot found paths for pattern %s", pat)
+ }
+ cgoarchive = ms[0]
+ }
+
return nil
}
t.Errorf(`%s: symbol "mypkg.go2" not found`, path)
}
}
+
+func TestParseCGOArchive(t *testing.T) {
+ testenv.MustHaveCGO(t)
+
+ path := cgoarchive
+
+ f, err := os.Open(path)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer f.Close()
+
+ p, err := Parse(f, "mycgo")
+ if err != nil {
+ t.Fatal(err)
+ }
+ if p.Arch != runtime.GOARCH {
+ t.Errorf("%s: got %v, want %v", path, p.Arch, runtime.GOARCH)
+ }
+ var found1 bool
+ var found2 bool
+ for _, s := range p.Syms {
+ if s.Name == "mycgo.go1" {
+ found1 = true
+ }
+ if s.Name == "mycgo.go2" {
+ found2 = true
+ }
+ }
+ if !found1 {
+ t.Errorf(`%s: symbol "mycgo.go1" not found`, path)
+ }
+ if !found2 {
+ t.Errorf(`%s: symbol "mycgo.go2" not found`, path)
+ }
+
+ c1 := "c1"
+ c2 := "c2"
+
+ found1 = false
+ found2 = false
+
+ switch runtime.GOOS {
+ case "darwin":
+ c1 = "_" + c1
+ c2 = "_" + c2
+ for _, obj := range p.Native {
+ mf, err := macho.NewFile(obj)
+ if err != nil {
+ t.Fatal(err)
+ }
+ for _, s := range mf.Symtab.Syms {
+ switch s.Name {
+ case c1:
+ found1 = true
+ case c2:
+ found2 = true
+ }
+ }
+ }
+ case "windows":
+ if runtime.GOARCH == "386" {
+ c1 = "_" + c1
+ c2 = "_" + c2
+ }
+ for _, obj := range p.Native {
+ pf, err := pe.NewFile(obj)
+ if err != nil {
+ t.Fatal(err)
+ }
+ for _, s := range pf.Symbols {
+ switch s.Name {
+ case c1:
+ found1 = true
+ case c2:
+ found2 = true
+ }
+ }
+ }
+ default:
+ for _, obj := range p.Native {
+ ef, err := elf.NewFile(obj)
+ if err != nil {
+ t.Fatal(err)
+ }
+ syms, err := ef.Symbols()
+ if err != nil {
+ t.Fatal(err)
+ }
+ for _, s := range syms {
+ switch s.Name {
+ case c1:
+ found1 = true
+ case c2:
+ found2 = true
+ }
+ }
+ }
+ }
+
+ if !found1 {
+ t.Errorf(`%s: symbol %q not found`, path, c1)
+ }
+ if !found2 {
+ t.Errorf(`%s: symbol %q not found`, path, c2)
+ }
+}
//
// TODO(rsc): Decide where this package should live. (golang.org/issue/6932)
// TODO(rsc): Decide the appropriate integer types for various fields.
-// TODO(rsc): Write tests. (File format still up in the air a little.)
package goobj
import (
"errors"
"fmt"
"io"
+ "os"
"strconv"
"strings"
)
// A Package is a parsed Go object file or archive defining a Go package.
type Package struct {
- ImportPath string // import path denoting this package
- Imports []string // packages imported by this package
- SymRefs []SymID // list of symbol names and versions referred to by this pack
- Syms []*Sym // symbols defined by this package
- MaxVersion int // maximum Version in any SymID in Syms
- Arch string // architecture
+ ImportPath string // import path denoting this package
+ Imports []string // packages imported by this package
+ SymRefs []SymID // list of symbol names and versions referred to by this pack
+ Syms []*Sym // symbols defined by this package
+ MaxVersion int // maximum Version in any SymID in Syms
+ Arch string // architecture
+ Native []io.ReaderAt // native object data (e.g. ELF)
}
var (
type objReader struct {
p *Package
b *bufio.Reader
- f io.ReadSeeker
+ f *os.File
err error
offset int64
dataOffset int64
}
// init initializes r to read package p from f.
-func (r *objReader) init(f io.ReadSeeker, p *Package) {
+func (r *objReader) init(f *os.File, p *Package) {
r.f = f
r.p = p
r.offset, _ = f.Seek(0, io.SeekCurrent)
return r.err
}
+// peek returns the next n bytes without advancing the reader.
+func (r *objReader) peek(n int) ([]byte, error) {
+ if r.err != nil {
+ return nil, r.err
+ }
+ if r.offset >= r.limit {
+ r.error(io.ErrUnexpectedEOF)
+ return nil, r.err
+ }
+ b, err := r.b.Peek(n)
+ if err != nil {
+ if err != bufio.ErrBufferFull {
+ r.error(err)
+ }
+ }
+ return b, err
+}
+
// readByte reads and returns a byte from the input file.
// On I/O error or EOF, it records the error but returns byte 0.
// A sequence of 0 bytes will eventually terminate any
}
}
-// Parse parses an object file or archive from r,
+// Parse parses an object file or archive from f,
// assuming that its import path is pkgpath.
-func Parse(r io.ReadSeeker, pkgpath string) (*Package, error) {
+func Parse(f *os.File, pkgpath string) (*Package, error) {
if pkgpath == "" {
pkgpath = `""`
}
p.ImportPath = pkgpath
var rd objReader
- rd.init(r, p)
+ rd.init(f, p)
err := rd.readFull(rd.tmp[:8])
if err != nil {
if err == io.EOF {
}
// parseArchive parses a Unix archive of Go object files.
-// TODO(rsc): Need to skip non-Go object files.
-// TODO(rsc): Maybe record table of contents in r.p so that
-// linker can avoid having code to parse archives too.
func (r *objReader) parseArchive() error {
for r.offset < r.limit {
if err := r.readFull(r.tmp[:60]); err != nil {
default:
oldLimit := r.limit
r.limit = r.offset + size
- if err := r.parseObject(nil); err != nil {
- return fmt.Errorf("parsing archive member %q: %v", name, err)
+
+ p, err := r.peek(8)
+ if err != nil {
+ return err
+ }
+ if bytes.Equal(p, goobjHeader) {
+ if err := r.parseObject(nil); err != nil {
+ return fmt.Errorf("parsing archive member %q: %v", name, err)
+ }
+ } else {
+ r.p.Native = append(r.p.Native, io.NewSectionReader(r.f, r.offset, size))
}
+
r.skip(r.limit - r.offset)
r.limit = oldLimit
}
--- /dev/null
+// Copyright 2017 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.
+
+#include <stdio.h>
+
+void c1(void) {
+ puts("c1");
+}
--- /dev/null
+// Copyright 2017 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.
+
+#include <stdio.h>
+
+void c2(void) {
+ puts("c2");
+}
--- /dev/null
+package mycgo
+
+// void c1(void);
+// void c2(void);
+import "C"
--- /dev/null
+// Copyright 2017 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 mycgo
+
+import "fmt"
+
+func go1() {
+ fmt.Println("go1")
+}
--- /dev/null
+// Copyright 2017 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 mycgo
+
+import "fmt"
+
+func go2() {
+ fmt.Println("go2")
+}
var haveCGO bool
+// HasCGO reports whether the current system can use cgo.
+func HasCGO() bool {
+ return haveCGO
+}
+
// MustHaveCGO calls t.Skip if cgo is not available.
func MustHaveCGO(t *testing.T) {
if !haveCGO {