github.com/kubeshark/ebpf@v0.9.2/link/uprobe.go (about) 1 package link 2 3 import ( 4 "debug/elf" 5 "errors" 6 "fmt" 7 "os" 8 "path/filepath" 9 "strings" 10 "sync" 11 12 "github.com/kubeshark/ebpf" 13 "github.com/kubeshark/ebpf/internal" 14 ) 15 16 var ( 17 uprobeEventsPath = filepath.Join(tracefsPath, "uprobe_events") 18 19 uprobeRetprobeBit = struct { 20 once sync.Once 21 value uint64 22 err error 23 }{} 24 25 uprobeRefCtrOffsetPMUPath = "/sys/bus/event_source/devices/uprobe/format/ref_ctr_offset" 26 // elixir.bootlin.com/linux/v5.15-rc7/source/kernel/events/core.c#L9799 27 uprobeRefCtrOffsetShift = 32 28 haveRefCtrOffsetPMU = internal.FeatureTest("RefCtrOffsetPMU", "4.20", func() error { 29 _, err := os.Stat(uprobeRefCtrOffsetPMUPath) 30 if err != nil { 31 return internal.ErrNotSupported 32 } 33 return nil 34 }) 35 36 // ErrNoSymbol indicates that the given symbol was not found 37 // in the ELF symbols table. 38 ErrNoSymbol = errors.New("not found") 39 ) 40 41 // Executable defines an executable program on the filesystem. 42 type Executable struct { 43 // Path of the executable on the filesystem. 44 path string 45 // Parsed ELF symbols and dynamic symbols offsets. 46 offsets map[string]uint64 47 } 48 49 // UprobeOptions defines additional parameters that will be used 50 // when loading Uprobes. 51 type UprobeOptions struct { 52 // Symbol offset. Must be provided in case of external symbols (shared libs). 53 // If set, overrides the offset eventually parsed from the executable. 54 Offset uint64 55 // The offset relative to given symbol. Useful when tracing an arbitrary point 56 // inside the frame of given symbol and eliminates the need of recalculating 57 // the absolute offset. 58 RelativeOffset uint64 59 // Only set the uprobe on the given process ID. Useful when tracing 60 // shared library calls or programs that have many running instances. 61 PID int 62 // Automatically manage SDT reference counts (semaphores). 63 // 64 // If this field is set, the Kernel will increment/decrement the 65 // semaphore located in the process memory at the provided address on 66 // probe attach/detach. 67 // 68 // See also: 69 // sourceware.org/systemtap/wiki/UserSpaceProbeImplementation (Semaphore Handling) 70 // github.com/torvalds/linux/commit/1cc33161a83d 71 // github.com/torvalds/linux/commit/a6ca88b241d5 72 RefCtrOffset uint64 73 // Arbitrary value that can be fetched from an eBPF program 74 // via `bpf_get_attach_cookie()`. 75 // 76 // Needs kernel 5.15+. 77 Cookie uint64 78 } 79 80 // To open a new Executable, use: 81 // 82 // OpenExecutable("/bin/bash") 83 // 84 // The returned value can then be used to open Uprobe(s). 85 func OpenExecutable(path string) (*Executable, error) { 86 if path == "" { 87 return nil, fmt.Errorf("path cannot be empty") 88 } 89 90 f, err := os.Open(path) 91 if err != nil { 92 return nil, fmt.Errorf("open file '%s': %w", path, err) 93 } 94 defer f.Close() 95 96 se, err := internal.NewSafeELFFile(f) 97 if err != nil { 98 return nil, fmt.Errorf("parse ELF file: %w", err) 99 } 100 101 if se.Type != elf.ET_EXEC && se.Type != elf.ET_DYN { 102 // ELF is not an executable or a shared object. 103 return nil, errors.New("the given file is not an executable or a shared object") 104 } 105 106 ex := Executable{ 107 path: path, 108 offsets: make(map[string]uint64), 109 } 110 111 if err := ex.load(se); err != nil { 112 return nil, err 113 } 114 115 return &ex, nil 116 } 117 118 func (ex *Executable) load(f *internal.SafeELFFile) error { 119 syms, err := f.Symbols() 120 if err != nil && !errors.Is(err, elf.ErrNoSymbols) { 121 return err 122 } 123 124 dynsyms, err := f.DynamicSymbols() 125 if err != nil && !errors.Is(err, elf.ErrNoSymbols) { 126 return err 127 } 128 129 syms = append(syms, dynsyms...) 130 131 for _, s := range syms { 132 if elf.ST_TYPE(s.Info) != elf.STT_FUNC { 133 // Symbol not associated with a function or other executable code. 134 continue 135 } 136 137 off := s.Value 138 139 // Loop over ELF segments. 140 for _, prog := range f.Progs { 141 // Skip uninteresting segments. 142 if prog.Type != elf.PT_LOAD || (prog.Flags&elf.PF_X) == 0 { 143 continue 144 } 145 146 if prog.Vaddr <= s.Value && s.Value < (prog.Vaddr+prog.Memsz) { 147 // If the symbol value is contained in the segment, calculate 148 // the symbol offset. 149 // 150 // fn symbol offset = fn symbol VA - .text VA + .text offset 151 // 152 // stackoverflow.com/a/40249502 153 off = s.Value - prog.Vaddr + prog.Off 154 break 155 } 156 } 157 158 ex.offsets[s.Name] = off 159 } 160 161 return nil 162 } 163 164 func (ex *Executable) offset(symbol string) (uint64, error) { 165 if off, ok := ex.offsets[symbol]; ok { 166 // Symbols with location 0 from section undef are shared library calls and 167 // are relocated before the binary is executed. Dynamic linking is not 168 // implemented by the library, so mark this as unsupported for now. 169 // 170 // Since only offset values are stored and not elf.Symbol, if the value is 0, 171 // assume it's an external symbol. 172 if off == 0 { 173 return 0, fmt.Errorf("cannot resolve %s library call '%s', "+ 174 "consider providing the offset via options: %w", ex.path, symbol, ErrNotSupported) 175 } 176 return off, nil 177 } 178 return 0, fmt.Errorf("symbol %s: %w", symbol, ErrNoSymbol) 179 } 180 181 // offsetWithOpts adds opts.RelativeOffset to symbol's offset 182 // if opts.Offset is not set. 183 func (ex *Executable) offsetWithOpts(symbol string, opts *UprobeOptions) (uint64, error) { 184 if opts == nil { 185 return ex.offset(symbol) 186 } 187 188 offset := opts.Offset 189 if offset == 0 { 190 offset = opts.RelativeOffset 191 off, err := ex.offset(symbol) 192 if err != nil { 193 return 0, err 194 } 195 offset += off 196 } 197 return offset, nil 198 } 199 200 // Uprobe attaches the given eBPF program to a perf event that fires when the 201 // given symbol starts executing in the given Executable. 202 // For example, /bin/bash::main(): 203 // 204 // ex, _ = OpenExecutable("/bin/bash") 205 // ex.Uprobe("main", prog, nil) 206 // 207 // When using symbols which belongs to shared libraries, 208 // an offset must be provided via options: 209 // 210 // up, err := ex.Uprobe("main", prog, &UprobeOptions{Offset: 0x123}) 211 // 212 // Note: Setting the Offset field in the options supersedes the symbol's offset. 213 // 214 // Losing the reference to the resulting Link (up) will close the Uprobe 215 // and prevent further execution of prog. The Link must be Closed during 216 // program shutdown to avoid leaking system resources. 217 // 218 // Functions provided by shared libraries can currently not be traced and 219 // will result in an ErrNotSupported. 220 func (ex *Executable) Uprobe(symbol string, prog *ebpf.Program, opts *UprobeOptions) (Link, error) { 221 u, err := ex.uprobe(symbol, prog, opts, false) 222 if err != nil { 223 return nil, err 224 } 225 226 lnk, err := attachPerfEvent(u, prog) 227 if err != nil { 228 u.Close() 229 return nil, err 230 } 231 232 return lnk, nil 233 } 234 235 // Uretprobe attaches the given eBPF program to a perf event that fires right 236 // before the given symbol exits. For example, /bin/bash::main(): 237 // 238 // ex, _ = OpenExecutable("/bin/bash") 239 // ex.Uretprobe("main", prog, nil) 240 // 241 // When using symbols which belongs to shared libraries, 242 // an offset must be provided via options: 243 // 244 // up, err := ex.Uretprobe("main", prog, &UprobeOptions{Offset: 0x123}) 245 // 246 // Note: Setting the Offset field in the options supersedes the symbol's offset. 247 // 248 // Losing the reference to the resulting Link (up) will close the Uprobe 249 // and prevent further execution of prog. The Link must be Closed during 250 // program shutdown to avoid leaking system resources. 251 // 252 // Functions provided by shared libraries can currently not be traced and 253 // will result in an ErrNotSupported. 254 func (ex *Executable) Uretprobe(symbol string, prog *ebpf.Program, opts *UprobeOptions) (Link, error) { 255 u, err := ex.uprobe(symbol, prog, opts, true) 256 if err != nil { 257 return nil, err 258 } 259 260 lnk, err := attachPerfEvent(u, prog) 261 if err != nil { 262 u.Close() 263 return nil, err 264 } 265 266 return lnk, nil 267 } 268 269 // uprobe opens a perf event for the given binary/symbol and attaches prog to it. 270 // If ret is true, create a uretprobe. 271 func (ex *Executable) uprobe(symbol string, prog *ebpf.Program, opts *UprobeOptions, ret bool) (*perfEvent, error) { 272 if prog == nil { 273 return nil, fmt.Errorf("prog cannot be nil: %w", errInvalidInput) 274 } 275 if prog.Type() != ebpf.Kprobe { 276 return nil, fmt.Errorf("eBPF program type %s is not Kprobe: %w", prog.Type(), errInvalidInput) 277 } 278 if opts == nil { 279 opts = &UprobeOptions{} 280 } 281 282 offset, err := ex.offsetWithOpts(symbol, opts) 283 if err != nil { 284 return nil, err 285 } 286 287 pid := opts.PID 288 if pid == 0 { 289 pid = perfAllThreads 290 } 291 292 if opts.RefCtrOffset != 0 { 293 if err := haveRefCtrOffsetPMU(); err != nil { 294 return nil, fmt.Errorf("uprobe ref_ctr_offset: %w", err) 295 } 296 } 297 298 args := probeArgs{ 299 symbol: symbol, 300 path: ex.path, 301 offset: offset, 302 pid: pid, 303 refCtrOffset: opts.RefCtrOffset, 304 ret: ret, 305 cookie: opts.Cookie, 306 } 307 308 // Use uprobe PMU if the kernel has it available. 309 tp, err := pmuUprobe(args) 310 if err == nil { 311 return tp, nil 312 } 313 if err != nil && !errors.Is(err, ErrNotSupported) { 314 return nil, fmt.Errorf("creating perf_uprobe PMU: %w", err) 315 } 316 317 // Use tracefs if uprobe PMU is missing. 318 args.symbol = sanitizeSymbol(symbol) 319 tp, err = tracefsUprobe(args) 320 if err != nil { 321 return nil, fmt.Errorf("creating trace event '%s:%s' in tracefs: %w", ex.path, symbol, err) 322 } 323 324 return tp, nil 325 } 326 327 // pmuUprobe opens a perf event based on the uprobe PMU. 328 func pmuUprobe(args probeArgs) (*perfEvent, error) { 329 return pmuProbe(uprobeType, args) 330 } 331 332 // tracefsUprobe creates a Uprobe tracefs entry. 333 func tracefsUprobe(args probeArgs) (*perfEvent, error) { 334 return tracefsProbe(uprobeType, args) 335 } 336 337 // sanitizeSymbol replaces every invalid character for the tracefs api with an underscore. 338 // It is equivalent to calling regexp.MustCompile("[^a-zA-Z0-9]+").ReplaceAllString("_"). 339 func sanitizeSymbol(s string) string { 340 var b strings.Builder 341 b.Grow(len(s)) 342 var skip bool 343 for _, c := range []byte(s) { 344 switch { 345 case c >= 'a' && c <= 'z', 346 c >= 'A' && c <= 'Z', 347 c >= '0' && c <= '9': 348 skip = false 349 b.WriteByte(c) 350 351 default: 352 if !skip { 353 b.WriteByte('_') 354 skip = true 355 } 356 } 357 } 358 359 return b.String() 360 } 361 362 // uprobeToken creates the PATH:OFFSET(REF_CTR_OFFSET) token for the tracefs api. 363 func uprobeToken(args probeArgs) string { 364 po := fmt.Sprintf("%s:%#x", args.path, args.offset) 365 366 if args.refCtrOffset != 0 { 367 // This is not documented in Documentation/trace/uprobetracer.txt. 368 // elixir.bootlin.com/linux/v5.15-rc7/source/kernel/trace/trace.c#L5564 369 po += fmt.Sprintf("(%#x)", args.refCtrOffset) 370 } 371 372 return po 373 } 374 375 func uretprobeBit() (uint64, error) { 376 uprobeRetprobeBit.once.Do(func() { 377 uprobeRetprobeBit.value, uprobeRetprobeBit.err = determineRetprobeBit(uprobeType) 378 }) 379 return uprobeRetprobeBit.value, uprobeRetprobeBit.err 380 }