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