diff --git a/link/kprobe.go b/link/kprobe.go index 6a84d965c..f689a07bd 100644 --- a/link/kprobe.go +++ b/link/kprobe.go @@ -50,6 +50,10 @@ type KprobeOptions struct { // // Needs kernel 5.15+. Cookie uint64 + // Offset of the kprobe relative to the traced symbol. + // Can be used to insert kprobes at arbitrary offsets in kernel functions, + // e.g. in places where functions have been inlined. + Offset uint64 } const ( @@ -163,6 +167,7 @@ func kprobe(symbol string, prog *ebpf.Program, opts *KprobeOptions, ret bool) (* if opts != nil { args.cookie = opts.Cookie + args.offset = opts.Offset } // Use kprobe PMU if the kernel has it available. @@ -235,8 +240,12 @@ func pmuProbe(typ probeType, args probeArgs) (*perfEvent, error) { } attr = unix.PerfEventAttr{ + // The minimum size required for PMU kprobes is PERF_ATTR_SIZE_VER1, + // since it added the config2 (Ext2) field. Use Ext2 as probe_offset. + Size: unix.PERF_ATTR_SIZE_VER1, Type: uint32(et), // PMU event type read from sysfs Ext1: uint64(uintptr(sp)), // Kernel symbol to trace + Ext2: args.offset, // Kernel symbol offset Config: config, // Retprobe flag } case uprobeType: @@ -392,7 +401,7 @@ func createTraceFSProbeEvent(typ probeType, args probeArgs) error { // subsampling or rate limiting logic can be more accurately implemented in // the eBPF program itself. // See Documentation/kprobes.txt for more details. - pe = fmt.Sprintf("%s:%s/%s %s", probePrefix(args.ret), args.group, sanitizedSymbol(args.symbol), args.symbol) + pe = fmt.Sprintf("%s:%s/%s %s", probePrefix(args.ret), args.group, sanitizedSymbol(args.symbol), kprobeToken(args)) case uprobeType: // The uprobe_events syntax is as follows: // p[:[GRP/]EVENT] PATH:OFFSET [FETCHARGS] : Set a probe @@ -496,3 +505,14 @@ func kretprobeBit() (uint64, error) { }) return kprobeRetprobeBit.value, kprobeRetprobeBit.err } + +// 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 +} diff --git a/link/kprobe_test.go b/link/kprobe_test.go index 444033cd1..34b330601 100644 --- a/link/kprobe_test.go +++ b/link/kprobe_test.go @@ -2,6 +2,8 @@ package link import ( "errors" + "fmt" + "math" "os" "testing" @@ -405,3 +407,50 @@ func TestKprobeCookie(t *testing.T) { } k.Close() } + +func TestKprobeToken(t *testing.T) { + tests := []struct { + args probeArgs + expected string + }{ + {probeArgs{symbol: "symbol"}, "symbol"}, + {probeArgs{symbol: "symbol", offset: 1}, "symbol+0x1"}, + {probeArgs{symbol: "symbol", offset: 65535}, "symbol+0xffff"}, + {probeArgs{symbol: "symbol", offset: 65536}, "symbol+0x10000"}, + } + + for i, tt := range tests { + t.Run(fmt.Sprint(i), func(t *testing.T) { + po := kprobeToken(tt.args) + if tt.expected != po { + t.Errorf("Expected symbol+offset to be '%s', got '%s'", tt.expected, po) + } + }) + } +} + +// Test Kprobe with Offset +func TestKprobeOffset(t *testing.T) { + tests := []struct { + offset uint64 + ok bool + }{ + {0, true}, + {math.MaxUint64, false}, + } + + prog := mustLoadProgram(t, ebpf.Kprobe, 0, "") + for i, tt := range tests { + t.Run(fmt.Sprint(i), func(t *testing.T) { + k, err := Kprobe(ksym, prog, &KprobeOptions{Offset: tt.offset}) + + ok := err == nil + if tt.ok != ok { + t.Errorf("Expected symbol+offset load %v', got '%v'", tt.ok, ok) + } + if ok { + k.Close() + } + }) + } +}