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.
18 var errBuildIDMalformed = fmt.Errorf("malformed object file")
21 bangArch = []byte("!<arch>")
22 pkgdef = []byte("__.PKGDEF")
23 goobject = []byte("go object ")
24 buildid = []byte("build id ")
27 // ReadFile reads the build ID from an archive or executable file.
28 func ReadFile(name string) (id string, err error) {
29 f, err := os.Open(name)
35 buf := make([]byte, 8)
36 if _, err := f.ReadAt(buf, 0); err != nil {
39 if string(buf) != "!<arch>\n" {
40 if string(buf) == "<bigaf>\n" {
41 return "", errors.New("unsupported")
43 return readBinary(name, f)
46 // Read just enough of the target to fetch the build ID.
47 // The archive is expected to look like:
50 // __.PKGDEF 0 0 0 644 7955 `
51 // go object darwin amd64 devel X:none
52 // build id "b41e5c45250e25c9fd5e9f9a1de7857ea0d41224"
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 {
62 tryGccgo := func() (string, error) {
63 return readGccgoArchive(name, f)
67 for i := 0; ; i++ { // returns during i==3
68 j := bytes.IndexByte(data, '\n')
76 if !bytes.Equal(line, bangArch) {
80 if !bytes.HasPrefix(line, pkgdef) {
84 if !bytes.HasPrefix(line, goobject) {
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.
93 id, err := strconv.Unquote(string(line[len(buildid):]))
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}
113 if _, err := f.Seek(off, io.SeekStart); err != nil {
117 // TODO(iant): Make a debug/ar package, and use it
118 // here and in cmd/link.
120 if _, err := io.ReadFull(f, hdr[:]); err != nil {
122 // No more entries, no build ID.
129 sizeStr := strings.TrimSpace(string(hdr[48:58]))
130 size, err := strconv.ParseInt(sizeStr, 0, 64)
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)
142 s := e.Section(".go.buildid")
146 data, err := s.Data()
150 return string(data), nil
161 goBuildPrefix = []byte("\xff Go build ID: \"")
162 goBuildEnd = []byte("\"\n \xff")
164 elfPrefix = []byte("\x7fELF")
166 machoPrefixes = [][]byte{
167 {0xfe, 0xed, 0xfa, 0xce},
168 {0xfe, 0xed, 0xfa, 0xcf},
169 {0xce, 0xfa, 0xed, 0xfe},
170 {0xcf, 0xfa, 0xed, 0xfe},
174 var readSize = 32 * 1024 // changed for testing
176 // readBinary reads the build ID from a binary.
178 // ELF binaries store the build ID in a proper PT_NOTE section.
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:
199 data := make([]byte, readSize)
200 _, err = io.ReadFull(f, data)
201 if err == io.ErrUnexpectedEOF {
208 if bytes.HasPrefix(data, elfPrefix) {
209 return readELF(name, f, data)
211 for _, m := range machoPrefixes {
212 if bytes.HasPrefix(data, m) {
213 return readMacho(name, f, data)
216 return readRaw(name, data)
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)
223 // Missing. Treat as successful but build ID empty.
227 j := bytes.Index(data[i+len(goBuildPrefix):], goBuildEnd)
229 return "", &os.PathError{Op: "parse", Path: name, Err: errBuildIDMalformed}
232 quoted := data[i+len(goBuildPrefix)-1 : i+len(goBuildPrefix)+j+1]
233 id, err = strconv.Unquote(string(quoted))
235 return "", &os.PathError{Op: "parse", Path: name, Err: errBuildIDMalformed}