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