cmd/go: buildid support for AIX archives.
[official-gcc.git] / libgo / go / cmd / internal / buildid / buildid.go
blob41e6c773e1b6eec049d9b12270dd68d94ab39a9f
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.
5 package buildid
7 import (
8 "bytes"
9 "debug/elf"
10 "debug/xcoff"
11 "fmt"
12 "io"
13 "os"
14 "strconv"
15 "strings"
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")
24 var (
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)
34 if err != nil {
35 return "", err
37 defer f.Close()
39 buf := make([]byte, 8)
40 if _, err := f.ReadAt(buf, 0); err != nil {
41 return "", err
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:
53 // !<arch>
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 {
63 return "", err
66 tryGccgo := func() (string, error) {
67 return readGccgoArchive(name, f)
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()
76 line := data[:j]
77 data = data[j+1:]
78 switch i {
79 case 0:
80 if !bytes.Equal(line, bangArch) {
81 return tryGccgo()
83 case 1:
84 if !bytes.HasPrefix(line, pkgdef) {
85 return tryGccgo()
87 case 2:
88 if !bytes.HasPrefix(line, goobject) {
89 return tryGccgo()
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
97 id, err := strconv.Unquote(string(line[len(buildid):]))
98 if err != nil {
99 return tryGccgo()
101 return id, nil
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}
115 off := int64(8)
116 for {
117 if _, err := f.Seek(off, io.SeekStart); err != nil {
118 return "", err
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
129 return "", err
131 off += 60
133 sizeStr := strings.TrimSpace(string(hdr[48:58]))
134 size, err := strconv.ParseInt(sizeStr, 0, 64)
135 if err != nil {
136 return bad()
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()
146 s := e.Section(".go.buildid")
147 if s == nil {
148 return bad()
150 data, err := s.Data()
151 if err != nil {
152 return bad()
154 return string(data), nil
157 off += size
158 if off&1 != 0 {
159 off++
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 {
175 return "", err
177 var flhdr [128]byte
178 if _, err := io.ReadFull(f, flhdr[:]); err != nil {
179 return "", err
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()
187 for {
188 if off == 0 {
189 // No more entries, no build ID.
190 return "", nil
192 if _, err := f.Seek(off, io.SeekStart); err != nil {
193 return "", err
195 // Read member header.
196 var hdr [112]byte
197 if _, err := io.ReadFull(f, hdr[:]); err != nil {
198 return "", err
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()
206 if namLen == 10 {
207 var nam [10]byte
208 if _, err := io.ReadFull(f, nam[:]); err != nil {
209 return "", err
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()
217 off += int64(len(hdr)) + namLen + 2
218 if off&1 != 0 {
219 off++
221 sr := io.NewSectionReader(f, off, size)
222 x, err := xcoff.NewFile(sr)
223 if err != nil {
224 return bad()
226 data := x.CSect(".go.buildid")
227 if data == nil {
228 return bad()
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)
237 if err != nil {
238 return bad()
243 var (
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:
279 // Plan 9: 0x20
280 // Windows: 0x600
282 data := make([]byte, readSize)
283 _, err = io.ReadFull(f, data)
284 if err == io.ErrUnexpectedEOF {
285 err = nil
287 if err != nil {
288 return "", err
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)
305 if i < 0 {
306 // Missing. Treat as successful but build ID empty.
307 return "", nil
310 j := bytes.Index(data[i+len(goBuildPrefix):], goBuildEnd)
311 if j < 0 {
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))
317 if err != nil {
318 return "", &os.PathError{Op: "parse", Path: name, Err: errBuildIDMalformed}
320 return id, nil