Home | History | Annotate | Download | only in buildid
      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 
     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 // ReadFile reads the build ID from an archive or executable file.
     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 	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 }
    239