github.com/kubearmor/cilium@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  }