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