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  }