diff options
Diffstat (limited to 'src/cmd/internal/archive')
-rw-r--r-- | src/cmd/internal/archive/archive.go | 517 | ||||
-rw-r--r-- | src/cmd/internal/archive/archive_test.go | 388 | ||||
-rw-r--r-- | src/cmd/internal/archive/testdata/go1.go | 11 | ||||
-rw-r--r-- | src/cmd/internal/archive/testdata/go2.go | 11 | ||||
-rw-r--r-- | src/cmd/internal/archive/testdata/mycgo/c1.c | 9 | ||||
-rw-r--r-- | src/cmd/internal/archive/testdata/mycgo/c2.c | 9 | ||||
-rw-r--r-- | src/cmd/internal/archive/testdata/mycgo/go.go | 5 | ||||
-rw-r--r-- | src/cmd/internal/archive/testdata/mycgo/go1.go | 11 | ||||
-rw-r--r-- | src/cmd/internal/archive/testdata/mycgo/go2.go | 11 |
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") +} |