diff --git a/cli_flags.go b/cli_flags.go index f743de45e..425b0bef7 100644 --- a/cli_flags.go +++ b/cli_flags.go @@ -71,7 +71,7 @@ var ( envVarsHelp = "Comma separated list of environment variables that will be reported with the" + "captured profiling samples." probeLinkHelper = "Attach a probe to a symbol of an executable. " + - "Expected format: /path/to/executable:symbol" + "Expected format: probe_type:target[:symbol]. probe_type can be kprobe, kretprobe, uprobe, or uretprobe." loadProbeHelper = "Load generic eBPF program that can be attached externally to " + "various user or kernel space hooks." ) @@ -133,8 +133,8 @@ func parseArgs() (*controller.Config, error) { fs.StringVar(&args.IncludeEnvVars, "env-vars", defaultEnvVarsValue, envVarsHelp) - fs.Func("uprobe-link", probeLinkHelper, func(link string) error { - args.UProbeLinks = append(args.UProbeLinks, link) + fs.Func("probe-link", probeLinkHelper, func(link string) error { + args.ProbeLinks = append(args.ProbeLinks, link) return nil }) diff --git a/collector/config/config.go b/collector/config/config.go index 97d24c3f0..04a4c6b6b 100644 --- a/collector/config/config.go +++ b/collector/config/config.go @@ -19,7 +19,7 @@ type Config struct { VerboseMode bool `mapstructure:"verbose_mode"` OffCPUThreshold float64 `mapstructure:"off_cpu_threshold"` IncludeEnvVars string `mapstructure:"include_env_vars"` - UProbeLinks []string `mapstructure:"u_probe_links"` + ProbeLinks []string `mapstructure:"probe_links"` LoadProbe bool `mapstructure:"load_probe"` MapScaleFactor uint `mapstructure:"map_scale_factor"` BPFVerifierLogLevel uint `mapstructure:"bpf_verifier_log_level"` diff --git a/internal/controller/controller.go b/internal/controller/controller.go index 9e666cf88..69cfcd889 100644 --- a/internal/controller/controller.go +++ b/internal/controller/controller.go @@ -88,7 +88,7 @@ func (c *Controller) Start(ctx context.Context) error { ProbabilisticThreshold: c.config.ProbabilisticThreshold, OffCPUThreshold: uint32(c.config.OffCPUThreshold * float64(math.MaxUint32)), IncludeEnvVars: envVars, - UProbeLinks: c.config.UProbeLinks, + ProbeLinks: c.config.ProbeLinks, LoadProbe: c.config.LoadProbe, ExecutableReporter: c.config.ExecutableReporter, }) @@ -118,11 +118,11 @@ func (c *Controller) Start(ctx context.Context) error { log.Infof("Enabled off-cpu profiling with p=%f", c.config.OffCPUThreshold) } - if len(c.config.UProbeLinks) > 0 { - if err := trc.AttachUProbes(c.config.UProbeLinks); err != nil { - return fmt.Errorf("failed to attach uprobes: %v", err) + if len(c.config.ProbeLinks) > 0 { + if err := trc.AttachProbes(c.config.ProbeLinks); err != nil { + return fmt.Errorf("failed to attach probes: %v", err) } - log.Info("Attached uprobes") + log.Info("Attached probes") } if c.config.ProbabilisticThreshold < tracer.ProbabilisticThresholdMax { diff --git a/reporter/base_reporter.go b/reporter/base_reporter.go index 20fed8c79..ca99aad3f 100644 --- a/reporter/base_reporter.go +++ b/reporter/base_reporter.go @@ -45,7 +45,7 @@ func (b *baseReporter) ReportTraceEvent(trace *libpf.Trace, meta *samples.TraceE switch meta.Origin { case support.TraceOriginSampling: case support.TraceOriginOffCPU: - case support.TraceOriginUProbe: + case support.TraceOriginProbe: default: return fmt.Errorf("skip reporting trace for %d origin: %w", meta.Origin, errUnknownOrigin) diff --git a/reporter/internal/pdata/generate.go b/reporter/internal/pdata/generate.go index 4dde4022c..5e92f1220 100644 --- a/reporter/internal/pdata/generate.go +++ b/reporter/internal/pdata/generate.go @@ -82,7 +82,7 @@ func (p *Pdata) Generate(tree samples.TraceEventsTree, for _, origin := range []libpf.Origin{ support.TraceOriginSampling, support.TraceOriginOffCPU, - support.TraceOriginUProbe, + support.TraceOriginProbe, } { if len(originToEvents[origin]) == 0 { // Do not append empty profiles. @@ -146,7 +146,7 @@ func (p *Pdata) setProfile( case support.TraceOriginOffCPU: st.SetTypeStrindex(stringSet.Add("off_cpu")) st.SetUnitStrindex(stringSet.Add("nanoseconds")) - case support.TraceOriginUProbe: + case support.TraceOriginProbe: st.SetTypeStrindex(stringSet.Add("events")) st.SetUnitStrindex(stringSet.Add("count")) default: diff --git a/support/ebpf/uprobe.ebpf.c b/support/ebpf/generic_probe.ebpf.c similarity index 53% rename from support/ebpf/uprobe.ebpf.c rename to support/ebpf/generic_probe.ebpf.c index 2414f7a8e..228dca54e 100644 --- a/support/ebpf/uprobe.ebpf.c +++ b/support/ebpf/generic_probe.ebpf.c @@ -2,9 +2,7 @@ #include "tracemgmt.h" #include "types.h" -// uprobe__generic serves as entry point for uprobe based profiling. -SEC("uprobe/generic") -int uprobe__generic(void *ctx) +static EBPF_INLINE int probe__generic(struct pt_regs *ctx) { u64 pid_tgid = bpf_get_current_pid_tgid(); u32 pid = pid_tgid >> 32; @@ -16,5 +14,19 @@ int uprobe__generic(void *ctx) u64 ts = bpf_ktime_get_ns(); - return collect_trace(ctx, TRACE_UPROBE, pid, tid, ts, 0); + return collect_trace(ctx, TRACE_PROBE, pid, tid, ts, 0); +} + +// uprobe__generic serves as entry point for uprobe based profiling. +SEC("uprobe/generic") +int uprobe__generic(void *ctx) +{ + return probe__generic((struct pt_regs *)ctx); +} + +// kprobe__generic serves as entry point for kprobe based profiling. +SEC("kprobe/generic") +int kprobe__generic(struct pt_regs *ctx) +{ + return probe__generic(ctx); } diff --git a/support/ebpf/tracer.ebpf.amd64 b/support/ebpf/tracer.ebpf.amd64 index e43167d83..fafcd8e15 100644 Binary files a/support/ebpf/tracer.ebpf.amd64 and b/support/ebpf/tracer.ebpf.amd64 differ diff --git a/support/ebpf/tracer.ebpf.arm64 b/support/ebpf/tracer.ebpf.arm64 index f0069fa1e..2d0a1bd77 100644 Binary files a/support/ebpf/tracer.ebpf.arm64 and b/support/ebpf/tracer.ebpf.arm64 differ diff --git a/support/ebpf/types.h b/support/ebpf/types.h index 65491be1c..00b959553 100644 --- a/support/ebpf/types.h +++ b/support/ebpf/types.h @@ -362,7 +362,7 @@ typedef enum TraceOrigin { TRACE_UNKNOWN, TRACE_SAMPLING, TRACE_OFF_CPU, - TRACE_UPROBE, + TRACE_PROBE, } TraceOrigin; // MAX_FRAME_UNWINDS defines the maximum number of frames per diff --git a/support/types.go b/support/types.go index 74323b8c1..77839a0f2 100644 --- a/support/types.go +++ b/support/types.go @@ -89,7 +89,7 @@ const ( TraceOriginUnknown = 0x0 TraceOriginSampling = 0x1 TraceOriginOffCPU = 0x2 - TraceOriginUProbe = 0x3 + TraceOriginProbe = 0x3 ) type ApmSpanID [8]byte diff --git a/support/types_def.go b/support/types_def.go index ca47598ce..77ec341df 100644 --- a/support/types_def.go +++ b/support/types_def.go @@ -101,7 +101,7 @@ const ( TraceOriginUnknown = C.TRACE_UNKNOWN TraceOriginSampling = C.TRACE_SAMPLING TraceOriginOffCPU = C.TRACE_OFF_CPU - TraceOriginUProbe = C.TRACE_UPROBE + TraceOriginProbe = C.TRACE_PROBE ) type ApmSpanID C.ApmSpanID diff --git a/tools/probe-ctrl/cmd/probe-ctrl/main.go b/tools/probe-ctrl/cmd/probe-ctrl/main.go index 35decb358..ad82d1b17 100644 --- a/tools/probe-ctrl/cmd/probe-ctrl/main.go +++ b/tools/probe-ctrl/cmd/probe-ctrl/main.go @@ -4,25 +4,28 @@ import ( "flag" "fmt" "os" + "path/filepath" "time" - "github.com/cilium/ebpf/link" + "go.opentelemetry.io/ebpf-profiler/tracer" ) var ( - argExec string - argSymbol string - argClear bool + argProbeLink string + argClear bool + argListProgs bool + argBPFFS string ) var ( - pinPath = "/sys/fs/bpf/probe-ctrl/" + defaultBPFFSPath = "/sys/fs/bpf" ) func init() { - flag.StringVar(&argExec, "exec", "", "Executable to which the probe should be attached.") - flag.StringVar(&argSymbol, "symb", "", "Symbol in the executable to which the probe will be attached.") + flag.StringVar(&argProbeLink, "probe-link", "", "kprobe|kretprobe|uprobe|uretprobe:[:]") flag.BoolVar(&argClear, "clear", false, "Remove probe from all links.") + flag.BoolVar(&argListProgs, "list", false, "List all loaded eBPF programs and exit.") + flag.StringVar(&argBPFFS, "bpffs", defaultBPFFSPath, "Path to BPF filesystem mount point.") } func main() { @@ -32,48 +35,51 @@ func main() { func run() int { flag.Parse() + if argListProgs { + return listAllPrograms() + } + + pinPath := fmt.Sprintf("%s/probe-ctrl/", argBPFFS) + if argClear { - // bpf_link_detach does not exist for uprobes. So just remove - // the pinned path to deactivate uprobes as a work around. if err := os.RemoveAll(pinPath); err != nil { - fmt.Fprintf(os.Stderr, "%v\n", err) + fmt.Fprintf(os.Stderr, "Failed to remove pin path: %v\n", err) return -1 } return 0 } - if argExec == "" || argSymbol == "" { - fmt.Fprintf(os.Stderr, "Both -exec and -symb need to be set\n") - return -1 - } - - exec, err := link.OpenExecutable(argExec) + probeSpec, err := tracer.ParseProbe(argProbeLink) if err != nil { - fmt.Fprintf(os.Stderr, "%v\n", err) + fmt.Fprintf(os.Stderr, "Failed to parse probe: %v\n", err) return -1 } - probe, err := getProbe() + tracerProg, err := getProbe(probeSpec.ProgName) if err != nil { - fmt.Fprintf(os.Stderr, "%v\n", err) + fmt.Fprintf(os.Stderr, "Failed to get probe %q: %v\n", probeSpec.ProgName, err) return -1 } - probeLink, err := exec.Uprobe(argSymbol, probe, nil) + probeLink, err := tracer.AttachProbe(tracerProg, probeSpec) if err != nil { - fmt.Fprintf(os.Stderr, "Failed attaching probe to '%s' in '%s': %v\n", argSymbol, argExec, err) + fmt.Fprintf(os.Stderr, "Failed to attach probe: %v\n", err) return -1 } if err := os.MkdirAll(pinPath, 0700); err != nil { - fmt.Fprintf(os.Stderr, "%s\n", err) + fmt.Fprintf(os.Stderr, "Failed to create pin path: %v\n", err) return -1 } - if err := probeLink.Pin(fmt.Sprintf("%s/%d", pinPath, time.Now().Unix())); err != nil { + pinFile := filepath.Join(pinPath, fmt.Sprintf("%d", time.Now().Unix())) + if err := probeLink.Pin(pinFile); err != nil { fmt.Fprintf(os.Stderr, "Failed to pin link: %v\n", err) return -1 } + fmt.Printf("Attached probe %s to %s:%s\n", probeSpec.ProgName, probeSpec.Target, probeSpec.Symbol) + fmt.Printf("Pinned to: %s\n", pinFile) + return 0 } diff --git a/tools/probe-ctrl/cmd/probe-ctrl/probe.go b/tools/probe-ctrl/cmd/probe-ctrl/probe.go index 7edc866f1..60dc9f065 100644 --- a/tools/probe-ctrl/cmd/probe-ctrl/probe.go +++ b/tools/probe-ctrl/cmd/probe-ctrl/probe.go @@ -1,32 +1,71 @@ package main import ( + "fmt" + "github.com/cilium/ebpf" ) -func getProbe() (*ebpf.Program, error) { +// iteratePrograms iterates through all eBPF programs and calls the visitor function for each. +// The visitor receives the program ID and info, and should return true to continue iteration. +func iteratePrograms(visitor func(id ebpf.ProgramID, info *ebpf.ProgramInfo) bool) { var lastID ebpf.ProgramID for { nextID, err := ebpf.ProgramGetNextID(lastID) if err != nil { - return nil, err + return } lastID = nextID prog, err := ebpf.NewProgramFromID(nextID) if err != nil { - return nil, err + continue } - defer prog.Close() info, err := prog.Info() + prog.Close() if err != nil { - return nil, err + continue } - if info.Name == "uprobe__generic" { - return prog.Clone() + if !visitor(nextID, info) { + return } } } + +func getProbe(name string) (*ebpf.Program, error) { + var found *ebpf.Program + + iteratePrograms(func(id ebpf.ProgramID, info *ebpf.ProgramInfo) bool { + if info.Name == name { + prog, err := ebpf.NewProgramFromID(id) + if err == nil { + found = prog + } + return false + } + return true + }) + + if found != nil { + return found, nil + } + return nil, fmt.Errorf("probe %q not found", name) +} + +func listAllPrograms() int { + fmt.Println("Loaded eBPF programs:") + fmt.Println("=====================") + + count := 0 + iteratePrograms(func(id ebpf.ProgramID, info *ebpf.ProgramInfo) bool { + count++ + fmt.Printf(" [%d] ID=%d Name=%s Type=%s\n", count, id, info.Name, info.Type) + return true + }) + + fmt.Printf("\nTotal: %d programs\n", count) + return 0 +} diff --git a/tools/probe-ctrl/go.mod b/tools/probe-ctrl/go.mod index 11efd1807..a3e6cbd45 100644 --- a/tools/probe-ctrl/go.mod +++ b/tools/probe-ctrl/go.mod @@ -7,18 +7,51 @@ tool ( honnef.co/go/tools/cmd/staticcheck ) -require github.com/cilium/ebpf v0.20.0 +replace go.opentelemetry.io/ebpf-profiler => ../../ + +require ( + github.com/cilium/ebpf v0.20.0 + go.opentelemetry.io/ebpf-profiler v0.0.202545 +) require ( github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c // indirect + github.com/elastic/go-freelru v0.16.0 // indirect + github.com/elastic/go-perf v0.0.0-20241029065020-30bec95324b8 // indirect + github.com/google/go-cmp v0.7.0 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/hashicorp/go-version v1.7.0 // indirect + github.com/josharian/native v1.1.0 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/klauspost/cpuid/v2 v2.2.8 // indirect + github.com/mdlayher/kobject v0.0.0-20200520190114-19ca17470d7d // indirect + github.com/mdlayher/netlink v1.7.2 // indirect + github.com/mdlayher/socket v0.4.1 // indirect + github.com/minio/sha256-simd v1.0.1 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect + github.com/zeebo/xxh3 v1.0.2 // indirect + go.opentelemetry.io/collector/consumer v1.45.0 // indirect + go.opentelemetry.io/collector/consumer/xconsumer v0.139.0 // indirect + go.opentelemetry.io/collector/featuregate v1.45.0 // indirect + go.opentelemetry.io/collector/pdata v1.45.0 // indirect + go.opentelemetry.io/collector/pdata/pprofile v0.139.0 // indirect + go.opentelemetry.io/otel v1.38.0 // indirect + go.opentelemetry.io/otel/metric v1.38.0 // indirect + go.uber.org/multierr v1.11.0 // indirect + golang.org/x/arch v0.22.0 // indirect + golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 // indirect golang.org/x/exp/typeparams v0.0.0-20231108232855-2478ac86f678 // indirect golang.org/x/mod v0.29.0 // indirect + golang.org/x/net v0.46.0 // indirect golang.org/x/sync v0.17.0 // indirect golang.org/x/sys v0.37.0 // indirect golang.org/x/telemetry v0.0.0-20251008203120-078029d740a8 // indirect + golang.org/x/text v0.30.0 // indirect golang.org/x/tools v0.38.0 // indirect - golang.org/x/tools/go/expect v0.1.1-deprecated // indirect - golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated // indirect golang.org/x/vuln v1.1.4 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250804133106-a7a43d27e69b // indirect + google.golang.org/grpc v1.76.0 // indirect + google.golang.org/protobuf v1.36.10 // indirect honnef.co/go/tools v0.6.1 // indirect ) diff --git a/tools/probe-ctrl/go.sum b/tools/probe-ctrl/go.sum index 6d1586cb9..084d4fbc7 100644 --- a/tools/probe-ctrl/go.sum +++ b/tools/probe-ctrl/go.sum @@ -2,40 +2,143 @@ github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c h1:pxW6RcqyfI9/k github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= github.com/cilium/ebpf v0.20.0 h1:atwWj9d3NffHyPZzVlx3hmw1on5CLe9eljR8VuHTwhM= github.com/cilium/ebpf v0.20.0/go.mod h1:pzLjFymM+uZPLk/IXZUL63xdx5VXEo+enTzxkZXdycw= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/elastic/go-freelru v0.16.0 h1:gG2HJ1WXN2tNl5/p40JS/l59HjvjRhjyAa+oFTRArYs= +github.com/elastic/go-freelru v0.16.0/go.mod h1:bSdWT4M0lW79K8QbX6XY2heQYSCqD7THoYf82pT/H3I= +github.com/elastic/go-perf v0.0.0-20241029065020-30bec95324b8 h1:FD01NjsTes0RxZVQ22ebNYJA4KDdInVnR9cn1hmaMwA= +github.com/elastic/go-perf v0.0.0-20241029065020-30bec95324b8/go.mod h1:Nt+pnRYvf0POC+7pXsrv8ubsEOSsaipJP0zlz1Ms1RM= +github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= +github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-quicktest/qt v1.101.1-0.20240301121107-c6c8733fa1e6 h1:teYtXy9B7y5lHTp8V9KPxpYRAVA7dozigQcMiBust1s= github.com/go-quicktest/qt v1.101.1-0.20240301121107-c6c8733fa1e6/go.mod h1:p4lGIVX+8Wa6ZPNDvqcxq36XpUDLh42FLetFU7odllI= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/go-cmdtest v0.4.1-0.20220921163831-55ab3332a786 h1:rcv+Ippz6RAtvaGgKxc+8FQIpxHgsF+HBzPyYL2cyVU= github.com/google/go-cmdtest v0.4.1-0.20220921163831-55ab3332a786/go.mod h1:apVn/GCasLZUVpAJ6oWAuyP7Ne7CEsQbTnc0plM3m+o= -github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= -github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/renameio v0.1.0 h1:GOZbcHa3HfsPKPlmyPyN2KEohoMXOhdMbHrvbpl2QaA= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY= +github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA= github.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w= -github.com/jsimonetti/rtnetlink/v2 v2.0.1 h1:xda7qaHDSVOsADNouv7ukSuicKZO7GgVUCXxpaIEIlM= -github.com/jsimonetti/rtnetlink/v2 v2.0.1/go.mod h1:7MoNYNbb3UaDHtF8udiJo/RH6VsTKP1pqKLUTVCvToE= +github.com/jsimonetti/rtnetlink v0.0.0-20190606172950-9527aa82566a/go.mod h1:Oz+70psSo5OFh8DBl0Zv2ACw7Esh6pPUphlvZG9x7uw= +github.com/jsimonetti/rtnetlink v0.0.0-20200117123717-f846d4f6c1f4 h1:nwOc1YaOrYJ37sEBrtWZrdqzK22hiJs3GpDmP3sR2Yw= +github.com/jsimonetti/rtnetlink v0.0.0-20200117123717-f846d4f6c1f4/go.mod h1:WGuG/smIU4J/54PblvSbh+xvCZmpJnFgr3ds6Z55XMQ= +github.com/jsimonetti/rtnetlink/v2 v2.0.3 h1:Jcp7GTnTPepoUAJ9+LhTa7ZiebvNS56T1GtlEUaPNFE= +github.com/jsimonetti/rtnetlink/v2 v2.0.3/go.mod h1:atIkksp/9fqtf6rpAw45JnttnP2gtuH9X88WPfWfS9A= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/klauspost/cpuid/v2 v2.2.8 h1:+StwCXwm9PdpiEkPyzBXIy+M9KUb4ODm0Zarf1kS5BM= +github.com/klauspost/cpuid/v2 v2.2.8/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/mdlayher/kobject v0.0.0-20200520190114-19ca17470d7d h1:JmrZTpS0GAyMV4ZQVVH/AS0Y6r2PbnYNSRUuRX+HOLA= +github.com/mdlayher/kobject v0.0.0-20200520190114-19ca17470d7d/go.mod h1:+SexPO1ZvdbbWUdUnyXEWv3+4NwHZjKhxOmQqHY4Pqc= +github.com/mdlayher/netlink v0.0.0-20190409211403-11939a169225/go.mod h1:eQB3mZE4aiYnlUsyGGCOpPETfdQq4Jhsgf1fk3cwQaA= +github.com/mdlayher/netlink v1.0.0/go.mod h1:KxeJAFOFLG6AjpyDkQ/iIhxygIUKD+vcwqcnu43w/+M= +github.com/mdlayher/netlink v1.1.0/go.mod h1:H4WCitaheIsdF9yOYu8CFmCgQthAPIWZmcKp9uZHgmY= github.com/mdlayher/netlink v1.7.2 h1:/UtM3ofJap7Vl4QWCPDGXY8d3GIY2UGSDbK+QWmY8/g= github.com/mdlayher/netlink v1.7.2/go.mod h1:xraEF7uJbxLhc5fpHL4cPe221LI2bdttWlU+ZGLfQSw= github.com/mdlayher/socket v0.4.1 h1:eM9y2/jlbs1M615oshPQOHZzj6R6wMT7bX5NPiQvn2U= github.com/mdlayher/socket v0.4.1/go.mod h1:cAqeGjoufqdxWkD7DkpyS+wcefOtmu5OQ8KuoJGIReA= +github.com/minio/sha256-simd v1.0.1 h1:6kaan5IFmwTNynnKKpDHe6FWHohJOHhCPchzK49dzMM= +github.com/minio/sha256-simd v1.0.1/go.mod h1:Pz6AKMiUdngCLpeTL/RJY1M9rUuPMYujV5xJjtbRSN8= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8= +github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/zeebo/assert v1.3.0 h1:g7C04CbJuIDKNPFHmsk4hwZDO5O+kntRxzaUoNXj+IQ= +github.com/zeebo/assert v1.3.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0= +github.com/zeebo/xxh3 v1.0.2 h1:xZmwmqxHZA8AI603jOQ0tMqmBr9lPeFwGg6d+xy9DC0= +github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA= +go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= +go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= +go.opentelemetry.io/collector/consumer v1.45.0 h1:TtqXxgW+1GSCwdoohq0fzqnfqrZBKbfo++1XRj8mrEA= +go.opentelemetry.io/collector/consumer v1.45.0/go.mod h1:pJzqTWBubwLt8mVou+G4/Hs23b3m425rVmld3LqOYpY= +go.opentelemetry.io/collector/consumer/xconsumer v0.139.0 h1:FhzDv+idglnrfjqPvnUw3YAEOkXSNv/FuNsuMiXQwcY= +go.opentelemetry.io/collector/consumer/xconsumer v0.139.0/go.mod h1:yWrg/6FE/A4Q7eo/Mg++CzkBoSILHdeMnTlxV3serI0= +go.opentelemetry.io/collector/featuregate v1.45.0 h1:D06hpf1F2KzKC+qXLmVv5e8IZpgCyZVeVVC8iOQxVmw= +go.opentelemetry.io/collector/featuregate v1.45.0/go.mod h1:d0tiRzVYrytB6LkcYgz2ESFTv7OktRPQe0QEQcPt1L4= +go.opentelemetry.io/collector/pdata v1.45.0 h1:q4XaISpeX640BcwXwb2mKOVw/gb67r22HjGWl8sbWsk= +go.opentelemetry.io/collector/pdata v1.45.0/go.mod h1:5q2f001YhwMQO8QvpFhCOa4Cq/vtwX9W4HRMsXkU/nE= +go.opentelemetry.io/collector/pdata/pprofile v0.139.0 h1:UA5TgFzYmRuJN3Wz0GR1efLUfjbs5rH0HTaxfASpTR8= +go.opentelemetry.io/collector/pdata/pprofile v0.139.0/go.mod h1:sI5qHt+zzE2fhOWFdJIaiDBR0yGGjD4A4ZvDFU0tiHk= +go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8= +go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM= +go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA= +go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI= +go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E= +go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg= +go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM= +go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA= +go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE= +go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs= +go.opentelemetry.io/proto/slim/otlp v1.8.0 h1:afcLwp2XOeCbGrjufT1qWyruFt+6C9g5SOuymrSPUXQ= +go.opentelemetry.io/proto/slim/otlp v1.8.0/go.mod h1:Yaa5fjYm1SMCq0hG0x/87wV1MP9H5xDuG/1+AhvBcsI= +go.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development v0.1.0 h1:Uc+elixz922LHx5colXGi1ORbsW8DTIGM+gg+D9V7HE= +go.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development v0.1.0/go.mod h1:VyU6dTWBWv6h9w/+DYgSZAPMabWbPTFTuxp25sM8+s0= +go.opentelemetry.io/proto/slim/otlp/profiles/v1development v0.1.0 h1:i8YpvWGm/Uq1koL//bnbJ/26eV3OrKWm09+rDYo7keU= +go.opentelemetry.io/proto/slim/otlp/profiles/v1development v0.1.0/go.mod h1:pQ70xHY/ZVxNUBPn+qUWPl8nwai87eWdqL3M37lNi9A= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +golang.org/x/arch v0.22.0 h1:c/Zle32i5ttqRXjdLyyHZESLD/bB90DCU1g9l/0YBDI= +golang.org/x/arch v0.22.0/go.mod h1:dNHoOeKiyja7GTvF9NJS1l3Z2yntpQNzgrjh1cU103A= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 h1:mgKeJMpvi0yx/sU5GsxQ7p6s2wtOnGAHZWCHUM4KGzY= +golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546/go.mod h1:j/pmGrbnkbPtQfxEe5D0VQhZC6qKbfKifgD0oM7sR70= golang.org/x/exp/typeparams v0.0.0-20231108232855-2478ac86f678 h1:1P7xPZEwZMoBoz0Yze5Nx2/4pxj6nw9ZqHWXqP0iRgQ= golang.org/x/exp/typeparams v0.0.0-20231108232855-2478ac86f678/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= golang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA= golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191007182048-72f939374954/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.46.0 h1:giFlY12I07fugqwPuWJi68oOnpfqFnJIJzaIIm2JVV4= golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210= golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug= golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190411185658-b44545bcd369/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ= golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/telemetry v0.0.0-20251008203120-078029d740a8 h1:LvzTn0GQhWuvKH/kVRS3R3bVAsdQWI7hvfLHGgh9+lU= golang.org/x/telemetry v0.0.0-20251008203120-078029d740a8/go.mod h1:Pi4ztBfryZoJEkyFTI5/Ocsu2jXyDr6iSdgJiYE/uwE= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k= +golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM= golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ= golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs= golang.org/x/tools/go/expect v0.1.1-deprecated h1:jpBZDwmgPhXsKZC6WhL20P4b/wmnpsEAGHaNy0n/rJM= @@ -44,5 +147,16 @@ golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated h1:1h2MnaIAIXISqTF golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated/go.mod h1:RVAQXBGNv1ib0J382/DPCRS/BPnsGebyM1Gj5VSDpG8= golang.org/x/vuln v1.1.4 h1:Ju8QsuyhX3Hk8ma3CesTbO8vfJD9EvUBgHvkxHBzj0I= golang.org/x/vuln v1.1.4/go.mod h1:F+45wmU18ym/ca5PLTPLsSzr2KppzswxPP603ldA67s= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= +gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250804133106-a7a43d27e69b h1:zPKJod4w6F1+nRGDI9ubnXYhU9NSWoFAijkHkUXeTK8= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250804133106-a7a43d27e69b/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= +google.golang.org/grpc v1.76.0 h1:UnVkv1+uMLYXoIz6o7chp59WfQUYA2ex/BXQ9rHZu7A= +google.golang.org/grpc v1.76.0/go.mod h1:Ju12QI8M6iQJtbcsV+awF5a4hfJMLi4X0JLo94ULZ6c= +google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= +google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.6.1 h1:R094WgE8K4JirYjBaOpz/AvTyUu/3wbmAoskKN/pxTI= honnef.co/go/tools v0.6.1/go.mod h1:3puzxxljPCe8RGJX7BIy1plGbxEOZni5mR2aXe3/uk4= diff --git a/tracer/probe.go b/tracer/probe.go new file mode 100644 index 000000000..fffc3d6ee --- /dev/null +++ b/tracer/probe.go @@ -0,0 +1,74 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package tracer // import "go.opentelemetry.io/ebpf-profiler/tracer" + +import ( + "fmt" + "strings" + + "github.com/cilium/ebpf" + "github.com/cilium/ebpf/link" +) + +type ProbeSpec struct { + Type string + Target string + Symbol string + ProgName string +} + +func ParseProbe(spec string) (*ProbeSpec, error) { + parts := strings.SplitN(spec, ":", 3) + var progName string + + switch parts[0] { + case "kprobe", "kretprobe": + progName = "kprobe__generic" + if len(parts) != 2 || parts[1] == "" { + return nil, fmt.Errorf("invalid format: %s", spec) + } + return &ProbeSpec{ + Type: parts[0], + Symbol: parts[1], + ProgName: progName, + }, nil + + case "uprobe", "uretprobe": + progName = "uprobe__generic" + if len(parts) != 3 || parts[2] == "" { + return nil, fmt.Errorf("invalid format: %s", spec) + } + return &ProbeSpec{ + Type: parts[0], + Target: parts[1], + Symbol: parts[2], + ProgName: progName, + }, nil + + default: + return nil, fmt.Errorf("unknown probe type: %s", parts[0]) + } +} + +func AttachProbe(prog *ebpf.Program, spec *ProbeSpec) (link.Link, error) { + switch spec.Type { + case "kprobe": + return link.Kprobe(spec.Symbol, prog, nil) + case "kretprobe": + return link.Kretprobe(spec.Symbol, prog, nil) + case "uprobe": + ex, err := link.OpenExecutable(spec.Target) + if err != nil { + return nil, err + } + return ex.Uprobe(spec.Symbol, prog, nil) + case "uretprobe": + ex, err := link.OpenExecutable(spec.Target) + if err != nil { + return nil, err + } + return ex.Uretprobe(spec.Symbol, prog, nil) + } + return nil, fmt.Errorf("unsupported probe type") +} diff --git a/tracer/probe_test.go b/tracer/probe_test.go new file mode 100644 index 000000000..a7087cba6 --- /dev/null +++ b/tracer/probe_test.go @@ -0,0 +1,137 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package tracer + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestParseProbe(t *testing.T) { + tests := map[string]struct { + input string + expected *ProbeSpec + wantErr bool + errMsg string + }{ + "kprobe_valid": { + input: "kprobe:vfs_read", + expected: &ProbeSpec{ + Type: "kprobe", + Symbol: "vfs_read", + ProgName: "kprobe__generic", + }, + wantErr: false, + }, + "kretprobe_valid": { + input: "kretprobe:vfs_read", + expected: &ProbeSpec{ + Type: "kretprobe", + Symbol: "vfs_read", + ProgName: "kprobe__generic", + }, + wantErr: false, + }, + "uprobe_valid": { + input: "uprobe:/usr/lib/libc.so.6:malloc", + expected: &ProbeSpec{ + Type: "uprobe", + Target: "/usr/lib/libc.so.6", + Symbol: "malloc", + ProgName: "uprobe__generic", + }, + wantErr: false, + }, + "uretprobe_valid": { + input: "uretprobe:/usr/lib/libc.so.6:malloc", + expected: &ProbeSpec{ + Type: "uretprobe", + Target: "/usr/lib/libc.so.6", + Symbol: "malloc", + ProgName: "uprobe__generic", + }, + wantErr: false, + }, + "kprobe_missing_symbol": { + input: "kprobe:", + wantErr: true, + errMsg: "invalid format", + }, + "kprobe_too_many_parts": { + input: "kprobe:symbol:extra", + wantErr: true, + errMsg: "invalid format", + }, + "kretprobe_missing_symbol": { + input: "kretprobe:", + wantErr: true, + errMsg: "invalid format", + }, + "uprobe_missing_symbol": { + input: "uprobe:/usr/lib/libc.so.6", + wantErr: true, + errMsg: "invalid format", + }, + "uprobe_empty_symbol": { + input: "uprobe:/usr/lib/libc.so.6:", + wantErr: true, + errMsg: "invalid format", + }, + "uprobe_missing_target": { + input: "uprobe::malloc", + wantErr: false, // This will parse but target will be empty + expected: &ProbeSpec{ + Type: "uprobe", + Target: "", + Symbol: "malloc", + ProgName: "uprobe__generic", + }, + }, + "uretprobe_empty_symbol": { + input: "uretprobe:/bin/bash:", + wantErr: true, + errMsg: "invalid format", + }, + "unknown_probe_type": { + input: "tracepoint:syscalls:sys_enter_read", + wantErr: true, + errMsg: "unknown probe type", + }, + "empty_string": { + input: "", + wantErr: true, + errMsg: "unknown probe type", + }, + "no_colon": { + input: "kprobe", + wantErr: true, + errMsg: "invalid format", + }, + } + + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + got, err := ParseProbe(tc.input) + + if tc.wantErr { + require.Error(t, err) + assert.Contains(t, err.Error(), tc.errMsg) + assert.Nil(t, got) + } else { + require.NoError(t, err) + require.NotNil(t, got) + assert.Equal(t, tc.expected.Type, got.Type) + assert.Equal(t, tc.expected.Symbol, got.Symbol) + if tc.expected.Target != "" { + assert.Equal(t, tc.expected.Target, got.Target) + } + if tc.expected.ProgName != "" { + assert.Equal(t, tc.expected.ProgName, got.ProgName) + } + } + }) + } +} diff --git a/tracer/tracer.go b/tracer/tracer.go index 3525e05b2..f2bf3b42f 100644 --- a/tracer/tracer.go +++ b/tracer/tracer.go @@ -153,10 +153,10 @@ type Config struct { // IncludeEnvVars holds a list of environment variables that should be captured and reported // from processes IncludeEnvVars libpf.Set[string] - // UProbes holds a list of executable:symbol elements to which - // a uprobe will be attached. - UProbeLinks []string - // LoadProbe inidicates whether the generic eBPF program should be loaded + // Probes holds a list of probe_type:target[:symbol] elements to which + // a probe will be attached. + ProbeLinks []string + // LoadProbe indicates whether the generic eBPF program should be loaded // without being attached to something. LoadProbe bool } @@ -400,7 +400,7 @@ func initializeMapsAndPrograms(kmod *kallsyms.Module, cfg *Config) ( return nil, nil, fmt.Errorf("failed to load perf eBPF programs: %v", err) } - if cfg.OffCPUThreshold > 0 || len(cfg.UProbeLinks) > 0 || cfg.LoadProbe { + if cfg.OffCPUThreshold > 0 || len(cfg.ProbeLinks) > 0 || cfg.LoadProbe { // Load the tail call destinations if any kind of event profiling is enabled. if err = loadProbeUnwinders(coll, ebpfProgs, ebpfMaps["kprobe_progs"], tailCallProgs, cfg.BPFVerifierLogLevel, ebpfMaps["perf_progs"].FD()); err != nil { @@ -427,13 +427,18 @@ func initializeMapsAndPrograms(kmod *kallsyms.Module, cfg *Config) ( } } - if len(cfg.UProbeLinks) > 0 || cfg.LoadProbe { + if len(cfg.ProbeLinks) > 0 || cfg.LoadProbe { uprobeProgs := []progLoaderHelper{ { name: "uprobe__generic", noTailCallTarget: true, enable: true, }, + { + name: "kprobe__generic", + noTailCallTarget: true, + enable: true, + }, } if err = loadProbeUnwinders(coll, ebpfProgs, ebpfMaps["kprobe_progs"], uprobeProgs, cfg.BPFVerifierLogLevel, ebpfMaps["perf_progs"].FD()); err != nil { @@ -905,7 +910,7 @@ func (t *Tracer) loadBpfTrace(raw []byte, cpu int) *host.Trace { switch trace.Origin { case support.TraceOriginSampling: case support.TraceOriginOffCPU: - case support.TraceOriginUProbe: + case support.TraceOriginProbe: default: log.Warnf("Skip handling trace from unexpected %d origin", trace.Origin) return nil @@ -1146,23 +1151,24 @@ func (t *Tracer) StartOffCPUProfiling() error { return nil } -func (t *Tracer) AttachUProbes(uprobes []string) error { - uProbeProg, ok := t.ebpfProgs["uprobe__generic"] - if !ok { - return errors.New("uprobe__generic is not available") - } - for _, uprobeStr := range uprobes { - split := strings.SplitN(uprobeStr, ":", 2) - - exec, err := link.OpenExecutable(split[0]) +func (t *Tracer) AttachProbes(probes []string) error { + for _, probeStr := range probes { + probeSpec, err := ParseProbe(probeStr) if err != nil { return err } - uprobeLink, err := exec.Uprobe(split[1], uProbeProg, nil) + + uProbeProg, ok := t.ebpfProgs[probeSpec.ProgName] + if !ok { + return fmt.Errorf("%s is not available", probeSpec.ProgName) + } + + probeLink, err := AttachProbe(uProbeProg, probeSpec) if err != nil { return err } - t.hooks[hookPoint{group: "uprobe", name: uprobeStr}] = uprobeLink + + t.hooks[hookPoint{group: probeSpec.Type, name: probeStr}] = probeLink } return nil }