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