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