github.com/inspektor-gadget/inspektor-gadget@v0.28.1/pkg/kallsyms/kallsyms.go (about)

     1  // Copyright 2023 The Inspektor Gadget authors
     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 kallsyms provides functions to resolve kernel symbols.
    16  package kallsyms
    17  
    18  import (
    19  	"bufio"
    20  	"fmt"
    21  	"io"
    22  	"os"
    23  	"strconv"
    24  	"strings"
    25  	"sync"
    26  
    27  	"github.com/cilium/ebpf"
    28  )
    29  
    30  type KAllSyms struct {
    31  	// symbols is a slice of kernel symbols. Order is preserved.
    32  	symbols []kernelSymbol
    33  
    34  	// symbolsMap is a map of kernel symbols. Provides fast lookup.
    35  	symbolsMap map[string]uint64
    36  }
    37  
    38  type kernelSymbol struct {
    39  	addr uint64
    40  	name string
    41  }
    42  
    43  // NewKAllSyms reads /proc/kallsyms and returns a KAllSyms.
    44  func NewKAllSyms() (*KAllSyms, error) {
    45  	file, err := os.Open("/proc/kallsyms")
    46  	if err != nil {
    47  		return nil, err
    48  	}
    49  	defer file.Close()
    50  
    51  	return NewKAllSymsFromReader(file)
    52  }
    53  
    54  // NewKAllSymsFromReader reads a kallsyms file from the given reader and returns
    55  // a KAllSyms.
    56  func NewKAllSymsFromReader(reader io.Reader) (*KAllSyms, error) {
    57  	symbols := []kernelSymbol{}
    58  	symbolsMap := map[string]uint64{}
    59  
    60  	scanner := bufio.NewScanner(reader)
    61  	for scanner.Scan() {
    62  		line := scanner.Text()
    63  
    64  		fields := strings.Fields(line)
    65  		if len(fields) < 3 {
    66  			return nil, fmt.Errorf("line %q has less than 3 fields", line)
    67  		}
    68  
    69  		addr, err := strconv.ParseUint(fields[0], 16, 64)
    70  		if err != nil {
    71  			return nil, err
    72  		}
    73  
    74  		// The kernel function is the third field in /proc/kallsyms line:
    75  		// 0000000000000000 t acpi_video_unregister_backlight      [video]
    76  		// First is the symbol address and second is described in man nm.
    77  		symbols = append(symbols, kernelSymbol{
    78  			addr: addr,
    79  			name: fields[2],
    80  		})
    81  		symbolsMap[fields[2]] = addr
    82  	}
    83  
    84  	err := scanner.Err()
    85  	if err != nil {
    86  		return nil, err
    87  	}
    88  
    89  	return &KAllSyms{
    90  		symbols:    symbols,
    91  		symbolsMap: symbolsMap,
    92  	}, nil
    93  }
    94  
    95  // LookupByInstructionPointer tries to find the kernel symbol corresponding to
    96  // the given instruction pointer.
    97  // For example, if instruction pointer is 0x1004 and there is a symbol which
    98  // address is 0x1000, this function will return the name of this symbol.
    99  // If no symbol is found, it returns "[unknown]".
   100  func (k *KAllSyms) LookupByInstructionPointer(ip uint64) string {
   101  	// Go translation of iovisor/bcc ksyms__map_addr():
   102  	// https://github.com/iovisor/bcc/blob/c65446b765c9f7df7e357ee9343192de8419234a/libbpf-tools/trace_helpers.c#L149
   103  	end := len(k.symbols) - 1
   104  	var symAddr uint64
   105  	start := 0
   106  
   107  	// find largest symAddr <= ip using binary search
   108  	for start < end {
   109  		mid := start + (end-start+1)/2
   110  
   111  		symAddr = k.symbols[mid].addr
   112  
   113  		if symAddr <= ip {
   114  			start = mid
   115  		} else {
   116  			end = mid - 1
   117  		}
   118  	}
   119  
   120  	if start == end && k.symbols[start].addr <= ip {
   121  		return k.symbols[start].name
   122  	}
   123  
   124  	return "[unknown]"
   125  }
   126  
   127  // SymbolExists returns true if the given symbol exists in the kernel.
   128  func (k *KAllSyms) SymbolExists(symbol string) bool {
   129  	_, ok := k.symbolsMap[symbol]
   130  	return ok
   131  }
   132  
   133  var (
   134  	addrLock sync.Mutex
   135  
   136  	symbolsMap   = map[string]uint64{}
   137  	triedGetAddr = map[string]error{}
   138  )
   139  
   140  // SpecUpdateAddresses updates the addresses of the given symbols in the given
   141  // collection spec.
   142  //
   143  // The ebpf program is expected to be have global variables with the suffix
   144  // "_addr" for each symbol:
   145  //
   146  //	const volatile __u64 socket_file_ops_addr = 0;
   147  //
   148  // Then, SpecUpdateAddresses() can be called in this way:
   149  //
   150  //	kallsyms.SpecUpdateAddresses(spec, []string{"socket_file_ops"})
   151  func SpecUpdateAddresses(spec *ebpf.CollectionSpec, symbols []string) error {
   152  	if len(symbols) == 0 {
   153  		// Nothing to do
   154  		return nil
   155  	}
   156  
   157  	return specUpdateAddresses(
   158  		[]symbolResolver{
   159  			newKAllSymsResolver(),
   160  			newEbpfResolver(),
   161  		},
   162  		spec,
   163  		symbols,
   164  	)
   165  }
   166  
   167  func specUpdateAddresses(
   168  	symbolResolvers []symbolResolver,
   169  	spec *ebpf.CollectionSpec,
   170  	symbols []string,
   171  ) error {
   172  	addrLock.Lock()
   173  	defer addrLock.Unlock()
   174  
   175  	// Are all the requested symbols in the cache?
   176  	allFoundInCache := true
   177  	for _, symbol := range symbols {
   178  		if _, ok := symbolsMap[symbol]; !ok {
   179  			if err, errFound := triedGetAddr[symbol]; errFound {
   180  				// We previously tried to find this symbol in the cache and failed.
   181  				return err
   182  			}
   183  			allFoundInCache = false
   184  			break
   185  		}
   186  	}
   187  
   188  	// Add the symbols that are not in the cache to the cache
   189  	if !allFoundInCache {
   190  		for _, symbol := range symbols {
   191  			err := os.ErrNotExist
   192  			var addr uint64
   193  			for _, resolver := range symbolResolvers {
   194  				addr, err = resolver.resolve(symbol)
   195  				if err == nil {
   196  					symbolsMap[symbol] = addr
   197  					break
   198  				}
   199  			}
   200  			if err != nil {
   201  				triedGetAddr[symbol] = err
   202  				return err
   203  			}
   204  		}
   205  	}
   206  
   207  	// Rewrite the constants using symbol addresses from the cache
   208  	consts := map[string]interface{}{}
   209  	for _, symbol := range symbols {
   210  		consts[symbol+"_addr"] = symbolsMap[symbol]
   211  	}
   212  
   213  	if err := spec.RewriteConstants(consts); err != nil {
   214  		return fmt.Errorf("rewriting constants: %w", err)
   215  	}
   216  
   217  	return nil
   218  }