Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(helpers): kernel_symbols change and fix #399

Merged
merged 3 commits into from
Jan 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
487 changes: 231 additions & 256 deletions helpers/kernel_symbols.go

Large diffs are not rendered by default.

87 changes: 31 additions & 56 deletions helpers/kernel_symbols_test.go
Original file line number Diff line number Diff line change
@@ -1,70 +1,45 @@
package helpers

import (
"math/rand"
"reflect"
"testing"
"time"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

// run tests as such
// alias mysudo='sudo -E env "PATH=$PATH"'
// cd helpers
// mysudo go test
func TestKernelSymbols(t *testing.T) {
const (
owner = "system"
symbol = "do_sys_openat2"
)
syms, err := NewKernelSymbolsMap()
require.NoError(t, err)
lSyms, err := NewLazyKernelSymbolsMap()
require.NoError(t, err)
res, err1 := syms.GetSymbolByName(owner, symbol)
res2, err2 := lSyms.GetSymbolByName(owner, symbol)
require.NoError(t, err1)
require.NoError(t, err2)
assert.Equal(t, res, res2)
addr := res.Address
res, err1 = syms.GetSymbolByAddr(addr)
res2, err2 = lSyms.GetSymbolByAddr(addr)
require.NoError(t, err1)
require.NoError(t, err2)
assert.Equal(t, res, res2)
// TestParseLine tests the parseLine function.
func TestParseLine(t *testing.T) {
testCases := []struct {
line []string
expected *KernelSymbol
}{
{[]string{"00000000", "t", "my_symbol", "[my_owner]"}, &KernelSymbol{Name: "my_symbol", Type: "t", Address: 0, Owner: "my_owner"}},
// Add more test cases as needed.
}

for _, tc := range testCases {
result := parseLine(tc.line)
if !reflect.DeepEqual(result, tc.expected) {
t.Errorf("parseLine(%v) = %v; want %v", tc.line, result, tc.expected)
}
}
}

// run benchmarks as such
// alias mysudo='sudo -E env "PATH=$PATH"'
// cd helpers
// mysudo go test -bench=.
func TestRefresh(t *testing.T) {
kst, err := NewKernelSymbolTable()
if err != nil {
t.Fatalf("NewKernelSymbolTable() failed: %v", err)
}

const (
max = 812435456
start = 0xffffffff90000000
end = 0xffffffffc06cc738
)
// Test well-known symbols like _stext and _etext.
symbolsToTest := []string{"_stext", "_etext"}

func BenchmarkLazySymByAddr(b *testing.B) {
rand.Seed(time.Now().UnixNano())
seed := uint64(rand.Intn(max))
addr := seed + uint64(start)
syms, err := NewLazyKernelSymbolsMap()
require.NoError(b, err)
for i := 0; i < b.N; i++ {
syms.GetSymbolByAddr(addr)
for _, symbol := range symbolsToTest {
if syms, err := kst.GetSymbolByName(symbol); err != nil || len(syms) == 0 {
t.Errorf("Expected to find symbol %s, but it was not found", symbol)
}
}
}

func BenchmarkLazySymByAddrNotBinary(b *testing.B) {
rand.Seed(time.Now().UnixNano())
seed := uint64(rand.Intn(max))
addr := seed + uint64(start)
syms, err := NewLazyKernelSymbolsMap()
lSyms := syms.(*lazyKernelSymbols)
require.NoError(b, err)
for i := 0; i < b.N; i++ {
lSyms.getSymbolByAddrNotBinary(addr)
// Text the text swegment contains function.
if _, err := kst.TextSegmentContains(0); err != nil {
t.Errorf("TextSegmentContains failed: %v", err)
}
}
24 changes: 24 additions & 0 deletions libbpfgo.c
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,30 @@ void cgo_bpf_tc_hook_free(struct bpf_tc_hook *hook)
free(hook);
}

struct bpf_kprobe_opts *cgo_bpf_kprobe_opts_new(__u64 bpf_cookie,
size_t offset,
bool retprobe,
int attach_mode)
{
struct bpf_kprobe_opts *opts;
opts = calloc(1, sizeof(*opts));
if (!opts)
return NULL;

opts->sz = sizeof(*opts);
opts->bpf_cookie = bpf_cookie;
opts->offset = offset;
opts->retprobe = retprobe;
opts->attach_mode = attach_mode;

return opts;
}

void cgo_bpf_kprobe_opts_free(struct bpf_kprobe_opts *opts)
{
free(opts);
}

//
// struct getters
//
Expand Down
6 changes: 6 additions & 0 deletions libbpfgo.h
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,12 @@ void cgo_bpf_tc_opts_free(struct bpf_tc_opts *opts);
struct bpf_tc_hook *cgo_bpf_tc_hook_new();
void cgo_bpf_tc_hook_free(struct bpf_tc_hook *hook);

struct bpf_kprobe_opts *cgo_bpf_kprobe_opts_new(__u64 bpf_cookie,
size_t offset,
bool retprobe,
int attach_mode);
void cgo_bpf_kprobe_opts_free(struct bpf_kprobe_opts *opts);

//
// struct getters
//
Expand Down
100 changes: 80 additions & 20 deletions prog.go
Original file line number Diff line number Diff line change
Expand Up @@ -370,41 +370,101 @@ func (p *BPFProg) AttachPerfEvent(fd int) (*BPFLink, error) {
return bpfLink, nil
}

// this API should be used for kernels > 4.17
func (p *BPFProg) AttachKprobe(kp string) (*BPFLink, error) {
return doAttachKprobe(p, kp, false)
}
//
// Kprobe and Kretprobe
//

// this API should be used for kernels > 4.17
func (p *BPFProg) AttachKretprobe(kp string) (*BPFLink, error) {
return doAttachKprobe(p, kp, true)
type attachTo struct {
symName string
symAddr uint64
isRet bool
}

func doAttachKprobe(prog *BPFProg, kp string, isKretprobe bool) (*BPFLink, error) {
kpC := C.CString(kp)
defer C.free(unsafe.Pointer(kpC))
// attachKprobeCommon is a common function for attaching kprobe and kretprobe.
func (p *BPFProg) attachKprobeCommon(a attachTo) (*BPFLink, error) {
// Create kprobe_opts.
optsC, errno := C.cgo_bpf_kprobe_opts_new(
C.ulonglong(0), // bpf cookie (not used)
C.size_t(a.symAddr), // might be 0 if attaching using symbol name
C.bool(a.isRet), // is kretprobe or kprobe
C.int(0), // attach mode (default)
)
if optsC == nil {
return nil, fmt.Errorf("failed to create kprobe_opts of %v: %v", a, errno)
}
defer C.cgo_bpf_kprobe_opts_free(optsC)

// Create kprobe symbol name.
symNameC := C.CString(a.symName)
defer C.free(unsafe.Pointer(symNameC))

linkC, errno := C.bpf_program__attach_kprobe(prog.prog, C.bool(isKretprobe), kpC)
// Create kprobe link.
var linkC *C.struct_bpf_link
linkC, errno = C.bpf_program__attach_kprobe_opts(p.prog, symNameC, optsC)
if linkC == nil {
return nil, fmt.Errorf("failed to attach %s k(ret)probe to program %s: %w", kp, prog.Name(), errno)
return nil, fmt.Errorf("failed to attach to %v: %v", a, errno)
}

kpType := Kprobe
if isKretprobe {
kpType = Kretprobe
linkType := Kprobe
if a.isRet {
linkType = Kretprobe
}

eventName := a.symName
if eventName == "" {
eventName = fmt.Sprintf("%d", a.symAddr)
}

// Create bpfLink and append it to the module.
bpfLink := &BPFLink{
link: linkC,
prog: prog,
linkType: kpType,
eventName: kp,
link: linkC, // linkC is a pointer to a struct bpf_link
prog: p, // p is a pointer to the related BPFProg
linkType: linkType, // linkType is a BPFLinkType
eventName: eventName, // eventName is a string
}
prog.module.links = append(prog.module.links, bpfLink)
p.module.links = append(p.module.links, bpfLink)

return bpfLink, nil
}

// AttachKprobe attaches the BPFProgram to the given symbol name.
func (p *BPFProg) AttachKprobe(symbol string) (*BPFLink, error) {
a := attachTo{
symName: symbol,
isRet: false,
}
return p.attachKprobeCommon(a)
}

// AttachKretprobe attaches the BPFProgram to the given symbol name (for return).
func (p *BPFProg) AttachKretprobe(symbol string) (*BPFLink, error) {
a := attachTo{
symName: symbol,
isRet: true,
}
return p.attachKprobeCommon(a)
}

// AttachKprobeOnOffset attaches the BPFProgram to the given offset.
func (p *BPFProg) AttachKprobeOffset(offset uint64) (*BPFLink, error) {
a := attachTo{
symAddr: offset,
isRet: false,
}
return p.attachKprobeCommon(a)
}

// AttachKretprobeOnOffset attaches the BPFProgram to the given offset (for return).
func (p *BPFProg) AttachKretprobeOnOffset(offset uint64) (*BPFLink, error) {
a := attachTo{
symAddr: offset,
isRet: true,
}
return p.attachKprobeCommon(a)
}

// End of Kprobe and Kretprobe

func (p *BPFProg) AttachNetns(networkNamespacePath string) (*BPFLink, error) {
fd, err := syscall.Open(networkNamespacePath, syscall.O_RDONLY, 0)
if fd < 0 {
Expand Down
1 change: 1 addition & 0 deletions selftest/tracing-by-offset/Makefile
14 changes: 14 additions & 0 deletions selftest/tracing-by-offset/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
module github.com/aquasecurity/libbpfgo/selftest/tracing-by-offset

go 1.18

require (
github.com/aquasecurity/libbpfgo v0.4.7-libbpf-1.2.0-b2e29a1
github.com/aquasecurity/libbpfgo/helpers v0.4.5
)

require golang.org/x/sys v0.7.0 // indirect

replace github.com/aquasecurity/libbpfgo => ../../

replace github.com/aquasecurity/libbpfgo/helpers => ../../helpers
6 changes: 6 additions & 0 deletions selftest/tracing-by-offset/go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU=
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
14 changes: 14 additions & 0 deletions selftest/tracing-by-offset/main.bpf.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
//+build ignore

#include <vmlinux.h>

#include <bpf/bpf_helpers.h>

SEC("kprobe/sys_mmap")
int kprobe__sys_mmap(struct pt_regs *ctx)
{
bpf_printk("Hello, World!\n");
return 0;
}

char LICENSE[] SEC("license") = "Dual BSD/GPL";
70 changes: 70 additions & 0 deletions selftest/tracing-by-offset/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package main

import "C"

import (
"os"
"runtime"
"time"

"fmt"
"syscall"

bpf "github.com/aquasecurity/libbpfgo"
"github.com/aquasecurity/libbpfgo/helpers"
)

func main() {
funcName := fmt.Sprintf("__%s_sys_mmap", ksymArch())

kst, err := helpers.NewKernelSymbolTable()
if err != nil {
fmt.Fprintln(os.Stderr, "NewKernelSymbolTable() failed: %v", err)
os.Exit(-1)
}

funcSymbol, err := kst.GetSymbolByName(funcName)
if err != nil {
fmt.Fprintln(os.Stderr, "Expected to find symbol %s, but it was not found", funcSymbol)
os.Exit(-1)
}

bpfModule, err := bpf.NewModuleFromFile("main.bpf.o")
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(-1)
}
defer bpfModule.Close()

bpfModule.BPFLoadObject()
prog, err := bpfModule.GetProgram("kprobe__sys_mmap")
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(-1)
}

_, err = prog.AttachKprobeOffset(funcSymbol[0].Address)
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(-1)
}

go func() {
time.Sleep(time.Second)
syscall.Mmap(999, 999, 999, 1, 1)
syscall.Mmap(999, 999, 999, 1, 1)
}()

time.Sleep(time.Second * 2)
}

func ksymArch() string {
switch runtime.GOARCH {
case "amd64":
return "x64"
case "arm64":
return "arm64"
default:
panic("unsupported architecture")
}
}
1 change: 1 addition & 0 deletions selftest/tracing-by-offset/run.sh
Loading