Skip to content

Commit

Permalink
link/uprobe: support user provided offsets
Browse files Browse the repository at this point in the history
Signed-off-by: Mattia Meleleo <[email protected]>
  • Loading branch information
mmat11 authored and lmb committed Jun 29, 2021
1 parent 78bed15 commit a250732
Show file tree
Hide file tree
Showing 2 changed files with 81 additions and 21 deletions.
66 changes: 48 additions & 18 deletions link/uprobe.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,14 @@ type Executable struct {
symbols map[string]elf.Symbol
}

// UprobeOptions defines additional parameters that will be used
// when loading Uprobes.
type UprobeOptions struct {
// Symbol offset. Must be provided in case of external symbols (shared libs).
// If set, overrides the offset eventually parsed from the executable.
Offset uint64
}

// To open a new Executable, use:
//
// OpenExecutable("/bin/bash")
Expand Down Expand Up @@ -78,6 +86,10 @@ func (ex *Executable) addSymbols(f func() ([]elf.Symbol, error)) error {
return err
}
for _, s := range syms {
if elf.ST_TYPE(s.Info) != elf.STT_FUNC {
// Symbol not associated with a function or other executable code.
continue
}
ex.symbols[s.Name] = s
}
return nil
Expand All @@ -95,13 +107,18 @@ func (ex *Executable) symbol(symbol string) (*elf.Symbol, error) {
// For example, /bin/bash::main():
//
// ex, _ = OpenExecutable("/bin/bash")
// ex.Uprobe("main", prog)
// ex.Uprobe("main", prog, nil)
//
// When using symbols which belongs to shared libraries,
// an offset must be provided via options:
//
// ex.Uprobe("main", prog, &UprobeOptions{Offset: 0x123})
//
// The resulting 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) (Link, error) {
u, err := ex.uprobe(symbol, prog, false)
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
}
Expand All @@ -119,13 +136,18 @@ func (ex *Executable) Uprobe(symbol string, prog *ebpf.Program) (Link, error) {
// before the given symbol exits. For example, /bin/bash::main():
//
// ex, _ = OpenExecutable("/bin/bash")
// ex.Uretprobe("main", prog)
// ex.Uretprobe("main", prog, nil)
//
// When using symbols which belongs to shared libraries,
// an offset must be provided via options:
//
// ex.Uretprobe("main", prog, &UprobeOptions{Offset: 0x123})
//
// The resulting 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) (Link, error) {
u, err := ex.uprobe(symbol, prog, true)
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
}
Expand All @@ -141,28 +163,36 @@ func (ex *Executable) Uretprobe(symbol string, prog *ebpf.Program) (Link, error)

// 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, ret bool) (*perfEvent, error) {
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)
}

sym, err := ex.symbol(symbol)
if err != nil {
return nil, fmt.Errorf("symbol '%s' not found in '%s': %w", symbol, ex.path, err)
}
var offset uint64
if opts != nil && opts.Offset != 0 {
offset = opts.Offset
} else {
sym, err := ex.symbol(symbol)
if err != nil {
return nil, fmt.Errorf("symbol '%s' not found: %w", symbol, err)
}

// 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.
if sym.Section == elf.SHN_UNDEF && sym.Value == 0 {
return nil, fmt.Errorf("cannot resolve %s library call '%s', "+
"consider providing the offset via options: %w", ex.path, symbol, ErrNotSupported)
}

// 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.
if sym.Section == elf.SHN_UNDEF && sym.Value == 0 {
return nil, fmt.Errorf("cannot resolve %s library call '%s': %w", ex.path, symbol, ErrNotSupported)
offset = sym.Value
}

// Use uprobe PMU if the kernel has it available.
tp, err := pmuUprobe(sym.Name, ex.path, sym.Value, ret)
tp, err := pmuUprobe(symbol, ex.path, offset, ret)
if err == nil {
return tp, nil
}
Expand All @@ -171,7 +201,7 @@ func (ex *Executable) uprobe(symbol string, prog *ebpf.Program, ret bool) (*perf
}

// Use tracefs if uprobe PMU is missing.
tp, err = tracefsUprobe(uprobeSanitizedSymbol(sym.Name), ex.path, sym.Value, ret)
tp, err = tracefsUprobe(uprobeSanitizedSymbol(symbol), ex.path, offset, ret)
if err != nil {
return nil, fmt.Errorf("creating trace event '%s:%s' in tracefs: %w", ex.path, symbol, err)
}
Expand Down
36 changes: 33 additions & 3 deletions link/uprobe_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ func TestUprobe(t *testing.T) {
}
defer prog.Close()

up, err := bashEx.Uprobe(bashSym, prog)
up, err := bashEx.Uprobe(bashSym, prog, nil)
c.Assert(err, qt.IsNil)
defer up.Close()

Expand All @@ -58,6 +58,36 @@ func TestUprobe(t *testing.T) {
})
}

func TestUprobeExtNotFound(t *testing.T) {
prog, err := ebpf.NewProgram(&kprobeSpec)
if err != nil {
t.Fatal(err)
}
defer prog.Close()

// This symbol will not be present in Executable (elf.SHN_UNDEF).
_, err = bashEx.Uprobe("open", prog, nil)
if err == nil {
t.Fatal("expected error")
}
}

func TestUprobeExtWithOpts(t *testing.T) {
prog, err := ebpf.NewProgram(&kprobeSpec)
if err != nil {
t.Fatal(err)
}
defer prog.Close()

// This Uprobe is broken and will not work because the offset is not
// correct. This is expected since the offset is provided by the user.
up, err := bashEx.Uprobe("open", prog, &UprobeOptions{Offset: 0x12345})
if err != nil {
t.Fatal(err)
}
defer up.Close()
}

func TestUretprobe(t *testing.T) {
c := qt.New(t)

Expand All @@ -67,7 +97,7 @@ func TestUretprobe(t *testing.T) {
}
defer prog.Close()

up, err := bashEx.Uretprobe(bashSym, prog)
up, err := bashEx.Uretprobe(bashSym, prog, nil)
c.Assert(err, qt.IsNil)
defer up.Close()

Expand Down Expand Up @@ -261,7 +291,7 @@ func TestUprobeProgramCall(t *testing.T) {

// Open Uprobe on '/bin/bash' for the symbol 'main'
// and attach it to the ebpf program created above.
u, err := ex.Uprobe("main", p)
u, err := ex.Uprobe("main", p, nil)
if err != nil {
t.Fatal(err)
}
Expand Down

0 comments on commit a250732

Please sign in to comment.