github.com/zhyoulun/cilium@v1.6.12/pkg/elf/elf.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 "debug/elf" 19 "encoding/binary" 20 "fmt" 21 "io" 22 "os" 23 "strings" 24 "unsafe" 25 26 "github.com/cilium/cilium/pkg/lock" 27 28 "github.com/sirupsen/logrus" 29 ) 30 31 var ( 32 ignoredPrefixes []string 33 ) 34 35 // ELF is an in-memory representation of a BPF ELF object from the filesystem. 36 type ELF struct { 37 metadata *elf.File 38 file *os.File 39 symbols symbols 40 log *logrus.Entry 41 42 // lock concurrent writes of the ELF. This library probably isn't far 43 // off from allowing concurrent Write() execution, but it's just not 44 // supported for now. 45 lock.Mutex 46 } 47 48 // newReader creates a new reader that can safely seek the input in parallel. 49 func newReader(ra io.ReaderAt) *io.SectionReader { 50 // If 1<<63-1 is good enough for pkg/debug/elf, it's good enough for us. 51 return io.NewSectionReader(ra, 0, 1<<63-1) 52 } 53 54 // NewELF returns a new object from the specified reader. 55 // 56 // The ELF binary is expected to start at position 0 in the specified reader. 57 func NewELF(ra io.ReaderAt, scopedLog *logrus.Entry) (*ELF, error) { 58 ef, err := elf.NewFile(ra) 59 if err != nil { 60 return nil, fmt.Errorf("unable to open ELF: %s", err) 61 } 62 63 // EM_NONE is generated by older Clang (eg 3.8.x), which we currently 64 // use in Travis. We should be able to drop that part pretty soon. 65 if ef.Machine != elf.EM_NONE && ef.Machine != elf.EM_BPF { 66 return nil, fmt.Errorf("unsupported ELF machine type %s", ef.Machine) 67 } 68 69 result := &ELF{ 70 metadata: ef, 71 log: scopedLog, 72 } 73 if err := result.symbols.extractFrom(ef); err != nil { 74 return nil, fmt.Errorf("unable to read ELF symbols: %s", err) 75 } 76 77 return result, nil 78 } 79 80 // Open an ELF file from the specified path. 81 func Open(path string) (*ELF, error) { 82 scopedLog := log.WithField(srcPath, path) 83 84 f, err := os.Open(path) 85 if err != nil { 86 return nil, &os.PathError{ 87 Op: "failed to open ELF file", 88 Path: path, 89 Err: err, 90 } 91 } 92 93 result, err := NewELF(f, scopedLog) 94 if err != nil { 95 if err2 := f.Close(); err2 != nil { 96 scopedLog.WithError(err).Warning("Failed to close ELF") 97 } 98 return nil, &os.PathError{ 99 Op: "failed to parse ELF file", 100 Path: path, 101 Err: err, 102 } 103 } 104 result.file = f 105 return result, nil 106 } 107 108 // Close closes the ELF. If the File was created using NewELF directly instead 109 // of Open, Close has no effect. 110 func (elf *ELF) Close() (err error) { 111 if elf.file != nil { 112 err = elf.file.Close() 113 } 114 return err 115 } 116 117 func (elf *ELF) readValue(offset int64, size int64) ([]byte, error) { 118 reader := io.NewSectionReader(elf.file, offset, size) 119 result := make([]byte, size) 120 if err := binary.Read(reader, elf.metadata.ByteOrder, &result); err != nil { 121 return nil, err 122 } 123 return result, nil 124 } 125 126 func (elf *ELF) readOption(key string) (result uint32, err error) { 127 opt, exists := elf.symbols.data[key] 128 if !exists { 129 return 0, fmt.Errorf("no such option %q in ELF", key) 130 } 131 value, err := elf.readValue(int64(opt.offset), int64(opt.size)) 132 if err != nil { 133 return 0, err 134 } 135 return elf.metadata.ByteOrder.Uint32(value), err 136 } 137 138 func (elf *ELF) findString(key string) error { 139 opt, exists := elf.symbols.strings[key] 140 if !exists { 141 return fmt.Errorf("no such string %q in ELF", key) 142 } 143 if _, err := elf.readValue(int64(opt.offset), int64(opt.size)); err != nil { 144 return err 145 } 146 return nil 147 } 148 149 // copy the ELF from the reader to the writer, substituting the constants with 150 // names specified in 'intOptions' with their corresponding values, and the 151 // strings specified in 'strOptions' with their corresponding values. 152 // 153 // Keys in the 'intOptions' / 'strOptions' maps are case-sensitive. 154 func (elf *ELF) copy(w io.Writer, r *io.SectionReader, intOptions map[string]uint32, strOptions map[string]string) error { 155 if len(intOptions) == 0 && len(strOptions) == 0 { 156 // Copy the remaining portion of the file 157 if _, err := io.Copy(w, r); err != nil { 158 return err 159 } 160 return nil 161 } 162 163 globalOff := uint64(0) // current position in file 164 processedOptions := make(map[string]struct{}, len(intOptions)+len(strOptions)) 165 processSymbols: 166 for _, symbol := range elf.symbols.sort() { 167 scopedLog := log.WithField("symbol", symbol.name) 168 169 // Figure out the value to substitute 170 var value []byte 171 switch symbol.kind { 172 case symbolUint32: 173 v, exists := intOptions[symbol.name] 174 if exists { 175 value = make([]byte, unsafe.Sizeof(v)) 176 elf.metadata.ByteOrder.PutUint32(value, v) 177 } 178 case symbolString: 179 v, exists := strOptions[symbol.name] 180 if exists { 181 if uint64(len(v)) != symbol.size { 182 return fmt.Errorf("symbol substitution value %q (len %d) must equal length of symbol name %q (len %d)", v, len(v), symbol.name, symbol.size) 183 } 184 value = []byte(v) 185 } 186 } 187 if value == nil { 188 for _, prefix := range ignoredPrefixes { 189 if strings.HasPrefix(symbol.name, prefix) { 190 continue processSymbols 191 } 192 } 193 scopedLog.Warning("Skipping symbol substitution") 194 continue processSymbols 195 } 196 197 // Copy data up until this symbol into the new file; 198 // Write the new value and seek past it. 199 dataToCopy := int64(symbol.offset - globalOff) 200 if _, err := io.CopyN(w, r, dataToCopy); err != nil { 201 return err 202 } 203 if err := binary.Write(w, elf.metadata.ByteOrder, value); err != nil { 204 return fmt.Errorf("failed to substitute %s: %s", symbol.name, err) 205 } 206 if _, err := r.Seek(int64(symbol.size), io.SeekCurrent); err != nil { 207 return err 208 } 209 processedOptions[symbol.name] = struct{}{} 210 globalOff = symbol.offset + symbol.size 211 } 212 213 // Check for additional options that weren't applied 214 for symbol := range strOptions { 215 if _, processed := processedOptions[symbol]; !processed { 216 return fmt.Errorf("no such string %q in ELF", symbol) 217 } 218 } 219 for symbol := range intOptions { 220 if _, processed := processedOptions[symbol]; !processed { 221 return fmt.Errorf("no such symbol %q in ELF", symbol) 222 } 223 } 224 225 // Copy the remaining portion of the file 226 if _, err := io.Copy(w, r); err != nil { 227 return err 228 } 229 230 return nil 231 } 232 233 // Write the received ELF to a new file at the specified location, with the 234 // specified options (indexed by name) substituted: 235 // - intOptions: 32-bit values substituted in the data section. 236 // - strOptions: strings susbtituted in the string table. For each key/value 237 // pair, both key and value must be same length. 238 // 239 // Only one goroutine may Write() the same *ELF concurrently. 240 // 241 // On success, writes the new file to the specified path. 242 // On failure, returns an error and no file is left on the filesystem. 243 func (elf *ELF) Write(path string, intOptions map[string]uint32, strOptions map[string]string) error { 244 elf.Lock() 245 defer elf.Unlock() 246 247 scopedLog := elf.log.WithField(dstPath, path) 248 249 f, err := os.Create(path) 250 if err != nil { 251 return &os.PathError{ 252 Op: "failed to create ELF file", 253 Path: path, 254 Err: err, 255 } 256 } 257 defer func() { 258 if err2 := f.Close(); err2 != nil { 259 scopedLog.WithError(err).Warning("Failed to close new ELF") 260 } 261 if err != nil { 262 if err2 := os.RemoveAll(path); err2 != nil { 263 scopedLog.WithError(err).Warning("Failed to clean up new ELF path on error") 264 } 265 } 266 }() 267 268 reader := newReader(elf.file) 269 if err = elf.copy(f, reader, intOptions, strOptions); err != nil { 270 return &os.PathError{ 271 Op: "failed to write ELF file:", 272 Path: path, 273 Err: err, 274 } 275 } 276 if err = f.Sync(); err != nil { 277 return &os.PathError{ 278 Op: "failed to sync ELF file:", 279 Path: path, 280 Err: err, 281 } 282 } 283 284 scopedLog.WithError(err).Debugf("Finished writing ELF") 285 return nil 286 } 287 288 // IgnoreSymbolPrefixes configures the ELF package to ignore symbols that have 289 // any of the specified prefixes. It must be called by only one thread at a time. 290 // 291 // This slice will be iterated once per ELF.Write(), so try not to let it grow 292 // out of hand... 293 func IgnoreSymbolPrefixes(prefixes []string) { 294 ignoredPrefixes = prefixes 295 }