gtsocial-umbx

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

uprobe.go (10286B)


      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 }