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