github.com/goproxy0/go@v0.0.0-20171111080102-49cc0c489d2c/src/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 "fmt" 10 "io" 11 "os" 12 "strconv" 13 ) 14 15 var ( 16 errBuildIDToolchain = fmt.Errorf("build ID only supported in gc toolchain") 17 errBuildIDMalformed = fmt.Errorf("malformed object file") 18 errBuildIDUnknown = fmt.Errorf("lost build ID") 19 ) 20 21 var ( 22 bangArch = []byte("!<arch>") 23 pkgdef = []byte("__.PKGDEF") 24 goobject = []byte("go object ") 25 buildid = []byte("build id ") 26 ) 27 28 // ReadFile reads the build ID from an archive or executable file. 29 // It only supports archives from the gc toolchain. 30 // TODO(rsc): Figure out what gccgo and llvm are going to do for archives. 31 func ReadFile(name string) (id string, err error) { 32 f, err := os.Open(name) 33 if err != nil { 34 return "", err 35 } 36 defer f.Close() 37 38 buf := make([]byte, 8) 39 if _, err := f.ReadAt(buf, 0); err != nil { 40 return "", err 41 } 42 if string(buf) != "!<arch>\n" { 43 return readBinary(name, f) 44 } 45 46 // Read just enough of the target to fetch the build ID. 47 // The archive is expected to look like: 48 // 49 // !<arch> 50 // __.PKGDEF 0 0 0 644 7955 ` 51 // go object darwin amd64 devel X:none 52 // build id "b41e5c45250e25c9fd5e9f9a1de7857ea0d41224" 53 // 54 // The variable-sized strings are GOOS, GOARCH, and the experiment list (X:none). 55 // Reading the first 1024 bytes should be plenty. 56 data := make([]byte, 1024) 57 n, err := io.ReadFull(f, data) 58 if err != nil && n == 0 { 59 return "", err 60 } 61 62 bad := func() (string, error) { 63 return "", &os.PathError{Op: "parse", Path: name, Err: errBuildIDMalformed} 64 } 65 66 // Archive header. 67 for i := 0; ; i++ { // returns during i==3 68 j := bytes.IndexByte(data, '\n') 69 if j < 0 { 70 return bad() 71 } 72 line := data[:j] 73 data = data[j+1:] 74 switch i { 75 case 0: 76 if !bytes.Equal(line, bangArch) { 77 return bad() 78 } 79 case 1: 80 if !bytes.HasPrefix(line, pkgdef) { 81 return bad() 82 } 83 case 2: 84 if !bytes.HasPrefix(line, goobject) { 85 return bad() 86 } 87 case 3: 88 if !bytes.HasPrefix(line, buildid) { 89 // Found the object header, just doesn't have a build id line. 90 // Treat as successful, with empty build id. 91 return "", nil 92 } 93 id, err := strconv.Unquote(string(line[len(buildid):])) 94 if err != nil { 95 return bad() 96 } 97 return id, nil 98 } 99 } 100 } 101 102 var ( 103 goBuildPrefix = []byte("\xff Go build ID: \"") 104 goBuildEnd = []byte("\"\n \xff") 105 106 elfPrefix = []byte("\x7fELF") 107 108 machoPrefixes = [][]byte{ 109 {0xfe, 0xed, 0xfa, 0xce}, 110 {0xfe, 0xed, 0xfa, 0xcf}, 111 {0xce, 0xfa, 0xed, 0xfe}, 112 {0xcf, 0xfa, 0xed, 0xfe}, 113 } 114 ) 115 116 var readSize = 32 * 1024 // changed for testing 117 118 // readBinary reads the build ID from a binary. 119 // 120 // ELF binaries store the build ID in a proper PT_NOTE section. 121 // 122 // Other binary formats are not so flexible. For those, the linker 123 // stores the build ID as non-instruction bytes at the very beginning 124 // of the text segment, which should appear near the beginning 125 // of the file. This is clumsy but fairly portable. Custom locations 126 // can be added for other binary types as needed, like we did for ELF. 127 func readBinary(name string, f *os.File) (id string, err error) { 128 // Read the first 32 kB of the binary file. 129 // That should be enough to find the build ID. 130 // In ELF files, the build ID is in the leading headers, 131 // which are typically less than 4 kB, not to mention 32 kB. 132 // In Mach-O files, there's no limit, so we have to parse the file. 133 // On other systems, we're trying to read enough that 134 // we get the beginning of the text segment in the read. 135 // The offset where the text segment begins in a hello 136 // world compiled for each different object format today: 137 // 138 // Plan 9: 0x20 139 // Windows: 0x600 140 // 141 data := make([]byte, readSize) 142 _, err = io.ReadFull(f, data) 143 if err == io.ErrUnexpectedEOF { 144 err = nil 145 } 146 if err != nil { 147 return "", err 148 } 149 150 if bytes.HasPrefix(data, elfPrefix) { 151 return readELF(name, f, data) 152 } 153 for _, m := range machoPrefixes { 154 if bytes.HasPrefix(data, m) { 155 return readMacho(name, f, data) 156 } 157 } 158 return readRaw(name, data) 159 } 160 161 // readRaw finds the raw build ID stored in text segment data. 162 func readRaw(name string, data []byte) (id string, err error) { 163 i := bytes.Index(data, goBuildPrefix) 164 if i < 0 { 165 // Missing. Treat as successful but build ID empty. 166 return "", nil 167 } 168 169 j := bytes.Index(data[i+len(goBuildPrefix):], goBuildEnd) 170 if j < 0 { 171 return "", &os.PathError{Op: "parse", Path: name, Err: errBuildIDMalformed} 172 } 173 174 quoted := data[i+len(goBuildPrefix)-1 : i+len(goBuildPrefix)+j+1] 175 id, err = strconv.Unquote(string(quoted)) 176 if err != nil { 177 return "", &os.PathError{Op: "parse", Path: name, Err: errBuildIDMalformed} 178 } 179 return id, nil 180 }