123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359 |
- package tracefs
- import (
- "crypto/rand"
- "errors"
- "fmt"
- "os"
- "path/filepath"
- "runtime"
- "strings"
- "syscall"
- "github.com/cilium/ebpf/internal"
- "github.com/cilium/ebpf/internal/unix"
- )
- var (
- ErrInvalidInput = errors.New("invalid input")
- ErrInvalidMaxActive = errors.New("can only set maxactive on kretprobes")
- )
- //go:generate stringer -type=ProbeType -linecomment
- type ProbeType uint8
- const (
- Kprobe ProbeType = iota // kprobe
- Uprobe // uprobe
- )
- func (pt ProbeType) eventsFile() (*os.File, error) {
- path, err := sanitizeTracefsPath(fmt.Sprintf("%s_events", pt.String()))
- if err != nil {
- return nil, err
- }
- return os.OpenFile(path, os.O_APPEND|os.O_WRONLY, 0666)
- }
- type ProbeArgs struct {
- Type ProbeType
- Symbol, Group, Path string
- Offset, RefCtrOffset, Cookie uint64
- Pid, RetprobeMaxActive int
- Ret bool
- }
- // RandomGroup generates a pseudorandom string for use as a tracefs group name.
- // Returns an error when the output string would exceed 63 characters (kernel
- // limitation), when rand.Read() fails or when prefix contains characters not
- // allowed by IsValidTraceID.
- func RandomGroup(prefix string) (string, error) {
- if !validIdentifier(prefix) {
- return "", fmt.Errorf("prefix '%s' must be alphanumeric or underscore: %w", prefix, ErrInvalidInput)
- }
- b := make([]byte, 8)
- if _, err := rand.Read(b); err != nil {
- return "", fmt.Errorf("reading random bytes: %w", err)
- }
- group := fmt.Sprintf("%s_%x", prefix, b)
- if len(group) > 63 {
- return "", fmt.Errorf("group name '%s' cannot be longer than 63 characters: %w", group, ErrInvalidInput)
- }
- return group, nil
- }
- // validIdentifier implements the equivalent of a regex match
- // against "^[a-zA-Z_][0-9a-zA-Z_]*$".
- //
- // Trace event groups, names and kernel symbols must adhere to this set
- // of characters. Non-empty, first character must not be a number, all
- // characters must be alphanumeric or underscore.
- func validIdentifier(s string) bool {
- if len(s) < 1 {
- return false
- }
- for i, c := range []byte(s) {
- switch {
- case c >= 'a' && c <= 'z':
- case c >= 'A' && c <= 'Z':
- case c == '_':
- case i > 0 && c >= '0' && c <= '9':
- default:
- return false
- }
- }
- return true
- }
- func sanitizeTracefsPath(path ...string) (string, error) {
- base, err := getTracefsPath()
- if err != nil {
- return "", err
- }
- l := filepath.Join(path...)
- p := filepath.Join(base, l)
- if !strings.HasPrefix(p, base) {
- return "", fmt.Errorf("path '%s' attempts to escape base path '%s': %w", l, base, ErrInvalidInput)
- }
- return p, nil
- }
- // getTracefsPath will return a correct path to the tracefs mount point.
- // Since kernel 4.1 tracefs should be mounted by default at /sys/kernel/tracing,
- // but may be also be available at /sys/kernel/debug/tracing if debugfs is mounted.
- // The available tracefs paths will depends on distribution choices.
- var getTracefsPath = internal.Memoize(func() (string, error) {
- for _, p := range []struct {
- path string
- fsType int64
- }{
- {"/sys/kernel/tracing", unix.TRACEFS_MAGIC},
- {"/sys/kernel/debug/tracing", unix.TRACEFS_MAGIC},
- // RHEL/CentOS
- {"/sys/kernel/debug/tracing", unix.DEBUGFS_MAGIC},
- } {
- if fsType, err := internal.FSType(p.path); err == nil && fsType == p.fsType {
- return p.path, nil
- }
- }
- return "", errors.New("neither debugfs nor tracefs are mounted")
- })
- // sanitizeIdentifier replaces every invalid character for the tracefs api with an underscore.
- //
- // It is equivalent to calling regexp.MustCompile("[^a-zA-Z0-9]+").ReplaceAllString("_").
- func sanitizeIdentifier(s string) string {
- var skip bool
- return strings.Map(func(c rune) rune {
- switch {
- case c >= 'a' && c <= 'z',
- c >= 'A' && c <= 'Z',
- c >= '0' && c <= '9':
- skip = false
- return c
- case skip:
- return -1
- default:
- skip = true
- return '_'
- }
- }, s)
- }
- // EventID reads a trace event's ID from tracefs given its group and name.
- // The kernel requires group and name to be alphanumeric or underscore.
- func EventID(group, name string) (uint64, error) {
- if !validIdentifier(group) {
- return 0, fmt.Errorf("invalid tracefs group: %q", group)
- }
- if !validIdentifier(name) {
- return 0, fmt.Errorf("invalid tracefs name: %q", name)
- }
- path, err := sanitizeTracefsPath("events", group, name, "id")
- if err != nil {
- return 0, err
- }
- tid, err := internal.ReadUint64FromFile("%d\n", path)
- if errors.Is(err, os.ErrNotExist) {
- return 0, err
- }
- if err != nil {
- return 0, fmt.Errorf("reading trace event ID of %s/%s: %w", group, name, err)
- }
- return tid, nil
- }
- func probePrefix(ret bool, maxActive int) string {
- if ret {
- if maxActive > 0 {
- return fmt.Sprintf("r%d", maxActive)
- }
- return "r"
- }
- return "p"
- }
- // Event represents an entry in a tracefs probe events file.
- type Event struct {
- typ ProbeType
- group, name string
- // event id allocated by the kernel. 0 if the event has already been removed.
- id uint64
- }
- // NewEvent creates a new ephemeral trace event.
- //
- // Returns os.ErrNotExist if symbol is not a valid
- // kernel symbol, or if it is not traceable with kprobes. Returns os.ErrExist
- // if a probe with the same group and symbol already exists. Returns an error if
- // args.RetprobeMaxActive is used on non kprobe types. Returns ErrNotSupported if
- // the kernel is too old to support kretprobe maxactive.
- func NewEvent(args ProbeArgs) (*Event, error) {
- // Before attempting to create a trace event through tracefs,
- // check if an event with the same group and name already exists.
- // Kernels 4.x and earlier don't return os.ErrExist on writing a duplicate
- // entry, so we need to rely on reads for detecting uniqueness.
- eventName := sanitizeIdentifier(args.Symbol)
- _, err := EventID(args.Group, eventName)
- if err == nil {
- return nil, fmt.Errorf("trace event %s/%s: %w", args.Group, eventName, os.ErrExist)
- }
- if err != nil && !errors.Is(err, os.ErrNotExist) {
- return nil, fmt.Errorf("checking trace event %s/%s: %w", args.Group, eventName, err)
- }
- // Open the kprobe_events file in tracefs.
- f, err := args.Type.eventsFile()
- if err != nil {
- return nil, err
- }
- defer f.Close()
- var pe, token string
- switch args.Type {
- case Kprobe:
- // The kprobe_events syntax is as follows (see Documentation/trace/kprobetrace.txt):
- // p[:[GRP/]EVENT] [MOD:]SYM[+offs]|MEMADDR [FETCHARGS] : Set a probe
- // r[MAXACTIVE][:[GRP/]EVENT] [MOD:]SYM[+0] [FETCHARGS] : Set a return probe
- // -:[GRP/]EVENT : Clear a probe
- //
- // Some examples:
- // r:ebpf_1234/r_my_kretprobe nf_conntrack_destroy
- // p:ebpf_5678/p_my_kprobe __x64_sys_execve
- //
- // Leaving the kretprobe's MAXACTIVE set to 0 (or absent) will make the
- // kernel default to NR_CPUS. This is desired in most eBPF cases since
- // subsampling or rate limiting logic can be more accurately implemented in
- // the eBPF program itself.
- // See Documentation/kprobes.txt for more details.
- if args.RetprobeMaxActive != 0 && !args.Ret {
- return nil, ErrInvalidMaxActive
- }
- token = KprobeToken(args)
- pe = fmt.Sprintf("%s:%s/%s %s", probePrefix(args.Ret, args.RetprobeMaxActive), args.Group, eventName, token)
- case Uprobe:
- // The uprobe_events syntax is as follows:
- // p[:[GRP/]EVENT] PATH:OFFSET [FETCHARGS] : Set a probe
- // r[:[GRP/]EVENT] PATH:OFFSET [FETCHARGS] : Set a return probe
- // -:[GRP/]EVENT : Clear a probe
- //
- // Some examples:
- // r:ebpf_1234/readline /bin/bash:0x12345
- // p:ebpf_5678/main_mySymbol /bin/mybin:0x12345(0x123)
- //
- // See Documentation/trace/uprobetracer.txt for more details.
- if args.RetprobeMaxActive != 0 {
- return nil, ErrInvalidMaxActive
- }
- token = UprobeToken(args)
- pe = fmt.Sprintf("%s:%s/%s %s", probePrefix(args.Ret, 0), args.Group, eventName, token)
- }
- _, err = f.WriteString(pe)
- // Since commit 97c753e62e6c, ENOENT is correctly returned instead of EINVAL
- // when trying to create a retprobe for a missing symbol.
- if errors.Is(err, os.ErrNotExist) {
- return nil, fmt.Errorf("token %s: not found: %w", token, err)
- }
- // Since commit ab105a4fb894, EILSEQ is returned when a kprobe sym+offset is resolved
- // to an invalid insn boundary. The exact conditions that trigger this error are
- // arch specific however.
- if errors.Is(err, syscall.EILSEQ) {
- return nil, fmt.Errorf("token %s: bad insn boundary: %w", token, os.ErrNotExist)
- }
- // ERANGE is returned when the `SYM[+offs]` token is too big and cannot
- // be resolved.
- if errors.Is(err, syscall.ERANGE) {
- return nil, fmt.Errorf("token %s: offset too big: %w", token, os.ErrNotExist)
- }
- if err != nil {
- return nil, fmt.Errorf("token %s: writing '%s': %w", token, pe, err)
- }
- // Get the newly-created trace event's id.
- tid, err := EventID(args.Group, eventName)
- if args.RetprobeMaxActive != 0 && errors.Is(err, os.ErrNotExist) {
- // Kernels < 4.12 don't support maxactive and therefore auto generate
- // group and event names from the symbol and offset. The symbol is used
- // without any sanitization.
- // See https://elixir.bootlin.com/linux/v4.10/source/kernel/trace/trace_kprobe.c#L712
- event := fmt.Sprintf("kprobes/r_%s_%d", args.Symbol, args.Offset)
- if err := removeEvent(args.Type, event); err != nil {
- return nil, fmt.Errorf("failed to remove spurious maxactive event: %s", err)
- }
- return nil, fmt.Errorf("create trace event with non-default maxactive: %w", internal.ErrNotSupported)
- }
- if err != nil {
- return nil, fmt.Errorf("get trace event id: %w", err)
- }
- evt := &Event{args.Type, args.Group, eventName, tid}
- runtime.SetFinalizer(evt, (*Event).Close)
- return evt, nil
- }
- // Close removes the event from tracefs.
- //
- // Returns os.ErrClosed if the event has already been closed before.
- func (evt *Event) Close() error {
- if evt.id == 0 {
- return os.ErrClosed
- }
- evt.id = 0
- runtime.SetFinalizer(evt, nil)
- pe := fmt.Sprintf("%s/%s", evt.group, evt.name)
- return removeEvent(evt.typ, pe)
- }
- func removeEvent(typ ProbeType, pe string) error {
- f, err := typ.eventsFile()
- if err != nil {
- return err
- }
- defer f.Close()
- // See [k,u]probe_events syntax above. The probe type does not need to be specified
- // for removals.
- if _, err = f.WriteString("-:" + pe); err != nil {
- return fmt.Errorf("remove event %q from %s: %w", pe, f.Name(), err)
- }
- return nil
- }
- // ID returns the tracefs ID associated with the event.
- func (evt *Event) ID() uint64 {
- return evt.id
- }
- // Group returns the tracefs group used by the event.
- func (evt *Event) Group() string {
- return evt.group
- }
- // KprobeToken creates the SYM[+offs] token for the tracefs api.
- func KprobeToken(args ProbeArgs) string {
- po := args.Symbol
- if args.Offset != 0 {
- po += fmt.Sprintf("+%#x", args.Offset)
- }
- return po
- }
|