diff --git a/README.md b/README.md index 7937e3f3..5fdb50df 100644 --- a/README.md +++ b/README.md @@ -60,5 +60,5 @@ sudo -E go run examples/bcc/perf/perf.go ## Tests ``` -go test -tags integration -v ./... +sudo go test -tags integration -v ./... ``` diff --git a/bpf_test.go b/bpf_test.go index 8a8a4969..cd10e215 100644 --- a/bpf_test.go +++ b/bpf_test.go @@ -20,17 +20,21 @@ import ( "bytes" "encoding/binary" "fmt" + "github.com/iovisor/gobpf/pkg/test" + "io/ioutil" "os" "path/filepath" "strconv" "syscall" "testing" + "time" "unsafe" "github.com/iovisor/gobpf/bcc" "github.com/iovisor/gobpf/elf" "github.com/iovisor/gobpf/pkg/bpffs" "github.com/iovisor/gobpf/pkg/progtestrun" + "github.com/stretchr/testify/assert" ) // redefine flags here as cgo in test is not supported @@ -351,8 +355,8 @@ func checkMaps(t *testing.T, b *elf.Module) { func checkProbes(t *testing.T, b *elf.Module) { var expectedProbes = []string{ - "kprobe/dummy", - "kretprobe/dummy", + "kprobe/__x64_sys_write", + "kretprobe/__x64_sys_write", } var probes []*elf.Kprobe @@ -626,6 +630,89 @@ func checkProgTestRun(t *testing.T, b *elf.Module) { } } +func checkPerfArray(t *testing.T, b *elf.Module) { + receiverChan := make(chan []byte, 100) + lostChan := make(chan uint64, 100) + + perfMap, err := elf.InitPerfMap(b, "dummy_perf", receiverChan, lostChan) + if err != nil { + t.Fatal(err) + } + + perfMap.PollStart() + defer perfMap.PollStop() + + var actualS1 *test.S1 + var actualS2 *test.S2 + var actualS8 *test.S8 + + finished := make(chan bool) + + go func() { + L: + for { + select { + case event := <-receiverChan: + s1 := test.ReadS1(event) + if s1 != nil { + actualS1 = s1 + } + s2 := test.ReadS2(event) + if s2 != nil { + actualS2 = s2 + } + s8 := test.ReadS8(event) + if s8 != nil { + actualS8 = s8 + } + if actualS1 != nil && actualS2 != nil && actualS8 != nil { + break L + } + case lost := <-lostChan: + assert.Fail(t, "Unexpectedly lost %d events", lost) + break L + case <-time.After(5000 * time.Millisecond): + assert.Fail(t, "Didn't get all expected messages") + break L + } + } + finished <- true + }() + + // perform test operation that should be detected by kprobe/sys_write + tfd, err := ioutil.TempFile("", "*") + if err != nil { + t.Fatal(err) + } + _, err = tfd.WriteString("bpf_integration_test") + if err != nil { + t.Fatal(err) + } + _ = tfd.Close() + + <-finished + + expectedS1 := test.S1{0x10000011} + expectedS2 := test.S2{0x20000011, 0x20000022} + expectedS8 := test.S8{ + A: 0x80000011, + B: 0x80000022, + C: 0x80000033, + D: 0x80000044, + E: 0x80000055, + F: 0x80000066, + G: 0x80000077, + H: 0x80000088, + } + + assert.NotNil(t, actualS1) + assert.Equal(t, expectedS1, *actualS1) + assert.NotNil(t, actualS2) + assert.Equal(t, expectedS2, *actualS2) + assert.NotNil(t, actualS8) + assert.Equal(t, expectedS8, *actualS8) +} + func TestModuleLoadELF(t *testing.T) { var err error kernelVersion, err = elf.CurrentKernelVersion() @@ -648,6 +735,9 @@ func TestModuleLoadELF(t *testing.T) { "maps/dummy_array_custom": elf.SectionParams{ PinPath: filepath.Join("gobpf-test", "testgroup1"), }, + "maps/dummy_perf": elf.SectionParams{ + PerfRingBufferPageCount: 256, + }, } var closeOptions = map[string]elf.CloseOptions{ "maps/dummy_array_custom": elf.CloseOptions{ @@ -664,9 +754,14 @@ func TestModuleLoadELF(t *testing.T) { if b == nil { t.Fatal("prog is nil") } + b.EnableOptionCompatProbe() if err := b.Load(secParams); err != nil { t.Fatal(err) } + err = b.EnableKprobes(256) + if err != nil { + t.Fatal(err) + } defer func() { if err := b.CloseExt(closeOptions); err != nil { t.Fatal(err) @@ -685,4 +780,5 @@ func TestModuleLoadELF(t *testing.T) { checkUpdateDeleteElement(t, b) checkLookupElement(t, b) checkProgTestRun(t, b) + checkPerfArray(t, b) } diff --git a/elf/perf.go b/elf/perf.go index 585968c8..4a4bf348 100644 --- a/elf/perf.go +++ b/elf/perf.go @@ -369,7 +369,12 @@ func (pm *PerfMap) PollStart() { case 0: break ringBufferLoop // nothing to read case C.PERF_RECORD_SAMPLE: - size := sample.Size - 4 + var size uint32 + if sample.Size > 8 { + size = sample.Size - 4 + } else { + size = sample.Size + } b := C.GoBytes(unsafe.Pointer(&sample.data), C.int(size)) incoming.bytesArray = append(incoming.bytesArray, b) harvestCount++ diff --git a/go.mod b/go.mod index be4d70a1..74142608 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,5 @@ module github.com/iovisor/gobpf go 1.15 + +require github.com/stretchr/testify v1.7.0 diff --git a/pkg/test/test_structs.go b/pkg/test/test_structs.go new file mode 100644 index 00000000..ac4ac899 --- /dev/null +++ b/pkg/test/test_structs.go @@ -0,0 +1,65 @@ +package test + +/* +#include "../../tests/dummy_structs.h" +*/ +import "C" +import ( + "unsafe" +) + +type S1 struct { + A uint +} +type S2 struct { + A uint + B uint +} +type S8 struct { + A uint + B uint + C uint + D uint + E uint + F uint + G uint + H uint +} + +func ReadS1(data []byte) *S1 { + if len(data) != int(unsafe.Sizeof(C.struct_S1{})) { + return nil + } + eventC := (*C.struct_S1)(unsafe.Pointer(&data[0])) + return &S1{ + A: uint((*eventC).a), + } +} + +func ReadS2(data []byte) *S2 { + if len(data) != int(unsafe.Sizeof(C.struct_S2{})) { + return nil + } + eventC := (*C.struct_S2)(unsafe.Pointer(&data[0])) + return &S2{ + A: uint((*eventC).a), + B: uint((*eventC).b), + } +} + +func ReadS8(data []byte) *S8 { + if len(data) != int(unsafe.Sizeof(C.struct_S8{})) { + return nil + } + eventC := (*C.struct_S8)(unsafe.Pointer(&data[0])) + return &S8{ + A: uint((*eventC).a), + B: uint((*eventC).b), + C: uint((*eventC).c), + D: uint((*eventC).d), + E: uint((*eventC).e), + F: uint((*eventC).f), + G: uint((*eventC).g), + H: uint((*eventC).h), + } +} diff --git a/tests/dummy-410.o b/tests/dummy-410.o index 7e8b938a..a0b1791c 100644 Binary files a/tests/dummy-410.o and b/tests/dummy-410.o differ diff --git a/tests/dummy-414.o b/tests/dummy-414.o index 7e8b938a..a0b1791c 100644 Binary files a/tests/dummy-414.o and b/tests/dummy-414.o differ diff --git a/tests/dummy-46.o b/tests/dummy-46.o index 315c0dd8..9c6e6a63 100644 Binary files a/tests/dummy-46.o and b/tests/dummy-46.o differ diff --git a/tests/dummy-48.o b/tests/dummy-48.o index 052a2682..53dd20eb 100644 Binary files a/tests/dummy-48.o and b/tests/dummy-48.o differ diff --git a/tests/dummy.c b/tests/dummy.c index c6edf072..cfe11f5d 100644 --- a/tests/dummy.c +++ b/tests/dummy.c @@ -4,9 +4,15 @@ #include "../elf/include/uapi/linux/bpf.h" #include "../elf/include/bpf_map.h" +#include "./dummy_structs.h" #define SEC(NAME) __attribute__((section(NAME), used)) +static int (*bpf_perf_event_output)(void *ctx, void *map, + unsigned long long flags, void *data, + int size) = + (void *) BPF_FUNC_perf_event_output; + #define PERF_MAX_STACK_DEPTH 127 #define KERNEL_VERSION_GTE(X) (KERNEL_VERSION >= X) @@ -81,14 +87,30 @@ struct bpf_map_def SEC("maps/dummy_array_custom") dummy_array_custom = { .pinning = PIN_CUSTOM_NS, }; -SEC("kprobe/dummy") -int kprobe__dummy(struct pt_regs *ctx) -{ - return 0; +SEC("kprobe/sys_write") +int kprobe__sys_write(struct pt_regs *ctx) { + struct S1 s1 = {0x10000011}; + struct S2 s2 = {0x20000011, 0x20000022}; + struct S8 s8 = { + .a = 0x80000011, + .b = 0x80000022, + .c = 0x80000033, + .d = 0x80000044, + .e = 0x80000055, + .f = 0x80000066, + .g = 0x80000077, + .h = 0x80000088 + }; + + bpf_perf_event_output(ctx, &dummy_perf, 0, &s1, sizeof(s1)); + bpf_perf_event_output(ctx, &dummy_perf, 0, &s2, sizeof(s2)); + bpf_perf_event_output(ctx, &dummy_perf, 0, &s8, sizeof(s8)); + return 0; } -SEC("kretprobe/dummy") -int kretprobe__dummy(struct pt_regs *ctx) + +SEC("kretprobe/sys_write") +int kretprobe__sys_write(struct pt_regs *ctx) { return 0; } @@ -146,3 +168,5 @@ int xdp_pass(struct xdp_md *ctx) { #endif unsigned int _version SEC("version") = 0xFFFFFFFE; + +char _license[] SEC("license") = "GPL"; diff --git a/tests/dummy.o b/tests/dummy.o index 57ea29d9..119404f2 100644 Binary files a/tests/dummy.o and b/tests/dummy.o differ diff --git a/tests/dummy_structs.h b/tests/dummy_structs.h new file mode 100644 index 00000000..a193dced --- /dev/null +++ b/tests/dummy_structs.h @@ -0,0 +1,18 @@ + +struct S1 { + unsigned int a; +}; +struct S2 { + unsigned int a; + unsigned int b; +}; +struct S8 { + unsigned int a; + unsigned int b; + unsigned int c; + unsigned int d; + unsigned int e; + unsigned int f; + unsigned int g; + unsigned int h; +};