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
59 var buildInfoMagic = []byte("\xff Go buildinf:")
60
61
62
63
64 func ReadFile(name string) (info *BuildInfo, err error) {
65 defer func() {
66 if pathErr := (*fs.PathError)(nil); errors.As(err, &pathErr) {
67 err = fmt.Errorf("could not read Go build info: %w", err)
68 } else if err != nil {
69 err = fmt.Errorf("could not read Go build info from %s: %w", name, err)
70 }
71 }()
72
73 f, err := os.Open(name)
74 if err != nil {
75 return nil, err
76 }
77 defer f.Close()
78 return Read(f)
79 }
80
81
82
83
84 func Read(r io.ReaderAt) (*BuildInfo, error) {
85 vers, mod, err := readRawBuildInfo(r)
86 if err != nil {
87 return nil, err
88 }
89 bi, err := debug.ParseBuildInfo(mod)
90 if err != nil {
91 return nil, err
92 }
93 bi.GoVersion = vers
94 return bi, nil
95 }
96
97 type exe interface {
98
99 ReadData(addr, size uint64) ([]byte, error)
100
101
102
103
104 DataStart() (uint64, uint64)
105 }
106
107
108
109
110 func readRawBuildInfo(r io.ReaderAt) (vers, mod string, err error) {
111
112
113 ident := make([]byte, 16)
114 if n, err := r.ReadAt(ident, 0); n < len(ident) || err != nil {
115 return "", "", errUnrecognizedFormat
116 }
117
118 var x exe
119 switch {
120 case bytes.HasPrefix(ident, []byte("\x7FELF")):
121 f, err := elf.NewFile(r)
122 if err != nil {
123 return "", "", errUnrecognizedFormat
124 }
125 x = &elfExe{f}
126 case bytes.HasPrefix(ident, []byte("MZ")):
127 f, err := pe.NewFile(r)
128 if err != nil {
129 return "", "", errUnrecognizedFormat
130 }
131 x = &peExe{f}
132 case bytes.HasPrefix(ident, []byte("\xFE\xED\xFA")) || bytes.HasPrefix(ident[1:], []byte("\xFA\xED\xFE")):
133 f, err := macho.NewFile(r)
134 if err != nil {
135 return "", "", errUnrecognizedFormat
136 }
137 x = &machoExe{f}
138 case bytes.HasPrefix(ident, []byte("\xCA\xFE\xBA\xBE")) || bytes.HasPrefix(ident, []byte("\xCA\xFE\xBA\xBF")):
139 f, err := macho.NewFatFile(r)
140 if err != nil || len(f.Arches) == 0 {
141 return "", "", errUnrecognizedFormat
142 }
143 x = &machoExe{f.Arches[0].File}
144 case bytes.HasPrefix(ident, []byte{0x01, 0xDF}) || bytes.HasPrefix(ident, []byte{0x01, 0xF7}):
145 f, err := xcoff.NewFile(r)
146 if err != nil {
147 return "", "", errUnrecognizedFormat
148 }
149 x = &xcoffExe{f}
150 case hasPlan9Magic(ident):
151 f, err := plan9obj.NewFile(r)
152 if err != nil {
153 return "", "", errUnrecognizedFormat
154 }
155 x = &plan9objExe{f}
156 default:
157 return "", "", errUnrecognizedFormat
158 }
159
160
161
162
163
164
165 dataAddr, dataSize := x.DataStart()
166 if dataSize == 0 {
167 return "", "", errNotGoExe
168 }
169 data, err := x.ReadData(dataAddr, dataSize)
170 if err != nil {
171 return "", "", err
172 }
173 const (
174 buildInfoAlign = 16
175 buildInfoSize = 32
176 )
177 for {
178 i := bytes.Index(data, buildInfoMagic)
179 if i < 0 || len(data)-i < buildInfoSize {
180 return "", "", errNotGoExe
181 }
182 if i%buildInfoAlign == 0 && len(data)-i >= buildInfoSize {
183 data = data[i:]
184 break
185 }
186 data = data[(i+buildInfoAlign-1)&^(buildInfoAlign-1):]
187 }
188
189
190
191
192
193
194
195
196
197
198
199 ptrSize := int(data[14])
200 if data[15]&2 != 0 {
201 vers, data = decodeString(data[32:])
202 mod, data = decodeString(data)
203 } else {
204 bigEndian := data[15] != 0
205 var bo binary.ByteOrder
206 if bigEndian {
207 bo = binary.BigEndian
208 } else {
209 bo = binary.LittleEndian
210 }
211 var readPtr func([]byte) uint64
212 if ptrSize == 4 {
213 readPtr = func(b []byte) uint64 { return uint64(bo.Uint32(b)) }
214 } else if ptrSize == 8 {
215 readPtr = bo.Uint64
216 } else {
217 return "", "", errNotGoExe
218 }
219 vers = readString(x, ptrSize, readPtr, readPtr(data[16:]))
220 mod = readString(x, ptrSize, readPtr, readPtr(data[16+ptrSize:]))
221 }
222 if vers == "" {
223 return "", "", errNotGoExe
224 }
225 if len(mod) >= 33 && mod[len(mod)-17] == '\n' {
226
227
228 mod = mod[16 : len(mod)-16]
229 } else {
230 mod = ""
231 }
232
233 return vers, mod, nil
234 }
235
236 func hasPlan9Magic(magic []byte) bool {
237 if len(magic) >= 4 {
238 m := binary.BigEndian.Uint32(magic)
239 switch m {
240 case plan9obj.Magic386, plan9obj.MagicAMD64, plan9obj.MagicARM:
241 return true
242 }
243 }
244 return false
245 }
246
247 func decodeString(data []byte) (s string, rest []byte) {
248 u, n := binary.Uvarint(data)
249 if n <= 0 || u > uint64(len(data)-n) {
250 return "", nil
251 }
252 return string(data[n : uint64(n)+u]), data[uint64(n)+u:]
253 }
254
255
256 func readString(x exe, ptrSize int, readPtr func([]byte) uint64, addr uint64) string {
257 hdr, err := x.ReadData(addr, uint64(2*ptrSize))
258 if err != nil || len(hdr) < 2*ptrSize {
259 return ""
260 }
261 dataAddr := readPtr(hdr)
262 dataLen := readPtr(hdr[ptrSize:])
263 data, err := x.ReadData(dataAddr, dataLen)
264 if err != nil || uint64(len(data)) < dataLen {
265 return ""
266 }
267 return string(data)
268 }
269
270
271 type elfExe struct {
272 f *elf.File
273 }
274
275 func (x *elfExe) ReadData(addr, size uint64) ([]byte, error) {
276 for _, prog := range x.f.Progs {
277 if prog.Vaddr <= addr && addr <= prog.Vaddr+prog.Filesz-1 {
278 n := prog.Vaddr + prog.Filesz - addr
279 if n > size {
280 n = size
281 }
282 return saferio.ReadDataAt(prog, n, int64(addr-prog.Vaddr))
283 }
284 }
285 return nil, errUnrecognizedFormat
286 }
287
288 func (x *elfExe) DataStart() (uint64, uint64) {
289 for _, s := range x.f.Sections {
290 if s.Name == ".go.buildinfo" {
291 return s.Addr, s.Size
292 }
293 }
294 for _, p := range x.f.Progs {
295 if p.Type == elf.PT_LOAD && p.Flags&(elf.PF_X|elf.PF_W) == elf.PF_W {
296 return p.Vaddr, p.Memsz
297 }
298 }
299 return 0, 0
300 }
301
302
303 type peExe struct {
304 f *pe.File
305 }
306
307 func (x *peExe) imageBase() uint64 {
308 switch oh := x.f.OptionalHeader.(type) {
309 case *pe.OptionalHeader32:
310 return uint64(oh.ImageBase)
311 case *pe.OptionalHeader64:
312 return oh.ImageBase
313 }
314 return 0
315 }
316
317 func (x *peExe) ReadData(addr, size uint64) ([]byte, error) {
318 addr -= x.imageBase()
319 for _, sect := range x.f.Sections {
320 if uint64(sect.VirtualAddress) <= addr && addr <= uint64(sect.VirtualAddress+sect.Size-1) {
321 n := uint64(sect.VirtualAddress+sect.Size) - addr
322 if n > size {
323 n = size
324 }
325 return saferio.ReadDataAt(sect, n, int64(addr-uint64(sect.VirtualAddress)))
326 }
327 }
328 return nil, errUnrecognizedFormat
329 }
330
331 func (x *peExe) DataStart() (uint64, uint64) {
332
333 const (
334 IMAGE_SCN_CNT_CODE = 0x00000020
335 IMAGE_SCN_CNT_INITIALIZED_DATA = 0x00000040
336 IMAGE_SCN_CNT_UNINITIALIZED_DATA = 0x00000080
337 IMAGE_SCN_MEM_EXECUTE = 0x20000000
338 IMAGE_SCN_MEM_READ = 0x40000000
339 IMAGE_SCN_MEM_WRITE = 0x80000000
340 IMAGE_SCN_MEM_DISCARDABLE = 0x2000000
341 IMAGE_SCN_LNK_NRELOC_OVFL = 0x1000000
342 IMAGE_SCN_ALIGN_32BYTES = 0x600000
343 )
344 for _, sect := range x.f.Sections {
345 if sect.VirtualAddress != 0 && sect.Size != 0 &&
346 sect.Characteristics&^IMAGE_SCN_ALIGN_32BYTES == IMAGE_SCN_CNT_INITIALIZED_DATA|IMAGE_SCN_MEM_READ|IMAGE_SCN_MEM_WRITE {
347 return uint64(sect.VirtualAddress) + x.imageBase(), uint64(sect.VirtualSize)
348 }
349 }
350 return 0, 0
351 }
352
353
354 type machoExe struct {
355 f *macho.File
356 }
357
358 func (x *machoExe) ReadData(addr, size uint64) ([]byte, error) {
359 for _, load := range x.f.Loads {
360 seg, ok := load.(*macho.Segment)
361 if !ok {
362 continue
363 }
364 if seg.Addr <= addr && addr <= seg.Addr+seg.Filesz-1 {
365 if seg.Name == "__PAGEZERO" {
366 continue
367 }
368 n := seg.Addr + seg.Filesz - addr
369 if n > size {
370 n = size
371 }
372 return saferio.ReadDataAt(seg, n, int64(addr-seg.Addr))
373 }
374 }
375 return nil, errUnrecognizedFormat
376 }
377
378 func (x *machoExe) DataStart() (uint64, uint64) {
379
380 for _, sec := range x.f.Sections {
381 if sec.Name == "__go_buildinfo" {
382 return sec.Addr, sec.Size
383 }
384 }
385
386 const RW = 3
387 for _, load := range x.f.Loads {
388 seg, ok := load.(*macho.Segment)
389 if ok && seg.Addr != 0 && seg.Filesz != 0 && seg.Prot == RW && seg.Maxprot == RW {
390 return seg.Addr, seg.Memsz
391 }
392 }
393 return 0, 0
394 }
395
396
397 type xcoffExe struct {
398 f *xcoff.File
399 }
400
401 func (x *xcoffExe) ReadData(addr, size uint64) ([]byte, error) {
402 for _, sect := range x.f.Sections {
403 if sect.VirtualAddress <= addr && addr <= sect.VirtualAddress+sect.Size-1 {
404 n := sect.VirtualAddress + sect.Size - addr
405 if n > size {
406 n = size
407 }
408 return saferio.ReadDataAt(sect, n, int64(addr-sect.VirtualAddress))
409 }
410 }
411 return nil, errors.New("address not mapped")
412 }
413
414 func (x *xcoffExe) DataStart() (uint64, uint64) {
415 if s := x.f.SectionByType(xcoff.STYP_DATA); s != nil {
416 return s.VirtualAddress, s.Size
417 }
418 return 0, 0
419 }
420
421
422 type plan9objExe struct {
423 f *plan9obj.File
424 }
425
426 func (x *plan9objExe) DataStart() (uint64, uint64) {
427 if s := x.f.Section("data"); s != nil {
428 return uint64(s.Offset), uint64(s.Size)
429 }
430 return 0, 0
431 }
432
433 func (x *plan9objExe) ReadData(addr, size uint64) ([]byte, error) {
434 for _, sect := range x.f.Sections {
435 if uint64(sect.Offset) <= addr && addr <= uint64(sect.Offset+sect.Size-1) {
436 n := uint64(sect.Offset+sect.Size) - addr
437 if n > size {
438 n = size
439 }
440 return saferio.ReadDataAt(sect, n, int64(addr-uint64(sect.Offset)))
441 }
442 }
443 return nil, errors.New("address not mapped")
444 }
445
View as plain text