Skip to content

Commit

Permalink
link: expose kprobe's maxactive parameter
Browse files Browse the repository at this point in the history
This commit exposes kretprobe's maxactive parameter to the user.
You can modify the limit on the number of concurrent events with
maxactive. This feature is currently supported from kernel version
4.12. Using maxactive on kernels lower than version 4.12 will result
in the creation of events with names that do not match expectations.

Signed-off-by: daikunhai <daikunhai@didiglobal.com>
  • Loading branch information
daikunhai committed Dec 12, 2022
1 parent 7fb0b56 commit a253f5f
Show file tree
Hide file tree
Showing 4 changed files with 86 additions and 20 deletions.
59 changes: 50 additions & 9 deletions link/kprobe.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ type probeType uint8
type probeArgs struct {
symbol, group, path string
offset, refCtrOffset, cookie uint64
pid int
pid, retprobeMaxActive int
ret bool
}

Expand All @@ -49,6 +49,12 @@ type KprobeOptions struct {
// Can be used to insert kprobes at arbitrary offsets in kernel functions,
// e.g. in places where functions have been inlined.
Offset uint64
// Increase the maximum number of concurrent invocations of a kretprobe.
// Required when tracing some long running functions in the kernel.
//
// Deprecated: this setting forces the use of an outdated kernel API and is not portable
// across kernel versions.
RetprobeMaxActive int
}

