github.com/m10x/go/src@v0.0.0-20220112094212-ba61592315da/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 "encoding/binary" 19 "errors" 20 "fmt" 21 "internal/xcoff" 22 "io" 23 "io/fs" 24 "os" 25 "runtime/debug" 26 ) 27 28 // Type alias for build info. We cannot move the types here, since 29 // runtime/debug would need to import this package, which would make it 30 // a much larger dependency. 31 type BuildInfo = debug.BuildInfo 32 33 var ( 34 // errUnrecognizedFormat is returned when a given executable file doesn't 35 // appear to be in a known format, or it breaks the rules of that format, 36 // or when there are I/O errors reading the file. 37 errUnrecognizedFormat = errors.New("unrecognized file format") 38 39 // errNotGoExe is returned when a given executable file is valid but does 40 // not contain Go build information. 41 errNotGoExe = errors.New("not a Go executable") 42 43 // The build info blob left by the linker is identified by 44 // a 16-byte header, consisting of buildInfoMagic (14 bytes), 45 // the binary's pointer size (1 byte), 46 // and whether the binary is big endian (1 byte). 47 buildInfoMagic = []byte("\xff Go buildinf:") 48 ) 49 50 // ReadFile returns build information embedded in a Go binary 51 // file at the given path. Most information is only available for binaries built 52 // with module support. 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 // Read returns build information embedded in a Go binary file 71 // accessed through the given ReaderAt. Most information is only available for 72 // binaries built with module support. 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 := &BuildInfo{} 79 if err := bi.UnmarshalText([]byte(mod)); err != nil { 80 return nil, err 81 } 82 bi.GoVersion = vers 83 return bi, nil 84 } 85 86 type exe interface { 87 // ReadData reads and returns up to size bytes starting at virtual address addr. 88 ReadData(addr, size uint64) ([]byte, error) 89 90 // DataStart returns the virtual address of the segment or section that 91 // should contain build information. This is either a specially named section 92 // or the first writable non-zero data segment. 93 DataStart() uint64 94 } 95 96 // readRawBuildInfo extracts the Go toolchain version and module information 97 // strings from a Go binary. On success, vers should be non-empty. mod 98 // is empty if the binary was not built with modules enabled. 99 func readRawBuildInfo(r io.ReaderAt) (vers, mod string, err error) { 100 // Read the first bytes of the file to identify the format, then delegate to 101 // a format-specific function to load segment and section headers. 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 // Read the first 64kB of dataAddr to find the build info blob. 138 // On some platforms, the blob will be in its own section, and DataStart 139 // returns the address of that section. On others, it's somewhere in the 140 // data segment; the linker puts it near the beginning. 141 // See cmd/link/internal/ld.Link.buildinfo. 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 // Decode the blob. 164 // The first 14 bytes are buildInfoMagic. 165 // The next two bytes indicate pointer size in bytes (4 or 8) and endianness 166 // (0 for little, 1 for big). 167 // Two virtual addresses to Go strings follow that: runtime.buildVersion, 168 // and runtime.modinfo. 169 // On 32-bit platforms, the last 8 bytes are unused. 170 // If the endianness has the 2 bit set, then the pointers are zero 171 // and the 32-byte header is followed by varint-prefixed string data 172 // for the two string values we care about. 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 // Strip module framing: sentinel strings delimiting the module info. 199 // These are cmd/go/internal/modload.infoStart and infoEnd. 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 // readString returns the string at address addr in the executable x. 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 // elfExe is the ELF implementation of the exe interface. 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 // peExe is the PE (Windows Portable Executable) implementation of the exe interface. 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 // Assume data is first writable section. 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 // machoExe is the Mach-O (Apple macOS/iOS) implementation of the exe interface. 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 // Look for section named "__go_buildinfo". 356 for _, sec := range x.f.Sections { 357 if sec.Name == "__go_buildinfo" { 358 return sec.Addr 359 } 360 } 361 // Try the first non-empty writable segment. 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 // xcoffExe is the XCOFF (AIX eXtended COFF) implementation of the exe interface. 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 }