blob: 091c9090c86bb0caa81ab8111de99a6538ee14e3 [file] [log] [blame]
// Copyright 2017 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package buildid
import (
"bytes"
"cmd/go/internal/cfg"
"fmt"
"io"
"os"
"strconv"
"strings"
)
var (
errBuildIDToolchain = fmt.Errorf("build ID only supported in gc toolchain")
errBuildIDMalformed = fmt.Errorf("malformed object file")
errBuildIDUnknown = fmt.Errorf("lost build ID")
)
var (
bangArch = []byte("!<arch>")
pkgdef = []byte("__.PKGDEF")
goobject = []byte("go object ")
buildid = []byte("build id ")
)
// ReadBuildID reads the build ID from an archive or binary.
// It only supports the gc toolchain.
// Other toolchain maintainers should adjust this function.
func ReadBuildID(name, target string) (id string, err error) {
if cfg.BuildToolchainName != "gc" {
return "", errBuildIDToolchain
}
// For commands, read build ID directly from binary.
if name == "main" {
return ReadBuildIDFromBinary(target)
}
// Otherwise, we expect to have an archive (.a) file,
// and we can read the build ID from the Go export data.
if !strings.HasSuffix(target, ".a") {
return "", &os.PathError{Op: "parse", Path: target, Err: errBuildIDUnknown}
}
// Read just enough of the target to fetch the build ID.
// The archive is expected to look like:
//
// !<arch>
// __.PKGDEF 0 0 0 644 7955 `
// go object darwin amd64 devel X:none
// build id "b41e5c45250e25c9fd5e9f9a1de7857ea0d41224"
//
// The variable-sized strings are GOOS, GOARCH, and the experiment list (X:none).
// Reading the first 1024 bytes should be plenty.
f, err := os.Open(target)
if err != nil {
return "", err
}
data := make([]byte, 1024)
n, err := io.ReadFull(f, data)
f.Close()
if err != nil && n == 0 {
return "", err
}
bad := func() (string, error) {
return "", &os.PathError{Op: "parse", Path: target, Err: errBuildIDMalformed}
}
// Archive header.
for i := 0; ; i++ { // returns during i==3
j := bytes.IndexByte(data, '\n')
if j < 0 {
return bad()
}
line := data[:j]
data = data[j+1:]
switch i {
case 0:
if !bytes.Equal(line, bangArch) {
return bad()
}
case 1:
if !bytes.HasPrefix(line, pkgdef) {
return bad()
}
case 2:
if !bytes.HasPrefix(line, goobject) {
return bad()
}
case 3:
if !bytes.HasPrefix(line, buildid) {
// Found the object header, just doesn't have a build id line.
// Treat as successful, with empty build id.
return "", nil
}
id, err := strconv.Unquote(string(line[len(buildid):]))
if err != nil {
return bad()
}
return id, nil
}
}
}
var (
goBuildPrefix = []byte("\xff Go build ID: \"")
goBuildEnd = []byte("\"\n \xff")
elfPrefix = []byte("\x7fELF")
machoPrefixes = [][]byte{
{0xfe, 0xed, 0xfa, 0xce},
{0xfe, 0xed, 0xfa, 0xcf},
{0xce, 0xfa, 0xed, 0xfe},
{0xcf, 0xfa, 0xed, 0xfe},
}
)
var BuildIDReadSize = 32 * 1024 // changed for testing
// ReadBuildIDFromBinary reads the build ID from a binary.
//
// ELF binaries store the build ID in a proper PT_NOTE section.
//
// Other binary formats are not so flexible. For those, the linker
// stores the build ID as non-instruction bytes at the very beginning
// of the text segment, which should appear near the beginning
// of the file. This is clumsy but fairly portable. Custom locations
// can be added for other binary types as needed, like we did for ELF.
func ReadBuildIDFromBinary(filename string) (id string, err error) {
if filename == "" {
return "", &os.PathError{Op: "parse", Path: filename, Err: errBuildIDUnknown}
}
// Read the first 32 kB of the binary file.
// That should be enough to find the build ID.
// In ELF files, the build ID is in the leading headers,
// which are typically less than 4 kB, not to mention 32 kB.
// In Mach-O files, there's no limit, so we have to parse the file.
// On other systems, we're trying to read enough that
// we get the beginning of the text segment in the read.
// The offset where the text segment begins in a hello
// world compiled for each different object format today:
//
// Plan 9: 0x20
// Windows: 0x600
//
f, err := os.Open(filename)
if err != nil {
return "", err
}
defer f.Close()
data := make([]byte, BuildIDReadSize)
_, err = io.ReadFull(f, data)
if err == io.ErrUnexpectedEOF {
err = nil
}
if err != nil {
return "", err
}
if bytes.HasPrefix(data, elfPrefix) {
return readELFGoBuildID(filename, f, data)
}
for _, m := range machoPrefixes {
if bytes.HasPrefix(data, m) {
return readMachoGoBuildID(filename, f, data)
}
}
return readRawGoBuildID(filename, data)
}
// readRawGoBuildID finds the raw build ID stored in text segment data.
func readRawGoBuildID(filename string, data []byte) (id string, err error) {
i := bytes.Index(data, goBuildPrefix)
if i < 0 {
// Missing. Treat as successful but build ID empty.
return "", nil
}
j := bytes.Index(data[i+len(goBuildPrefix):], goBuildEnd)
if j < 0 {
return "", &os.PathError{Op: "parse", Path: filename, Err: errBuildIDMalformed}
}
quoted := data[i+len(goBuildPrefix)-1 : i+len(goBuildPrefix)+j+1]
id, err = strconv.Unquote(string(quoted))
if err != nil {
return "", &os.PathError{Op: "parse", Path: filename, Err: errBuildIDMalformed}
}
return id, nil
}