summaryrefslogtreecommitdiffstats
path: root/src/cmd/internal/archive
diff options
context:
space:
mode:
Diffstat (limited to 'src/cmd/internal/archive')
-rw-r--r--src/cmd/internal/archive/archive.go517
-rw-r--r--src/cmd/internal/archive/archive_test.go388
-rw-r--r--src/cmd/internal/archive/testdata/go1.go11
-rw-r--r--src/cmd/internal/archive/testdata/go2.go11
-rw-r--r--src/cmd/internal/archive/testdata/mycgo/c1.c9
-rw-r--r--src/cmd/internal/archive/testdata/mycgo/c2.c9
-rw-r--r--src/cmd/internal/archive/testdata/mycgo/go.go5
-rw-r--r--src/cmd/internal/archive/testdata/mycgo/go1.go11
-rw-r--r--src/cmd/internal/archive/testdata/mycgo/go2.go11
9 files changed, 972 insertions, 0 deletions
diff --git a/src/cmd/internal/archive/archive.go b/src/cmd/internal/archive/archive.go
new file mode 100644
index 0000000..393034d
--- /dev/null
+++ b/src/cmd/internal/archive/archive.go
@@ -0,0 +1,517 @@
+// Copyright 2013 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 archive implements reading of archive files generated by the Go
+// toolchain.
+package archive
+
+import (
+ "bufio"
+ "bytes"
+ "cmd/internal/bio"
+ "cmd/internal/goobj"
+ "errors"
+ "fmt"
+ "io"
+ "log"
+ "os"
+ "strconv"
+ "strings"
+ "time"
+ "unicode/utf8"
+)
+
+/*
+The archive format is:
+
+First, on a line by itself
+ !<arch>
+
+Then zero or more file records. Each file record has a fixed-size one-line header
+followed by data bytes followed by an optional padding byte. The header is:
+
+ %-16s%-12d%-6d%-6d%-8o%-10d`
+ name mtime uid gid mode size
+
+(note the trailing backquote). The %-16s here means at most 16 *bytes* of
+the name, and if shorter, space padded on the right.
+*/
+
+// A Data is a reference to data stored in an object file.
+// It records the offset and size of the data, so that a client can
+// read the data only if necessary.
+type Data struct {
+ Offset int64
+ Size int64
+}
+
+type Archive struct {
+ f *os.File
+ Entries []Entry
+}
+
+func (a *Archive) File() *os.File { return a.f }
+
+type Entry struct {
+ Name string
+ Type EntryType
+ Mtime int64
+ Uid int
+ Gid int
+ Mode os.FileMode
+ Data
+ Obj *GoObj // nil if this entry is not a Go object file
+}
+
+type EntryType int
+
+const (
+ EntryPkgDef EntryType = iota
+ EntryGoObj
+ EntryNativeObj
+ EntrySentinelNonObj
+)
+
+func (e *Entry) String() string {
+ return fmt.Sprintf("%s %6d/%-6d %12d %s %s",
+ (e.Mode & 0777).String(),
+ e.Uid,
+ e.Gid,
+ e.Size,
+ time.Unix(e.Mtime, 0).Format(timeFormat),
+ e.Name)
+}
+
+type GoObj struct {
+ TextHeader []byte
+ Arch string
+ Data
+}
+
+const (
+ entryHeader = "%s%-12d%-6d%-6d%-8o%-10d`\n"
+ // In entryHeader the first entry, the name, is always printed as 16 bytes right-padded.
+ entryLen = 16 + 12 + 6 + 6 + 8 + 10 + 1 + 1
+ timeFormat = "Jan _2 15:04 2006"
+)
+
+var (
+ archiveHeader = []byte("!<arch>\n")
+ archiveMagic = []byte("`\n")
+ goobjHeader = []byte("go objec") // truncated to size of archiveHeader
+
+ errCorruptArchive = errors.New("corrupt archive")
+ errTruncatedArchive = errors.New("truncated archive")
+ errCorruptObject = errors.New("corrupt object file")
+ errNotObject = errors.New("unrecognized object file format")
+)
+
+type ErrGoObjOtherVersion struct{ magic []byte }
+
+func (e ErrGoObjOtherVersion) Error() string {
+ return fmt.Sprintf("go object of a different version: %q", e.magic)
+}
+
+// An objReader is an object file reader.
+type objReader struct {
+ a *Archive
+ b *bio.Reader
+ err error
+ offset int64
+ limit int64
+ tmp [256]byte
+}
+
+func (r *objReader) init(f *os.File) {
+ r.a = &Archive{f, nil}
+ r.offset, _ = f.Seek(0, io.SeekCurrent)
+ r.limit, _ = f.Seek(0, io.SeekEnd)
+ f.Seek(r.offset, io.SeekStart)
+ r.b = bio.NewReader(f)
+}
+
+// error records that an error occurred.
+// It returns only the first error, so that an error
+// caused by an earlier error does not discard information
+// about the earlier error.
+func (r *objReader) error(err error) error {
+ if r.err == nil {
+ if err == io.EOF {
+ err = io.ErrUnexpectedEOF
+ }
+ r.err = err
+ }
+ // panic("corrupt") // useful for debugging
+ 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
+// parsing state in the object file. In particular, it ends the
+// reading of a varint.
+func (r *objReader) readByte() byte {
+ if r.err != nil {
+ return 0
+ }
+ if r.offset >= r.limit {
+ r.error(io.ErrUnexpectedEOF)
+ return 0
+ }
+ b, err := r.b.ReadByte()
+ if err != nil {
+ if err == io.EOF {
+ err = io.ErrUnexpectedEOF
+ }
+ r.error(err)
+ b = 0
+ } else {
+ r.offset++
+ }
+ return b
+}
+
+// readFull reads exactly len(b) bytes from the input file.
+// If an error occurs, read returns the error but also
+// records it, so it is safe for callers to ignore the result
+// as long as delaying the report is not a problem.
+func (r *objReader) readFull(b []byte) error {
+ if r.err != nil {
+ return r.err
+ }
+ if r.offset+int64(len(b)) > r.limit {
+ return r.error(io.ErrUnexpectedEOF)
+ }
+ n, err := io.ReadFull(r.b, b)
+ r.offset += int64(n)
+ if err != nil {
+ return r.error(err)
+ }
+ return nil
+}
+
+// skip skips n bytes in the input.
+func (r *objReader) skip(n int64) {
+ if n < 0 {
+ r.error(fmt.Errorf("debug/goobj: internal error: misuse of skip"))
+ }
+ if n < int64(len(r.tmp)) {
+ // Since the data is so small, a just reading from the buffered
+ // reader is better than flushing the buffer and seeking.
+ r.readFull(r.tmp[:n])
+ } else if n <= int64(r.b.Buffered()) {
+ // Even though the data is not small, it has already been read.
+ // Advance the buffer instead of seeking.
+ for n > int64(len(r.tmp)) {
+ r.readFull(r.tmp[:])
+ n -= int64(len(r.tmp))
+ }
+ r.readFull(r.tmp[:n])
+ } else {
+ // Seek, giving up buffered data.
+ r.b.MustSeek(r.offset+n, io.SeekStart)
+ r.offset += n
+ }
+}
+
+// New writes to f to make a new archive.
+func New(f *os.File) (*Archive, error) {
+ _, err := f.Write(archiveHeader)
+ if err != nil {
+ return nil, err
+ }
+ return &Archive{f: f}, nil
+}
+
+// Parse parses an object file or archive from f.
+func Parse(f *os.File, verbose bool) (*Archive, error) {
+ var r objReader
+ r.init(f)
+ t, err := r.peek(8)
+ if err != nil {
+ if err == io.EOF {
+ err = io.ErrUnexpectedEOF
+ }
+ return nil, err
+ }
+
+ switch {
+ default:
+ return nil, errNotObject
+
+ case bytes.Equal(t, archiveHeader):
+ if err := r.parseArchive(verbose); err != nil {
+ return nil, err
+ }
+ case bytes.Equal(t, goobjHeader):
+ off := r.offset
+ o := &GoObj{}
+ if err := r.parseObject(o, r.limit-off); err != nil {
+ return nil, err
+ }
+ r.a.Entries = []Entry{{
+ Name: f.Name(),
+ Type: EntryGoObj,
+ Data: Data{off, r.limit - off},
+ Obj: o,
+ }}
+ }
+
+ return r.a, nil
+}
+
+// trimSpace removes trailing spaces from b and returns the corresponding string.
+// This effectively parses the form used in archive headers.
+func trimSpace(b []byte) string {
+ return string(bytes.TrimRight(b, " "))
+}
+
+// parseArchive parses a Unix archive of Go object files.
+func (r *objReader) parseArchive(verbose bool) error {
+ r.readFull(r.tmp[:8]) // consume header (already checked)
+ for r.offset < r.limit {
+ if err := r.readFull(r.tmp[:60]); err != nil {
+ return err
+ }
+ data := r.tmp[:60]
+
+ // Each file is preceded by this text header (slice indices in first column):
+ // 0:16 name
+ // 16:28 date
+ // 28:34 uid
+ // 34:40 gid
+ // 40:48 mode
+ // 48:58 size
+ // 58:60 magic - `\n
+ // We only care about name, size, and magic, unless in verbose mode.
+ // The fields are space-padded on the right.
+ // The size is in decimal.
+ // The file data - size bytes - follows the header.
+ // Headers are 2-byte aligned, so if size is odd, an extra padding
+ // byte sits between the file data and the next header.
+ // The file data that follows is padded to an even number of bytes:
+ // if size is odd, an extra padding byte is inserted betw the next header.
+ if len(data) < 60 {
+ return errTruncatedArchive
+ }
+ if !bytes.Equal(data[58:60], archiveMagic) {
+ return errCorruptArchive
+ }
+ name := trimSpace(data[0:16])
+ var err error
+ get := func(start, end, base, bitsize int) int64 {
+ if err != nil {
+ return 0
+ }
+ var v int64
+ v, err = strconv.ParseInt(trimSpace(data[start:end]), base, bitsize)
+ return v
+ }
+ size := get(48, 58, 10, 64)
+ var (
+ mtime int64
+ uid, gid int
+ mode os.FileMode
+ )
+ if verbose {
+ mtime = get(16, 28, 10, 64)
+ uid = int(get(28, 34, 10, 32))
+ gid = int(get(34, 40, 10, 32))
+ mode = os.FileMode(get(40, 48, 8, 32))
+ }
+ if err != nil {
+ return errCorruptArchive
+ }
+ data = data[60:]
+ fsize := size + size&1
+ if fsize < 0 || fsize < size {
+ return errCorruptArchive
+ }
+ switch name {
+ case "__.PKGDEF":
+ r.a.Entries = append(r.a.Entries, Entry{
+ Name: name,
+ Type: EntryPkgDef,
+ Mtime: mtime,
+ Uid: uid,
+ Gid: gid,
+ Mode: mode,
+ Data: Data{r.offset, size},
+ })
+ r.skip(size)
+ case "preferlinkext", "dynimportfail":
+ if size == 0 {
+ // These are not actual objects, but rather sentinel
+ // entries put into the archive by the Go command to
+ // be read by the linker. See #62036.
+ r.a.Entries = append(r.a.Entries, Entry{
+ Name: name,
+ Type: EntrySentinelNonObj,
+ Mtime: mtime,
+ Uid: uid,
+ Gid: gid,
+ Mode: mode,
+ Data: Data{r.offset, size},
+ })
+ break
+ }
+ fallthrough
+ default:
+ var typ EntryType
+ var o *GoObj
+ offset := r.offset
+ p, err := r.peek(8)
+ if err != nil {
+ return err
+ }
+ if bytes.Equal(p, goobjHeader) {
+ typ = EntryGoObj
+ o = &GoObj{}
+ err := r.parseObject(o, size)
+ if err != nil {
+ return err
+ }
+ } else {
+ typ = EntryNativeObj
+ r.skip(size)
+ }
+ r.a.Entries = append(r.a.Entries, Entry{
+ Name: name,
+ Type: typ,
+ Mtime: mtime,
+ Uid: uid,
+ Gid: gid,
+ Mode: mode,
+ Data: Data{offset, size},
+ Obj: o,
+ })
+ }
+ if size&1 != 0 {
+ r.skip(1)
+ }
+ }
+ return nil
+}
+
+// parseObject parses a single Go object file.
+// The object file consists of a textual header ending in "\n!\n"
+// and then the part we want to parse begins.
+// The format of that part is defined in a comment at the top
+// of cmd/internal/goobj/objfile.go.
+func (r *objReader) parseObject(o *GoObj, size int64) error {
+ h := make([]byte, 0, 256)
+ var c1, c2, c3 byte
+ for {
+ c1, c2, c3 = c2, c3, r.readByte()
+ h = append(h, c3)
+ // The new export format can contain 0 bytes.
+ // Don't consider them errors, only look for r.err != nil.
+ if r.err != nil {
+ return errCorruptObject
+ }
+ if c1 == '\n' && c2 == '!' && c3 == '\n' {
+ break
+ }
+ }
+ o.TextHeader = h
+ hs := strings.Fields(string(h))
+ if len(hs) >= 4 {
+ o.Arch = hs[3]
+ }
+ o.Offset = r.offset
+ o.Size = size - int64(len(h))
+
+ p, err := r.peek(8)
+ if err != nil {
+ return err
+ }
+ if !bytes.Equal(p, []byte(goobj.Magic)) {
+ if bytes.HasPrefix(p, []byte("\x00go1")) && bytes.HasSuffix(p, []byte("ld")) {
+ return r.error(ErrGoObjOtherVersion{p[1:]}) // strip the \x00 byte
+ }
+ return r.error(errCorruptObject)
+ }
+ r.skip(o.Size)
+ return nil
+}
+
+// AddEntry adds an entry to the end of a, with the content from r.
+func (a *Archive) AddEntry(typ EntryType, name string, mtime int64, uid, gid int, mode os.FileMode, size int64, r io.Reader) {
+ off, err := a.f.Seek(0, io.SeekEnd)
+ if err != nil {
+ log.Fatal(err)
+ }
+ n, err := fmt.Fprintf(a.f, entryHeader, exactly16Bytes(name), mtime, uid, gid, mode, size)
+ if err != nil || n != entryLen {
+ log.Fatal("writing entry header: ", err)
+ }
+ n1, _ := io.CopyN(a.f, r, size)
+ if n1 != size {
+ log.Fatal(err)
+ }
+ if (off+size)&1 != 0 {
+ a.f.Write([]byte{0}) // pad to even byte
+ }
+ a.Entries = append(a.Entries, Entry{
+ Name: name,
+ Type: typ,
+ Mtime: mtime,
+ Uid: uid,
+ Gid: gid,
+ Mode: mode,
+ Data: Data{off + entryLen, size},
+ })
+}
+
+// exactly16Bytes truncates the string if necessary so it is at most 16 bytes long,
+// then pads the result with spaces to be exactly 16 bytes.
+// Fmt uses runes for its width calculation, but we need bytes in the entry header.
+func exactly16Bytes(s string) string {
+ for len(s) > 16 {
+ _, wid := utf8.DecodeLastRuneInString(s)
+ s = s[:len(s)-wid]
+ }
+ const sixteenSpaces = " "
+ s += sixteenSpaces[:16-len(s)]
+ return s
+}
+
+// architecture-independent object file output
+const HeaderSize = 60
+
+func ReadHeader(b *bufio.Reader, name string) int {
+ var buf [HeaderSize]byte
+ if _, err := io.ReadFull(b, buf[:]); err != nil {
+ return -1
+ }
+ aname := strings.Trim(string(buf[0:16]), " ")
+ if !strings.HasPrefix(aname, name) {
+ return -1
+ }
+ asize := strings.Trim(string(buf[48:58]), " ")
+ i, _ := strconv.Atoi(asize)
+ return i
+}
+
+func FormatHeader(arhdr []byte, name string, size int64) {
+ copy(arhdr[:], fmt.Sprintf("%-16s%-12d%-6d%-6d%-8o%-10d`\n", name, 0, 0, 0, 0644, size))
+}
diff --git a/src/cmd/internal/archive/archive_test.go b/src/cmd/internal/archive/archive_test.go
new file mode 100644
index 0000000..10a3d6e
--- /dev/null
+++ b/src/cmd/internal/archive/archive_test.go
@@ -0,0 +1,388 @@
+// 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 archive
+
+import (
+ "bytes"
+ "debug/elf"
+ "debug/macho"
+ "debug/pe"
+ "fmt"
+ "internal/testenv"
+ "internal/xcoff"
+ "io"
+ "os"
+ "path/filepath"
+ "runtime"
+ "sync"
+ "testing"
+ "unicode/utf8"
+)
+
+var buildDir string
+
+func TestMain(m *testing.M) {
+ if !testenv.HasGoBuild() {
+ return
+ }
+
+ exit := m.Run()
+
+ if buildDir != "" {
+ os.RemoveAll(buildDir)
+ }
+ os.Exit(exit)
+}
+
+func copyDir(dst, src string) error {
+ err := os.MkdirAll(dst, 0777)
+ if err != nil {
+ return err
+ }
+ entries, err := os.ReadDir(src)
+ if err != nil {
+ return err
+ }
+ for _, entry := range entries {
+ err = copyFile(filepath.Join(dst, entry.Name()), filepath.Join(src, entry.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
+}
+
+var (
+ buildOnce sync.Once
+ builtGoobjs goobjPaths
+ buildErr error
+)
+
+type goobjPaths struct {
+ go1obj string
+ go2obj string
+ goarchive string
+ cgoarchive string
+}
+
+func buildGoobj(t *testing.T) goobjPaths {
+ buildOnce.Do(func() {
+ buildErr = func() (err error) {
+ buildDir, err = os.MkdirTemp("", "TestGoobj")
+ if err != nil {
+ return err
+ }
+
+ go1obj := filepath.Join(buildDir, "go1.o")
+ go2obj := filepath.Join(buildDir, "go2.o")
+ goarchive := filepath.Join(buildDir, "go.a")
+ cgoarchive := ""
+
+ gotool, err := testenv.GoTool()
+ if err != nil {
+ return err
+ }
+
+ go1src := filepath.Join("testdata", "go1.go")
+ go2src := filepath.Join("testdata", "go2.go")
+
+ importcfgfile := filepath.Join(buildDir, "importcfg")
+ testenv.WriteImportcfg(t, importcfgfile, nil, go1src, go2src)
+
+ out, err := testenv.Command(t, gotool, "tool", "compile", "-importcfg="+importcfgfile, "-p=p", "-o", go1obj, go1src).CombinedOutput()
+ if err != nil {
+ return fmt.Errorf("go tool compile -o %s %s: %v\n%s", go1obj, go1src, err, out)
+ }
+ out, err = testenv.Command(t, gotool, "tool", "compile", "-importcfg="+importcfgfile, "-p=p", "-o", go2obj, go2src).CombinedOutput()
+ if err != nil {
+ return fmt.Errorf("go tool compile -o %s %s: %v\n%s", go2obj, go2src, err, out)
+ }
+ out, err = testenv.Command(t, gotool, "tool", "pack", "c", goarchive, go1obj, go2obj).CombinedOutput()
+ if err != nil {
+ return fmt.Errorf("go tool pack c %s %s %s: %v\n%s", goarchive, go1obj, go2obj, err, out)
+ }
+
+ if testenv.HasCGO() {
+ cgoarchive = filepath.Join(buildDir, "mycgo.a")
+ gopath := filepath.Join(buildDir, "gopath")
+ err = copyDir(filepath.Join(gopath, "src", "mycgo"), filepath.Join("testdata", "mycgo"))
+ if err == nil {
+ err = os.WriteFile(filepath.Join(gopath, "src", "mycgo", "go.mod"), []byte("module mycgo\n"), 0666)
+ }
+ if err != nil {
+ return err
+ }
+ cmd := testenv.Command(t, gotool, "build", "-buildmode=archive", "-o", cgoarchive, "-gcflags=all="+os.Getenv("GO_GCFLAGS"), "mycgo")
+ cmd.Dir = filepath.Join(gopath, "src", "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)
+ }
+ }
+
+ builtGoobjs = goobjPaths{
+ go1obj: go1obj,
+ go2obj: go2obj,
+ goarchive: goarchive,
+ cgoarchive: cgoarchive,
+ }
+ return nil
+ }()
+ })
+
+ if buildErr != nil {
+ t.Helper()
+ t.Fatal(buildErr)
+ }
+ return builtGoobjs
+}
+
+func TestParseGoobj(t *testing.T) {
+ path := buildGoobj(t).go1obj
+
+ f, err := os.Open(path)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer f.Close()
+
+ a, err := Parse(f, false)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if len(a.Entries) != 2 {
+ t.Errorf("expect 2 entry, found %d", len(a.Entries))
+ }
+ for _, e := range a.Entries {
+ if e.Type == EntryPkgDef {
+ continue
+ }
+ if e.Type != EntryGoObj {
+ t.Errorf("wrong type of object: want EntryGoObj, got %v", e.Type)
+ }
+ if !bytes.Contains(e.Obj.TextHeader, []byte(runtime.GOARCH)) {
+ t.Errorf("text header does not contain GOARCH %s: %q", runtime.GOARCH, e.Obj.TextHeader)
+ }
+ }
+}
+
+func TestParseArchive(t *testing.T) {
+ path := buildGoobj(t).goarchive
+
+ f, err := os.Open(path)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer f.Close()
+
+ a, err := Parse(f, false)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if len(a.Entries) != 3 {
+ t.Errorf("expect 3 entry, found %d", len(a.Entries))
+ }
+ var found1 bool
+ var found2 bool
+ for _, e := range a.Entries {
+ if e.Type == EntryPkgDef {
+ continue
+ }
+ if e.Type != EntryGoObj {
+ t.Errorf("wrong type of object: want EntryGoObj, got %v", e.Type)
+ }
+ if !bytes.Contains(e.Obj.TextHeader, []byte(runtime.GOARCH)) {
+ t.Errorf("text header does not contain GOARCH %s: %q", runtime.GOARCH, e.Obj.TextHeader)
+ }
+ if e.Name == "go1.o" {
+ found1 = true
+ }
+ if e.Name == "go2.o" {
+ found2 = true
+ }
+ }
+ if !found1 {
+ t.Errorf(`object "go1.o" not found`)
+ }
+ if !found2 {
+ t.Errorf(`object "go2.o" not found`)
+ }
+}
+
+func TestParseCGOArchive(t *testing.T) {
+ testenv.MustHaveCGO(t)
+
+ path := buildGoobj(t).cgoarchive
+
+ f, err := os.Open(path)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer f.Close()
+
+ a, err := Parse(f, false)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ c1 := "c1"
+ c2 := "c2"
+ switch runtime.GOOS {
+ case "darwin", "ios":
+ c1 = "_" + c1
+ c2 = "_" + c2
+ case "windows":
+ if runtime.GOARCH == "386" {
+ c1 = "_" + c1
+ c2 = "_" + c2
+ }
+ case "aix":
+ c1 = "." + c1
+ c2 = "." + c2
+ }
+
+ var foundgo, found1, found2 bool
+
+ for _, e := range a.Entries {
+ switch e.Type {
+ default:
+ t.Errorf("unknown object type")
+ case EntryPkgDef:
+ continue
+ case EntryGoObj:
+ foundgo = true
+ if !bytes.Contains(e.Obj.TextHeader, []byte(runtime.GOARCH)) {
+ t.Errorf("text header does not contain GOARCH %s: %q", runtime.GOARCH, e.Obj.TextHeader)
+ }
+ continue
+ case EntryNativeObj:
+ }
+
+ obj := io.NewSectionReader(f, e.Offset, e.Size)
+ switch runtime.GOOS {
+ case "darwin", "ios":
+ mf, err := macho.NewFile(obj)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if mf.Symtab == nil {
+ continue
+ }
+ for _, s := range mf.Symtab.Syms {
+ switch s.Name {
+ case c1:
+ found1 = true
+ case c2:
+ found2 = true
+ }
+ }
+ case "windows":
+ 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
+ }
+ }
+ case "aix":
+ xf, err := xcoff.NewFile(obj)
+ if err != nil {
+ t.Fatal(err)
+ }
+ for _, s := range xf.Symbols {
+ switch s.Name {
+ case c1:
+ found1 = true
+ case c2:
+ found2 = true
+ }
+ }
+ default: // ELF
+ 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 !foundgo {
+ t.Errorf(`go object not found`)
+ }
+ if !found1 {
+ t.Errorf(`symbol %q not found`, c1)
+ }
+ if !found2 {
+ t.Errorf(`symbol %q not found`, c2)
+ }
+}
+
+func TestExactly16Bytes(t *testing.T) {
+ var tests = []string{
+ "",
+ "a",
+ "日本語",
+ "1234567890123456",
+ "12345678901234567890",
+ "1234567890123本語4567890",
+ "12345678901234日本語567890",
+ "123456789012345日本語67890",
+ "1234567890123456日本語7890",
+ "1234567890123456日本語7日本語890",
+ }
+ for _, str := range tests {
+ got := exactly16Bytes(str)
+ if len(got) != 16 {
+ t.Errorf("exactly16Bytes(%q) is %q, length %d", str, got, len(got))
+ }
+ // Make sure it is full runes.
+ for _, c := range got {
+ if c == utf8.RuneError {
+ t.Errorf("exactly16Bytes(%q) is %q, has partial rune", str, got)
+ }
+ }
+ }
+}
diff --git a/src/cmd/internal/archive/testdata/go1.go b/src/cmd/internal/archive/testdata/go1.go
new file mode 100644
index 0000000..37d1ec1
--- /dev/null
+++ b/src/cmd/internal/archive/testdata/go1.go
@@ -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 mypkg
+
+import "fmt"
+
+func go1() {
+ fmt.Println("go1")
+}
diff --git a/src/cmd/internal/archive/testdata/go2.go b/src/cmd/internal/archive/testdata/go2.go
new file mode 100644
index 0000000..0e9c0d7
--- /dev/null
+++ b/src/cmd/internal/archive/testdata/go2.go
@@ -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 mypkg
+
+import "fmt"
+
+func go2() {
+ fmt.Println("go2")
+}
diff --git a/src/cmd/internal/archive/testdata/mycgo/c1.c b/src/cmd/internal/archive/testdata/mycgo/c1.c
new file mode 100644
index 0000000..869a324
--- /dev/null
+++ b/src/cmd/internal/archive/testdata/mycgo/c1.c
@@ -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");
+}
diff --git a/src/cmd/internal/archive/testdata/mycgo/c2.c b/src/cmd/internal/archive/testdata/mycgo/c2.c
new file mode 100644
index 0000000..1cf904f
--- /dev/null
+++ b/src/cmd/internal/archive/testdata/mycgo/c2.c
@@ -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");
+}
diff --git a/src/cmd/internal/archive/testdata/mycgo/go.go b/src/cmd/internal/archive/testdata/mycgo/go.go
new file mode 100644
index 0000000..7b74f91
--- /dev/null
+++ b/src/cmd/internal/archive/testdata/mycgo/go.go
@@ -0,0 +1,5 @@
+package mycgo
+
+// void c1(void);
+// void c2(void);
+import "C"
diff --git a/src/cmd/internal/archive/testdata/mycgo/go1.go b/src/cmd/internal/archive/testdata/mycgo/go1.go
new file mode 100644
index 0000000..eb3924c
--- /dev/null
+++ b/src/cmd/internal/archive/testdata/mycgo/go1.go
@@ -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")
+}
diff --git a/src/cmd/internal/archive/testdata/mycgo/go2.go b/src/cmd/internal/archive/testdata/mycgo/go2.go
new file mode 100644
index 0000000..ea3e26f
--- /dev/null
+++ b/src/cmd/internal/archive/testdata/mycgo/go2.go
@@ -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")
+}