mirror of
				https://github.com/superseriousbusiness/gotosocial.git
				synced 2025-11-03 18:32:25 -06:00 
			
		
		
		
	Bumps [github.com/KimMachineGun/automemlimit](https://github.com/KimMachineGun/automemlimit) from 0.2.4 to 0.2.5. - [Release notes](https://github.com/KimMachineGun/automemlimit/releases) - [Commits](https://github.com/KimMachineGun/automemlimit/compare/v0.2.4...v0.2.5) --- updated-dependencies: - dependency-name: github.com/KimMachineGun/automemlimit dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
		
			
				
	
	
		
			373 lines
		
	
	
	
		
			10 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			373 lines
		
	
	
	
		
			10 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
package link
 | 
						|
 | 
						|
import (
 | 
						|
	"debug/elf"
 | 
						|
	"errors"
 | 
						|
	"fmt"
 | 
						|
	"os"
 | 
						|
	"path/filepath"
 | 
						|
	"strings"
 | 
						|
	"sync"
 | 
						|
 | 
						|
	"github.com/cilium/ebpf"
 | 
						|
	"github.com/cilium/ebpf/internal"
 | 
						|
)
 | 
						|
 | 
						|
var (
 | 
						|
	uprobeEventsPath = filepath.Join(tracefsPath, "uprobe_events")
 | 
						|
 | 
						|
	uprobeRetprobeBit = struct {
 | 
						|
		once  sync.Once
 | 
						|
		value uint64
 | 
						|
		err   error
 | 
						|
	}{}
 | 
						|
 | 
						|
	uprobeRefCtrOffsetPMUPath = "/sys/bus/event_source/devices/uprobe/format/ref_ctr_offset"
 | 
						|
	// elixir.bootlin.com/linux/v5.15-rc7/source/kernel/events/core.c#L9799
 | 
						|
	uprobeRefCtrOffsetShift = 32
 | 
						|
	haveRefCtrOffsetPMU     = internal.FeatureTest("RefCtrOffsetPMU", "4.20", func() error {
 | 
						|
		_, err := os.Stat(uprobeRefCtrOffsetPMUPath)
 | 
						|
		if err != nil {
 | 
						|
			return internal.ErrNotSupported
 | 
						|
		}
 | 
						|
		return nil
 | 
						|
	})
 | 
						|
 | 
						|
	// ErrNoSymbol indicates that the given symbol was not found
 | 
						|
	// in the ELF symbols table.
 | 
						|
	ErrNoSymbol = errors.New("not found")
 | 
						|
)
 | 
						|
 | 
						|
// Executable defines an executable program on the filesystem.
 | 
						|
type Executable struct {
 | 
						|
	// Path of the executable on the filesystem.
 | 
						|
	path string
 | 
						|
	// Parsed ELF and dynamic symbols' addresses.
 | 
						|
	addresses map[string]uint64
 | 
						|
}
 | 
						|
 | 
						|
// UprobeOptions defines additional parameters that will be used
 | 
						|
// when loading Uprobes.
 | 
						|
type UprobeOptions struct {
 | 
						|
	// Symbol address. Must be provided in case of external symbols (shared libs).
 | 
						|
	// If set, overrides the address eventually parsed from the executable.
 | 
						|
	Address uint64
 | 
						|
	// The offset relative to given symbol. Useful when tracing an arbitrary point
 | 
						|
	// inside the frame of given symbol.
 | 
						|
	//
 | 
						|
	// Note: this field changed from being an absolute offset to being relative
 | 
						|
	// to Address.
 | 
						|
	Offset uint64
 | 
						|
	// Only set the uprobe on the given process ID. Useful when tracing
 | 
						|
	// shared library calls or programs that have many running instances.
 | 
						|
	PID int
 | 
						|
	// Automatically manage SDT reference counts (semaphores).
 | 
						|
	//
 | 
						|
	// If this field is set, the Kernel will increment/decrement the
 | 
						|
	// semaphore located in the process memory at the provided address on
 | 
						|
	// probe attach/detach.
 | 
						|
	//
 | 
						|
	// See also:
 | 
						|
	// sourceware.org/systemtap/wiki/UserSpaceProbeImplementation (Semaphore Handling)
 | 
						|
	// github.com/torvalds/linux/commit/1cc33161a83d
 | 
						|
	// github.com/torvalds/linux/commit/a6ca88b241d5
 | 
						|
	RefCtrOffset uint64
 | 
						|
	// Arbitrary value that can be fetched from an eBPF program
 | 
						|
	// via `bpf_get_attach_cookie()`.
 | 
						|
	//
 | 
						|
	// Needs kernel 5.15+.
 | 
						|
	Cookie uint64
 | 
						|
}
 | 
						|
 | 
						|
// To open a new Executable, use:
 | 
						|
//
 | 
						|
//  OpenExecutable("/bin/bash")
 | 
						|
//
 | 
						|
// The returned value can then be used to open Uprobe(s).
 | 
						|
func OpenExecutable(path string) (*Executable, error) {
 | 
						|
	if path == "" {
 | 
						|
		return nil, fmt.Errorf("path cannot be empty")
 | 
						|
	}
 | 
						|
 | 
						|
	f, err := os.Open(path)
 | 
						|
	if err != nil {
 | 
						|
		return nil, fmt.Errorf("open file '%s': %w", path, err)
 | 
						|
	}
 | 
						|
	defer f.Close()
 | 
						|
 | 
						|
	se, err := internal.NewSafeELFFile(f)
 | 
						|
	if err != nil {
 | 
						|
		return nil, fmt.Errorf("parse ELF file: %w", err)
 | 
						|
	}
 | 
						|
 | 
						|
	if se.Type != elf.ET_EXEC && se.Type != elf.ET_DYN {
 | 
						|
		// ELF is not an executable or a shared object.
 | 
						|
		return nil, errors.New("the given file is not an executable or a shared object")
 | 
						|
	}
 | 
						|
 | 
						|
	ex := Executable{
 | 
						|
		path:      path,
 | 
						|
		addresses: make(map[string]uint64),
 | 
						|
	}
 | 
						|
 | 
						|
	if err := ex.load(se); err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
 | 
						|
	return &ex, nil
 | 
						|
}
 | 
						|
 | 
						|
func (ex *Executable) load(f *internal.SafeELFFile) error {
 | 
						|
	syms, err := f.Symbols()
 | 
						|
	if err != nil && !errors.Is(err, elf.ErrNoSymbols) {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
 | 
						|
	dynsyms, err := f.DynamicSymbols()
 | 
						|
	if err != nil && !errors.Is(err, elf.ErrNoSymbols) {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
 | 
						|
	syms = append(syms, dynsyms...)
 | 
						|
 | 
						|
	for _, s := range syms {
 | 
						|
		if elf.ST_TYPE(s.Info) != elf.STT_FUNC {
 | 
						|
			// Symbol not associated with a function or other executable code.
 | 
						|
			continue
 | 
						|
		}
 | 
						|
 | 
						|
		address := s.Value
 | 
						|
 | 
						|
		// Loop over ELF segments.
 | 
						|
		for _, prog := range f.Progs {
 | 
						|
			// Skip uninteresting segments.
 | 
						|
			if prog.Type != elf.PT_LOAD || (prog.Flags&elf.PF_X) == 0 {
 | 
						|
				continue
 | 
						|
			}
 | 
						|
 | 
						|
			if prog.Vaddr <= s.Value && s.Value < (prog.Vaddr+prog.Memsz) {
 | 
						|
				// If the symbol value is contained in the segment, calculate
 | 
						|
				// the symbol offset.
 | 
						|
				//
 | 
						|
				// fn symbol offset = fn symbol VA - .text VA + .text offset
 | 
						|
				//
 | 
						|
				// stackoverflow.com/a/40249502
 | 
						|
				address = s.Value - prog.Vaddr + prog.Off
 | 
						|
				break
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		ex.addresses[s.Name] = address
 | 
						|
	}
 | 
						|
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
// address calculates the address of a symbol in the executable.
 | 
						|
//
 | 
						|
// opts must not be nil.
 | 
						|
func (ex *Executable) address(symbol string, opts *UprobeOptions) (uint64, error) {
 | 
						|
	if opts.Address > 0 {
 | 
						|
		return opts.Address + opts.Offset, nil
 | 
						|
	}
 | 
						|
 | 
						|
	address, ok := ex.addresses[symbol]
 | 
						|
	if !ok {
 | 
						|
		return 0, fmt.Errorf("symbol %s: %w", symbol, ErrNoSymbol)
 | 
						|
	}
 | 
						|
 | 
						|
	// Symbols with location 0 from section undef are shared library calls and
 | 
						|
	// are relocated before the binary is executed. Dynamic linking is not
 | 
						|
	// implemented by the library, so mark this as unsupported for now.
 | 
						|
	//
 | 
						|
	// Since only offset values are stored and not elf.Symbol, if the value is 0,
 | 
						|
	// assume it's an external symbol.
 | 
						|
	if address == 0 {
 | 
						|
		return 0, fmt.Errorf("cannot resolve %s library call '%s': %w "+
 | 
						|
			"(consider providing UprobeOptions.Address)", ex.path, symbol, ErrNotSupported)
 | 
						|
	}
 | 
						|
 | 
						|
	return address + opts.Offset, nil
 | 
						|
}
 | 
						|
 | 
						|
// Uprobe attaches the given eBPF program to a perf event that fires when the
 | 
						|
// given symbol starts executing in the given Executable.
 | 
						|
// For example, /bin/bash::main():
 | 
						|
//
 | 
						|
//  ex, _ = OpenExecutable("/bin/bash")
 | 
						|
//  ex.Uprobe("main", prog, nil)
 | 
						|
//
 | 
						|
// When using symbols which belongs to shared libraries,
 | 
						|
// an offset must be provided via options:
 | 
						|
//
 | 
						|
//  up, err := ex.Uprobe("main", prog, &UprobeOptions{Offset: 0x123})
 | 
						|
//
 | 
						|
// Note: Setting the Offset field in the options supersedes the symbol's offset.
 | 
						|
//
 | 
						|
// Losing the reference to the resulting Link (up) will close the Uprobe
 | 
						|
// and prevent further execution of prog. The Link must be Closed during
 | 
						|
// program shutdown to avoid leaking system resources.
 | 
						|
//
 | 
						|
// Functions provided by shared libraries can currently not be traced and
 | 
						|
// will result in an ErrNotSupported.
 | 
						|
func (ex *Executable) Uprobe(symbol string, prog *ebpf.Program, opts *UprobeOptions) (Link, error) {
 | 
						|
	u, err := ex.uprobe(symbol, prog, opts, false)
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
 | 
						|
	lnk, err := attachPerfEvent(u, prog)
 | 
						|
	if err != nil {
 | 
						|
		u.Close()
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
 | 
						|
	return lnk, nil
 | 
						|
}
 | 
						|
 | 
						|
// Uretprobe attaches the given eBPF program to a perf event that fires right
 | 
						|
// before the given symbol exits. For example, /bin/bash::main():
 | 
						|
//
 | 
						|
//  ex, _ = OpenExecutable("/bin/bash")
 | 
						|
//  ex.Uretprobe("main", prog, nil)
 | 
						|
//
 | 
						|
// When using symbols which belongs to shared libraries,
 | 
						|
// an offset must be provided via options:
 | 
						|
//
 | 
						|
//  up, err := ex.Uretprobe("main", prog, &UprobeOptions{Offset: 0x123})
 | 
						|
//
 | 
						|
// Note: Setting the Offset field in the options supersedes the symbol's offset.
 | 
						|
//
 | 
						|
// Losing the reference to the resulting Link (up) will close the Uprobe
 | 
						|
// and prevent further execution of prog. The Link must be Closed during
 | 
						|
// program shutdown to avoid leaking system resources.
 | 
						|
//
 | 
						|
// Functions provided by shared libraries can currently not be traced and
 | 
						|
// will result in an ErrNotSupported.
 | 
						|
func (ex *Executable) Uretprobe(symbol string, prog *ebpf.Program, opts *UprobeOptions) (Link, error) {
 | 
						|
	u, err := ex.uprobe(symbol, prog, opts, true)
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
 | 
						|
	lnk, err := attachPerfEvent(u, prog)
 | 
						|
	if err != nil {
 | 
						|
		u.Close()
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
 | 
						|
	return lnk, nil
 | 
						|
}
 | 
						|
 | 
						|
// uprobe opens a perf event for the given binary/symbol and attaches prog to it.
 | 
						|
// If ret is true, create a uretprobe.
 | 
						|
func (ex *Executable) uprobe(symbol string, prog *ebpf.Program, opts *UprobeOptions, ret bool) (*perfEvent, error) {
 | 
						|
	if prog == nil {
 | 
						|
		return nil, fmt.Errorf("prog cannot be nil: %w", errInvalidInput)
 | 
						|
	}
 | 
						|
	if prog.Type() != ebpf.Kprobe {
 | 
						|
		return nil, fmt.Errorf("eBPF program type %s is not Kprobe: %w", prog.Type(), errInvalidInput)
 | 
						|
	}
 | 
						|
	if opts == nil {
 | 
						|
		opts = &UprobeOptions{}
 | 
						|
	}
 | 
						|
 | 
						|
	offset, err := ex.address(symbol, opts)
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
 | 
						|
	pid := opts.PID
 | 
						|
	if pid == 0 {
 | 
						|
		pid = perfAllThreads
 | 
						|
	}
 | 
						|
 | 
						|
	if opts.RefCtrOffset != 0 {
 | 
						|
		if err := haveRefCtrOffsetPMU(); err != nil {
 | 
						|
			return nil, fmt.Errorf("uprobe ref_ctr_offset: %w", err)
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	args := probeArgs{
 | 
						|
		symbol:       symbol,
 | 
						|
		path:         ex.path,
 | 
						|
		offset:       offset,
 | 
						|
		pid:          pid,
 | 
						|
		refCtrOffset: opts.RefCtrOffset,
 | 
						|
		ret:          ret,
 | 
						|
		cookie:       opts.Cookie,
 | 
						|
	}
 | 
						|
 | 
						|
	// Use uprobe PMU if the kernel has it available.
 | 
						|
	tp, err := pmuUprobe(args)
 | 
						|
	if err == nil {
 | 
						|
		return tp, nil
 | 
						|
	}
 | 
						|
	if err != nil && !errors.Is(err, ErrNotSupported) {
 | 
						|
		return nil, fmt.Errorf("creating perf_uprobe PMU: %w", err)
 | 
						|
	}
 | 
						|
 | 
						|
	// Use tracefs if uprobe PMU is missing.
 | 
						|
	args.symbol = sanitizeSymbol(symbol)
 | 
						|
	tp, err = tracefsUprobe(args)
 | 
						|
	if err != nil {
 | 
						|
		return nil, fmt.Errorf("creating trace event '%s:%s' in tracefs: %w", ex.path, symbol, err)
 | 
						|
	}
 | 
						|
 | 
						|
	return tp, nil
 | 
						|
}
 | 
						|
 | 
						|
// pmuUprobe opens a perf event based on the uprobe PMU.
 | 
						|
func pmuUprobe(args probeArgs) (*perfEvent, error) {
 | 
						|
	return pmuProbe(uprobeType, args)
 | 
						|
}
 | 
						|
 | 
						|
// tracefsUprobe creates a Uprobe tracefs entry.
 | 
						|
func tracefsUprobe(args probeArgs) (*perfEvent, error) {
 | 
						|
	return tracefsProbe(uprobeType, args)
 | 
						|
}
 | 
						|
 | 
						|
// sanitizeSymbol replaces every invalid character for the tracefs api with an underscore.
 | 
						|
// It is equivalent to calling regexp.MustCompile("[^a-zA-Z0-9]+").ReplaceAllString("_").
 | 
						|
func sanitizeSymbol(s string) string {
 | 
						|
	var b strings.Builder
 | 
						|
	b.Grow(len(s))
 | 
						|
	var skip bool
 | 
						|
	for _, c := range []byte(s) {
 | 
						|
		switch {
 | 
						|
		case c >= 'a' && c <= 'z',
 | 
						|
			c >= 'A' && c <= 'Z',
 | 
						|
			c >= '0' && c <= '9':
 | 
						|
			skip = false
 | 
						|
			b.WriteByte(c)
 | 
						|
 | 
						|
		default:
 | 
						|
			if !skip {
 | 
						|
				b.WriteByte('_')
 | 
						|
				skip = true
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	return b.String()
 | 
						|
}
 | 
						|
 | 
						|
// uprobeToken creates the PATH:OFFSET(REF_CTR_OFFSET) token for the tracefs api.
 | 
						|
func uprobeToken(args probeArgs) string {
 | 
						|
	po := fmt.Sprintf("%s:%#x", args.path, args.offset)
 | 
						|
 | 
						|
	if args.refCtrOffset != 0 {
 | 
						|
		// This is not documented in Documentation/trace/uprobetracer.txt.
 | 
						|
		// elixir.bootlin.com/linux/v5.15-rc7/source/kernel/trace/trace.c#L5564
 | 
						|
		po += fmt.Sprintf("(%#x)", args.refCtrOffset)
 | 
						|
	}
 | 
						|
 | 
						|
	return po
 | 
						|
}
 | 
						|
 | 
						|
func uretprobeBit() (uint64, error) {
 | 
						|
	uprobeRetprobeBit.once.Do(func() {
 | 
						|
		uprobeRetprobeBit.value, uprobeRetprobeBit.err = determineRetprobeBit(uprobeType)
 | 
						|
	})
 | 
						|
	return uprobeRetprobeBit.value, uprobeRetprobeBit.err
 | 
						|
}
 |