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.
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")
25 bangArch
= []byte("!<arch>")
26 pkgdef
= []byte("__.PKGDEF")
27 goobject
= []byte("go object ")
28 buildid
= []byte("build id ")
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
)
39 buf
:= make([]byte, 8)
40 if _
, err
:= f
.ReadAt(buf
, 0); err
!= nil {
43 if string(buf
) != "!<arch>\n" {
44 if string(buf
) == "<bigaf>\n" {
45 return readGccgoBigArchive(name
, f
)
47 return readBinary(name
, f
)
50 // Read just enough of the target to fetch the build ID.
51 // The archive is expected to look like:
54 // __.PKGDEF 0 0 0 644 7955 `
55 // go object darwin amd64 devel X:none
56 // build id "b41e5c45250e25c9fd5e9f9a1de7857ea0d41224"
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 {
66 tryGccgo
:= func() (string, error
) {
67 return readGccgoArchive(name
, f
)
71 for i
:= 0; ; i
++ { // returns during i==3
72 j
:= bytes
.IndexByte(data
, '\n')
80 if !bytes
.Equal(line
, bangArch
) {
84 if !bytes
.HasPrefix(line
, pkgdef
) {
88 if !bytes
.HasPrefix(line
, goobject
) {
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.
97 id
, err
:= strconv
.Unquote(string(line
[len(buildid
):]))
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
}
117 if _
, err
:= f
.Seek(off
, io
.SeekStart
); err
!= nil {
121 // TODO(iant): Make a debug/ar package, and use it
122 // here and in cmd/link.
124 if _
, err
:= io
.ReadFull(f
, hdr
[:]); err
!= nil {
126 // No more entries, no build ID.
133 sizeStr
:= strings
.TrimSpace(string(hdr
[48:58]))
134 size
, err
:= strconv
.ParseInt(sizeStr
, 0, 64)
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
)
146 s
:= e
.Section(".go.buildid")
150 data
, err
:= s
.Data()
154 return string(data
), nil
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
}
173 // Read fixed-length header.
174 if _
, err
:= f
.Seek(0, io
.SeekStart
); err
!= nil {
178 if _
, err
:= io
.ReadFull(f
, flhdr
[:]); err
!= nil {
181 // Read first member offset.
182 offStr
:= strings
.TrimSpace(string(flhdr
[68:88]))
183 off
, err
:= strconv
.ParseInt(offStr
, 10, 64)
189 // No more entries, no build ID.
192 if _
, err
:= f
.Seek(off
, io
.SeekStart
); err
!= nil {
195 // Read member header.
197 if _
, err
:= io
.ReadFull(f
, hdr
[:]); err
!= nil {
200 // Read member name length.
201 namLenStr
:= strings
.TrimSpace(string(hdr
[108:112]))
202 namLen
, err
:= strconv
.ParseInt(namLenStr
, 10, 32)
208 if _
, err
:= io
.ReadFull(f
, nam
[:]); err
!= nil {
211 if string(nam
[:]) == "_buildid.o" {
212 sizeStr
:= strings
.TrimSpace(string(hdr
[0:20]))
213 size
, err
:= strconv
.ParseInt(sizeStr
, 10, 64)
217 off
+= int64(len(hdr
)) + namLen
+ 2
221 sr
:= io
.NewSectionReader(f
, off
, size
)
222 x
, err
:= xcoff
.NewFile(sr
)
226 data
:= x
.CSect(".go.buildid")
230 return string(data
), nil
234 // Read next member offset.
235 offStr
= strings
.TrimSpace(string(hdr
[20:40]))
236 off
, err
= strconv
.ParseInt(offStr
, 10, 64)
244 goBuildPrefix
= []byte("\xff Go build ID: \"")
245 goBuildEnd
= []byte("\"\n \xff")
247 elfPrefix
= []byte("\x7fELF")
249 machoPrefixes
= [][]byte{
250 {0xfe, 0xed, 0xfa, 0xce},
251 {0xfe, 0xed, 0xfa, 0xcf},
252 {0xce, 0xfa, 0xed, 0xfe},
253 {0xcf, 0xfa, 0xed, 0xfe},
257 var readSize
= 32 * 1024 // changed for testing
259 // readBinary reads the build ID from a binary.
261 // ELF binaries store the build ID in a proper PT_NOTE section.
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:
282 data
:= make([]byte, readSize
)
283 _
, err
= io
.ReadFull(f
, data
)
284 if err
== io
.ErrUnexpectedEOF
{
291 if bytes
.HasPrefix(data
, elfPrefix
) {
292 return readELF(name
, f
, data
)
294 for _
, m
:= range machoPrefixes
{
295 if bytes
.HasPrefix(data
, m
) {
296 return readMacho(name
, f
, data
)
299 return readRaw(name
, data
)
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
)
306 // Missing. Treat as successful but build ID empty.
310 j
:= bytes
.Index(data
[i
+len(goBuildPrefix
):], goBuildEnd
)
312 return "", &os
.PathError
{Op
: "parse", Path
: name
, Err
: errBuildIDMalformed
}
315 quoted
:= data
[i
+len(goBuildPrefix
)-1 : i
+len(goBuildPrefix
)+j
+1]
316 id
, err
= strconv
.Unquote(string(quoted
))
318 return "", &os
.PathError
{Op
: "parse", Path
: name
, Err
: errBuildIDMalformed
}