github.com/bir3/gocompiler@v0.9.2202/src/debug/buildinfo/buildinfo.go (about) 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 "github.com/bir3/gocompiler/src/internal/saferio" 23 "github.com/bir3/gocompiler/src/internal/xcoff" 24 "io" 25 "io/fs" 26 "os" 27 "runtime/debug" 28 ) 29 30 // Type alias for build info. We cannot move the types here, since 31 // runtime/debug would need to import this package, which would make it 32 // a much larger dependency. 33 type BuildInfo = debug.BuildInfo 34 35 var ( 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 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 errNotGoExe = errors.New("not a Go executable") 44 45 // The build info blob left by the linker is identified by 46 // a 16-byte header, consisting of buildInfoMagic (14 bytes), 47 // the binary's pointer size (1 byte), 48 // and whether the binary is big endian (1 byte). 49 buildInfoMagic = []byte("\xff Go buildinf:") 50 ) 51 52 // ReadFile returns build information embedded in a Go binary 53 // file at the given path. Most information is only available for binaries built 54 // with module support. 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 // Read returns build information embedded in a Go binary file 73 // accessed through the given ReaderAt. Most information is only available for 74 // binaries built with module support. 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 // ReadData reads and returns up to size bytes starting at virtual address addr. 90 ReadData(addr, size uint64) ([]byte, error) 91 92 // DataStart returns the virtual address and size of the segment or section that 93 // should contain build information. This is either a specially named section 94 // or the first writable non-zero data segment. 95 DataStart() (uint64, uint64) 96 } 97 98 // readRawBuildInfo extracts the Go toolchain version and module information 99 // strings from a Go binary. On success, vers should be non-empty. mod 100 // is empty if the binary was not built with modules enabled. 101 func readRawBuildInfo(r io.ReaderAt) (vers, mod string, err error) { 102 // Read the first bytes of the file to identify the format, then delegate to 103 // a format-specific function to load segment and section headers. 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 // Read segment or section to find the build info blob. 152 // On some platforms, the blob will be in its own section, and DataStart 153 // returns the address of that section. On others, it's somewhere in the 154 // data segment; the linker puts it near the beginning. 155 // See cmd/link/internal/ld.Link.buildinfo. 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 // Decode the blob. 181 // The first 14 bytes are buildInfoMagic. 182 // The next two bytes indicate pointer size in bytes (4 or 8) and endianness 183 // (0 for little, 1 for big). 184 // Two virtual addresses to Go strings follow that: runtime.buildVersion, 185 // and runtime.modinfo. 186 // On 32-bit platforms, the last 8 bytes are unused. 187 // If the endianness has the 2 bit set, then the pointers are zero 188 // and the 32-byte header is followed by varint-prefixed string data 189 // for the two string values we care about. 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 // Strip module framing: sentinel strings delimiting the module info. 218 // These are cmd/go/internal/modload.infoStart and infoEnd. 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 // readString returns the string at address addr in the executable x. 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 // elfExe is the ELF implementation of the exe interface. 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 // peExe is the PE (Windows Portable Executable) implementation of the exe interface. 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 // Assume data is first writable section. 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 // machoExe is the Mach-O (Apple macOS/iOS) implementation of the exe interface. 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 // Look for section named "__go_buildinfo". 371 for _, sec := range x.f.Sections { 372 if sec.Name == "__go_buildinfo" { 373 return sec.Addr, sec.Size 374 } 375 } 376 // Try the first non-empty writable segment. 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 // xcoffExe is the XCOFF (AIX eXtended COFF) implementation of the exe interface. 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 // plan9objExe is the Plan 9 a.out implementation of the exe interface. 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 }