mirror of
https://github.com/golang/go
synced 2024-11-20 08:14:41 -07:00
cmd/internal/goobj: parse native objects in the archive
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>
This commit is contained in:
parent
d254c61309
commit
1053ae5cf5
@ -5,8 +5,12 @@
|
|||||||
package goobj
|
package goobj
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"debug/elf"
|
||||||
|
"debug/macho"
|
||||||
|
"debug/pe"
|
||||||
"fmt"
|
"fmt"
|
||||||
"internal/testenv"
|
"internal/testenv"
|
||||||
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
@ -16,10 +20,11 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
buildDir string
|
buildDir string
|
||||||
go1obj string
|
go1obj string
|
||||||
go2obj string
|
go2obj string
|
||||||
goarchive string
|
goarchive string
|
||||||
|
cgoarchive string
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestMain(m *testing.M) {
|
func TestMain(m *testing.M) {
|
||||||
@ -47,6 +52,48 @@ func TestMain(m *testing.M) {
|
|||||||
os.Exit(exit)
|
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 {
|
func buildGoobj() error {
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
@ -80,6 +127,29 @@ func buildGoobj() error {
|
|||||||
return fmt.Errorf("go tool pack c %s %s %s: %v\n%s", goarchive, go1obj, go2obj, err, out)
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -144,3 +214,110 @@ func TestParseArchive(t *testing.T) {
|
|||||||
t.Errorf(`%s: symbol "mypkg.go2" not found`, path)
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -6,7 +6,6 @@
|
|||||||
//
|
//
|
||||||
// TODO(rsc): Decide where this package should live. (golang.org/issue/6932)
|
// TODO(rsc): Decide where this package should live. (golang.org/issue/6932)
|
||||||
// TODO(rsc): Decide the appropriate integer types for various fields.
|
// 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
|
package goobj
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@ -16,6 +15,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
@ -127,12 +127,13 @@ type InlinedCall struct {
|
|||||||
|
|
||||||
// A Package is a parsed Go object file or archive defining a Go package.
|
// A Package is a parsed Go object file or archive defining a Go package.
|
||||||
type Package struct {
|
type Package struct {
|
||||||
ImportPath string // import path denoting this package
|
ImportPath string // import path denoting this package
|
||||||
Imports []string // packages imported by this package
|
Imports []string // packages imported by this package
|
||||||
SymRefs []SymID // list of symbol names and versions referred to by this pack
|
SymRefs []SymID // list of symbol names and versions referred to by this pack
|
||||||
Syms []*Sym // symbols defined by this package
|
Syms []*Sym // symbols defined by this package
|
||||||
MaxVersion int // maximum Version in any SymID in Syms
|
MaxVersion int // maximum Version in any SymID in Syms
|
||||||
Arch string // architecture
|
Arch string // architecture
|
||||||
|
Native []io.ReaderAt // native object data (e.g. ELF)
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -150,7 +151,7 @@ var (
|
|||||||
type objReader struct {
|
type objReader struct {
|
||||||
p *Package
|
p *Package
|
||||||
b *bufio.Reader
|
b *bufio.Reader
|
||||||
f io.ReadSeeker
|
f *os.File
|
||||||
err error
|
err error
|
||||||
offset int64
|
offset int64
|
||||||
dataOffset int64
|
dataOffset int64
|
||||||
@ -160,7 +161,7 @@ type objReader struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// init initializes r to read package p from f.
|
// 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.f = f
|
||||||
r.p = p
|
r.p = p
|
||||||
r.offset, _ = f.Seek(0, io.SeekCurrent)
|
r.offset, _ = f.Seek(0, io.SeekCurrent)
|
||||||
@ -185,6 +186,24 @@ func (r *objReader) error(err error) error {
|
|||||||
return r.err
|
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.
|
// readByte reads and returns a byte from the input file.
|
||||||
// On I/O error or EOF, it records the error but returns byte 0.
|
// On I/O error or EOF, it records the error but returns byte 0.
|
||||||
// A sequence of 0 bytes will eventually terminate any
|
// A sequence of 0 bytes will eventually terminate any
|
||||||
@ -322,9 +341,9 @@ func (r *objReader) skip(n int64) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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.
|
// 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 == "" {
|
if pkgpath == "" {
|
||||||
pkgpath = `""`
|
pkgpath = `""`
|
||||||
}
|
}
|
||||||
@ -332,7 +351,7 @@ func Parse(r io.ReadSeeker, pkgpath string) (*Package, error) {
|
|||||||
p.ImportPath = pkgpath
|
p.ImportPath = pkgpath
|
||||||
|
|
||||||
var rd objReader
|
var rd objReader
|
||||||
rd.init(r, p)
|
rd.init(f, p)
|
||||||
err := rd.readFull(rd.tmp[:8])
|
err := rd.readFull(rd.tmp[:8])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err == io.EOF {
|
if err == io.EOF {
|
||||||
@ -365,9 +384,6 @@ func trimSpace(b []byte) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// parseArchive parses a Unix archive of Go object files.
|
// 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 {
|
func (r *objReader) parseArchive() error {
|
||||||
for r.offset < r.limit {
|
for r.offset < r.limit {
|
||||||
if err := r.readFull(r.tmp[:60]); err != nil {
|
if err := r.readFull(r.tmp[:60]); err != nil {
|
||||||
@ -413,9 +429,19 @@ func (r *objReader) parseArchive() error {
|
|||||||
default:
|
default:
|
||||||
oldLimit := r.limit
|
oldLimit := r.limit
|
||||||
r.limit = r.offset + size
|
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.skip(r.limit - r.offset)
|
||||||
r.limit = oldLimit
|
r.limit = oldLimit
|
||||||
}
|
}
|
||||||
|
9
src/cmd/internal/goobj/testdata/mycgo/c1.c
vendored
Normal file
9
src/cmd/internal/goobj/testdata/mycgo/c1.c
vendored
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
// 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");
|
||||||
|
}
|
9
src/cmd/internal/goobj/testdata/mycgo/c2.c
vendored
Normal file
9
src/cmd/internal/goobj/testdata/mycgo/c2.c
vendored
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
// 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");
|
||||||
|
}
|
5
src/cmd/internal/goobj/testdata/mycgo/go.go
vendored
Normal file
5
src/cmd/internal/goobj/testdata/mycgo/go.go
vendored
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
package mycgo
|
||||||
|
|
||||||
|
// void c1(void);
|
||||||
|
// void c2(void);
|
||||||
|
import "C"
|
11
src/cmd/internal/goobj/testdata/mycgo/go1.go
vendored
Normal file
11
src/cmd/internal/goobj/testdata/mycgo/go1.go
vendored
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
// 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")
|
||||||
|
}
|
11
src/cmd/internal/goobj/testdata/mycgo/go2.go
vendored
Normal file
11
src/cmd/internal/goobj/testdata/mycgo/go2.go
vendored
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
// 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")
|
||||||
|
}
|
@ -153,6 +153,11 @@ func MustHaveExternalNetwork(t *testing.T) {
|
|||||||
|
|
||||||
var haveCGO bool
|
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.
|
// MustHaveCGO calls t.Skip if cgo is not available.
|
||||||
func MustHaveCGO(t *testing.T) {
|
func MustHaveCGO(t *testing.T) {
|
||||||
if !haveCGO {
|
if !haveCGO {
|
||||||
|
Loading…
Reference in New Issue
Block a user