From f86b1fdf7a6097857a697ae6674333aa87359b57 Mon Sep 17 00:00:00 2001 From: Jiri Olsa Date: Sun, 10 Dec 2023 21:29:05 +0000 Subject: [PATCH] link: add test for uprobe multi link This commit adds test for uprobe multi link. Signed-off-by: Jiri Olsa --- link/kprobe_test.go | 65 +++++++++++++++++++ link/uprobe_multi_test.go | 133 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 198 insertions(+) create mode 100644 link/uprobe_multi_test.go diff --git a/link/kprobe_test.go b/link/kprobe_test.go index 7ac0a42bd..838a0b120 100644 --- a/link/kprobe_test.go +++ b/link/kprobe_test.go @@ -337,6 +337,71 @@ func newUpdaterMapProg(t *testing.T, typ ebpf.ProgramType, attach ebpf.AttachTyp return m, p } +func newUpdaterMapIncProg(t *testing.T, typ ebpf.ProgramType, attach ebpf.AttachType) (*ebpf.Map, *ebpf.Program) { + // Create ebpf map. Will contain only one key with initial value 0. + m, err := ebpf.NewMap(&ebpf.MapSpec{ + Type: ebpf.Array, + KeySize: 4, + ValueSize: 4, + MaxEntries: 1, + }) + if err != nil { + t.Fatal(err) + } + + // Create ebpf program. When called, will increase the value of key 0 by 1 + // in the map created above + p, err := ebpf.NewProgram(&ebpf.ProgramSpec{ + Type: typ, + Instructions: asm.Instructions{ + // R1 map + asm.LoadMapPtr(asm.R1, m.FD()), + // R2 key + asm.Mov.Reg(asm.R2, asm.R10), + asm.Add.Imm(asm.R2, -4), + asm.StoreImm(asm.R2, 0, 0, asm.Word), + // Lookup map[0] + asm.FnMapLookupElem.Call(), + asm.JEq.Imm(asm.R0, 0, "ret"), + + // u32 val = R0++ + asm.LoadMem(asm.R1, asm.R0, 0, asm.Word), + asm.Add.Imm(asm.R1, 1), + asm.StoreMem(asm.RFP, -8, asm.R1, asm.Word), + + // u32 key = 0 + asm.Mov.Imm(asm.R1, 0), + asm.StoreMem(asm.RFP, -4, asm.R1, asm.Word), + + // bpf_map_update_elem(...) + asm.Mov.Reg(asm.R2, asm.RFP), + asm.Add.Imm(asm.R2, -4), + asm.Mov.Reg(asm.R3, asm.RFP), + asm.Add.Imm(asm.R3, -8), + asm.LoadMapPtr(asm.R1, m.FD()), + asm.Mov.Imm(asm.R4, 0), + asm.FnMapUpdateElem.Call(), + + // exit 0 + asm.Mov.Imm(asm.R0, 0), + asm.Return().WithSymbol("ret"), + }, + AttachType: attach, + License: "Dual MIT/GPL", + }) + if err != nil { + t.Fatal(err) + } + + // Close the program and map on test teardown. + t.Cleanup(func() { + m.Close() + p.Close() + }) + + return m, p +} + func assertMapValue(t *testing.T, m *ebpf.Map, k, v uint32) { var val uint32 if err := m.Lookup(k, &val); err != nil { diff --git a/link/uprobe_multi_test.go b/link/uprobe_multi_test.go new file mode 100644 index 000000000..c8edd0c0d --- /dev/null +++ b/link/uprobe_multi_test.go @@ -0,0 +1,133 @@ +package link + +import ( + "errors" + "os/exec" + "testing" + + "github.com/cilium/ebpf" + "github.com/cilium/ebpf/internal/testutils" + "github.com/cilium/ebpf/internal/unix" +) + +func TestUprobeMulti(t *testing.T) { + testutils.SkipIfNotSupported(t, haveBPFLinkUprobeMulti()) + + prog := mustLoadProgram(t, ebpf.Kprobe, ebpf.AttachTraceUprobeMulti, "") + + um, err := bashEx.UprobeMulti([]string{"bash_logout", "main"}, prog, nil) + if err != nil { + t.Fatal(err) + } + defer um.Close() + + testLink(t, um, prog) +} + +func TestUprobeMultiInput(t *testing.T) { + testutils.SkipIfNotSupported(t, haveBPFLinkUprobeMulti()) + + prog := mustLoadProgram(t, ebpf.Kprobe, ebpf.AttachTraceUprobeMulti, "") + + // One of Symbols or Addresses must be given. + _, err := bashEx.UprobeMulti([]string{}, prog, nil) + if !errors.Is(err, errInvalidInput) { + t.Fatalf("expected errInvalidInput, got: %v", err) + } + + // One Symbol, two cookies.. + _, err = bashEx.UprobeMulti([]string{}, prog, &UprobeMultiOptions{ + Offsets: []uint64{1}, + Cookies: []uint64{2, 3}, + }) + if !errors.Is(err, errInvalidInput) { + t.Fatalf("expected errInvalidInput, got: %v", err) + } +} + +func TestUprobeMultiErrors(t *testing.T) { + testutils.SkipIfNotSupported(t, haveBPFLinkUprobeMulti()) + + prog := mustLoadProgram(t, ebpf.Kprobe, ebpf.AttachTraceUprobeMulti, "") + + // Wrong offset + um, err := bashEx.UprobeMulti([]string{}, prog, &UprobeMultiOptions{Offsets: []uint64{1<<64 - 1}}) + if !errors.Is(err, unix.EINVAL) { + um.Close() + t.Skipf("need kernel change, skipping for now, expected EINVAL, got: %s", err) + } +} + +func TestUprobeMultiCookie(t *testing.T) { + testutils.SkipIfNotSupported(t, haveBPFLinkUprobeMulti()) + + prog := mustLoadProgram(t, ebpf.Kprobe, ebpf.AttachTraceUprobeMulti, "") + + um, err := bashEx.UprobeMulti([]string{"bash_logout", "main"}, prog, + &UprobeMultiOptions{ + Cookies: []uint64{1, 2}, + }) + if err != nil { + t.Fatal(err) + } + _ = um.Close() +} + +func TestUprobeMultiProgramCall(t *testing.T) { + testutils.SkipIfNotSupported(t, haveBPFLinkUprobeMulti()) + + args := []string{"--help"} + elf := "/bin/bash" + + m, p := newUpdaterMapIncProg(t, ebpf.Kprobe, ebpf.AttachTraceUprobeMulti) + + // Load the executable. + ex, err := OpenExecutable(elf) + if err != nil { + t.Fatal(err) + } + + // Open UprobeMulti on the executable for the given symbol + // and attach it to the ebpf program created above. + um, err := ex.UprobeMulti([]string{"main", "_start"}, p, nil) + if errors.Is(err, ErrNoSymbol) { + // Assume bash::main and go::main.main always exists + // and skip the test if the symbol can't be found as + // certain OS (eg. Debian) strip binaries. + t.Skipf("executable %s appear to be stripped, skipping", elf) + } + if err != nil { + t.Fatal(err) + } + + // Trigger ebpf program call. + trigger := func(t *testing.T) { + if err := exec.Command(elf, args...).Run(); err != nil { + t.Fatal(err) + } + } + trigger(t) + + // Assert that the value at index 0 has been updated to 2. + assertMapValue(t, m, 0, 2) + + // Detach the Uprobe. + if err := um.Close(); err != nil { + t.Fatal(err) + } + + // Reset map value to 0 at index 0. + if err := m.Update(uint32(0), uint32(0), ebpf.UpdateExist); err != nil { + t.Fatal(err) + } + + // Retrigger the ebpf program call. + trigger(t) + + // Assert that this time the value has not been updated. + assertMapValue(t, m, 0, 0) +} + +func TestHaveBPFLinkUprobeMulti(t *testing.T) { + testutils.CheckFeatureTest(t, haveBPFLinkUprobeMulti) +}