github.com/lovishpuri/go-40569/src@v0.0.0-20230519171745-f8623e7c56cf/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 "internal/saferio" 23 "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 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 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 the first 64kB of dataAddr 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 := x.DataStart() 157 data, err := x.ReadData(dataAddr, 64*1024) 158 if err != nil { 159 return "", "", err 160 } 161 const ( 162 buildInfoAlign = 16 163 buildInfoSize = 32 164 ) 165 for { 166 i := bytes.Index(data, buildInfoMagic) 167 if i < 0 || len(data)-i < buildInfoSize { 168 return "", "", errNotGoExe 169 } 170 if i%buildInfoAlign == 0 && len(data)-i >= buildInfoSize { 171 data = data[i:] 172 break 173 } 174 data = data[(i+buildInfoAlign-1)&^(buildInfoAlign-1):] 175 } 176 177 // Decode the blob. 178 // The first 14 bytes are buildInfoMagic. 179 // The next two bytes indicate pointer size in bytes (4 or 8) and endianness 180 // (0 for little, 1 for big). 181 // Two virtual addresses to Go strings follow that: runtime.buildVersion, 182 // and runtime.modinfo. 183 // On 32-bit platforms, the last 8 bytes are unused. 184 // If the endianness has the 2 bit set, then the pointers are zero 185 // and the 32-byte header is followed by varint-prefixed string data 186 // for the two string values we care about. 187 ptrSize := int(data[14]) 188 if data[15]&2 != 0 { 189 vers, data = decodeString(data[32:]) 190 mod, data = decodeString(data) 191 } else { 192 bigEndian := data[15] != 0 193 var bo binary.ByteOrder 194 if bigEndian { 195 bo = binary.BigEndian 196 } else { 197 bo = binary.LittleEndian 198 } 199 var readPtr func([]byte) uint64 200 if ptrSize == 4 { 201 readPtr = func(b []byte) uint64 { return uint64(bo.Uint32(b)) } 202 } else if ptrSize == 8 { 203 readPtr = bo.Uint64 204 } else { 205 return "", "", errNotGoExe 206 } 207 vers = readString(x, ptrSize, readPtr, readPtr(data[16:])) 208 mod = readString(x, ptrSize, readPtr, readPtr(data[16+ptrSize:])) 209 } 210 if vers == "" { 211 return "", "", errNotGoExe 212 } 213 if len(mod) >= 33 && mod[len(mod)-17] == '\n' { 214 // Strip module framing: sentinel strings delimiting the module info. 215 // These are cmd/go/internal/modload.infoStart and infoEnd. 216 mod = mod[16 : len(mod)-16] 217 } else { 218 mod = "" 219 } 220 221 return vers, mod, nil 222 } 223 224 func hasPlan9Magic(magic []byte) bool { 225 if len(magic) >= 4 { 226 m := binary.BigEndian.Uint32(magic) 227 switch m { 228 case plan9obj.Magic386, plan9obj.MagicAMD64, plan9obj.MagicARM: 229 return true 230 } 231 } 232 return false 233 } 234 235 func decodeString(data []byte) (s string, rest []byte) { 236 u, n := binary.Uvarint(data) 237 if n <= 0 || u >= uint64(len(data)-n) { 238 return "", nil 239 } 240 return string(data[n : uint64(n)+u]), data[uint64(n)+u:] 241 } 242 243 // readString returns the string at address addr in the executable x. 244 func readString(x exe, ptrSize int, readPtr func([]byte) uint64, addr uint64) string { 245 hdr, err := x.ReadData(addr, uint64(2*ptrSize)) 246 if err != nil || len(hdr) < 2*ptrSize { 247 return "" 248 } 249 dataAddr := readPtr(hdr) 250 dataLen := readPtr(hdr[ptrSize:]) 251 data, err := x.ReadData(dataAddr, dataLen) 252 if err != nil || uint64(len(data)) < dataLen { 253 return "" 254 } 255 return string(data) 256 } 257 258 // elfExe is the ELF implementation of the exe interface. 259 type elfExe struct { 260 f *elf.File 261 } 262 263 func (x *elfExe) ReadData(addr, size uint64) ([]byte, error) { 264 for _, prog := range x.f.Progs { 265 if prog.Vaddr <= addr && addr <= prog.Vaddr+prog.Filesz-1 { 266 n := prog.Vaddr + prog.Filesz - addr 267 if n > size { 268 n = size 269 } 270 return saferio.ReadDataAt(prog, n, int64(addr-prog.Vaddr)) 271 } 272 } 273 return nil, errUnrecognizedFormat 274 } 275 276 func (x *elfExe) DataStart() uint64 { 277 for _, s := range x.f.Sections { 278 if s.Name == ".go.buildinfo" { 279 return s.Addr 280 } 281 } 282 for _, p := range x.f.Progs { 283 if p.Type == elf.PT_LOAD && p.Flags&(elf.PF_X|elf.PF_W) == elf.PF_W { 284 return p.Vaddr 285 } 286 } 287 return 0 288 } 289 290 // peExe is the PE (Windows Portable Executable) implementation of the exe interface. 291 type peExe struct { 292 f *pe.File 293 } 294 295 func (x *peExe) imageBase() uint64 { 296 switch oh := x.f.OptionalHeader.(type) { 297 case *pe.OptionalHeader32: 298 return uint64(oh.ImageBase) 299 case *pe.OptionalHeader64: 300 return oh.ImageBase 301 } 302 return 0 303 } 304 305 func (x *peExe) ReadData(addr, size uint64) ([]byte, error) { 306 addr -= x.imageBase() 307 for _, sect := range x.f.Sections { 308 if uint64(sect.VirtualAddress) <= addr && addr <= uint64(sect.VirtualAddress+sect.Size-1) { 309 n := uint64(sect.VirtualAddress+sect.Size) - addr 310 if n > size { 311 n = size 312 } 313 return saferio.ReadDataAt(sect, n, int64(addr-uint64(sect.VirtualAddress))) 314 } 315 } 316 return nil, errUnrecognizedFormat 317 } 318 319 func (x *peExe) DataStart() uint64 { 320 // Assume data is first writable section. 321 const ( 322 IMAGE_SCN_CNT_CODE = 0x00000020 323 IMAGE_SCN_CNT_INITIALIZED_DATA = 0x00000040 324 IMAGE_SCN_CNT_UNINITIALIZED_DATA = 0x00000080 325 IMAGE_SCN_MEM_EXECUTE = 0x20000000 326 IMAGE_SCN_MEM_READ = 0x40000000 327 IMAGE_SCN_MEM_WRITE = 0x80000000 328 IMAGE_SCN_MEM_DISCARDABLE = 0x2000000 329 IMAGE_SCN_LNK_NRELOC_OVFL = 0x1000000 330 IMAGE_SCN_ALIGN_32BYTES = 0x600000 331 ) 332 for _, sect := range x.f.Sections { 333 if sect.VirtualAddress != 0 && sect.Size != 0 && 334 sect.Characteristics&^IMAGE_SCN_ALIGN_32BYTES == IMAGE_SCN_CNT_INITIALIZED_DATA|IMAGE_SCN_MEM_READ|IMAGE_SCN_MEM_WRITE { 335 return uint64(sect.VirtualAddress) + x.imageBase() 336 } 337 } 338 return 0 339 } 340 341 // machoExe is the Mach-O (Apple macOS/iOS) implementation of the exe interface. 342 type machoExe struct { 343 f *macho.File 344 } 345 346 func (x *machoExe) ReadData(addr, size uint64) ([]byte, error) { 347 for _, load := range x.f.Loads { 348 seg, ok := load.(*macho.Segment) 349 if !ok { 350 continue 351 } 352 if seg.Addr <= addr && addr <= seg.Addr+seg.Filesz-1 { 353 if seg.Name == "__PAGEZERO" { 354 continue 355 } 356 n := seg.Addr + seg.Filesz - addr 357 if n > size { 358 n = size 359 } 360 return saferio.ReadDataAt(seg, n, int64(addr-seg.Addr)) 361 } 362 } 363 return nil, errUnrecognizedFormat 364 } 365 366 func (x *machoExe) DataStart() uint64 { 367 // Look for section named "__go_buildinfo". 368 for _, sec := range x.f.Sections { 369 if sec.Name == "__go_buildinfo" { 370 return sec.Addr 371 } 372 } 373 // Try the first non-empty writable segment. 374 const RW = 3 375 for _, load := range x.f.Loads { 376 seg, ok := load.(*macho.Segment) 377 if ok && seg.Addr != 0 && seg.Filesz != 0 && seg.Prot == RW && seg.Maxprot == RW { 378 return seg.Addr 379 } 380 } 381 return 0 382 } 383 384 // xcoffExe is the XCOFF (AIX eXtended COFF) implementation of the exe interface. 385 type xcoffExe struct { 386 f *xcoff.File 387 } 388 389 func (x *xcoffExe) ReadData(addr, size uint64) ([]byte, error) { 390 for _, sect := range x.f.Sections { 391 if sect.VirtualAddress <= addr && addr <= sect.VirtualAddress+sect.Size-1 { 392 n := sect.VirtualAddress + sect.Size - addr 393 if n > size { 394 n = size 395 } 396 return saferio.ReadDataAt(sect, n, int64(addr-sect.VirtualAddress)) 397 } 398 } 399 return nil, errors.New("address not mapped") 400 } 401 402 func (x *xcoffExe) DataStart() uint64 { 403 if s := x.f.SectionByType(xcoff.STYP_DATA); s != nil { 404 return s.VirtualAddress 405 } 406 return 0 407 } 408 409 // plan9objExe is the Plan 9 a.out implementation of the exe interface. 410 type plan9objExe struct { 411 f *plan9obj.File 412 } 413 414 func (x *plan9objExe) DataStart() uint64 { 415 if s := x.f.Section("data"); s != nil { 416 return uint64(s.Offset) 417 } 418 return 0 419 } 420 421 func (x *plan9objExe) ReadData(addr, size uint64) ([]byte, error) { 422 for _, sect := range x.f.Sections { 423 if uint64(sect.Offset) <= addr && addr <= uint64(sect.Offset+sect.Size-1) { 424 n := uint64(sect.Offset+sect.Size) - addr 425 if n > size { 426 n = size 427 } 428 return saferio.ReadDataAt(sect, n, int64(addr-uint64(sect.Offset))) 429 } 430 } 431 return nil, errors.New("address not mapped") 432 }