...

Source file src/pkg/cmd/internal/buildid/buildid.go

     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		"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	}
   322	

View as plain text