const (
Expand Down Expand Up @@ -191,6 +197,7 @@ func kprobe(symbol string, prog *ebpf.Program, opts *KprobeOptions, ret bool) (*
}

if opts != nil {
args.retprobeMaxActive = opts.RetprobeMaxActive
args.cookie = opts.Cookie
args.offset = opts.Offset
}
Expand Down Expand Up @@ -243,6 +250,11 @@ func pmuProbe(typ probeType, args probeArgs) (*perfEvent, error) {
return nil, err
}

// Use tracefs if we want to set kretprobe's retprobeMaxActive.
if args.retprobeMaxActive != 0 {
return nil, fmt.Errorf("pmu probe: non-zero retprobeMaxActive: %w", ErrNotSupported)
}

var config uint64
if args.ret {
bit, err := typ.RetprobeBit()
Expand Down Expand Up @@ -391,12 +403,25 @@ func tracefsProbe(typ probeType, args probeArgs) (_ *perfEvent, err error) {
// If a livepatch handler is already active on the symbol, the write to
// tracefs will succeed, a trace event will show up, but creating the
// perf event will fail with EBUSY.
_ = closeTraceFSProbeEvent(typ, args.group, args.symbol)
_ = closeTraceFSProbeEvent(typ, args.group, args.symbol, false)
}
}()

// Get the newly-created trace event's id.
tid, err := getTraceEventID(group, args.symbol)
if errors.Is(err, os.ErrNotExist) {
if typ == kprobeType && args.ret == true && args.retprobeMaxActive != 0 {
// In kernels earlier than 4.12, if maxactive is used to create kretprobe events,
// the event names created are not expected. We need to delete that event and
// create again without maxactive.
_ = closeTraceFSProbeEvent(typ, args.group, args.symbol, true)
args.retprobeMaxActive = 0
if err = createTraceFSProbeEvent(typ, args); err != nil {
return nil, fmt.Errorf("creating probe entry on tracefs: %w", err)
}
tid, err = getTraceEventID(group, args.symbol)
}
}
if err != nil {
return nil, fmt.Errorf("getting trace event id: %w", err)
}
Expand All @@ -420,8 +445,15 @@ func tracefsProbe(typ probeType, args probeArgs) (_ *perfEvent, err error) {
// createTraceFSProbeEvent creates a new ephemeral trace event by writing to
// <tracefs>/[k,u]probe_events. 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.
// if a probe with the same group and symbol already exists. Returns os.ErrInvalid
// if maxactive is != 0 for anything but a kretprobe.
func createTraceFSProbeEvent(typ probeType, args probeArgs) error {
if args.retprobeMaxActive != 0 {
if !args.ret || typ == uprobeType {
return fmt.Errorf("maxactive must be 0 except kretprobe: %w", os.ErrInvalid)
}
}

// Open the kprobe_events file in tracefs.
f, err := os.OpenFile(typ.EventsPath(), os.O_APPEND|os.O_WRONLY, 0666)
if err != nil {
Expand All @@ -447,7 +479,7 @@ func createTraceFSProbeEvent(typ probeType, args probeArgs) error {
// the eBPF program itself.
// See Documentation/kprobes.txt for more details.
token = kprobeToken(args)
pe = fmt.Sprintf("%s:%s/%s %s", probePrefix(args.ret), args.group, sanitizeSymbol(args.symbol), token)
pe = fmt.Sprintf("%s:%s/%s %s", probePrefix(args.ret, args.retprobeMaxActive), args.group, sanitizeSymbol(args.symbol), token)
case uprobeType:
// The uprobe_events syntax is as follows:
// p[:[GRP/]EVENT] PATH:OFFSET [FETCHARGS] : Set a probe
Expand All @@ -460,7 +492,7 @@ func createTraceFSProbeEvent(typ probeType, args probeArgs) error {
//
// See Documentation/trace/uprobetracer.txt for more details.
token = uprobeToken(args)
pe = fmt.Sprintf("%s:%s/%s %s", probePrefix(args.ret), args.group, args.symbol, token)
pe = fmt.Sprintf("%s:%s/%s %s", probePrefix(args.ret, 0), args.group, args.symbol, token)
}
_, err = f.WriteString(pe)

Expand Down Expand Up @@ -489,8 +521,9 @@ func createTraceFSProbeEvent(typ probeType, args probeArgs) error {
}

// closeTraceFSProbeEvent removes the [k,u]probe with the given type, group and symbol
// from <tracefs>/[k,u]probe_events.
func closeTraceFSProbeEvent(typ probeType, group, symbol string) error {
// from <tracefs>/[k,u]probe_events. The lowkernel flag is used to delete kretprobe
// events that use maxactive in low version kernels (kernel < 4.12)
func closeTraceFSProbeEvent(typ probeType, group, symbol string, lowKernel bool) error {
f, err := os.OpenFile(typ.EventsPath(), os.O_APPEND|os.O_WRONLY, 0666)
if err != nil {
return fmt.Errorf("error opening %s: %w", typ.EventsPath(), err)
Expand All @@ -499,7 +532,12 @@ func closeTraceFSProbeEvent(typ probeType, group, symbol string) error {

// See [k,u]probe_events syntax above. The probe type does not need to be specified
// for removals.
pe := fmt.Sprintf("-:%s/%s", group, sanitizeSymbol(symbol))
var pe string
if lowKernel {
pe = fmt.Sprintf("-:kprobes/r_%s_0", symbol)
} else {
pe = fmt.Sprintf("-:%s/%s", group, sanitizeSymbol(symbol))
}
if _, err = f.WriteString(pe); err != nil {
return fmt.Errorf("writing '%s' to '%s': %w", pe, typ.EventsPath(), err)
}
Expand Down Expand Up @@ -529,8 +567,11 @@ func randomGroup(prefix string) (string, error) {
return group, nil
}

func probePrefix(ret bool) string {
func probePrefix(ret bool, maxActive int) string {
if ret {
if maxActive > 0 {
return fmt.Sprintf("r%d", maxActive)
}
return "r"
}
return "p"
Expand Down
33 changes: 29 additions & 4 deletions link/kprobe_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,31 @@ func TestKprobeOffset(t *testing.T) {
t.Fatal("Can't attach with non-zero offset")
}

func TestKretprobeMaxActive(t *testing.T) {
// Requires at least 4.12
// 696ced4 "tracing/kprobes: expose maxactive for kretprobe in kprobe_events"
testutils.SkipOnOldKernel(t, "4.12", "kretprobe maxactive")

prog := mustLoadProgram(t, ebpf.Kprobe, 0, "")

k, err := Kprobe("do_sys_open", prog, &KprobeOptions{RetprobeMaxActive: 4096})
if !errors.Is(err, os.ErrInvalid) {
fmt.Printf("err:%v", err)
t.Fatal(err)
}
if k != nil {
k.Close()
}

k, err = Kretprobe("do_sys_open", prog, &KprobeOptions{RetprobeMaxActive: 4096})
if err != nil {
t.Fatal(err)
}
if k != nil {
k.Close()
}
}

func TestKretprobe(t *testing.T) {
prog := mustLoadProgram(t, ebpf.Kprobe, 0, "")

Expand Down Expand Up @@ -251,8 +276,8 @@ func TestKprobeCreateTraceFS(t *testing.T) {

// Tee up cleanups in case any of the Asserts abort the function.
defer func() {
_ = closeTraceFSProbeEvent(kprobeType, pg, ksym)
_ = closeTraceFSProbeEvent(kprobeType, rg, ksym)
_ = closeTraceFSProbeEvent(kprobeType, pg, ksym, false)
_ = closeTraceFSProbeEvent(kprobeType, rg, ksym, false)
}()

// Prepare probe args.
Expand All @@ -269,7 +294,7 @@ func TestKprobeCreateTraceFS(t *testing.T) {
qt.Commentf("expected consecutive kprobe creation to contain os.ErrExist, got: %v", err))

// Expect a successful close of the kprobe.
c.Assert(closeTraceFSProbeEvent(kprobeType, pg, ksym), qt.IsNil)
c.Assert(closeTraceFSProbeEvent(kprobeType, pg, ksym, false), qt.IsNil)

args.group = rg
args.ret = true
Expand All @@ -283,7 +308,7 @@ func TestKprobeCreateTraceFS(t *testing.T) {
qt.Commentf("expected consecutive kretprobe creation to contain os.ErrExist, got: %v", err))

// Expect a successful close of the kretprobe.
c.Assert(closeTraceFSProbeEvent(kprobeType, rg, ksym), qt.IsNil)
c.Assert(closeTraceFSProbeEvent(kprobeType, rg, ksym, false), qt.IsNil)
}

func TestKprobeTraceFSGroup(t *testing.T) {
Expand Down
6 changes: 3 additions & 3 deletions link/perf_event.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,12 +93,12 @@ func (pe *perfEvent) Close() error {
case kprobeEvent, kretprobeEvent:
// Clean up kprobe tracefs entry.
if pe.tracefsID != 0 {
return closeTraceFSProbeEvent(kprobeType, pe.group, pe.name)
return closeTraceFSProbeEvent(kprobeType, pe.group, pe.name, false)
}
case uprobeEvent, uretprobeEvent:
// Clean up uprobe tracefs entry.
if pe.tracefsID != 0 {
return closeTraceFSProbeEvent(uprobeType, pe.group, pe.name)
return closeTraceFSProbeEvent(uprobeType, pe.group, pe.name, false)
}
case tracepointEvent:
// Tracepoint trace events don't hold any extra resources.
Expand Down Expand Up @@ -272,7 +272,7 @@ func getTraceEventID(group, name string) (uint64, error) {
name = sanitizeSymbol(name)
tid, err := uint64FromFile(tracefsPath, "events", group, name, "id")
if errors.Is(err, os.ErrNotExist) {
return 0, fmt.Errorf("trace event %s/%s: %w", group, name, os.ErrNotExist)
return 0, err
}
if err != nil {
return 0, fmt.Errorf("reading trace event ID of %s/%s: %w", group, name, err)
Expand Down
8 changes: 4 additions & 4 deletions link/uprobe_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -265,8 +265,8 @@ func TestUprobeCreateTraceFS(t *testing.T) {

// Tee up cleanups in case any of the Asserts abort the function.
defer func() {
_ = closeTraceFSProbeEvent(uprobeType, pg, ssym)
_ = closeTraceFSProbeEvent(uprobeType, rg, ssym)
_ = closeTraceFSProbeEvent(uprobeType, pg, ssym, false)
_ = closeTraceFSProbeEvent(uprobeType, rg, ssym, false)
}()

// Prepare probe args.
Expand All @@ -288,7 +288,7 @@ func TestUprobeCreateTraceFS(t *testing.T) {
qt.Commentf("expected consecutive uprobe creation to contain os.ErrExist, got: %v", err))

// Expect a successful close of the kprobe.
c.Assert(closeTraceFSProbeEvent(uprobeType, pg, ssym), qt.IsNil)
c.Assert(closeTraceFSProbeEvent(uprobeType, pg, ssym, false), qt.IsNil)

args.group = rg
args.ret = true
Expand All @@ -302,7 +302,7 @@ func TestUprobeCreateTraceFS(t *testing.T) {
qt.Commentf("expected consecutive uretprobe creation to contain os.ErrExist, got: %v", err))

// Expect a successful close of the uretprobe.
c.Assert(closeTraceFSProbeEvent(uprobeType, rg, ssym), qt.IsNil)
c.Assert(closeTraceFSProbeEvent(uprobeType, rg, ssym, false), qt.IsNil)
}

func TestUprobeSanitizedSymbol(t *testing.T) {
Expand Down

0 comments on commit a253f5f

Please sign in to comment.