buildinfo.mx raw
1 // Copyright 2021 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.
4
5 // Package buildinfo provides access to information embedded in a Go binary
6 // about how it was built. This includes the Go toolchain version, and the
7 // set of modules used (for binaries built in module mode).
8 //
9 // Build information is available for the currently running binary in
10 // runtime/debug.ReadBuildInfo.
11 package buildinfo
12
13 import (
14 "bytes"
15 "debug/elf"
16 "debug/macho"
17 "debug/pe"
18 "debug/plan9obj"
19 "encoding/binary"
20 "errors"
21 "fmt"
22 "internal/saferio"
23 "internal/xcoff"
24 "io"
25 "io/fs"
26 "os"
27 "runtime/debug"
28 _ "unsafe" // for linkname
29 )
30
31 // Type alias for build info. We cannot move the types here, since
32 // runtime/debug would need to import this package, which would make it
33 // a much larger dependency.
34 type BuildInfo = debug.BuildInfo
35
36 // errUnrecognizedFormat is returned when a given executable file doesn't
37 // appear to be in a known format, or it breaks the rules of that format,
38 // or when there are I/O errors reading the file.
39 var errUnrecognizedFormat = errors.New("unrecognized file format")
40
41 // errNotGoExe is returned when a given executable file is valid but does
42 // not contain Go build information.
43 //
44 // errNotGoExe should be an internal detail,
45 // but widely used packages access it using linkname.
46 // Notable members of the hall of shame include:
47 // - github.com/quay/claircore
48 //
49 // Do not remove or change the type signature.
50 // See go.dev/issue/67401.
51 //
52 //go:linkname errNotGoExe
53 var errNotGoExe = errors.New("not a Go executable")
54
55 // The build info blob left by the linker is identified by a 32-byte header,
56 // consisting of buildInfoMagic (14 bytes), followed by version-dependent
57 // fields.
58 var buildInfoMagic = []byte("\xff Go buildinf:")
59
60 const (
61 buildInfoAlign = 16
62 buildInfoHeaderSize = 32
63 )
64
65 // ReadFile returns build information embedded in a Go binary
66 // file at the given path. Most information is only available for binaries built
67 // with module support.
68 func ReadFile(name string) (info *BuildInfo, err error) {
69 defer func() {
70 if _, ok := err.(*fs.PathError); ok {
71 err = fmt.Errorf("could not read Go build info: %w", err)
72 } else if err != nil {
73 err = fmt.Errorf("could not read Go build info from %s: %w", name, err)
74 }
75 }()
76
77 f, err := os.Open(name)
78 if err != nil {
79 return nil, err
80 }
81 defer f.Close()
82 return Read(f)
83 }
84
85 // Read returns build information embedded in a Go binary file
86 // accessed through the given ReaderAt. Most information is only available for
87 // binaries built with module support.
88 func Read(r io.ReaderAt) (*BuildInfo, error) {
89 vers, mod, err := readRawBuildInfo(r)
90 if err != nil {
91 return nil, err
92 }
93 bi, err := debug.ParseBuildInfo(mod)
94 if err != nil {
95 return nil, err
96 }
97 bi.GoVersion = vers
98 return bi, nil
99 }
100
101 type exe interface {
102 // DataStart returns the virtual address and size of the segment or section that
103 // should contain build information. This is either a specially named section
104 // or the first writable non-zero data segment.
105 DataStart() (uint64, uint64)
106
107 // DataReader returns an io.ReaderAt that reads from addr until the end
108 // of segment or section that contains addr.
109 DataReader(addr uint64) (io.ReaderAt, error)
110 }
111
112 // readRawBuildInfo extracts the Go toolchain version and module information
113 // strings from a Go binary. On success, vers should be non-empty. mod
114 // is empty if the binary was not built with modules enabled.
115 func readRawBuildInfo(r io.ReaderAt) (vers, mod string, err error) {
116 // Read the first bytes of the file to identify the format, then delegate to
117 // a format-specific function to load segment and section headers.
118 ident := []byte{:16}
119 if n, err := r.ReadAt(ident, 0); n < len(ident) || err != nil {
120 return "", "", errUnrecognizedFormat
121 }
122
123 var x exe
124 switch {
125 case bytes.HasPrefix(ident, []byte("\x7FELF")):
126 f, err := elf.NewFile(r)
127 if err != nil {
128 return "", "", errUnrecognizedFormat
129 }
130 x = &elfExe{f}
131 case bytes.HasPrefix(ident, []byte("MZ")):
132 f, err := pe.NewFile(r)
133 if err != nil {
134 return "", "", errUnrecognizedFormat
135 }
136 x = &peExe{f}
137 case bytes.HasPrefix(ident, []byte("\xFE\xED\xFA")) || bytes.HasPrefix(ident[1:], []byte("\xFA\xED\xFE")):
138 f, err := macho.NewFile(r)
139 if err != nil {
140 return "", "", errUnrecognizedFormat
141 }
142 x = &machoExe{f}
143 case bytes.HasPrefix(ident, []byte("\xCA\xFE\xBA\xBE")) || bytes.HasPrefix(ident, []byte("\xCA\xFE\xBA\xBF")):
144 f, err := macho.NewFatFile(r)
145 if err != nil || len(f.Arches) == 0 {
146 return "", "", errUnrecognizedFormat
147 }
148 x = &machoExe{f.Arches[0].File}
149 case bytes.HasPrefix(ident, []byte{0x01, 0xDF}) || bytes.HasPrefix(ident, []byte{0x01, 0xF7}):
150 f, err := xcoff.NewFile(r)
151 if err != nil {
152 return "", "", errUnrecognizedFormat
153 }
154 x = &xcoffExe{f}
155 case hasPlan9Magic(ident):
156 f, err := plan9obj.NewFile(r)
157 if err != nil {
158 return "", "", errUnrecognizedFormat
159 }
160 x = &plan9objExe{f}
161 default:
162 return "", "", errUnrecognizedFormat
163 }
164
165 // Read segment or section to find the build info blob.
166 // On some platforms, the blob will be in its own section, and DataStart
167 // returns the address of that section. On others, it's somewhere in the
168 // data segment; the linker puts it near the beginning.
169 // See cmd/link/internal/ld.Link.buildinfo.
170 dataAddr, dataSize := x.DataStart()
171 if dataSize == 0 {
172 return "", "", errNotGoExe
173 }
174
175 addr, err := searchMagic(x, dataAddr, dataSize)
176 if err != nil {
177 return "", "", err
178 }
179
180 // Read in the full header first.
181 header, err := readData(x, addr, buildInfoHeaderSize)
182 if err == io.EOF {
183 return "", "", errNotGoExe
184 } else if err != nil {
185 return "", "", err
186 }
187 if len(header) < buildInfoHeaderSize {
188 return "", "", errNotGoExe
189 }
190
191 const (
192 ptrSizeOffset = 14
193 flagsOffset = 15
194 versPtrOffset = 16
195
196 flagsEndianMask = 0x1
197 flagsEndianLittle = 0x0
198 flagsEndianBig = 0x1
199
200 flagsVersionMask = 0x2
201 flagsVersionPtr = 0x0
202 flagsVersionInl = 0x2
203 )
204
205 // Decode the blob. The blob is a 32-byte header, optionally followed
206 // by 2 varint-prefixed string contents.
207 //
208 // type buildInfoHeader struct {
209 // magic [14]byte
210 // ptrSize uint8 // used if flagsVersionPtr
211 // flags uint8
212 // versPtr targetUintptr // used if flagsVersionPtr
213 // modPtr targetUintptr // used if flagsVersionPtr
214 // }
215 //
216 // The version bit of the flags field determines the details of the format.
217 //
218 // Prior to 1.18, the flags version bit is flagsVersionPtr. In this
219 // case, the header includes pointers to the version and modinfo Go
220 // strings in the header. The ptrSize field indicates the size of the
221 // pointers and the endian bit of the flag indicates the pointer
222 // endianness.
223 //
224 // Since 1.18, the flags version bit is flagsVersionInl. In this case,
225 // the header is followed by the string contents inline as
226 // length-prefixed (as varint) string contents. First is the version
227 // string, followed immediately by the modinfo string.
228 flags := header[flagsOffset]
229 if flags&flagsVersionMask == flagsVersionInl {
230 vers, addr, err = decodeString(x, addr+buildInfoHeaderSize)
231 if err != nil {
232 return "", "", err
233 }
234 mod, _, err = decodeString(x, addr)
235 if err != nil {
236 return "", "", err
237 }
238 } else {
239 // flagsVersionPtr (<1.18)
240 ptrSize := int(header[ptrSizeOffset])
241 bigEndian := flags&flagsEndianMask == flagsEndianBig
242 var bo binary.ByteOrder
243 if bigEndian {
244 bo = binary.BigEndian
245 } else {
246 bo = binary.LittleEndian
247 }
248 var readPtr func([]byte) uint64
249 if ptrSize == 4 {
250 readPtr = func(b []byte) uint64 { return uint64(bo.Uint32(b)) }
251 } else if ptrSize == 8 {
252 readPtr = bo.Uint64
253 } else {
254 return "", "", errNotGoExe
255 }
256 vers = readString(x, ptrSize, readPtr, readPtr(header[versPtrOffset:]))
257 mod = readString(x, ptrSize, readPtr, readPtr(header[versPtrOffset+ptrSize:]))
258 }
259 if vers == "" {
260 return "", "", errNotGoExe
261 }
262 if len(mod) >= 33 && mod[len(mod)-17] == '\n' {
263 // Strip module framing: sentinel strings delimiting the module info.
264 // These are cmd/go/internal/modload.infoStart and infoEnd.
265 mod = mod[16 : len(mod)-16]
266 } else {
267 mod = ""
268 }
269
270 return vers, mod, nil
271 }
272
273 func hasPlan9Magic(magic []byte) bool {
274 if len(magic) >= 4 {
275 m := binary.BigEndian.Uint32(magic)
276 switch m {
277 case plan9obj.Magic386, plan9obj.MagicAMD64, plan9obj.MagicARM:
278 return true
279 }
280 }
281 return false
282 }
283
284 func decodeString(x exe, addr uint64) (string, uint64, error) {
285 // varint length followed by length bytes of data.
286
287 // N.B. ReadData reads _up to_ size bytes from the section containing
288 // addr. So we don't need to check that size doesn't overflow the
289 // section.
290 b, err := readData(x, addr, binary.MaxVarintLen64)
291 if err == io.EOF {
292 return "", 0, errNotGoExe
293 } else if err != nil {
294 return "", 0, err
295 }
296
297 length, n := binary.Uvarint(b)
298 if n <= 0 {
299 return "", 0, errNotGoExe
300 }
301 addr += uint64(n)
302
303 b, err = readData(x, addr, length)
304 if err == io.EOF {
305 return "", 0, errNotGoExe
306 } else if err == io.ErrUnexpectedEOF {
307 // Length too large to allocate. Clearly bogus value.
308 return "", 0, errNotGoExe
309 } else if err != nil {
310 return "", 0, err
311 }
312 if uint64(len(b)) < length {
313 // Section ended before we could read the full string.
314 return "", 0, errNotGoExe
315 }
316
317 return string(b), addr + length, nil
318 }
319
320 // readString returns the string at address addr in the executable x.
321 func readString(x exe, ptrSize int, readPtr func([]byte) uint64, addr uint64) string {
322 hdr, err := readData(x, addr, uint64(2*ptrSize))
323 if err != nil || len(hdr) < 2*ptrSize {
324 return ""
325 }
326 dataAddr := readPtr(hdr)
327 dataLen := readPtr(hdr[ptrSize:])
328 data, err := readData(x, dataAddr, dataLen)
329 if err != nil || uint64(len(data)) < dataLen {
330 return ""
331 }
332 return string(data)
333 }
334
335 const searchChunkSize = 1 << 20 // 1 MB
336
337 // searchMagic returns the aligned first instance of buildInfoMagic in the data
338 // range [addr, addr+size). Returns false if not found.
339 func searchMagic(x exe, start, size uint64) (uint64, error) {
340 end := start + size
341 if end < start {
342 // Overflow.
343 return 0, errUnrecognizedFormat
344 }
345
346 // Round up start; magic can't occur in the initial unaligned portion.
347 start = (start + buildInfoAlign - 1) &^ (buildInfoAlign - 1)
348 if start >= end {
349 return 0, errNotGoExe
350 }
351
352 var buf []byte
353 for start < end {
354 // Read in chunks to avoid consuming too much memory if data is large.
355 //
356 // Normally it would be somewhat painful to handle the magic crossing a
357 // chunk boundary, but since it must be 16-byte aligned we know it will
358 // fall within a single chunk.
359 remaining := end - start
360 chunkSize := uint64(searchChunkSize)
361 if chunkSize > remaining {
362 chunkSize = remaining
363 }
364
365 if buf == nil {
366 buf = []byte{:chunkSize}
367 } else {
368 // N.B. chunkSize can only decrease, and only on the
369 // last chunk.
370 buf = buf[:chunkSize]
371 clear(buf)
372 }
373
374 n, err := readDataInto(x, start, buf)
375 if err == io.EOF {
376 // EOF before finding the magic; must not be a Go executable.
377 return 0, errNotGoExe
378 } else if err != nil {
379 return 0, err
380 }
381
382 data := buf[:n]
383 for len(data) > 0 {
384 i := bytes.Index(data, buildInfoMagic)
385 if i < 0 {
386 break
387 }
388 if remaining-uint64(i) < buildInfoHeaderSize {
389 // Found magic, but not enough space left for the full header.
390 return 0, errNotGoExe
391 }
392 if i%buildInfoAlign != 0 {
393 // Found magic, but misaligned. Keep searching.
394 next := (i + buildInfoAlign - 1) &^ (buildInfoAlign - 1)
395 if next > len(data) {
396 // Corrupt object file: the remaining
397 // count says there is more data,
398 // but we didn't read it.
399 return 0, errNotGoExe
400 }
401 data = data[next:]
402 continue
403 }
404 // Good match!
405 return start + uint64(i), nil
406 }
407
408 start += chunkSize
409 }
410
411 return 0, errNotGoExe
412 }
413
414 func readData(x exe, addr, size uint64) ([]byte, error) {
415 r, err := x.DataReader(addr)
416 if err != nil {
417 return nil, err
418 }
419
420 b, err := saferio.ReadDataAt(r, size, 0)
421 if len(b) > 0 && err == io.EOF {
422 err = nil
423 }
424 return b, err
425 }
426
427 func readDataInto(x exe, addr uint64, b []byte) (int, error) {
428 r, err := x.DataReader(addr)
429 if err != nil {
430 return 0, err
431 }
432
433 n, err := r.ReadAt(b, 0)
434 if n > 0 && err == io.EOF {
435 err = nil
436 }
437 return n, err
438 }
439
440 // elfExe is the ELF implementation of the exe interface.
441 type elfExe struct {
442 f *elf.File
443 }
444
445 func (x *elfExe) DataReader(addr uint64) (io.ReaderAt, error) {
446 for _, prog := range x.f.Progs {
447 if prog.Vaddr <= addr && addr <= prog.Vaddr+prog.Filesz-1 {
448 remaining := prog.Vaddr + prog.Filesz - addr
449 return io.NewSectionReader(prog, int64(addr-prog.Vaddr), int64(remaining)), nil
450 }
451 }
452 return nil, errUnrecognizedFormat
453 }
454
455 func (x *elfExe) DataStart() (uint64, uint64) {
456 for _, s := range x.f.Sections {
457 if s.Name == ".go.buildinfo" {
458 return s.Addr, s.Size
459 }
460 }
461 for _, p := range x.f.Progs {
462 if p.Type == elf.PT_LOAD && p.Flags&(elf.PF_X|elf.PF_W) == elf.PF_W {
463 return p.Vaddr, p.Memsz
464 }
465 }
466 return 0, 0
467 }
468
469 // peExe is the PE (Windows Portable Executable) implementation of the exe interface.
470 type peExe struct {
471 f *pe.File
472 }
473
474 func (x *peExe) imageBase() uint64 {
475 switch oh := x.f.OptionalHeader.(type) {
476 case *pe.OptionalHeader32:
477 return uint64(oh.ImageBase)
478 case *pe.OptionalHeader64:
479 return oh.ImageBase
480 }
481 return 0
482 }
483
484 func (x *peExe) DataReader(addr uint64) (io.ReaderAt, error) {
485 addr -= x.imageBase()
486 for _, sect := range x.f.Sections {
487 if uint64(sect.VirtualAddress) <= addr && addr <= uint64(sect.VirtualAddress+sect.Size-1) {
488 remaining := uint64(sect.VirtualAddress+sect.Size) - addr
489 return io.NewSectionReader(sect, int64(addr-uint64(sect.VirtualAddress)), int64(remaining)), nil
490 }
491 }
492 return nil, errUnrecognizedFormat
493 }
494
495 func (x *peExe) DataStart() (uint64, uint64) {
496 // Assume data is first writable section.
497 const (
498 IMAGE_SCN_CNT_CODE = 0x00000020
499 IMAGE_SCN_CNT_INITIALIZED_DATA = 0x00000040
500 IMAGE_SCN_CNT_UNINITIALIZED_DATA = 0x00000080
501 IMAGE_SCN_MEM_EXECUTE = 0x20000000
502 IMAGE_SCN_MEM_READ = 0x40000000
503 IMAGE_SCN_MEM_WRITE = 0x80000000
504 IMAGE_SCN_MEM_DISCARDABLE = 0x2000000
505 IMAGE_SCN_LNK_NRELOC_OVFL = 0x1000000
506 IMAGE_SCN_ALIGN_32BYTES = 0x600000
507 )
508 for _, sect := range x.f.Sections {
509 if sect.VirtualAddress != 0 && sect.Size != 0 &&
510 sect.Characteristics&^IMAGE_SCN_ALIGN_32BYTES == IMAGE_SCN_CNT_INITIALIZED_DATA|IMAGE_SCN_MEM_READ|IMAGE_SCN_MEM_WRITE {
511 return uint64(sect.VirtualAddress) + x.imageBase(), uint64(sect.VirtualSize)
512 }
513 }
514 return 0, 0
515 }
516
517 // machoExe is the Mach-O (Apple macOS/iOS) implementation of the exe interface.
518 type machoExe struct {
519 f *macho.File
520 }
521
522 func (x *machoExe) DataReader(addr uint64) (io.ReaderAt, error) {
523 for _, load := range x.f.Loads {
524 seg, ok := load.(*macho.Segment)
525 if !ok {
526 continue
527 }
528 if seg.Addr <= addr && addr <= seg.Addr+seg.Filesz-1 {
529 if seg.Name == "__PAGEZERO" {
530 continue
531 }
532 remaining := seg.Addr + seg.Filesz - addr
533 return io.NewSectionReader(seg, int64(addr-seg.Addr), int64(remaining)), nil
534 }
535 }
536 return nil, errUnrecognizedFormat
537 }
538
539 func (x *machoExe) DataStart() (uint64, uint64) {
540 // Look for section named "__go_buildinfo".
541 for _, sec := range x.f.Sections {
542 if sec.Name == "__go_buildinfo" {
543 return sec.Addr, sec.Size
544 }
545 }
546 // Try the first non-empty writable segment.
547 const RW = 3
548 for _, load := range x.f.Loads {
549 seg, ok := load.(*macho.Segment)
550 if ok && seg.Addr != 0 && seg.Filesz != 0 && seg.Prot == RW && seg.Maxprot == RW {
551 return seg.Addr, seg.Memsz
552 }
553 }
554 return 0, 0
555 }
556
557 // xcoffExe is the XCOFF (AIX eXtended COFF) implementation of the exe interface.
558 type xcoffExe struct {
559 f *xcoff.File
560 }
561
562 func (x *xcoffExe) DataReader(addr uint64) (io.ReaderAt, error) {
563 for _, sect := range x.f.Sections {
564 if sect.VirtualAddress <= addr && addr <= sect.VirtualAddress+sect.Size-1 {
565 remaining := sect.VirtualAddress + sect.Size - addr
566 return io.NewSectionReader(sect, int64(addr-sect.VirtualAddress), int64(remaining)), nil
567 }
568 }
569 return nil, errors.New("address not mapped")
570 }
571
572 func (x *xcoffExe) DataStart() (uint64, uint64) {
573 if s := x.f.SectionByType(xcoff.STYP_DATA); s != nil {
574 return s.VirtualAddress, s.Size
575 }
576 return 0, 0
577 }
578
579 // plan9objExe is the Plan 9 a.out implementation of the exe interface.
580 type plan9objExe struct {
581 f *plan9obj.File
582 }
583
584 func (x *plan9objExe) DataStart() (uint64, uint64) {
585 if s := x.f.Section("data"); s != nil {
586 return uint64(s.Offset), uint64(s.Size)
587 }
588 return 0, 0
589 }
590
591 func (x *plan9objExe) DataReader(addr uint64) (io.ReaderAt, error) {
592 for _, sect := range x.f.Sections {
593 if uint64(sect.Offset) <= addr && addr <= uint64(sect.Offset+sect.Size-1) {
594 remaining := uint64(sect.Offset+sect.Size) - addr
595 return io.NewSectionReader(sect, int64(addr-uint64(sect.Offset)), int64(remaining)), nil
596 }
597 }
598 return nil, errors.New("address not mapped")
599 }
600