1
2
3
4
5
6
7
8
9
10
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"
29 )
30
31
32
33
34 type BuildInfo = debug.BuildInfo
35
36
37
38
39 var errUnrecognizedFormat = errors.New("unrecognized file format")
40
41
42
43
44
45
46
47
48
49
50
51
52
53 var errNotGoExe = errors.New("not a Go executable")
54
55
56
57
58 var buildInfoMagic = []byte("\xff Go buildinf:")
59
60 const (
61 buildInfoAlign = 16
62 buildInfoHeaderSize = 32
63 )
64
65
66
67
68 func ReadFile(name string) (info *BuildInfo, err error) {
69 defer func() {
70 if pathErr := (*fs.PathError)(nil); errors.As(err, &pathErr) {
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
86
87
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
103
104
105 DataStart() (uint64, uint64)
106
107
108
109 DataReader(addr uint64) (io.ReaderAt, error)
110 }
111
112
113
114
115 func readRawBuildInfo(r io.ReaderAt) (vers, mod string, err error) {
116
117
118 ident := make([]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
166
167
168
169
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
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
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
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
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
264
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
286
287
288
289
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
308 return "", 0, errNotGoExe
309 } else if err != nil {
310 return "", 0, err
311 }
312 if uint64(len(b)) < length {
313
314 return "", 0, errNotGoExe
315 }
316
317 return string(b), addr + length, nil
318 }
319
320
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
336
337
338
339 func searchMagic(x exe, start, size uint64) (uint64, error) {
340 end := start + size
341 if end < start {
342
343 return 0, errUnrecognizedFormat
344 }
345
346
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
355
356
357
358
359 remaining := end - start
360 chunkSize := uint64(searchChunkSize)
361 if chunkSize > remaining {
362 chunkSize = remaining
363 }
364
365 if buf == nil {
366 buf = make([]byte, chunkSize)
367 } else {
368
369
370 buf = buf[:chunkSize]
371 clear(buf)
372 }
373
374 n, err := readDataInto(x, start, buf)
375 if err == io.EOF {
376
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
390 return 0, errNotGoExe
391 }
392 if i%buildInfoAlign != 0 {
393
394 next := (i + buildInfoAlign - 1) &^ (buildInfoAlign - 1)
395 if next > len(data) {
396
397
398
399 return 0, errNotGoExe
400 }
401 data = data[next:]
402 continue
403 }
404
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
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
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
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
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
541 for _, sec := range x.f.Sections {
542 if sec.Name == "__go_buildinfo" {
543 return sec.Addr, sec.Size
544 }
545 }
546
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
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
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
View as plain text