Skip to content

Commit

Permalink
Merge branch 'main' into dinesh.gurumurthy/multiprocess
Browse files Browse the repository at this point in the history
  • Loading branch information
dineshg13 committed Oct 15, 2023
2 parents ed52f93 + 6ef659b commit 39cc03a
Show file tree
Hide file tree
Showing 6 changed files with 263 additions and 95 deletions.
106 changes: 13 additions & 93 deletions internal/pkg/process/analyze.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,24 +23,18 @@ import (
"github.com/hashicorp/go-version"

"go.opentelemetry.io/auto/internal/pkg/log"
"go.opentelemetry.io/auto/internal/pkg/process/binary"
)

// TargetDetails are the details about a target function.
type TargetDetails struct {
PID int
Functions []*Func
Functions []*binary.Func
GoVersion *version.Version
Libraries map[string]string
AllocationDetails *AllocationDetails
}

// Func represents a function target.
type Func struct {
Name string
Offset uint64
ReturnOffsets []uint64
}

// IsRegistersABI returns if t is supported.
func (t *TargetDetails) IsRegistersABI() bool {
regAbiMinVersion, _ := version.NewVersion("1.17")
Expand Down Expand Up @@ -94,103 +88,29 @@ func (a *Analyzer) Analyze(pid int, relevantFuncs map[string]interface{}) (*Targ
result.GoVersion = goVersion
result.Libraries = modules

funcs, err := findFunctions(elfF, relevantFuncs)
if err != nil {
return nil, err
}
symbols, err := elfF.Symbols()
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)
}
}
result.Functions = funcs
if len(result.Functions) == 0 {
return nil, errors.New("could not find function offsets for instrumenter")
}

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))
func findFunctions(elfF *elf.File, relevantFuncs map[string]interface{}) ([]*binary.Func, error) {
result, err := binary.FindFunctionsUnStripped(elfF, relevantFuncs)
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
if errors.Is(err, elf.ErrNoSymbols) {
log.Logger.V(0).Info("No symbols found in binary, trying to find functions using .gosymtab")
return binary.FindFunctionsStripped(elfF, relevantFuncs)
}
return nil, err
}

return newLocations, nil
return result, nil
}
31 changes: 31 additions & 0 deletions internal/pkg/process/binary/binary.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// 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 binary

import "go.opentelemetry.io/auto/internal/pkg/log"

// Func represents a function target.
type Func struct {
Name string
Offset uint64
ReturnOffsets []uint64
}

func logFoundFunction(name string, offset uint64, returns []uint64) {
log.Logger.V(0).Info("found relevant function for instrumentation",
"function", name,
"start", offset,
"returns", returns)
}
114 changes: 114 additions & 0 deletions internal/pkg/process/binary/funcs_nonstripped.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
// 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 binary

import (
"debug/elf"
"errors"
"fmt"
)

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
}

logFoundFunction(f.Name, offset, 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
}
103 changes: 103 additions & 0 deletions internal/pkg/process/binary/funcs_stripped.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
// 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 binary

import (
"debug/elf"
"debug/gosym"
"fmt"

"go.opentelemetry.io/auto/internal/pkg/log"
)

func FindFunctionsStripped(elfF *elf.File, relevantFuncs map[string]interface{}) ([]*Func, error) {
var sec *elf.Section
if sec = elfF.Section(".gopclntab"); sec == nil {
return nil, fmt.Errorf("%s section not found in target binary", ".gopclntab")
}
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()
if err != nil {
return nil, err
}
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
}

logFoundFunction(f.Name, start, 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) {
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")
}
Loading

0 comments on commit 39cc03a

Please sign in to comment.