github.com/noironetworks/cilium-net@v1.6.12/pkg/elf/symbols.go (about) 1 // Copyright 2019 Authors of Cilium 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package elf 16 17 import ( 18 "bufio" 19 "debug/elf" 20 "encoding/binary" 21 "fmt" 22 "io" 23 "sort" 24 "strings" 25 "unsafe" 26 ) 27 28 const ( 29 dataSection = ".data" 30 mapSection = "maps" 31 32 nullTerminator = byte(0) 33 relocSectionPrefix = ".rel" 34 35 // nMapRelocations is an approximation of the number of offsets in an 36 // ELF relating to map names that need to be adjusted. It's used for 37 // map initialization within 'Symbols'. 38 // 39 // $ readelf -a bpf/bpf_lxc.o | grep "cilium_.*_" | grep "^0000" | wc -l 40 // 51 41 nMapRelocations = 64 42 ) 43 44 type symbolKind uint32 45 46 var ( 47 symbolUint32 = symbolKind(1) 48 symbolString = symbolKind(2) 49 ) 50 51 func (k symbolKind) String() string { 52 switch k { 53 case symbolUint32: 54 return "uint32" 55 case symbolString: 56 return "string" 57 } 58 return "unknown" 59 } 60 61 // symbol stores the location and type of a symbol within the ELF file. 62 type symbol struct { 63 name string 64 kind symbolKind 65 offset uint64 66 size uint64 67 } 68 69 func newSymbol(name string, kind symbolKind, offset, size uint64) symbol { 70 return symbol{ 71 name: name, 72 kind: kind, 73 offset: offset, 74 size: size, 75 } 76 } 77 78 func newVariable(name string, offset uint64) symbol { 79 size := uint64(unsafe.Sizeof(symbolUint32)) 80 return newSymbol(name, symbolUint32, offset, size) 81 } 82 83 func newString(name string, offset uint64) symbol { 84 return newSymbol(name, symbolString, offset, uint64(len(name))) 85 } 86 87 type symbolSlice []symbol 88 89 // sort a slice of symbols by offset. 90 func (c symbolSlice) sort() symbolSlice { 91 sort.Slice(c, func(i, j int) bool { return c[i].offset < c[j].offset }) 92 return c 93 } 94 95 type symbols struct { 96 // data caches static 32-bit variables by name. 97 data map[string]symbol 98 // strings caches string symbols by name. 99 strings map[string]symbol 100 } 101 102 func (s *symbols) sort() symbolSlice { 103 result := make(symbolSlice, 0) 104 for _, c := range s.data { 105 result = append(result, c) 106 } 107 for _, c := range s.strings { 108 result = append(result, c) 109 } 110 return result.sort() 111 } 112 113 func isGlobalData(sym elf.Symbol) bool { 114 return (elf.ST_TYPE(sym.Info) == elf.STT_NOTYPE || 115 elf.ST_TYPE(sym.Info) == elf.STT_OBJECT) && 116 elf.ST_BIND(sym.Info) == elf.STB_GLOBAL && 117 elf.ST_VISIBILITY(sym.Other) == elf.STV_DEFAULT 118 } 119 120 func readStringOffset(e *elf.File, r io.ReadSeeker, symbolOffset int64) (uint64, error) { 121 if _, err := r.Seek(symbolOffset, io.SeekStart); err != nil { 122 return 0, err 123 } 124 125 switch e.Class { 126 case elf.ELFCLASS32: 127 var sym32 elf.Sym32 128 if err := binary.Read(r, e.ByteOrder, &sym32); err != nil { 129 return 0, err 130 } 131 return uint64(sym32.Name), nil 132 case elf.ELFCLASS64: 133 var sym64 elf.Sym64 134 if err := binary.Read(r, e.ByteOrder, &sym64); err != nil { 135 return 0, err 136 } 137 return uint64(sym64.Name), nil 138 } 139 return 0, fmt.Errorf("unsupported ELF type %d", e.Class) 140 } 141 142 // extractFrom processes the specified ELF and populates the received symbols 143 // object with data and string offsets in the file, for later substitution. 144 func (s *symbols) extractFrom(e *elf.File) error { 145 dataOffsets := make(map[string]symbol) 146 stringOffsets := make(map[string]symbol, nMapRelocations) 147 148 symbols, err := e.Symbols() 149 if err != nil { 150 return err 151 } 152 symtab := e.SectionByType(elf.SHT_SYMTAB) 153 strtab := e.Sections[symtab.Link] 154 155 // Scan symbol table for offsets of static data and symbol names. 156 symbolReader := symtab.Open() 157 for i, sym := range symbols { 158 // BTF extensions like line info not recognized by normal ELF parsers 159 if elf.ST_TYPE(sym.Info) == elf.STT_FILE { 160 continue 161 } 162 section := e.Sections[sym.Section] 163 switch { 164 case section.Flags&elf.SHF_COMPRESSED > 0: 165 return fmt.Errorf("compressed %s section not supported", section.Name) 166 case !isGlobalData(sym): 167 // LBB is a common llvm symbol prefix (basic block); 168 // Don't flood the logs with messages about it. 169 if !strings.HasPrefix(sym.Name, "LBB") { 170 log.Debugf("Skipping %s", sym.Name) 171 } 172 continue 173 case section.Name == dataSection: 174 // Offset from start of binary to variable inside .data 175 offset := section.Offset + sym.Value 176 dataOffsets[sym.Name] = newVariable(sym.Name, offset) 177 log.WithField(fieldSymbol, sym.Name).Debugf("Found variable with offset %d", offset) 178 case section.Name == mapSection: 179 // From the Golang Documentation: 180 // "For compatibility with Go 1.0, Symbols omits the 181 // the null symbol at index 0." 182 // We must reverse this when reading directly. 183 symbolOffset := int64(i+1) * int64(symtab.Entsize) 184 symOffsetInStrtab, err := readStringOffset(e, symbolReader, symbolOffset) 185 if err != nil { 186 return err 187 } 188 // Offset from start of binary to name inside .strtab 189 symOffset := strtab.Offset + symOffsetInStrtab 190 stringOffsets[sym.Name] = newString(sym.Name, symOffset) 191 log.WithField(fieldSymbol, sym.Name).Debugf("Found symbol with offset %d", symOffset) 192 default: 193 log.WithField(fieldSymbol, sym.Name).Debugf("Found symbol with unknown section reference %d", sym.Section) 194 } 195 } 196 197 // Scan string table for offsets of section names. 198 stringReader := bufio.NewReader(strtab.Open()) 199 var elfString string 200 for off := uint64(0); off < strtab.Size; off += uint64(len(elfString)) { 201 // off is the offset within the string table. 202 elfString, err = stringReader.ReadString(nullTerminator) 203 if err != nil && err != io.EOF { 204 return err 205 } 206 207 // We only need to worry about sections with relocations. 208 if !strings.HasPrefix(elfString, relocSectionPrefix) { 209 if err == io.EOF { 210 break 211 } 212 continue 213 } 214 215 elfEnd := len(elfString) 216 if elfString[elfEnd-1] == nullTerminator { 217 elfEnd-- 218 } 219 relocOffset := uint64(len(relocSectionPrefix)) 220 secName := elfString[relocOffset:elfEnd] 221 if sec := e.Section(secName); sec != nil { 222 // Offset from start of binary to name inside .strtab 223 globalOffset := strtab.Offset + off + relocOffset 224 stringOffsets[secName] = newString(secName, globalOffset) 225 log.WithField(fieldSymbol, secName).Debugf("Found section with offset %d", globalOffset) 226 } 227 228 if err == io.EOF { 229 break 230 } 231 } 232 233 s.data = dataOffsets 234 s.strings = stringOffsets 235 return nil 236 }