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