pkg/exporter/bpfutil/kallsyms.go (175 lines of code) (raw):

package bpfutil import ( "bufio" "errors" "fmt" "os" "sort" "strconv" "strings" "github.com/cilium/ebpf" "github.com/hashicorp/golang-lru/v2/expirable" ) type KernelSymbol struct { start uint64 symboltype string symbol string module string offset int } func (k *KernelSymbol) GetAddr() uint64 { return k.start } func (k *KernelSymbol) GetName() string { return k.symbol } func (k *KernelSymbol) GetExpr() string { return fmt.Sprintf("%s+0x%X", k.symbol, k.offset) } var kallsyms []KernelSymbol func init() { kallsyms = []KernelSymbol{} if err := getAllSyms(); err != nil { return } } func GetSymByPt(addr string) (*KernelSymbol, error) { var pt uint64 if strings.HasPrefix(addr, "0x") { addr = addr[2:] res, err := strconv.ParseUint(addr, 16, 64) if err != nil { return nil, err } pt = res } else { res, err := strconv.ParseUint(addr, 10, 64) if err != nil { return nil, err } pt = res } if pt > kallsyms[len(kallsyms)-1].start { return nil, errors.New("addr out of range") } for idx := range kallsyms { if kallsyms[idx].start <= pt && kallsyms[idx+1].start > pt { ks := KernelSymbol{ start: kallsyms[idx].start, symboltype: kallsyms[idx].symbol, symbol: kallsyms[idx].symbol, offset: int(pt - kallsyms[idx].start), module: kallsyms[idx].module, } return &ks, nil } } return nil, errors.New("addr not found") } var locationCache = expirable.NewLRU[uint64, KernelSymbol](100, nil, 0) // GetSymPtFromBpfLocation return symbol struct/offset/error with bpf location func GetSymPtFromBpfLocation(pt uint64) (*KernelSymbol, error) { sym, ok := locationCache.Get(pt) if ok { return &sym, nil } // since getAllSyms is already sorted, we can do a binary search here var i, j, mid int var midVal uint64 j = len(kallsyms) - 1 found := -1 for i < j { mid = i + (j-i)/2 midVal = kallsyms[mid].start if pt < midVal { j = mid } else if pt > midVal { i = mid + 1 } else { found = mid break } } if found == -1 { if i >= 1 && pt > kallsyms[i-1].start && pt < kallsyms[i].start { found = i - 1 } if pt >= kallsyms[i].start { found = i } } if found == -1 { return nil, errors.New("addr not found") } ks := KernelSymbol{ start: kallsyms[found].start, symboltype: kallsyms[found].symbol, symbol: kallsyms[found].symbol, offset: int(pt - kallsyms[found].start), module: kallsyms[found].module, } locationCache.Add(pt, ks) return &ks, nil } func getAllSyms() error { f, err := os.Open("/proc/kallsyms") if err != nil { return err } r := bufio.NewScanner(f) for r.Scan() { rawsym := strings.Fields(r.Text()) pt, err := strconv.ParseUint(rawsym[0], 16, 64) if err != nil { fmt.Println(err) continue } ks := KernelSymbol{ start: pt, symboltype: rawsym[1], symbol: rawsym[2], } if len(rawsym) == 4 { ks.module = rawsym[3] } else { ks.module = "kernel" } kallsyms = append(kallsyms, ks) } sort.Slice(kallsyms, func(i, j int) bool { return kallsyms[i].GetAddr() < kallsyms[j].GetAddr() }) return nil } func GetSymsStrByStack(stack uint32, stackmap *ebpf.Map) ([]string, error) { addrs := [10]uint64{} if err := stackmap.Lookup(stack, &addrs); err != nil { return nil, err } callers := []string{} for _, addr := range addrs { sym, err := GetSymPtFromBpfLocation(addr) if err != nil { continue } callerStr := fmt.Sprintf("%s+0x%X", sym.GetName(), sym.offset) callers = append(callers, callerStr) } return callers, nil } func GetSymsByStack(stack uint32, stackmap *ebpf.Map) ([]KernelSymbol, error) { addrs := [10]uint64{} if err := stackmap.Lookup(stack, &addrs); err != nil { return nil, err } callers := []KernelSymbol{} for _, addr := range addrs { sym, err := GetSymPtFromBpfLocation(addr) if err != nil { continue } if sym == nil { continue } callers = append(callers, *sym) } return callers, nil }