Home | History | Annotate | Download | only in gccgoimporter
      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.
      6 package gccgoimporter // import "go/internal/gccgoimporter"
      7 
      8 import (
      9 	"bytes"
     10 	"debug/elf"
     11 	"fmt"
     12 	"go/types"
     13 	"io"
     14 	"os"
     15 	"os/exec"
     16 	"path/filepath"
     17 	"strings"
     18 )
     19 
     20 // A PackageInit describes an imported package that needs initialization.
     21 type 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.
     28 type 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.
     42 func 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 
     64 const (
     65 	gccgov1Magic    = "v1;\n"
     66 	gccgov2Magic    = "v2;\n"
     67 	goimporterMagic = "\n$$ "
     68 	archiveMagic    = "!<ar"
     69 )
     70 
     71 // Opens the export data file at the given path. If this is an ELF file,
     72 // searches for and opens the .go_export section. If this is an archive,
     73 // reads the export data from the first member, which is assumed to be an ELF file.
     74 // This is intended to replicate the logic in gofrontend.
     75 func openExportFile(fpath string) (reader io.ReadSeeker, closer io.Closer, err error) {
     76 	f, err := os.Open(fpath)
     77 	if err != nil {
     78 		return
     79 	}
     80 	closer = f
     81 	defer func() {
     82 		if err != nil && closer != nil {
     83 			f.Close()
     84 		}
     85 	}()
     86 
     87 	var magic [4]byte
     88 	_, err = f.ReadAt(magic[:], 0)
     89 	if err != nil {
     90 		return
     91 	}
     92 
     93 	var elfreader io.ReaderAt
     94 	switch string(magic[:]) {
     95 	case gccgov1Magic, gccgov2Magic, goimporterMagic:
     96 		// Raw export data.
     97 		reader = f
     98 		return
     99 
    100 	case archiveMagic:
    101 		// TODO(pcc): Read the archive directly instead of using "ar".
    102 		f.Close()
    103 		closer = nil
    104 
    105 		cmd := exec.Command("ar", "p", fpath)
    106 		var out []byte
    107 		out, err = cmd.Output()
    108 		if err != nil {
    109 			return
    110 		}
    111 
    112 		elfreader = bytes.NewReader(out)
    113 
    114 	default:
    115 		elfreader = f
    116 	}
    117 
    118 	ef, err := elf.NewFile(elfreader)
    119 	if err != nil {
    120 		return
    121 	}
    122 
    123 	sec := ef.Section(".go_export")
    124 	if sec == nil {
    125 		err = fmt.Errorf("%s: .go_export section not found", fpath)
    126 		return
    127 	}
    128 
    129 	reader = sec.Open()
    130 	return
    131 }
    132 
    133 // An Importer resolves import paths to Packages. The imports map records
    134 // packages already known, indexed by package path.
    135 // An importer must determine the canonical package path and check imports
    136 // to see if it is already present in the map. If so, the Importer can return
    137 // the map entry. Otherwise, the importer must load the package data for the
    138 // given path into a new *Package, record it in imports map, and return the
    139 // package.
    140 type Importer func(imports map[string]*types.Package, path string) (*types.Package, error)
    141 
    142 func GetImporter(searchpaths []string, initmap map[*types.Package]InitData) Importer {
    143 	return func(imports map[string]*types.Package, pkgpath string) (pkg *types.Package, err error) {
    144 		if pkgpath == "unsafe" {
    145 			return types.Unsafe, nil
    146 		}
    147 
    148 		fpath, err := findExportFile(searchpaths, pkgpath)
    149 		if err != nil {
    150 			return
    151 		}
    152 
    153 		reader, closer, err := openExportFile(fpath)
    154 		if err != nil {
    155 			return
    156 		}
    157 		if closer != nil {
    158 			defer closer.Close()
    159 		}
    160 
    161 		var magic [4]byte
    162 		_, err = reader.Read(magic[:])
    163 		if err != nil {
    164 			return
    165 		}
    166 		_, err = reader.Seek(0, io.SeekStart)
    167 		if err != nil {
    168 			return
    169 		}
    170 
    171 		switch string(magic[:]) {
    172 		case gccgov1Magic, gccgov2Magic:
    173 			var p parser
    174 			p.init(fpath, reader, imports)
    175 			pkg = p.parsePackage()
    176 			if initmap != nil {
    177 				initmap[pkg] = p.initdata
    178 			}
    179 
    180 		// Excluded for now: Standard gccgo doesn't support this import format currently.
    181 		// case goimporterMagic:
    182 		// 	var data []byte
    183 		// 	data, err = ioutil.ReadAll(reader)
    184 		// 	if err != nil {
    185 		// 		return
    186 		// 	}
    187 		// 	var n int
    188 		// 	n, pkg, err = importer.ImportData(imports, data)
    189 		// 	if err != nil {
    190 		// 		return
    191 		// 	}
    192 
    193 		// 	if initmap != nil {
    194 		// 		suffixreader := bytes.NewReader(data[n:])
    195 		// 		var p parser
    196 		// 		p.init(fpath, suffixreader, nil)
    197 		// 		p.parseInitData()
    198 		// 		initmap[pkg] = p.initdata
    199 		// 	}
    200 
    201 		default:
    202 			err = fmt.Errorf("unrecognized magic string: %q", string(magic[:]))
    203 		}
    204 
    205 		return
    206 	}
    207 }
    208