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