...

Source file src/github.com/cilium/ebpf/link/uprobe.go

Documentation: github.com/cilium/ebpf/link

     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  }
   374  

View as plain text