github.com/bir3/gocompiler@v0.9.2202/src/cmd/internal/buildid/note.go (about) 1 // Copyright 2015 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 "debug/macho" 11 "encoding/binary" 12 "fmt" 13 "io" 14 "io/fs" 15 "os" 16 ) 17 18 func readAligned4(r io.Reader, sz int32) ([]byte, error) { 19 full := (sz + 3) &^ 3 20 data := make([]byte, full) 21 _, err := io.ReadFull(r, data) 22 if err != nil { 23 return nil, err 24 } 25 data = data[:sz] 26 return data, nil 27 } 28 29 func ReadELFNote(filename, name string, typ int32) ([]byte, error) { 30 f, err := elf.Open(filename) 31 if err != nil { 32 return nil, err 33 } 34 defer f.Close() 35 for _, sect := range f.Sections { 36 if sect.Type != elf.SHT_NOTE { 37 continue 38 } 39 r := sect.Open() 40 for { 41 var namesize, descsize, noteType int32 42 err = binary.Read(r, f.ByteOrder, &namesize) 43 if err != nil { 44 if err == io.EOF { 45 break 46 } 47 return nil, fmt.Errorf("read namesize failed: %v", err) 48 } 49 err = binary.Read(r, f.ByteOrder, &descsize) 50 if err != nil { 51 return nil, fmt.Errorf("read descsize failed: %v", err) 52 } 53 err = binary.Read(r, f.ByteOrder, ¬eType) 54 if err != nil { 55 return nil, fmt.Errorf("read type failed: %v", err) 56 } 57 noteName, err := readAligned4(r, namesize) 58 if err != nil { 59 return nil, fmt.Errorf("read name failed: %v", err) 60 } 61 desc, err := readAligned4(r, descsize) 62 if err != nil { 63 return nil, fmt.Errorf("read desc failed: %v", err) 64 } 65 if name == string(noteName) && typ == noteType { 66 return desc, nil 67 } 68 } 69 } 70 return nil, nil 71 } 72 73 var elfGoNote = []byte("Go\x00\x00") 74 var elfGNUNote = []byte("GNU\x00") 75 76 // The Go build ID is stored in a note described by an ELF PT_NOTE prog 77 // header. The caller has already opened filename, to get f, and read 78 // at least 4 kB out, in data. 79 func readELF(name string, f *os.File, data []byte) (buildid string, err error) { 80 // Assume the note content is in the data, already read. 81 // Rewrite the ELF header to set shoff and shnum to 0, so that we can pass 82 // the data to elf.NewFile and it will decode the Prog list but not 83 // try to read the section headers and the string table from disk. 84 // That's a waste of I/O when all we care about is the Prog list 85 // and the one ELF note. 86 switch elf.Class(data[elf.EI_CLASS]) { 87 case elf.ELFCLASS32: 88 data[32], data[33], data[34], data[35] = 0, 0, 0, 0 89 data[48] = 0 90 data[49] = 0 91 case elf.ELFCLASS64: 92 data[40], data[41], data[42], data[43] = 0, 0, 0, 0 93 data[44], data[45], data[46], data[47] = 0, 0, 0, 0 94 data[60] = 0 95 data[61] = 0 96 } 97 98 const elfGoBuildIDTag = 4 99 const gnuBuildIDTag = 3 100 101 ef, err := elf.NewFile(bytes.NewReader(data)) 102 if err != nil { 103 return "", &fs.PathError{Path: name, Op: "parse", Err: err} 104 } 105 var gnu string 106 for _, p := range ef.Progs { 107 if p.Type != elf.PT_NOTE || p.Filesz < 16 { 108 continue 109 } 110 111 var note []byte 112 if p.Off+p.Filesz < uint64(len(data)) { 113 note = data[p.Off : p.Off+p.Filesz] 114 } else { 115 // For some linkers, such as the Solaris linker, 116 // the buildid may not be found in data (which 117 // likely contains the first 16kB of the file) 118 // or even the first few megabytes of the file 119 // due to differences in note segment placement; 120 // in that case, extract the note data manually. 121 _, err = f.Seek(int64(p.Off), io.SeekStart) 122 if err != nil { 123 return "", err 124 } 125 126 note = make([]byte, p.Filesz) 127 _, err = io.ReadFull(f, note) 128 if err != nil { 129 return "", err 130 } 131 } 132 133 filesz := p.Filesz 134 off := p.Off 135 for filesz >= 16 { 136 nameSize := ef.ByteOrder.Uint32(note) 137 valSize := ef.ByteOrder.Uint32(note[4:]) 138 tag := ef.ByteOrder.Uint32(note[8:]) 139 nname := note[12:16] 140 if nameSize == 4 && 16+valSize <= uint32(len(note)) && tag == elfGoBuildIDTag && bytes.Equal(nname, elfGoNote) { 141 return string(note[16 : 16+valSize]), nil 142 } 143 144 if nameSize == 4 && 16+valSize <= uint32(len(note)) && tag == gnuBuildIDTag && bytes.Equal(nname, elfGNUNote) { 145 gnu = string(note[16 : 16+valSize]) 146 } 147 148 nameSize = (nameSize + 3) &^ 3 149 valSize = (valSize + 3) &^ 3 150 notesz := uint64(12 + nameSize + valSize) 151 if filesz <= notesz { 152 break 153 } 154 off += notesz 155 align := p.Align 156 if align != 0 { 157 alignedOff := (off + align - 1) &^ (align - 1) 158 notesz += alignedOff - off 159 off = alignedOff 160 } 161 filesz -= notesz 162 note = note[notesz:] 163 } 164 } 165 166 // If we didn't find a Go note, use a GNU note if available. 167 // This is what gccgo uses. 168 if gnu != "" { 169 return gnu, nil 170 } 171 172 // No note. Treat as successful but build ID empty. 173 return "", nil 174 } 175 176 // The Go build ID is stored at the beginning of the Mach-O __text segment. 177 // The caller has already opened filename, to get f, and read a few kB out, in data. 178 // Sadly, that's not guaranteed to hold the note, because there is an arbitrary amount 179 // of other junk placed in the file ahead of the main text. 180 func readMacho(name string, f *os.File, data []byte) (buildid string, err error) { 181 // If the data we want has already been read, don't worry about Mach-O parsing. 182 // This is both an optimization and a hedge against the Mach-O parsing failing 183 // in the future due to, for example, the name of the __text section changing. 184 if b, err := readRaw(name, data); b != "" && err == nil { 185 return b, err 186 } 187 188 mf, err := macho.NewFile(f) 189 if err != nil { 190 return "", &fs.PathError{Path: name, Op: "parse", Err: err} 191 } 192 193 sect := mf.Section("__text") 194 if sect == nil { 195 // Every binary has a __text section. Something is wrong. 196 return "", &fs.PathError{Path: name, Op: "parse", Err: fmt.Errorf("cannot find __text section")} 197 } 198 199 // It should be in the first few bytes, but read a lot just in case, 200 // especially given our past problems on OS X with the build ID moving. 201 // There shouldn't be much difference between reading 4kB and 32kB: 202 // the hard part is getting to the data, not transferring it. 203 n := sect.Size 204 if n > uint64(readSize) { 205 n = uint64(readSize) 206 } 207 buf := make([]byte, n) 208 if _, err := f.ReadAt(buf, int64(sect.Offset)); err != nil { 209 return "", err 210 } 211 212 return readRaw(name, buf) 213 }