diff --git a/pkg/process/analyze.go b/pkg/process/analyze.go index 51509da08..283bb9ed9 100644 --- a/pkg/process/analyze.go +++ b/pkg/process/analyze.go @@ -136,100 +136,26 @@ func (a *processAnalyzer) Analyze(pid int, relevantFuncs map[string]interface{}) if err != nil { return nil, err } - symbols, err := elfF.Symbols() + + funcs, err := findFunctions(elfF, relevantFuncs) if err != nil { + log.Logger.Error(err, "Failed to find functions") return nil, err } - for _, f := range symbols { - if _, exists := relevantFuncs[f.Name]; exists { - offset, err := getFuncOffset(elfF, f) - if err != nil { - return nil, err - } - - returns, err := findFuncReturns(elfF, f, offset) - if err != nil { - log.Logger.V(1).Info("can't find function offset. Skipping", "function", f.Name) - continue - } - - log.Logger.V(0).Info("found relevant function for instrumentation", - "function", f.Name, - "start", offset, - "returns", returns) - function := &Func{ - Name: f.Name, - Offset: offset, - ReturnOffsets: returns, - } - - result.Functions = append(result.Functions, function) - } - } - if len(result.Functions) == 0 { - return nil, errors.New("could not find function offsets for instrumenter") - } - + result.Functions = funcs return result, nil } -func getFuncOffset(f *elf.File, symbol elf.Symbol) (uint64, error) { - var sections []*elf.Section - - for i := range f.Sections { - if f.Sections[i].Flags == elf.SHF_ALLOC+elf.SHF_EXECINSTR { - sections = append(sections, f.Sections[i]) - } - } - - if len(sections) == 0 { - return 0, fmt.Errorf("function %q not found in file", symbol) - } - - var execSection *elf.Section - for m := range sections { - sectionStart := sections[m].Addr - sectionEnd := sectionStart + sections[m].Size - if symbol.Value >= sectionStart && symbol.Value < sectionEnd { - execSection = sections[m] - break - } - } - - if execSection == nil { - return 0, errors.New("could not find symbol in executable sections of binary") - } - - return uint64(symbol.Value - execSection.Addr + execSection.Offset), nil -} - -func findFuncReturns(elfFile *elf.File, sym elf.Symbol, functionOffset uint64) ([]uint64, error) { - textSection := elfFile.Section(".text") - if textSection == nil { - return nil, errors.New("could not find .text section in binary") - } - - lowPC := sym.Value - highPC := lowPC + sym.Size - offset := lowPC - textSection.Addr - buf := make([]byte, int(highPC-lowPC)) - - readBytes, err := textSection.ReadAt(buf, int64(offset)) - if err != nil { - return nil, fmt.Errorf("could not read text section: %w", err) - } - data := buf[:readBytes] - instructionIndices, err := findRetInstructions(data) +func findFunctions(elfF *elf.File, relevantFuncs map[string]interface{}) ([]*Func, error) { + result, err := FindFunctionsUnStripped(elfF, relevantFuncs) if err != nil { - return nil, fmt.Errorf("error while scanning instructions: %w", err) - } - - // Add the function lowPC to each index to obtain the actual locations - newLocations := make([]uint64, len(instructionIndices)) - for i, instructionIndex := range instructionIndices { - newLocations[i] = instructionIndex + functionOffset + if errors.Is(err, elf.ErrNoSymbols) { + log.Logger.V(0).Info("No symbols found in binary, trying to find functions using .gosymtab") + return FindFunctionsStripped(elfF, relevantFuncs) + } + return nil, err } - return newLocations, nil -} + return result, nil +} \ No newline at end of file diff --git a/pkg/process/funcs_nonstripped.go b/pkg/process/funcs_nonstripped.go new file mode 100644 index 000000000..d28bdd38b --- /dev/null +++ b/pkg/process/funcs_nonstripped.go @@ -0,0 +1,116 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed 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 process + +import ( + "debug/elf" + "errors" + "fmt" + + "go.opentelemetry.io/auto/pkg/log" +) + +func FindFunctionsUnStripped(elfF *elf.File, relevantFuncs map[string]interface{}) ([]*Func, error) { + symbols, err := elfF.Symbols() + if err != nil { + return nil, err + } + + var result []*Func + for _, f := range symbols { + if _, exists := relevantFuncs[f.Name]; exists { + offset, err := getFuncOffsetUnstripped(elfF, f) + if err != nil { + return nil, err + } + + returns, err := findFuncReturnsUnstripped(elfF, f, offset) + if err != nil { + return nil, err + } + + log.Logger.V(0).Info("found relevant function for instrumentation", "function", f.Name, "returns", len(returns)) + function := &Func{ + Name: f.Name, + Offset: offset, + ReturnOffsets: returns, + } + + result = append(result, function) + } + } + + return result, nil +} + +func getFuncOffsetUnstripped(f *elf.File, symbol elf.Symbol) (uint64, error) { + var sections []*elf.Section + + for i := range f.Sections { + if f.Sections[i].Flags == elf.SHF_ALLOC+elf.SHF_EXECINSTR { + sections = append(sections, f.Sections[i]) + } + } + + if len(sections) == 0 { + return 0, fmt.Errorf("function %q not found in file", symbol) + } + + var execSection *elf.Section + for m := range sections { + sectionStart := sections[m].Addr + sectionEnd := sectionStart + sections[m].Size + if symbol.Value >= sectionStart && symbol.Value < sectionEnd { + execSection = sections[m] + break + } + } + + if execSection == nil { + return 0, errors.New("could not find symbol in executable sections of binary") + } + + return uint64(symbol.Value - execSection.Addr + execSection.Offset), nil +} + +func findFuncReturnsUnstripped(elfFile *elf.File, sym elf.Symbol, functionOffset uint64) ([]uint64, error) { + textSection := elfFile.Section(".text") + if textSection == nil { + return nil, errors.New("could not find .text section in binary") + } + + lowPC := sym.Value + highPC := lowPC + sym.Size + offset := lowPC - textSection.Addr + buf := make([]byte, int(highPC-lowPC)) + + readBytes, err := textSection.ReadAt(buf, int64(offset)) + if err != nil { + return nil, fmt.Errorf("could not read text section: %w", err) + } + data := buf[:readBytes] + instructionIndices, err := findRetInstructions(data) + if err != nil { + return nil, fmt.Errorf("error while scanning instructions: %w", err) + } + + // Add the function lowPC to each index to obtain the actual locations + newLocations := make([]uint64, len(instructionIndices)) + for i, instructionIndex := range instructionIndices { + newLocations[i] = instructionIndex + functionOffset + } + + return newLocations, nil +} \ No newline at end of file diff --git a/pkg/process/funcs_stripped.go b/pkg/process/funcs_stripped.go new file mode 100644 index 000000000..dfaf65018 --- /dev/null +++ b/pkg/process/funcs_stripped.go @@ -0,0 +1,104 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed 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 process + +import ( + "debug/elf" + "debug/gosym" + "fmt" + + "go.opentelemetry.io/auto/pkg/log" +) + +func FindFunctionsStripped(elfF *elf.File, relevantFuncs map[string]interface{}) ([]*Func, error) { + var pclndat []byte + if sec := elfF.Section(".gopclntab"); sec != nil { + var err error + pclndat, err = sec.Data() + if err != nil { + return nil, err + } + } + + sec := elfF.Section(".gosymtab") + if sec == nil { + return nil, fmt.Errorf("%s section not found in target binary, make sure this is a Go application", ".gosymtab") + } + symTabRaw, err := sec.Data() + pcln := gosym.NewLineTable(pclndat, elfF.Section(".text").Addr) + symTab, err := gosym.NewTable(symTabRaw, pcln) + if err != nil { + return nil, err + } + + var result []*Func + for _, f := range symTab.Funcs { + if _, exists := relevantFuncs[f.Name]; exists { + start, returns, err := findFuncOffsetStripped(&f, elfF) + if err != nil { + return nil, err + } + + log.Logger.V(0).Info("found relevant function for instrumentation", "function", f.Name, "returns", len(returns)) + function := &Func{ + Name: f.Name, + Offset: start, + ReturnOffsets: returns, + } + + result = append(result, function) + } + } + + return result, nil +} + +func findFuncOffsetStripped(f *gosym.Func, elfF *elf.File) (uint64, []uint64, error) { + off := f.Value + for _, prog := range elfF.Progs { + if prog.Type != elf.PT_LOAD || (prog.Flags&elf.PF_X) == 0 { + continue + } + + // For more info on this calculation: stackoverflow.com/a/40249502 + if prog.Vaddr <= f.Value && f.Value < (prog.Vaddr+prog.Memsz) { + off = f.Value - prog.Vaddr + prog.Off + + funcLen := f.End - f.Entry + data := make([]byte, funcLen) + _, err := prog.ReadAt(data, int64(f.Value-prog.Vaddr)) + if err != nil { + log.Logger.Error(err, "error while finding function return") + return 0, nil, err + } + + instructionIndices, err := findRetInstructions(data) + if err != nil { + log.Logger.Error(err, "error while finding function returns") + return 0, nil, err + } + + newLocations := make([]uint64, len(instructionIndices)) + for i, instructionIndex := range instructionIndices { + newLocations[i] = instructionIndex + off + } + + return off, newLocations, nil + } + + } + + return 0, nil, fmt.Errorf("prog not found") +} \ No newline at end of file