github.com/amarpal/go-tools@v0.0.0-20240422043104-40142f59f616/go/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 "errors" 11 "fmt" 12 "io" 13 "os" 14 "strconv" 15 "strings" 16 ) 17 18 var errBuildIDMalformed = fmt.Errorf("malformed object file") 19 20 var ( 21 bangArch = []byte("!<arch>") 22 pkgdef = []byte("__.PKGDEF") 23 goobject = []byte("go object ") 24 buildid = []byte("build id ") 25 ) 26 27 // ReadFile reads the build ID from an archive or executable file. 28 func ReadFile(name string) (id string, err error) { 29 f, err := os.Open(name) 30 if err != nil { 31 return "", err 32 } 33 defer f.Close() 34 35 buf := make([]byte, 8) 36 if _, err := f.ReadAt(buf, 0); err != nil { 37 return "", err 38 } 39 if string(buf) != "!<arch>\n" { 40 if string(buf) == "<bigaf>\n" { 41 return "", errors.New("unsupported") 42 } 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 tryGccgo := func() (string, error) { 63 return readGccgoArchive(name, f) 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 tryGccgo() 71 } 72 line := data[:j] 73 data = data[j+1:] 74 switch i { 75 case 0: 76 if !bytes.Equal(line, bangArch) { 77 return tryGccgo() 78 } 79 case 1: 80 if !bytes.HasPrefix(line, pkgdef) { 81 return tryGccgo() 82 } 83 case 2: 84 if !bytes.HasPrefix(line, goobject) { 85 return tryGccgo() 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 tryGccgo() 96 } 97 return id, nil 98 } 99 } 100 } 101 102 // readGccgoArchive tries to parse the archive as a standard Unix 103 // archive file, and fetch the build ID from the _buildid.o entry. 104 // The _buildid.o entry is written by (*Builder).gccgoBuildIDELFFile 105 // in cmd/go/internal/work/exec.go. 106 func readGccgoArchive(name string, f *os.File) (string, error) { 107 bad := func() (string, error) { 108 return "", &os.PathError{Op: "parse", Path: name, Err: errBuildIDMalformed} 109 } 110 111 off := int64(8) 112 for { 113 if _, err := f.Seek(off, io.SeekStart); err != nil { 114 return "", err 115 } 116 117 // TODO(iant): Make a debug/ar package, and use it 118 // here and in cmd/link. 119 var hdr [60]byte 120 if _, err := io.ReadFull(f, hdr[:]); err != nil { 121 if err == io.EOF { 122 // No more entries, no build ID. 123 return "", nil 124 } 125 return "", err 126 } 127 off += 60 128 129 sizeStr := strings.TrimSpace(string(hdr[48:58])) 130 size, err := strconv.ParseInt(sizeStr, 0, 64) 131 if err != nil { 132 return bad() 133 } 134 135 name := strings.TrimSpace(string(hdr[:16])) 136 if name == "_buildid.o/" { 137 sr := io.NewSectionReader(f, off, size) 138 e, err := elf.NewFile(sr) 139 if err != nil { 140 return bad() 141 } 142 s := e.Section(".go.buildid") 143 if s == nil { 144 return bad() 145 } 146 data, err := s.Data() 147 if err != nil { 148 return bad() 149 } 150 return string(data), nil 151 } 152 153 off += size 154 if off&1 != 0 { 155 off++ 156 } 157 } 158 } 159 160 var ( 161 goBuildPrefix = []byte("\xff Go build ID: \"") 162 goBuildEnd = []byte("\"\n \xff") 163 164 elfPrefix = []byte("\x7fELF") 165 166 machoPrefixes = [][]byte{ 167 {0xfe, 0xed, 0xfa, 0xce}, 168 {0xfe, 0xed, 0xfa, 0xcf}, 169 {0xce, 0xfa, 0xed, 0xfe}, 170 {0xcf, 0xfa, 0xed, 0xfe}, 171 } 172 ) 173 174 var readSize = 32 * 1024 // changed for testing 175 176 // readBinary reads the build ID from a binary. 177 // 178 // ELF binaries store the build ID in a proper PT_NOTE section. 179 // 180 // Other binary formats are not so flexible. For those, the linker 181 // stores the build ID as non-instruction bytes at the very beginning 182 // of the text segment, which should appear near the beginning 183 // of the file. This is clumsy but fairly portable. Custom locations 184 // can be added for other binary types as needed, like we did for ELF. 185 func readBinary(name string, f *os.File) (id string, err error) { 186 // Read the first 32 kB of the binary file. 187 // That should be enough to find the build ID. 188 // In ELF files, the build ID is in the leading headers, 189 // which are typically less than 4 kB, not to mention 32 kB. 190 // In Mach-O files, there's no limit, so we have to parse the file. 191 // On other systems, we're trying to read enough that 192 // we get the beginning of the text segment in the read. 193 // The offset where the text segment begins in a hello 194 // world compiled for each different object format today: 195 // 196 // Plan 9: 0x20 197 // Windows: 0x600 198 // 199 data := make([]byte, readSize) 200 _, err = io.ReadFull(f, data) 201 if err == io.ErrUnexpectedEOF { 202 err = nil 203 } 204 if err != nil { 205 return "", err 206 } 207 208 if bytes.HasPrefix(data, elfPrefix) { 209 return readELF(name, f, data) 210 } 211 for _, m := range machoPrefixes { 212 if bytes.HasPrefix(data, m) { 213 return readMacho(name, f, data) 214 } 215 } 216 return readRaw(name, data) 217 } 218 219 // readRaw finds the raw build ID stored in text segment data. 220 func readRaw(name string, data []byte) (id string, err error) { 221 i := bytes.Index(data, goBuildPrefix) 222 if i < 0 { 223 // Missing. Treat as successful but build ID empty. 224 return "", nil 225 } 226 227 j := bytes.Index(data[i+len(goBuildPrefix):], goBuildEnd) 228 if j < 0 { 229 return "", &os.PathError{Op: "parse", Path: name, Err: errBuildIDMalformed} 230 } 231 232 quoted := data[i+len(goBuildPrefix)-1 : i+len(goBuildPrefix)+j+1] 233 id, err = strconv.Unquote(string(quoted)) 234 if err != nil { 235 return "", &os.PathError{Op: "parse", Path: name, Err: errBuildIDMalformed} 236 } 237 return id, nil 238 }