| // Licensed to Apache Software Foundation (ASF) under one or more contributor |
| // license agreements. See the NOTICE file distributed with |
| // this work for additional information regarding copyright |
| // ownership. Apache Software Foundation (ASF) licenses this file to you under |
| // the Apache License, Version 2.0 (the "License"); you may |
| // not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, |
| // software distributed under the License is distributed on an |
| // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| // KIND, either express or implied. See the License for the |
| // specific language governing permissions and limitations |
| // under the License. |
| |
| package profiling |
| |
| import ( |
| "fmt" |
| "regexp" |
| |
| "github.com/apache/skywalking-rover/pkg/logger" |
| |
| "github.com/ianlancetaylor/demangle" |
| ) |
| |
| type ModuleType int8 |
| |
| var ( |
| KernelProcSymbolFilePath = "kallsyms" // after host.GetHostProcInHost, should be "/proc/kallsyms" |
| |
| log = logger.GetLogger("tools", "profiling") |
| ) |
| |
| const ( |
| ModuleTypeExec ModuleType = iota |
| ModuleTypeSo |
| ModuleTypePerfMap |
| ModuleTypeVDSO |
| ModuleTypeUnknown |
| ) |
| |
| // Info of profiling process |
| type Info struct { |
| Modules []*Module |
| cacheAddrToSymbol map[uint64]string |
| } |
| |
| type Module struct { |
| Ranges []*ModuleRange |
| Name string |
| Path string |
| Type ModuleType |
| SoOffset, SoAddr uint64 |
| Symbols []*Symbol |
| } |
| |
| type ModuleRange struct { |
| StartAddr, EndAddr, FileOffset uint64 |
| } |
| |
| // Symbol of executable file |
| type Symbol struct { |
| Name string |
| Location uint64 |
| Size uint64 |
| } |
| |
| type StatFinder interface { |
| // IsSupport to stat the executable file for profiling |
| IsSupport(filePath string) bool |
| // AnalyzeSymbols in the file |
| AnalyzeSymbols(filePath string) ([]*Symbol, error) |
| // ToModule to init a new module |
| ToModule(pid int32, modName, modPath string, moduleRange []*ModuleRange) (*Module, error) |
| } |
| |
| func NewInfo(modules map[string]*Module) *Info { |
| ls := make([]*Module, 0) |
| for _, m := range modules { |
| ls = append(ls, m) |
| } |
| return &Info{Modules: ls, cacheAddrToSymbol: make(map[uint64]string)} |
| } |
| |
| // FindSymbols from address list, if could not found symbol name then append default symbol to array |
| func (i *Info) FindSymbols(addresses []uint64, defaultSymbol string) []string { |
| if len(addresses) == 0 { |
| return nil |
| } |
| result := make([]string, 0) |
| for _, addr := range addresses { |
| if addr <= 0 { |
| continue |
| } |
| s := i.FindSymbolName(addr) |
| if s == "" { |
| s = defaultSymbol |
| } |
| result = append(result, s) |
| } |
| return result |
| } |
| |
| // FindSymbolName by address |
| func (i *Info) FindSymbolName(address uint64) string { |
| if d := i.cacheAddrToSymbol[address]; d != "" { |
| return d |
| } |
| log.Debugf("ready to find the symbol from address: %d", address) |
| foundModule := false |
| for _, mod := range i.Modules { |
| offset, c := mod.contains(address) |
| if !c { |
| continue |
| } |
| foundModule = true |
| |
| if sym := mod.findAddr(offset); sym != nil { |
| name := processSymbolName(sym.Name) |
| i.cacheAddrToSymbol[address] = name |
| return name |
| } |
| } |
| if !foundModule { |
| log.Debugf("could not found any module to handle address: %d", address) |
| } |
| return "" |
| } |
| |
| func (i *Info) FindSymbolAddress(name string) uint64 { |
| for _, m := range i.Modules { |
| for _, sym := range m.Symbols { |
| if sym.Name == name { |
| return sym.Location |
| } |
| } |
| } |
| return 0 |
| } |
| |
| func (i *Info) FindSymbolByRegex(rep string) (string, error) { |
| vals, err := i.FindMultipleSymbolByRegex(rep) |
| if err != nil { |
| return "", err |
| } |
| return vals[0], nil |
| } |
| |
| func (i *Info) FindMultipleSymbolByRegex(rep string) ([]string, error) { |
| compile, err := regexp.Compile(rep) |
| if err != nil { |
| return nil, err |
| } |
| result := make([]string, 0) |
| for _, m := range i.Modules { |
| for _, sym := range m.Symbols { |
| if compile.MatchString(sym.Name) { |
| result = append(result, sym.Name) |
| } |
| } |
| } |
| |
| if len(result) == 0 { |
| return nil, fmt.Errorf("cannot found any matches symbol: %s", rep) |
| } |
| return result, nil |
| } |
| |
| func (m *Module) contains(addr uint64) (uint64, bool) { |
| for _, r := range m.Ranges { |
| if addr >= r.StartAddr && addr < r.EndAddr { |
| log.Debugf("found module %s could hanlde address: %d", m.Name, addr) |
| if m.Type == ModuleTypeSo || m.Type == ModuleTypeVDSO { |
| offset := addr - r.StartAddr + r.FileOffset |
| offset += m.SoAddr - m.SoOffset |
| log.Debugf("update address %d to offset %d", addr, offset) |
| return offset, true |
| } |
| return addr, true |
| } |
| } |
| return 0, false |
| } |
| |
| func (m *Module) findAddr(offset uint64) *Symbol { |
| start := 0 |
| end := len(m.Symbols) - 1 |
| for start < end { |
| mid := start + (end-start)/2 |
| result := int64(offset) - int64(m.Symbols[mid].Location) |
| |
| if result < 0 { |
| end = mid |
| } else if result > 0 { |
| start = mid + 1 |
| } else { |
| return m.Symbols[mid] |
| } |
| } |
| |
| if start >= 1 && m.Symbols[start-1].Location < offset && offset < m.Symbols[start].Location { |
| return m.Symbols[start-1] |
| } |
| log.Debugf("could not found the address: %d in module %s", offset, m.Name) |
| |
| return nil |
| } |
| |
| func processSymbolName(name string) string { |
| if name == "" { |
| return "" |
| } |
| // fix process demangle symbol name, such as c++ language symbol |
| skip := 0 |
| if name[0] == '.' || name[0] == '$' { |
| skip++ |
| } |
| return demangle.Filter(name[skip:]) |
| } |