github.com/amarpal/go-tools@v0.0.0-20240422043104-40142f59f616/go/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  	"errors"
    11  	"fmt"
    12  	"io"
    13  	"os"
    14  	"strconv"
    15  	"strings"
    16  )
    17  
    18  var errBuildIDMalformed = fmt.Errorf("malformed object file")
    19  
    20  var (
    21  	bangArch = []byte("!<arch>")
    22  	pkgdef   = []byte("__.PKGDEF")
    23  	goobject = []byte("go object ")
    24  	buildid  = []byte("build id ")
    25  )
    26  
    27  // ReadFile reads the build ID from an archive or executable file.
    28  func ReadFile(name string) (id string, err error) {
    29  	f, err := os.Open(name)
    30  	if err != nil {
    31  		return "", err
    32  	}
    33  	defer f.Close()
    34  
    35  	buf := make([]byte, 8)
    36  	if _, err := f.ReadAt(buf, 0); err != nil {
    37  		return "", err
    38  	}
    39  	if string(buf) != "!<arch>\n" {
    40  		if string(buf) == "<bigaf>\n" {
    41  			return "", errors.New("unsupported")
    42  		}
    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  	tryGccgo := func() (string, error) {
    63  		return readGccgoArchive(name, f)
    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 tryGccgo()
    71  		}
    72  		line := data[:j]
    73  		data = data[j+1:]
    74  		switch i {
    75  		case 0:
    76  			if !bytes.Equal(line, bangArch) {
    77  				return tryGccgo()
    78  			}
    79  		case 1:
    80  			if !bytes.HasPrefix(line, pkgdef) {
    81  				return tryGccgo()
    82  			}
    83  		case 2:
    84  			if !bytes.HasPrefix(line, goobject) {
    85  				return tryGccgo()
    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 tryGccgo()
    96  			}
    97  			return id, nil
    98  		}
    99  	}
   100  }
   101  
   102  // readGccgoArchive tries to parse the archive as a standard Unix
   103  // archive file, and fetch the build ID from the _buildid.o entry.
   104  // The _buildid.o entry is written by (*Builder).gccgoBuildIDELFFile
   105  // in cmd/go/internal/work/exec.go.
   106  func readGccgoArchive(name string, f *os.File) (string, error) {
   107  	bad := func() (string, error) {
   108  		return "", &os.PathError{Op: "parse", Path: name, Err: errBuildIDMalformed}
   109  	}
   110  
   111  	off := int64(8)
   112  	for {
   113  		if _, err := f.Seek(off, io.SeekStart); err != nil {
   114  			return "", err
   115  		}
   116  
   117  		// TODO(iant): Make a debug/ar package, and use it
   118  		// here and in cmd/link.
   119  		var hdr [60]byte
   120  		if _, err := io.ReadFull(f, hdr[:]); err != nil {
   121  			if err == io.EOF {
   122  				// No more entries, no build ID.
   123  				return "", nil
   124  			}
   125  			return "", err
   126  		}
   127  		off += 60
   128  
   129  		sizeStr := strings.TrimSpace(string(hdr[48:58]))
   130  		size, err := strconv.ParseInt(sizeStr, 0, 64)
   131  		if err != nil {
   132  			return bad()
   133  		}
   134  
   135  		name := strings.TrimSpace(string(hdr[:16]))
   136  		if name == "_buildid.o/" {
   137  			sr := io.NewSectionReader(f, off, size)
   138  			e, err := elf.NewFile(sr)
   139  			if err != nil {
   140  				return bad()
   141  			}
   142  			s := e.Section(".go.buildid")
   143  			if s == nil {
   144  				return bad()
   145  			}
   146  			data, err := s.Data()
   147  			if err != nil {
   148  				return bad()
   149  			}
   150  			return string(data), nil
   151  		}
   152  
   153  		off += size
   154  		if off&1 != 0 {
   155  			off++
   156  		}
   157  	}
   158  }
   159  
   160  var (
   161  	goBuildPrefix = []byte("\xff Go build ID: \"")
   162  	goBuildEnd    = []byte("\"\n \xff")
   163  
   164  	elfPrefix = []byte("\x7fELF")
   165  
   166  	machoPrefixes = [][]byte{
   167  		{0xfe, 0xed, 0xfa, 0xce},
   168  		{0xfe, 0xed, 0xfa, 0xcf},
   169  		{0xce, 0xfa, 0xed, 0xfe},
   170  		{0xcf, 0xfa, 0xed, 0xfe},
   171  	}
   172  )
   173  
   174  var readSize = 32 * 1024 // changed for testing
   175  
   176  // readBinary reads the build ID from a binary.
   177  //
   178  // ELF binaries store the build ID in a proper PT_NOTE section.
   179  //
   180  // Other binary formats are not so flexible. For those, the linker
   181  // stores the build ID as non-instruction bytes at the very beginning
   182  // of the text segment, which should appear near the beginning
   183  // of the file. This is clumsy but fairly portable. Custom locations
   184  // can be added for other binary types as needed, like we did for ELF.
   185  func readBinary(name string, f *os.File) (id string, err error) {
   186  	// Read the first 32 kB of the binary file.
   187  	// That should be enough to find the build ID.
   188  	// In ELF files, the build ID is in the leading headers,
   189  	// which are typically less than 4 kB, not to mention 32 kB.
   190  	// In Mach-O files, there's no limit, so we have to parse the file.
   191  	// On other systems, we're trying to read enough that
   192  	// we get the beginning of the text segment in the read.
   193  	// The offset where the text segment begins in a hello
   194  	// world compiled for each different object format today:
   195  	//
   196  	//	Plan 9: 0x20
   197  	//	Windows: 0x600
   198  	//
   199  	data := make([]byte, readSize)
   200  	_, err = io.ReadFull(f, data)
   201  	if err == io.ErrUnexpectedEOF {
   202  		err = nil
   203  	}
   204  	if err != nil {
   205  		return "", err
   206  	}
   207  
   208  	if bytes.HasPrefix(data, elfPrefix) {
   209  		return readELF(name, f, data)
   210  	}
   211  	for _, m := range machoPrefixes {
   212  		if bytes.HasPrefix(data, m) {
   213  			return readMacho(name, f, data)
   214  		}
   215  	}
   216  	return readRaw(name, data)
   217  }
   218  
   219  // readRaw finds the raw build ID stored in text segment data.
   220  func readRaw(name string, data []byte) (id string, err error) {
   221  	i := bytes.Index(data, goBuildPrefix)
   222  	if i < 0 {
   223  		// Missing. Treat as successful but build ID empty.
   224  		return "", nil
   225  	}
   226  
   227  	j := bytes.Index(data[i+len(goBuildPrefix):], goBuildEnd)
   228  	if j < 0 {
   229  		return "", &os.PathError{Op: "parse", Path: name, Err: errBuildIDMalformed}
   230  	}
   231  
   232  	quoted := data[i+len(goBuildPrefix)-1 : i+len(goBuildPrefix)+j+1]
   233  	id, err = strconv.Unquote(string(quoted))
   234  	if err != nil {
   235  		return "", &os.PathError{Op: "parse", Path: name, Err: errBuildIDMalformed}
   236  	}
   237  	return id, nil
   238  }