Repository: cilium/ebpf
Pull request #332 (2021-06-29 10:21:20)

cmd/bpf2go: make formatting errors more descriptive
Include the offending line of generated source code if formatting
fails. This makes it easier to diagnose template problems:

    13:6: expected 'IDENT', found '.': .Name.Constants struct {} (and 1 more errors)


Patch:
diff --git a/cmd/bpf2go/output.go b/cmd/bpf2go/output.go
index 368c120..5b3578e 100644
--- a/cmd/bpf2go/output.go
+++ b/cmd/bpf2go/output.go
@@ -2,8 +2,10 @@ package main
 
 import (
 	"bytes"
+	"errors"
 	"fmt"
 	"go/format"
+	"go/scanner"
 	"go/token"
 	"io"
 	"io/ioutil"
@@ -277,12 +279,35 @@ func binaryString(buf []byte) string {
 
 func writeFormatted(src []byte, out io.Writer) error {
 	formatted, err := format.Source(src)
-	if err != nil {
-		return fmt.Errorf("can't format source: %s", err)
+	if err == nil {
+		_, err = out.Write(formatted)
+		return err
+	}
+
+	var el scanner.ErrorList
+	if !errors.As(err, &el) {
+		return err
+	}
+
+	var nel scanner.ErrorList
+	for _, err := range el {
+		if !err.Pos.IsValid() {
+			nel = append(nel, err)
+			continue
+		}
+
+		buf := src[err.Pos.Offset:]
+		nl := bytes.IndexRune(buf, '\n')
+		if nl == -1 {
+			nel = append(nel, err)
+			continue
+		}
+
+		err.Msg += ": " + string(buf[:nl])
+		nel = append(nel, err)
 	}
 
-	_, err = out.Write(formatted)
-	return err
+	return nel
 }
 
 func identifier(str string) string {
diff --git a/link/uprobe.go b/link/uprobe.go
index 11b8273..2bc395e 100644
--- a/link/uprobe.go
+++ b/link/uprobe.go
@@ -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")
@@ -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
@@ -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
 	}
@@ -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
 	}
@@ -141,7 +163,7 @@ 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)
 	}
@@ -149,20 +171,28 @@ func (ex *Executable) uprobe(symbol string, prog *ebpf.Program, ret bool) (*perf
 		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
 	}
@@ -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)
 	}


Test patch:
diff --git a/link/uprobe_test.go b/link/uprobe_test.go
index 1a279b4..c293c8f 100644
--- a/link/uprobe_test.go
+++ b/link/uprobe_test.go
@@ -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()
 
@@ -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)
 
@@ -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()
 
@@ -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)
 	}