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  }