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