• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright 2013 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5// Package gccgoimporter implements Import for gccgo-generated object files.
6package gccgoimporter // import "go/internal/gccgoimporter"
7
8import (
9	"bytes"
10	"debug/elf"
11	"fmt"
12	"go/types"
13	"internal/xcoff"
14	"io"
15	"os"
16	"path/filepath"
17	"strings"
18)
19
20// A PackageInit describes an imported package that needs initialization.
21type PackageInit struct {
22	Name     string // short package name
23	InitFunc string // name of init function
24	Priority int    // priority of init function, see InitData.Priority
25}
26
27// The gccgo-specific init data for a package.
28type InitData struct {
29	// Initialization priority of this package relative to other packages.
30	// This is based on the maximum depth of the package's dependency graph;
31	// it is guaranteed to be greater than that of its dependencies.
32	Priority int
33
34	// The list of packages which this package depends on to be initialized,
35	// including itself if needed. This is the subset of the transitive closure of
36	// the package's dependencies that need initialization.
37	Inits []PackageInit
38}
39
40// Locate the file from which to read export data.
41// This is intended to replicate the logic in gofrontend.
42func findExportFile(searchpaths []string, pkgpath string) (string, error) {
43	for _, spath := range searchpaths {
44		pkgfullpath := filepath.Join(spath, pkgpath)
45		pkgdir, name := filepath.Split(pkgfullpath)
46
47		for _, filepath := range [...]string{
48			pkgfullpath,
49			pkgfullpath + ".gox",
50			pkgdir + "lib" + name + ".so",
51			pkgdir + "lib" + name + ".a",
52			pkgfullpath + ".o",
53		} {
54			fi, err := os.Stat(filepath)
55			if err == nil && !fi.IsDir() {
56				return filepath, nil
57			}
58		}
59	}
60
61	return "", fmt.Errorf("%s: could not find export data (tried %s)", pkgpath, strings.Join(searchpaths, ":"))
62}
63
64const (
65	gccgov1Magic    = "v1;\n"
66	gccgov2Magic    = "v2;\n"
67	gccgov3Magic    = "v3;\n"
68	goimporterMagic = "\n$$ "
69	archiveMagic    = "!<ar"
70	aixbigafMagic   = "<big"
71)
72
73// Opens the export data file at the given path. If this is an ELF file,
74// searches for and opens the .go_export section. If this is an archive,
75// reads the export data from the first member, which is assumed to be an ELF file.
76// This is intended to replicate the logic in gofrontend.
77func openExportFile(fpath string) (reader io.ReadSeeker, closer io.Closer, err error) {
78	f, err := os.Open(fpath)
79	if err != nil {
80		return
81	}
82	closer = f
83	defer func() {
84		if err != nil && closer != nil {
85			f.Close()
86		}
87	}()
88
89	var magic [4]byte
90	_, err = f.ReadAt(magic[:], 0)
91	if err != nil {
92		return
93	}
94
95	var objreader io.ReaderAt
96	switch string(magic[:]) {
97	case gccgov1Magic, gccgov2Magic, gccgov3Magic, goimporterMagic:
98		// Raw export data.
99		reader = f
100		return
101
102	case archiveMagic, aixbigafMagic:
103		reader, err = arExportData(f)
104		return
105
106	default:
107		objreader = f
108	}
109
110	ef, err := elf.NewFile(objreader)
111	if err == nil {
112		sec := ef.Section(".go_export")
113		if sec == nil {
114			err = fmt.Errorf("%s: .go_export section not found", fpath)
115			return
116		}
117		reader = sec.Open()
118		return
119	}
120
121	xf, err := xcoff.NewFile(objreader)
122	if err == nil {
123		sdat := xf.CSect(".go_export")
124		if sdat == nil {
125			err = fmt.Errorf("%s: .go_export section not found", fpath)
126			return
127		}
128		reader = bytes.NewReader(sdat)
129		return
130	}
131
132	err = fmt.Errorf("%s: unrecognized file format", fpath)
133	return
134}
135
136// An Importer resolves import paths to Packages. The imports map records
137// packages already known, indexed by package path.
138// An importer must determine the canonical package path and check imports
139// to see if it is already present in the map. If so, the Importer can return
140// the map entry. Otherwise, the importer must load the package data for the
141// given path into a new *Package, record it in imports map, and return the
142// package.
143type Importer func(imports map[string]*types.Package, path, srcDir string, lookup func(string) (io.ReadCloser, error)) (*types.Package, error)
144
145func GetImporter(searchpaths []string, initmap map[*types.Package]InitData) Importer {
146	return func(imports map[string]*types.Package, pkgpath, srcDir string, lookup func(string) (io.ReadCloser, error)) (pkg *types.Package, err error) {
147		// TODO(gri): Use srcDir.
148		// Or not. It's possible that srcDir will fade in importance as
149		// the go command and other tools provide a translation table
150		// for relative imports (like ./foo or vendored imports).
151		if pkgpath == "unsafe" {
152			return types.Unsafe, nil
153		}
154
155		var reader io.ReadSeeker
156		var fpath string
157		var rc io.ReadCloser
158		if lookup != nil {
159			if p := imports[pkgpath]; p != nil && p.Complete() {
160				return p, nil
161			}
162			rc, err = lookup(pkgpath)
163			if err != nil {
164				return nil, err
165			}
166		}
167		if rc != nil {
168			defer rc.Close()
169			rs, ok := rc.(io.ReadSeeker)
170			if !ok {
171				return nil, fmt.Errorf("gccgo importer requires lookup to return an io.ReadSeeker, have %T", rc)
172			}
173			reader = rs
174			fpath = "<lookup " + pkgpath + ">"
175			// Take name from Name method (like on os.File) if present.
176			if n, ok := rc.(interface{ Name() string }); ok {
177				fpath = n.Name()
178			}
179		} else {
180			fpath, err = findExportFile(searchpaths, pkgpath)
181			if err != nil {
182				return nil, err
183			}
184
185			r, closer, err := openExportFile(fpath)
186			if err != nil {
187				return nil, err
188			}
189			if closer != nil {
190				defer closer.Close()
191			}
192			reader = r
193		}
194
195		var magics string
196		magics, err = readMagic(reader)
197		if err != nil {
198			return
199		}
200
201		if magics == archiveMagic || magics == aixbigafMagic {
202			reader, err = arExportData(reader)
203			if err != nil {
204				return
205			}
206			magics, err = readMagic(reader)
207			if err != nil {
208				return
209			}
210		}
211
212		switch magics {
213		case gccgov1Magic, gccgov2Magic, gccgov3Magic:
214			var p parser
215			p.init(fpath, reader, imports)
216			pkg = p.parsePackage()
217			if initmap != nil {
218				initmap[pkg] = p.initdata
219			}
220
221		// Excluded for now: Standard gccgo doesn't support this import format currently.
222		// case goimporterMagic:
223		// 	var data []byte
224		// 	data, err = io.ReadAll(reader)
225		// 	if err != nil {
226		// 		return
227		// 	}
228		// 	var n int
229		// 	n, pkg, err = importer.ImportData(imports, data)
230		// 	if err != nil {
231		// 		return
232		// 	}
233
234		// 	if initmap != nil {
235		// 		suffixreader := bytes.NewReader(data[n:])
236		// 		var p parser
237		// 		p.init(fpath, suffixreader, nil)
238		// 		p.parseInitData()
239		// 		initmap[pkg] = p.initdata
240		// 	}
241
242		default:
243			err = fmt.Errorf("unrecognized magic string: %q", magics)
244		}
245
246		return
247	}
248}
249
250// readMagic reads the four bytes at the start of a ReadSeeker and
251// returns them as a string.
252func readMagic(reader io.ReadSeeker) (string, error) {
253	var magic [4]byte
254	if _, err := reader.Read(magic[:]); err != nil {
255		return "", err
256	}
257	if _, err := reader.Seek(0, io.SeekStart); err != nil {
258		return "", err
259	}
260	return string(magic[:]), nil
261}
262