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