github.com/gagliardetto/golang-go@v0.0.0-20201020153340-53909ea70814/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  	"debug/elf"
    10  	"fmt"
    11  	"github.com/gagliardetto/golang-go/not-internal/xcoff"
    12  	"io"
    13  	"os"
    14  	"strconv"
    15  	"strings"
    16  )
    17  
    18  var (
    19  	errBuildIDToolchain = fmt.Errorf("build ID only supported in gc toolchain")
    20  	errBuildIDMalformed = fmt.Errorf("malformed object file")
    21  	errBuildIDUnknown   = fmt.Errorf("lost build ID")
    22  )
    23  
    24  var (
    25  	bangArch = []byte("!<arch>")
    26  	pkgdef   = []byte("__.PKGDEF")
    27  	goobject = []byte("go object ")
    28  	buildid  = []byte("build id ")
    29  )
    30  
    31  // ReadFile reads the build ID from an archive or executable file.
    32  func ReadFile(name string) (id string, err error) {
    33  	f, err := os.Open(name)
    34  	if err != nil {
    35  		return "", err
    36  	}
    37  	defer f.Close()
    38  
    39  	buf := make([]byte, 8)
    40  	if _, err := f.ReadAt(buf, 0); err != nil {
    41  		return "", err
    42  	}
    43  	if string(buf) != "!<arch>\n" {
    44  		if string(buf) == "<bigaf>\n" {
    45  			return readGccgoBigArchive(name, f)
    46  		}
    47  		return readBinary(name, f)
    48  	}
    49  
    50  	// Read just enough of the target to fetch the build ID.
    51  	// The archive is expected to look like:
    52  	//
    53  	//	!<arch>
    54  	//	__.PKGDEF       0           0     0     644     7955      `
    55  	//	go object darwin amd64 devel X:none
    56  	//	build id "b41e5c45250e25c9fd5e9f9a1de7857ea0d41224"
    57  	//
    58  	// The variable-sized strings are GOOS, GOARCH, and the experiment list (X:none).
    59  	// Reading the first 1024 bytes should be plenty.
    60  	data := make([]byte, 1024)
    61  	n, err := io.ReadFull(f, data)
    62  	if err != nil && n == 0 {
    63  		return "", err
    64  	}
    65  
    66  	tryGccgo := func() (string, error) {
    67  		return readGccgoArchive(name, f)
    68  	}
    69  
    70  	// Archive header.
    71  	for i := 0; ; i++ { // returns during i==3
    72  		j := bytes.IndexByte(data, '\n')
    73  		if j < 0 {
    74  			return tryGccgo()
    75  		}
    76  		line := data[:j]
    77  		data = data[j+1:]
    78  		switch i {
    79  		case 0:
    80  			if !bytes.Equal(line, bangArch) {
    81  				return tryGccgo()
    82  			}
    83  		case 1:
    84  			if !bytes.HasPrefix(line, pkgdef) {
    85  				return tryGccgo()
    86  			}
    87  		case 2:
    88  			if !bytes.HasPrefix(line, goobject) {
    89  				return tryGccgo()
    90  			}
    91  		case 3:
    92  			if !bytes.HasPrefix(line, buildid) {
    93  				// Found the object header, just doesn't have a build id line.
    94  				// Treat as successful, with empty build id.
    95  				return "", nil
    96  			}
    97  			id, err := strconv.Unquote(string(line[len(buildid):]))
    98  			if err != nil {
    99  				return tryGccgo()
   100  			}
   101  			return id, nil
   102  		}
   103  	}
   104  }
   105  
   106  // readGccgoArchive tries to parse the archive as a standard Unix
   107  // archive file, and fetch the build ID from the _buildid.o entry.
   108  // The _buildid.o entry is written by (*Builder).gccgoBuildIDELFFile
   109  // in cmd/go/internal/work/exec.go.
   110  func readGccgoArchive(name string, f *os.File) (string, error) {
   111  	bad := func() (string, error) {
   112  		return "", &os.PathError{Op: "parse", Path: name, Err: errBuildIDMalformed}
   113  	}
   114  
   115  	off := int64(8)
   116  	for {
   117  		if _, err := f.Seek(off, io.SeekStart); err != nil {
   118  			return "", err
   119  		}
   120  
   121  		// TODO(iant): Make a debug/ar package, and use it
   122  		// here and in cmd/link.
   123  		var hdr [60]byte
   124  		if _, err := io.ReadFull(f, hdr[:]); err != nil {
   125  			if err == io.EOF {
   126  				// No more entries, no build ID.
   127  				return "", nil
   128  			}
   129  			return "", err
   130  		}
   131  		off += 60
   132  
   133  		sizeStr := strings.TrimSpace(string(hdr[48:58]))
   134  		size, err := strconv.ParseInt(sizeStr, 0, 64)
   135  		if err != nil {
   136  			return bad()
   137  		}
   138  
   139  		name := strings.TrimSpace(string(hdr[:16]))
   140  		if name == "_buildid.o/" {
   141  			sr := io.NewSectionReader(f, off, size)
   142  			e, err := elf.NewFile(sr)
   143  			if err != nil {
   144  				return bad()
   145  			}
   146  			s := e.Section(".go.buildid")
   147  			if s == nil {
   148  				return bad()
   149  			}
   150  			data, err := s.Data()
   151  			if err != nil {
   152  				return bad()
   153  			}
   154  			return string(data), nil
   155  		}
   156  
   157  		off += size
   158  		if off&1 != 0 {
   159  			off++
   160  		}
   161  	}
   162  }
   163  
   164  // readGccgoBigArchive tries to parse the archive as an AIX big
   165  // archive file, and fetch the build ID from the _buildid.o entry.
   166  // The _buildid.o entry is written by (*Builder).gccgoBuildIDXCOFFFile
   167  // in cmd/go/internal/work/exec.go.
   168  func readGccgoBigArchive(name string, f *os.File) (string, error) {
   169  	bad := func() (string, error) {
   170  		return "", &os.PathError{Op: "parse", Path: name, Err: errBuildIDMalformed}
   171  	}
   172  
   173  	// Read fixed-length header.
   174  	if _, err := f.Seek(0, io.SeekStart); err != nil {
   175  		return "", err
   176  	}
   177  	var flhdr [128]byte
   178  	if _, err := io.ReadFull(f, flhdr[:]); err != nil {
   179  		return "", err
   180  	}
   181  	// Read first member offset.
   182  	offStr := strings.TrimSpace(string(flhdr[68:88]))
   183  	off, err := strconv.ParseInt(offStr, 10, 64)
   184  	if err != nil {
   185  		return bad()
   186  	}
   187  	for {
   188  		if off == 0 {
   189  			// No more entries, no build ID.
   190  			return "", nil
   191  		}
   192  		if _, err := f.Seek(off, io.SeekStart); err != nil {
   193  			return "", err
   194  		}
   195  		// Read member header.
   196  		var hdr [112]byte
   197  		if _, err := io.ReadFull(f, hdr[:]); err != nil {
   198  			return "", err
   199  		}
   200  		// Read member name length.
   201  		namLenStr := strings.TrimSpace(string(hdr[108:112]))
   202  		namLen, err := strconv.ParseInt(namLenStr, 10, 32)
   203  		if err != nil {
   204  			return bad()
   205  		}
   206  		if namLen == 10 {
   207  			var nam [10]byte
   208  			if _, err := io.ReadFull(f, nam[:]); err != nil {
   209  				return "", err
   210  			}
   211  			if string(nam[:]) == "_buildid.o" {
   212  				sizeStr := strings.TrimSpace(string(hdr[0:20]))
   213  				size, err := strconv.ParseInt(sizeStr, 10, 64)
   214  				if err != nil {
   215  					return bad()
   216  				}
   217  				off += int64(len(hdr)) + namLen + 2
   218  				if off&1 != 0 {
   219  					off++
   220  				}
   221  				sr := io.NewSectionReader(f, off, size)
   222  				x, err := xcoff.NewFile(sr)
   223  				if err != nil {
   224  					return bad()
   225  				}
   226  				data := x.CSect(".go.buildid")
   227  				if data == nil {
   228  					return bad()
   229  				}
   230  				return string(data), nil
   231  			}
   232  		}
   233  
   234  		// Read next member offset.
   235  		offStr = strings.TrimSpace(string(hdr[20:40]))
   236  		off, err = strconv.ParseInt(offStr, 10, 64)
   237  		if err != nil {
   238  			return bad()
   239  		}
   240  	}
   241  }
   242  
   243  var (
   244  	goBuildPrefix = []byte("\xff Go build ID: \"")
   245  	goBuildEnd    = []byte("\"\n \xff")
   246  
   247  	elfPrefix = []byte("\x7fELF")
   248  
   249  	machoPrefixes = [][]byte{
   250  		{0xfe, 0xed, 0xfa, 0xce},
   251  		{0xfe, 0xed, 0xfa, 0xcf},
   252  		{0xce, 0xfa, 0xed, 0xfe},
   253  		{0xcf, 0xfa, 0xed, 0xfe},
   254  	}
   255  )
   256  
   257  var readSize = 32 * 1024 // changed for testing
   258  
   259  // readBinary reads the build ID from a binary.
   260  //
   261  // ELF binaries store the build ID in a proper PT_NOTE section.
   262  //
   263  // Other binary formats are not so flexible. For those, the linker
   264  // stores the build ID as non-instruction bytes at the very beginning
   265  // of the text segment, which should appear near the beginning
   266  // of the file. This is clumsy but fairly portable. Custom locations
   267  // can be added for other binary types as needed, like we did for ELF.
   268  func readBinary(name string, f *os.File) (id string, err error) {
   269  	// Read the first 32 kB of the binary file.
   270  	// That should be enough to find the build ID.
   271  	// In ELF files, the build ID is in the leading headers,
   272  	// which are typically less than 4 kB, not to mention 32 kB.
   273  	// In Mach-O files, there's no limit, so we have to parse the file.
   274  	// On other systems, we're trying to read enough that
   275  	// we get the beginning of the text segment in the read.
   276  	// The offset where the text segment begins in a hello
   277  	// world compiled for each different object format today:
   278  	//
   279  	//	Plan 9: 0x20
   280  	//	Windows: 0x600
   281  	//
   282  	data := make([]byte, readSize)
   283  	_, err = io.ReadFull(f, data)
   284  	if err == io.ErrUnexpectedEOF {
   285  		err = nil
   286  	}
   287  	if err != nil {
   288  		return "", err
   289  	}
   290  
   291  	if bytes.HasPrefix(data, elfPrefix) {
   292  		return readELF(name, f, data)
   293  	}
   294  	for _, m := range machoPrefixes {
   295  		if bytes.HasPrefix(data, m) {
   296  			return readMacho(name, f, data)
   297  		}
   298  	}
   299  	return readRaw(name, data)
   300  }
   301  
   302  // readRaw finds the raw build ID stored in text segment data.
   303  func readRaw(name string, data []byte) (id string, err error) {
   304  	i := bytes.Index(data, goBuildPrefix)
   305  	if i < 0 {
   306  		// Missing. Treat as successful but build ID empty.
   307  		return "", nil
   308  	}
   309  
   310  	j := bytes.Index(data[i+len(goBuildPrefix):], goBuildEnd)
   311  	if j < 0 {
   312  		return "", &os.PathError{Op: "parse", Path: name, Err: errBuildIDMalformed}
   313  	}
   314  
   315  	quoted := data[i+len(goBuildPrefix)-1 : i+len(goBuildPrefix)+j+1]
   316  	id, err = strconv.Unquote(string(quoted))
   317  	if err != nil {
   318  		return "", &os.PathError{Op: "parse", Path: name, Err: errBuildIDMalformed}
   319  	}
   320  	return id, nil
   321  }