Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions cmd/utrace/run/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,15 +41,15 @@ func init() {
false,
`when set, utrace will generate a .dot graph with the collected statistics`)
Utrace.Flags().VarP(
NewUTraceOptionsSanitizer(&options.UTraceOptions, "binary"),
"binary",
"b",
`list of paths to the binaries you want to trace`)
NewUTraceOptionsSanitizer(&options.UTraceOptions, "executable"),
"executable",
"e",
`list of paths to the executables you want to trace`)
Utrace.Flags().VarP(
NewUTraceOptionsSanitizer(&options.UTraceOptions, "pattern"),
"pattern",
"p",
`user space function(s) pattern to trace`)
`user space function(s) pattern to trace, optionally prefixed with a path to binary containing these functions (eg. '/lib/x86_64-linux-gnu/libc.so.6:malloc')`)
Utrace.Flags().VarP(
NewUTraceOptionsSanitizer(&options.UTraceOptions, "kernel-pattern"),
"kernel-pattern",
Expand Down
23 changes: 15 additions & 8 deletions cmd/utrace/run/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"os"
"regexp"
"strconv"
"strings"

"github.com/sirupsen/logrus"

Expand Down Expand Up @@ -83,8 +84,8 @@ func (uos *UTraceOptionsSanitizer) String() string {
switch uos.field {
case "pid":
return fmt.Sprintf("%v", uos.options.PIDFilter)
case "binary":
return fmt.Sprintf("%v", uos.options.Binary)
case "executable":
return fmt.Sprintf("%v", uos.options.Executables)
case "pattern":
return fmt.Sprintf("%v", uos.options.FuncPattern)
case "kernel-pattern":
Expand All @@ -105,7 +106,7 @@ func (uos *UTraceOptionsSanitizer) Set(val string) error {
return fmt.Errorf("%v is not a valid pid value: %v", val, err)
}
uos.options.PIDFilter = append(uos.options.PIDFilter, pid)
case "binary":
case "executable":
if len(val) == 0 {
return nil
}
Expand All @@ -116,16 +117,22 @@ func (uos *UTraceOptionsSanitizer) Set(val string) error {
if _, err := os.Stat(val); err != nil {
return fmt.Errorf("can't trace %s: %v", val, err)
}
uos.options.Binary = append(uos.options.Binary, val)
uos.options.Executables = append(uos.options.Executables, val)
case "pattern":
if len(val) == 0 {
return fmt.Errorf("empty pattern")
}
pattern, err := regexp.Compile(val)
fields := strings.SplitN(val, ":", 2)
patternStr := fields[len(fields)-1]
pattern, err := regexp.Compile(patternStr)
if err != nil {
return fmt.Errorf("'%s' isn't a valid pattern: %v", val, err)
return fmt.Errorf("'%s' isn't a valid pattern: %v", patternStr, err)
}
binaryPath := ""
if len(fields) == 2 {
binaryPath = fields[0]
}
uos.options.FuncPattern = pattern
uos.options.FuncPattern = &utrace.FuncPattern{Pattern: pattern, Binary: binaryPath}
case "kernel-pattern":
if len(val) == 0 {
return fmt.Errorf("empty kernel pattern")
Expand Down Expand Up @@ -153,7 +160,7 @@ func (uos *UTraceOptionsSanitizer) Type() string {
switch uos.field {
case "pid":
return "int array"
case "binary":
case "executable":
return "string array"
case "pattern", "kernel-pattern":
return "regexp"
Expand Down
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@ go 1.15

require (
github.com/DataDog/ebpf-manager v0.0.0-20220113113843-c3e09dfcb60c
github.com/DataDog/gopsutil v0.0.0-20211117161807-6301733ae21b
github.com/cilium/ebpf v0.7.1-0.20211227144435-70d770f1e5f9
github.com/pkg/errors v0.9.1
github.com/shuLhan/go-bindata v4.0.0+incompatible
github.com/sirupsen/logrus v1.8.1
github.com/spf13/cobra v1.1.1
github.com/stretchr/testify v1.3.0
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9
golang.org/x/sys v0.0.0-20210921065528-437939a70204
)
8 changes: 3 additions & 5 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,11 @@ cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiy
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/DataDog/ebpf-manager v0.0.0-20220111092842-0c445a76d299 h1:KIbjlZP//7FgPR1x/mmUnJpwVXaHkOSPXXC3ws21Rd8=
github.com/DataDog/ebpf-manager v0.0.0-20220111092842-0c445a76d299/go.mod h1:q6FEcnn+mlqeRyd+nNN/xuoHfVKVsZav5k3xEIQxkpI=
github.com/DataDog/ebpf-manager v0.0.0-20220112215545-53ccf6db5fc5 h1:TcMuL1efB6EYeDB5UCOaTe1ixLXrH1VdNpWqrh16b+c=
github.com/DataDog/ebpf-manager v0.0.0-20220112215545-53ccf6db5fc5/go.mod h1:q6FEcnn+mlqeRyd+nNN/xuoHfVKVsZav5k3xEIQxkpI=
github.com/DataDog/ebpf-manager v0.0.0-20220113113843-c3e09dfcb60c h1:CCmCq3xAb0nrkDsJcmRSswY1ISwlsRhs3aq8cTcrHsY=
github.com/DataDog/ebpf-manager v0.0.0-20220113113843-c3e09dfcb60c/go.mod h1:q6FEcnn+mlqeRyd+nNN/xuoHfVKVsZav5k3xEIQxkpI=
github.com/DataDog/gopsutil v0.0.0-20200624212600-1b53412ef321 h1:OPAXA+r6yznoxWR5jQ2iTh5CvzIMrdw8AU0uFN2RwEw=
github.com/DataDog/gopsutil v0.0.0-20200624212600-1b53412ef321/go.mod h1:tGQp6XG4XpOyy67WG/YWXVxzOY6LejK35e8KcQhtRIQ=
github.com/DataDog/gopsutil v0.0.0-20211117161807-6301733ae21b h1:s3YBoPJLDVmaHdrTyFh5IvCGPT/9Axk+IAP7EEJwwKA=
github.com/DataDog/gopsutil v0.0.0-20211117161807-6301733ae21b/go.mod h1:glkxNt/qRu9lnpmUEQwOIAXW+COWDTBOTEAHqbgBPts=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/StackExchange/wmi v0.0.0-20181212234831-e0a55b97c705 h1:UUppSQnhf4Yc6xGxSkoQpPhb7RVzuv5Nb1mwJ5VId9s=
github.com/StackExchange/wmi v0.0.0-20181212234831-e0a55b97c705/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg=
Expand Down Expand Up @@ -328,6 +325,7 @@ golang.org/x/sys v0.0.0-20210110051926-789bb1bd4061/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210123111255-9b0068b26619/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210216163648-f7da38b97c65/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210906170528-6f6e22806c34/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210921065528-437939a70204 h1:JJhkWtBuTQKyz2bd5WG9H8iUsJRU3En/KRfN8B2RnDs=
golang.org/x/sys v0.0.0-20210921065528-437939a70204/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
Expand Down
65 changes: 62 additions & 3 deletions pkg/utrace/model.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,23 +61,28 @@ type BinaryCookie uint32
// PathMax - Maximum path length of the binary path handled by utrace
const PathMax = 350

type FuncPattern struct {
Pattern *regexp.Regexp
Binary string
}

// Options contains the parameters of UTrace
type Options struct {
FuncPattern *regexp.Regexp
FuncPattern *FuncPattern
KernelFuncPattern *regexp.Regexp
Tracepoints []string
PerfEvents []string
Latency bool
StackTraces bool
Binary []string
Executables []string
PIDFilter []int
}

func (o Options) check() error {
if o.FuncPattern == nil && o.KernelFuncPattern == nil {
return EmptyPatternsErr
}
if o.FuncPattern != nil && len(o.Binary) == 0 {
if o.FuncPattern != nil && len(o.Executables) == 0 {
return EmptyBinaryPathErr
}
return nil
Expand Down Expand Up @@ -353,6 +358,13 @@ func (te *TraceEvent) UnmarshalBinary(data []byte) (int, error) {
return 24, nil
}

// represents a traced function inside of a binary
type TracedFunc struct {
FuncID FuncID
Pids []int
TracedBinary *TracedBinary
}

type TracedBinary struct {
Path string
ResolvedPath string
Expand All @@ -366,7 +378,54 @@ type TracedBinary struct {
file *elf.File
}

type SymbolInfo struct {
elf.Symbol
BinaryPath string
ProcessAddr SymbolAddr
FileOffset SymbolAddr
}

type TracedSymbol struct {
symbol elf.Symbol
binary *TracedBinary
}

type BinarySymbols []elf.Symbol

type ProcMapRange struct {
Start, End uint64
Offset uint64
BinaryPath string
Symbols *BinarySymbols
}
type ProcMapRanges []ProcMapRange

func (r ProcMapRanges) Len() int { return len(r) }
func (r ProcMapRanges) Less(i, j int) bool { return r[i].Start < r[j].Start }
func (r ProcMapRanges) Swap(i, j int) { r[i], r[j] = r[j], r[i] }
func (r ProcMapRanges) Sort() { sort.Sort(r) }

func (r ProcMapRanges) Search(v uint64) *ProcMapRange {
ln := r.Len()
if i := sort.Search(ln, func(i int) bool { return v < r[i].End }); i < ln {
if it := &r[i]; v >= it.Start && v < it.End {
return it
}
}
return nil
}

func (r BinarySymbols) Len() int { return len(r) }
func (r BinarySymbols) Less(i, j int) bool { return r[i].Value < r[j].Value }
func (r BinarySymbols) Swap(i, j int) { r[i], r[j] = r[j], r[i] }
func (r BinarySymbols) Sort() { sort.Sort(r) }

func (r BinarySymbols) Search(v uint64) *elf.Symbol {
ln := r.Len()
if i := sort.Search(ln, func(i int) bool { return v < r[i].Value + r[i].Size }); i < ln {
if it := &r[i]; v >= it.Value && v < it.Value + it.Size {
return it
}
}
return nil
}
45 changes: 45 additions & 0 deletions pkg/utrace/object_model_proposal.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@

Options
---
PIDs: Find all binaries associated to these PIDs. uprobe is placed on a binary associated to these PIDs (loaded from proc maps)

BINARY: Associate uprobe to binary (no filter on PID unless explicitely given in other option).

EXECUTABLE: Find all PIDs that run these executable, load all binaries associated. Allow to probe on all these binaries.

Note:
- PIDs and EXECUTABLE are mutually exclusive (for now at least)
- BINARY if specified takes precedence on where we want to place the uprobe

Function pattern is associated to one of the options above.
example : --binary "libc malloc" --executable "exec1 fopen"
TODO : find a separator


FuncID
---
Represents the function being probed (uprobe and retprobe have the same ID)
Proposal: change to TracedFuncID ?


TracedBinary
---
contains a file
pid list: this is not stored at the right place. Proposal : create a trace context structure.
contains a symbol cache tied this file. Warning: addresses need to be independant from PID
symbolNameToFuncID : issue. Malloc is available in several libraries.
Malloc has several IDs, a symbol name can match several IDs
TBD : what do we do of this ?


New object proposal
----------

TracedFunc
----
TracedFuncID
Pids
pointer to Binary



88 changes: 88 additions & 0 deletions pkg/utrace/symbols.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
package utrace

import (
"debug/elf"
"fmt"
"strings"

manager "github.com/DataDog/ebpf-manager"
"github.com/DataDog/gopsutil/process"
)


func ListProcMaps(pid int) (*[]process.MemoryMapsStat, error) {
p, err := process.NewProcess(int32(pid))
if err != nil {
return nil, err
}
procMaps, err := p.MemoryMaps(true)
if err != nil {
return nil, err
}
filteredProcMaps := []process.MemoryMapsStat{}
for _, m := range *procMaps {
if m.Permission.Execute && !strings.HasPrefix(m.Path, "[") {
filteredProcMaps = append(filteredProcMaps, m)
}
}

return &filteredProcMaps, nil
}

func ListSymbolsFromProcMaps(procMaps *[]process.MemoryMapsStat, symbols *[]SymbolInfo) error {
for _, m := range *procMaps {
f, syms, err := manager.OpenAndListSymbols(m.Path)
if err != nil {
fmt.Println("[error] ListSymbols: Skipping", m.Path)
continue
}
//fmt.Printf("Processing %50s, Start: %x, End: 0x%x, Offset: 0x%x\n", m.Path, m.StartAddr, m.EndAddr, m.Offset)

seenSymbols := make(map[string]bool)

// from the entire list of symbols, only keep the functions that match the provided pattern
for _, sym := range syms {
if len(sym.Version) > 0 {
// skip dynamic symbols
continue
}

value := sym.Value
for _, prog := range f.Progs {
if prog.Type == elf.PT_LOAD {
if value >= prog.Vaddr && value < (prog.Vaddr+prog.Memsz) {
value = sym.Value - prog.Vaddr + prog.Off
}
}
}

if value < m.Offset {
// errors we still need to understand
//fmt.Println("[error] ListSymbols: Skipping symbol", sym)
continue
}

if !seenSymbols[sym.Name] {
// a symbol might be present in both standard and dynamic symbols
processAddr := SymbolAddr(value) - SymbolAddr(m.Offset) + SymbolAddr(m.StartAddr)
*symbols = append(*symbols, SymbolInfo{sym, m.Path, processAddr, SymbolAddr(value)})
seenSymbols[sym.Name] = true
}
}
}

return nil
}

func ListSymbolsFromPID(pid int) ([]SymbolInfo, error) {
procMaps, err := ListProcMaps(pid)
if err != nil {
return nil, err
}
var symbols []SymbolInfo
err = ListSymbolsFromProcMaps(procMaps, &symbols)
if err != nil {
return nil, err
}
return symbols, nil
}
16 changes: 16 additions & 0 deletions pkg/utrace/symbols_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package utrace

import (
"fmt"
"os"
"testing"

"github.com/stretchr/testify/require"
)

func TestListSymbols(t *testing.T) {
pid := os.Getpid()
symbols, err := ListSymbolsFromPID(pid)
require.NoError(t, err)
fmt.Println("map:", symbols)
}
Loading