github.com/goproxy0/go@v0.0.0-20171111080102-49cc0c489d2c/src/cmd/internal/buildid/buildid.go (about)

     1  // Copyright 2017 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 buildid
     6  
     7  import (
     8  	"bytes"
     9  	"fmt"
    10  	"io"
    11  	"os"
    12  	"strconv"
    13  )
    14  
    15  var (
    16  	errBuildIDToolchain = fmt.Errorf("build ID only supported in gc toolchain")
    17  	errBuildIDMalformed = fmt.Errorf("malformed object file")
    18  	errBuildIDUnknown   = fmt.Errorf("lost build ID")
    19  )
    20  
    21  var (
    22  	bangArch = []byte("!<arch>")
    23  	pkgdef   = []byte("__.PKGDEF")
    24  	goobject = []byte("go object ")
    25  	buildid  = []byte("build id ")
    26  )
    27  
    28  // ReadFile reads the build ID from an archive or executable file.
    29  // It only supports archives from the gc toolchain.
    30  // TODO(rsc): Figure out what gccgo and llvm are going to do for archives.
    31  func ReadFile(name string) (id string, err error) {
    32  	f, err := os.Open(name)
    33  	if err != nil {
    34  		return "", err
    35  	}
    36  	defer f.Close()
    37  
    38  	buf := make([]byte, 8)
    39  	if _, err := f.ReadAt(buf, 0); err != nil {
    40  		return "", err
    41  	}
    42  	if string(buf) != "!<arch>\n" {
    43  		return readBinary(name, f)
    44  	}
    45  
    46  	// Read just enough of the target to fetch the build ID.
    47  	// The archive is expected to look like:
    48  	//
    49  	//	!<arch>
    50  	//	__.PKGDEF       0           0     0     644     7955      `
    51  	//	go object darwin amd64 devel X:none
    52  	//	build id "b41e5c45250e25c9fd5e9f9a1de7857ea0d41224"
    53  	//
    54  	// The variable-sized strings are GOOS, GOARCH, and the experiment list (X:none).
    55  	// Reading the first 1024 bytes should be plenty.
    56  	data := make([]byte, 1024)
    57  	n, err := io.ReadFull(f, data)
    58  	if err != nil && n == 0 {
    59  		return "", err
    60  	}
    61  
    62  	bad := func() (string, error) {
    63  		return "", &os.PathError{Op: "parse", Path: name, Err: errBuildIDMalformed}
    64  	}
    65  
    66  	// Archive header.
    67  	for i := 0; ; i++ { // returns during i==3
    68  		j := bytes.IndexByte(data, '\n')
    69  		if j < 0 {
    70  			return bad()
    71  		}
    72  		line := data[:j]
    73  		data = data[j+1:]
    74  		switch i {
    75  		case 0:
    76  			if !bytes.Equal(line, bangArch) {
    77  				return bad()
    78  			}
    79  		case 1:
    80  			if !bytes.HasPrefix(line, pkgdef) {
    81  				return bad()
    82  			}
    83  		case 2:
    84  			if !bytes.HasPrefix(line, goobject) {
    85  				return bad()
    86  			}
    87  		case 3:
    88  			if !bytes.HasPrefix(line, buildid) {
    89  				// Found the object header, just doesn't have a build id line.
    90  				// Treat as successful, with empty build id.
    91  				return "", nil
    92  			}
    93  			id, err := strconv.Unquote(string(line[len(buildid):]))
    94  			if err != nil {
    95  				return bad()
    96  			}
    97  			return id, nil
    98  		}
    99  	}
   100  }
   101  
   102  var (
   103  	goBuildPrefix = []byte("\xff Go build ID: \"")
   104  	goBuildEnd    = []byte("\"\n \xff")
   105  
   106  	elfPrefix = []byte("\x7fELF")
   107  
   108  	machoPrefixes = [][]byte{
   109  		{0xfe, 0xed, 0xfa, 0xce},
   110  		{0xfe, 0xed, 0xfa, 0xcf},
   111  		{0xce, 0xfa, 0xed, 0xfe},
   112  		{0xcf, 0xfa, 0xed, 0xfe},
   113  	}
   114  )
   115  
   116  var readSize = 32 * 1024 // changed for testing
   117  
   118  // readBinary reads the build ID from a binary.
   119  //
   120  // ELF binaries store the build ID in a proper PT_NOTE section.
   121  //
   122  // Other binary formats are not so flexible. For those, the linker
   123  // stores the build ID as non-instruction bytes at the very beginning
   124  // of the text segment, which should appear near the beginning
   125  // of the file. This is clumsy but fairly portable. Custom locations
   126  // can be added for other binary types as needed, like we did for ELF.
   127  func readBinary(name string, f *os.File) (id string, err error) {
   128  	// Read the first 32 kB of the binary file.
   129  	// That should be enough to find the build ID.
   130  	// In ELF files, the build ID is in the leading headers,
   131  	// which are typically less than 4 kB, not to mention 32 kB.
   132  	// In Mach-O files, there's no limit, so we have to parse the file.
   133  	// On other systems, we're trying to read enough that
   134  	// we get the beginning of the text segment in the read.
   135  	// The offset where the text segment begins in a hello
   136  	// world compiled for each different object format today:
   137  	//
   138  	//	Plan 9: 0x20
   139  	//	Windows: 0x600
   140  	//
   141  	data := make([]byte, readSize)
   142  	_, err = io.ReadFull(f, data)
   143  	if err == io.ErrUnexpectedEOF {
   144  		err = nil
   145  	}
   146  	if err != nil {
   147  		return "", err
   148  	}
   149  
   150  	if bytes.HasPrefix(data, elfPrefix) {
   151  		return readELF(name, f, data)
   152  	}
   153  	for _, m := range machoPrefixes {
   154  		if bytes.HasPrefix(data, m) {
   155  			return readMacho(name, f, data)
   156  		}
   157  	}
   158  	return readRaw(name, data)
   159  }
   160  
   161  // readRaw finds the raw build ID stored in text segment data.
   162  func readRaw(name string, data []byte) (id string, err error) {
   163  	i := bytes.Index(data, goBuildPrefix)
   164  	if i < 0 {
   165  		// Missing. Treat as successful but build ID empty.
   166  		return "", nil
   167  	}
   168  
   169  	j := bytes.Index(data[i+len(goBuildPrefix):], goBuildEnd)
   170  	if j < 0 {
   171  		return "", &os.PathError{Op: "parse", Path: name, Err: errBuildIDMalformed}
   172  	}
   173  
   174  	quoted := data[i+len(goBuildPrefix)-1 : i+len(goBuildPrefix)+j+1]
   175  	id, err = strconv.Unquote(string(quoted))
   176  	if err != nil {
   177  		return "", &os.PathError{Op: "parse", Path: name, Err: errBuildIDMalformed}
   178  	}
   179  	return id, nil
   180  }