github.com/gagliardetto/golang-go@v0.0.0-20201020153340-53909ea70814/cmd/internal/buildid/buildid.go (about) 1 // Copyright 2017 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 buildid 6 7 import ( 8 "bytes" 9 "debug/elf" 10 "fmt" 11 "github.com/gagliardetto/golang-go/not-internal/xcoff" 12 "io" 13 "os" 14 "strconv" 15 "strings" 16 ) 17 18 var ( 19 errBuildIDToolchain = fmt.Errorf("build ID only supported in gc toolchain") 20 errBuildIDMalformed = fmt.Errorf("malformed object file") 21 errBuildIDUnknown = fmt.Errorf("lost build ID") 22 ) 23 24 var ( 25 bangArch = []byte("!<arch>") 26 pkgdef = []byte("__.PKGDEF") 27 goobject = []byte("go object ") 28 buildid = []byte("build id ") 29 ) 30 31 // ReadFile reads the build ID from an archive or executable file. 32 func ReadFile(name string) (id string, err error) { 33 f, err := os.Open(name) 34 if err != nil { 35 return "", err 36 } 37 defer f.Close() 38 39 buf := make([]byte, 8) 40 if _, err := f.ReadAt(buf, 0); err != nil { 41 return "", err 42 } 43 if string(buf) != "!<arch>\n" { 44 if string(buf) == "<bigaf>\n" { 45 return readGccgoBigArchive(name, f) 46 } 47 return readBinary(name, f) 48 } 49 50 // Read just enough of the target to fetch the build ID. 51 // The archive is expected to look like: 52 // 53 // !<arch> 54 // __.PKGDEF 0 0 0 644 7955 ` 55 // go object darwin amd64 devel X:none 56 // build id "b41e5c45250e25c9fd5e9f9a1de7857ea0d41224" 57 // 58 // The variable-sized strings are GOOS, GOARCH, and the experiment list (X:none). 59 // Reading the first 1024 bytes should be plenty. 60 data := make([]byte, 1024) 61 n, err := io.ReadFull(f, data) 62 if err != nil && n == 0 { 63 return "", err 64 } 65 66 tryGccgo := func() (string, error) { 67 return readGccgoArchive(name, f) 68 } 69 70 // Archive header. 71 for i := 0; ; i++ { // returns during i==3 72 j := bytes.IndexByte(data, '\n') 73 if j < 0 { 74 return tryGccgo() 75 } 76 line := data[:j] 77 data = data[j+1:] 78 switch i { 79 case 0: 80 if !bytes.Equal(line, bangArch) { 81 return tryGccgo() 82 } 83 case 1: 84 if !bytes.HasPrefix(line, pkgdef) { 85 return tryGccgo() 86 } 87 case 2: 88 if !bytes.HasPrefix(line, goobject) { 89 return tryGccgo() 90 } 91 case 3: 92 if !bytes.HasPrefix(line, buildid) { 93 // Found the object header, just doesn't have a build id line. 94 // Treat as successful, with empty build id. 95 return "", nil 96 } 97 id, err := strconv.Unquote(string(line[len(buildid):])) 98 if err != nil { 99 return tryGccgo() 100 } 101 return id, nil 102 } 103 } 104 } 105 106 // readGccgoArchive tries to parse the archive as a standard Unix 107 // archive file, and fetch the build ID from the _buildid.o entry. 108 // The _buildid.o entry is written by (*Builder).gccgoBuildIDELFFile 109 // in cmd/go/internal/work/exec.go. 110 func readGccgoArchive(name string, f *os.File) (string, error) { 111 bad := func() (string, error) { 112 return "", &os.PathError{Op: "parse", Path: name, Err: errBuildIDMalformed} 113 } 114 115 off := int64(8) 116 for { 117 if _, err := f.Seek(off, io.SeekStart); err != nil { 118 return "", err 119 } 120 121 // TODO(iant): Make a debug/ar package, and use it 122 // here and in cmd/link. 123 var hdr [60]byte 124 if _, err := io.ReadFull(f, hdr[:]); err != nil { 125 if err == io.EOF { 126 // No more entries, no build ID. 127 return "", nil 128 } 129 return "", err 130 } 131 off += 60 132 133 sizeStr := strings.TrimSpace(string(hdr[48:58])) 134 size, err := strconv.ParseInt(sizeStr, 0, 64) 135 if err != nil { 136 return bad() 137 } 138 139 name := strings.TrimSpace(string(hdr[:16])) 140 if name == "_buildid.o/" { 141 sr := io.NewSectionReader(f, off, size) 142 e, err := elf.NewFile(sr) 143 if err != nil { 144 return bad() 145 } 146 s := e.Section(".go.buildid") 147 if s == nil { 148 return bad() 149 } 150 data, err := s.Data() 151 if err != nil { 152 return bad() 153 } 154 return string(data), nil 155 } 156 157 off += size 158 if off&1 != 0 { 159 off++ 160 } 161 } 162 } 163 164 // readGccgoBigArchive tries to parse the archive as an AIX big 165 // archive file, and fetch the build ID from the _buildid.o entry. 166 // The _buildid.o entry is written by (*Builder).gccgoBuildIDXCOFFFile 167 // in cmd/go/internal/work/exec.go. 168 func readGccgoBigArchive(name string, f *os.File) (string, error) { 169 bad := func() (string, error) { 170 return "", &os.PathError{Op: "parse", Path: name, Err: errBuildIDMalformed} 171 } 172 173 // Read fixed-length header. 174 if _, err := f.Seek(0, io.SeekStart); err != nil { 175 return "", err 176 } 177 var flhdr [128]byte 178 if _, err := io.ReadFull(f, flhdr[:]); err != nil { 179 return "", err 180 } 181 // Read first member offset. 182 offStr := strings.TrimSpace(string(flhdr[68:88])) 183 off, err := strconv.ParseInt(offStr, 10, 64) 184 if err != nil { 185 return bad() 186 } 187 for { 188 if off == 0 { 189 // No more entries, no build ID. 190 return "", nil 191 } 192 if _, err := f.Seek(off, io.SeekStart); err != nil { 193 return "", err 194 } 195 // Read member header. 196 var hdr [112]byte 197 if _, err := io.ReadFull(f, hdr[:]); err != nil { 198 return "", err 199 } 200 // Read member name length. 201 namLenStr := strings.TrimSpace(string(hdr[108:112])) 202 namLen, err := strconv.ParseInt(namLenStr, 10, 32) 203 if err != nil { 204 return bad() 205 } 206 if namLen == 10 { 207 var nam [10]byte 208 if _, err := io.ReadFull(f, nam[:]); err != nil { 209 return "", err 210 } 211 if string(nam[:]) == "_buildid.o" { 212 sizeStr := strings.TrimSpace(string(hdr[0:20])) 213 size, err := strconv.ParseInt(sizeStr, 10, 64) 214 if err != nil { 215 return bad() 216 } 217 off += int64(len(hdr)) + namLen + 2 218 if off&1 != 0 { 219 off++ 220 } 221 sr := io.NewSectionReader(f, off, size) 222 x, err := xcoff.NewFile(sr) 223 if err != nil { 224 return bad() 225 } 226 data := x.CSect(".go.buildid") 227 if data == nil { 228 return bad() 229 } 230 return string(data), nil 231 } 232 } 233 234 // Read next member offset. 235 offStr = strings.TrimSpace(string(hdr[20:40])) 236 off, err = strconv.ParseInt(offStr, 10, 64) 237 if err != nil { 238 return bad() 239 } 240 } 241 } 242 243 var ( 244 goBuildPrefix = []byte("\xff Go build ID: \"") 245 goBuildEnd = []byte("\"\n \xff") 246 247 elfPrefix = []byte("\x7fELF") 248 249 machoPrefixes = [][]byte{ 250 {0xfe, 0xed, 0xfa, 0xce}, 251 {0xfe, 0xed, 0xfa, 0xcf}, 252 {0xce, 0xfa, 0xed, 0xfe}, 253 {0xcf, 0xfa, 0xed, 0xfe}, 254 } 255 ) 256 257 var readSize = 32 * 1024 // changed for testing 258 259 // readBinary reads the build ID from a binary. 260 // 261 // ELF binaries store the build ID in a proper PT_NOTE section. 262 // 263 // Other binary formats are not so flexible. For those, the linker 264 // stores the build ID as non-instruction bytes at the very beginning 265 // of the text segment, which should appear near the beginning 266 // of the file. This is clumsy but fairly portable. Custom locations 267 // can be added for other binary types as needed, like we did for ELF. 268 func readBinary(name string, f *os.File) (id string, err error) { 269 // Read the first 32 kB of the binary file. 270 // That should be enough to find the build ID. 271 // In ELF files, the build ID is in the leading headers, 272 // which are typically less than 4 kB, not to mention 32 kB. 273 // In Mach-O files, there's no limit, so we have to parse the file. 274 // On other systems, we're trying to read enough that 275 // we get the beginning of the text segment in the read. 276 // The offset where the text segment begins in a hello 277 // world compiled for each different object format today: 278 // 279 // Plan 9: 0x20 280 // Windows: 0x600 281 // 282 data := make([]byte, readSize) 283 _, err = io.ReadFull(f, data) 284 if err == io.ErrUnexpectedEOF { 285 err = nil 286 } 287 if err != nil { 288 return "", err 289 } 290 291 if bytes.HasPrefix(data, elfPrefix) { 292 return readELF(name, f, data) 293 } 294 for _, m := range machoPrefixes { 295 if bytes.HasPrefix(data, m) { 296 return readMacho(name, f, data) 297 } 298 } 299 return readRaw(name, data) 300 } 301 302 // readRaw finds the raw build ID stored in text segment data. 303 func readRaw(name string, data []byte) (id string, err error) { 304 i := bytes.Index(data, goBuildPrefix) 305 if i < 0 { 306 // Missing. Treat as successful but build ID empty. 307 return "", nil 308 } 309 310 j := bytes.Index(data[i+len(goBuildPrefix):], goBuildEnd) 311 if j < 0 { 312 return "", &os.PathError{Op: "parse", Path: name, Err: errBuildIDMalformed} 313 } 314 315 quoted := data[i+len(goBuildPrefix)-1 : i+len(goBuildPrefix)+j+1] 316 id, err = strconv.Unquote(string(quoted)) 317 if err != nil { 318 return "", &os.PathError{Op: "parse", Path: name, Err: errBuildIDMalformed} 319 } 320 return id, nil 321 }