// 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" "debug/elf" "fmt" "internal/xcoff" "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 ") ) // ReadFile reads the build ID from an archive or executable file. func ReadFile(name string) (id string, err error) { f, err := os.Open(name) if err != nil { return "", err } defer f.Close() buf := make([]byte, 8) if _, err := f.ReadAt(buf, 0); err != nil { return "", err } if string(buf) != "!<arch>\n" { if string(buf) == "<bigaf>\n" { return readGccgoBigArchive(name, f) } return readBinary(name, f) } // 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. data := make([]byte, 1024) n, err := io.ReadFull(f, data) if err != nil && n == 0 { return "", err } tryGccgo := func() (string, error) { return readGccgoArchive(name, f) } // Archive header. for i := 0; ; i++ { // returns during i==3 j := bytes.IndexByte(data, '\n') if j < 0 { return tryGccgo() } line := data[:j] data = data[j+1:] switch i { case 0: if !bytes.Equal(line, bangArch) { return tryGccgo() } case 1: if !bytes.HasPrefix(line, pkgdef) { return tryGccgo() } case 2: if !bytes.HasPrefix(line, goobject) { return tryGccgo() } 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 tryGccgo() } return id, nil } } } // readGccgoArchive tries to parse the archive as a standard Unix // archive file, and fetch the build ID from the _buildid.o entry. // The _buildid.o entry is written by (*Builder).gccgoBuildIDELFFile // in cmd/go/internal/work/exec.go. func readGccgoArchive(name string, f *os.File) (string, error) { bad := func() (string, error) { return "", &os.PathError{Op: "parse", Path: name, Err: errBuildIDMalformed} } off := int64(8) for { if _, err := f.Seek(off, io.SeekStart); err != nil { return "", err } // TODO(iant): Make a debug/ar package, and use it // here and in cmd/link. var hdr [60]byte if _, err := io.ReadFull(f, hdr[:]); err != nil { if err == io.EOF { // No more entries, no build ID. return "", nil } return "", err } off += 60 sizeStr := strings.TrimSpace(string(hdr[48:58])) size, err := strconv.ParseInt(sizeStr, 0, 64) if err != nil { return bad() } name := strings.TrimSpace(string(hdr[:16])) if name == "_buildid.o/" { sr := io.NewSectionReader(f, off, size) e, err := elf.NewFile(sr) if err != nil { return bad() } s := e.Section(".go.buildid") if s == nil { return bad() } data, err := s.Data() if err != nil { return bad() } return string(data), nil } off += size if off&1 != 0 { off++ } } } // readGccgoBigArchive tries to parse the archive as an AIX big // archive file, and fetch the build ID from the _buildid.o entry. // The _buildid.o entry is written by (*Builder).gccgoBuildIDXCOFFFile // in cmd/go/internal/work/exec.go. func readGccgoBigArchive(name string, f *os.File) (string, error) { bad := func() (string, error) { return "", &os.PathError{Op: "parse", Path: name, Err: errBuildIDMalformed} } // Read fixed-length header. if _, err := f.Seek(0, io.SeekStart); err != nil { return "", err } var flhdr [128]byte if _, err := io.ReadFull(f, flhdr[:]); err != nil { return "", err } // Read first member offset. offStr := strings.TrimSpace(string(flhdr[68:88])) off, err := strconv.ParseInt(offStr, 10, 64) if err != nil { return bad() } for { if off == 0 { // No more entries, no build ID. return "", nil } if _, err := f.Seek(off, io.SeekStart); err != nil { return "", err } // Read member header. var hdr [112]byte if _, err := io.ReadFull(f, hdr[:]); err != nil { return "", err } // Read member name length. namLenStr := strings.TrimSpace(string(hdr[108:112])) namLen, err := strconv.ParseInt(namLenStr, 10, 32) if err != nil { return bad() } if namLen == 10 { var nam [10]byte if _, err := io.ReadFull(f, nam[:]); err != nil { return "", err } if string(nam[:]) == "_buildid.o" { sizeStr := strings.TrimSpace(string(hdr[0:20])) size, err := strconv.ParseInt(sizeStr, 10, 64) if err != nil { return bad() } off += int64(len(hdr)) + namLen + 2 if off&1 != 0 { off++ } sr := io.NewSectionReader(f, off, size) x, err := xcoff.NewFile(sr) if err != nil { return bad() } data := x.CSect(".go.buildid") if data == nil { return bad() } return string(data), nil } } // Read next member offset. offStr = strings.TrimSpace(string(hdr[20:40])) off, err = strconv.ParseInt(offStr, 10, 64) if err != nil { return bad() } } } 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 readSize = 32 * 1024 // changed for testing // readBinary 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 readBinary(name string, f *os.File) (id string, err error) { // 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 // data := make([]byte, readSize) _, err = io.ReadFull(f, data) if err == io.ErrUnexpectedEOF { err = nil } if err != nil { return "", err } if bytes.HasPrefix(data, elfPrefix) { return readELF(name, f, data) } for _, m := range machoPrefixes { if bytes.HasPrefix(data, m) { return readMacho(name, f, data) } } return readRaw(name, data) } // readRaw finds the raw build ID stored in text segment data. func readRaw(name 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: name, 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: name, Err: errBuildIDMalformed} } return id, nil }