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

Add InitGlobalVariable to support setting global variables in bpf code #258

Merged
merged 8 commits into from
Oct 18, 2022
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
61 changes: 61 additions & 0 deletions elf.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package libbpfgo

import (
"debug/elf"
"encoding/binary"
"errors"
"strings"
)

type Symbol struct {
name string
size int
offset int

sectionName string
byteOrder binary.ByteOrder
}

func getGlobalVariableSymbol(elf *elf.File, varName string) (*Symbol, error) {
regularSymbols, err := elf.Symbols()
if err != nil {
return nil, err
}

var symbols []Symbol
for _, s := range regularSymbols {
i := int(s.Section)
if i >= len(elf.Sections) {
continue
}
sectionName := elf.Sections[i].Name
if isGlobalVariableSection(sectionName) {
symbols = append(symbols, Symbol{
name: s.Name,
size: int(s.Size),
offset: int(s.Value),
sectionName: sectionName,
byteOrder: elf.ByteOrder,
})
}
}

for _, s := range symbols {
if s.name == varName {
return &s, nil
}
}

return nil, errors.New("symbol not found")
}

func isGlobalVariableSection(sectionName string) bool {
if sectionName == ".data" || sectionName == ".rodata" {
return true
}
if strings.HasPrefix(sectionName, ".data.") ||
strings.HasPrefix(sectionName, ".rodata.") {
return true
}
return false
}
72 changes: 72 additions & 0 deletions libbpfgo.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ package libbpfgo
import "C"

import (
"bytes"
"debug/elf"
"encoding/binary"
"errors"
"fmt"
"net"
"path/filepath"
Expand Down Expand Up @@ -42,6 +46,8 @@ type Module struct {
links []*BPFLink
perfBufs []*PerfBuffer
ringBufs []*RingBuffer
elf *elf.File
loaded bool
}

type BPFMap struct {
Expand Down Expand Up @@ -295,6 +301,10 @@ func SetStrictMode(mode LibbpfStrictMode) {
}

func NewModuleFromFileArgs(args NewModuleArgs) (*Module, error) {
f, err := elf.Open(args.BPFObjPath)
if err != nil {
return nil, err
}
C.set_print_fn()

opts := C.struct_bpf_object_open_opts{}
Expand Down Expand Up @@ -324,6 +334,7 @@ func NewModuleFromFileArgs(args NewModuleArgs) (*Module, error) {

return &Module{
obj: obj,
elf: f,
}, nil
}

Expand All @@ -336,6 +347,10 @@ func NewModuleFromBuffer(bpfObjBuff []byte, bpfObjName string) (*Module, error)
}

func NewModuleFromBufferArgs(args NewModuleArgs) (*Module, error) {
f, err := elf.NewFile(bytes.NewReader(args.BPFObjBuff))
if err != nil {
return nil, err
}
C.set_print_fn()

if args.BTFObjPath == "" {
Expand Down Expand Up @@ -364,6 +379,7 @@ func NewModuleFromBufferArgs(args NewModuleArgs) (*Module, error) {

return &Module{
obj: obj,
elf: f,
}, nil
}

Expand All @@ -387,10 +403,50 @@ func (m *Module) BPFLoadObject() error {
if ret != 0 {
return fmt.Errorf("failed to load BPF object: %w", syscall.Errno(-ret))
}
m.loaded = true
m.elf.Close()

return nil
}

// InitGlobalVariable sets global variables (defined in .data or .rodata)
// in bpf code. It must be called before the BPF object is loaded.
func (m *Module) InitGlobalVariable(name string, value interface{}) error {
if m.loaded {
return errors.New("must be called before the BPF object is loaded")
}
s, err := getGlobalVariableSymbol(m.elf, name)
if err != nil {
return err
}
bpfMap, err := m.GetMap(s.sectionName)
if err != nil {
return err
}

// get current value
currMapValue := bpfMap.getInitialValue()

// generate new value
newMapValue := make([]byte, bpfMap.ValueSize())
copy(newMapValue, currMapValue)
data := bytes.NewBuffer(nil)
if err := binary.Write(data, s.byteOrder, value); err != nil {
return err
}
varValue := data.Bytes()
start := s.offset
end := s.offset + len(varValue)
if len(varValue) > s.size || end > bpfMap.ValueSize() {
return errors.New("invalid value")
}
copy(newMapValue[start:end], varValue)

// save new value
err = bpfMap.setInitialValue(unsafe.Pointer(&newMapValue[0]))
return err
}

// BPFMapCreateOpts mirrors the C structure bpf_map_create_opts
type BPFMapCreateOpts struct {
Size uint64
Expand Down Expand Up @@ -631,6 +687,22 @@ func (b *BPFMap) GetValueReadInto(key unsafe.Pointer, value *[]byte) error {
return nil
}

func (b *BPFMap) setInitialValue(value unsafe.Pointer) error {
sz := b.ValueSize()
ret := C.bpf_map__set_initial_value(b.bpfMap, value, C.ulong(sz))
mozillazg marked this conversation as resolved.
Show resolved Hide resolved
if ret != 0 {
return fmt.Errorf("failed to set inital value for map %s: %w", b.name, syscall.Errno(-ret))
}
return nil
}

func (b *BPFMap) getInitialValue() []byte {
value := make([]byte, b.ValueSize())
valuePtr := unsafe.Pointer(&value[0])
C.get_internal_map_init_value(b.bpfMap, valuePtr)
return value
}

// BPFMapBatchOpts mirrors the C structure bpf_map_batch_opts.
type BPFMapBatchOpts struct {
Sz uint64
Expand Down
7 changes: 7 additions & 0 deletions libbpfgo.h
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,13 @@ struct perf_buffer *init_perf_buf(int map_fd, int page_cnt, uintptr_t ctx) {
return pb;
}

void get_internal_map_init_value(struct bpf_map *map, void *value) {
size_t psize;
const void *data;
data = bpf_map__initial_value(map, &psize);
memcpy(value, data, psize);
}

int bpf_prog_attach_cgroup_legacy(
int prog_fd, // eBPF program file descriptor
int target_fd, // cgroup directory file descriptor
Expand Down
1 change: 1 addition & 0 deletions selftest/global-variable/Makefile
9 changes: 9 additions & 0 deletions selftest/global-variable/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
module github.com/aquasecurity/libbpfgo/selftest/global-variable

go 1.18

require github.com/aquasecurity/libbpfgo v0.2.1-libbpf-0.4.0

require golang.org/x/sys v0.0.0-20210514084401-e8d321eab015 // indirect

replace github.com/aquasecurity/libbpfgo => ../../
12 changes: 12 additions & 0 deletions selftest/global-variable/go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
golang.org/x/sys v0.0.0-20210514084401-e8d321eab015 h1:hZR0X1kPW+nwyJ9xRxqZk1vx5RUObAPBdKVvXPDUH/E=
golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
58 changes: 58 additions & 0 deletions selftest/global-variable/main.bpf.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
//+build ignore
#include "vmlinux.h"
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_tracing.h>

#ifdef asm_inline
#undef asm_inline
#define asm_inline asm
#endif

struct {
__uint(type, BPF_MAP_TYPE_RINGBUF);
__uint(max_entries, 1 << 24);
} events SEC(".maps");

struct config_t {
u64 a;
char c[6];
};

struct event_t {
u64 sum;
char c[6];
};

const volatile u32 abc = 1;
const volatile u32 efg = 2;
const volatile struct config_t foobar = {};
const volatile long foo = 3;
volatile int bar = 4;
const volatile int baz SEC(".rodata.baz") = 5;
const volatile int qux SEC(".data.qux") = 6;

long ringbuffer_flags = 0;

SEC("kprobe/sys_mmap")
int kprobe__sys_mmap(struct pt_regs *ctx)
{
struct event_t *event;
int i;

// Reserve space on the ringbuffer for the sample
event = bpf_ringbuf_reserve(&events, sizeof(*event), ringbuffer_flags);
if (!event) {
return 1;
}

event->sum = abc + efg + foobar.a + foo + bar + baz + qux;
for (i=0; i<sizeof(foobar.c); i++) {
event->c[i] = foobar.c[i];
}

bpf_ringbuf_submit(event, ringbuffer_flags);
return 1;
}

char LICENSE[] SEC("license") = "GPL";

98 changes: 98 additions & 0 deletions selftest/global-variable/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
package main

import (
"bytes"
"encoding/binary"
"fmt"
"os"
"reflect"
"syscall"
"time"

bpf "github.com/aquasecurity/libbpfgo"
)

func exitWithErr(err error) {
fmt.Fprintln(os.Stderr, err)
os.Exit(-1)
}

type Event struct {
Sum uint64
A [6]byte
}

type Config struct {
A uint64
B [6]byte
}

func initGlobalVariables(bpfModule *bpf.Module, variables map[string]interface{}) {
for name, value := range variables {
if err := bpfModule.InitGlobalVariable(name, value); err != nil {
exitWithErr(err)
}
}
}

func main() {
bpfModule, err := bpf.NewModuleFromFile("main.bpf.o")
if err != nil {
exitWithErr(err)
}
defer bpfModule.Close()

initGlobalVariables(bpfModule, map[string]interface{}{
"abc": uint32(9),
"efg": uint32(80),
"foobar": Config{A: uint64(700), B: [6]byte{'a', 'b'}},
"foo": uint64(6000),
"bar": uint32(50000),
"baz": uint32(400000),
"qux": uint32(3000000),
})

if err := bpfModule.BPFLoadObject(); err != nil {
exitWithErr(err)
}

prog, err := bpfModule.GetProgram("kprobe__sys_mmap")
if err != nil {
exitWithErr(err)
}
if _, err := prog.AttachKprobe("__x64_sys_mmap"); err != nil {
exitWithErr(err)
}

eventsChannel := make(chan []byte)
rb, err := bpfModule.InitRingBuf("events", eventsChannel)
if err != nil {
exitWithErr(err)
}

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

b := <-eventsChannel

var event Event
err = binary.Read(bytes.NewReader(b), binary.LittleEndian, &event)
if err != nil {
exitWithErr(err)
}

expect := Event{
Sum: 9 + 80 + 700 + 6000 + 50000 + 400000 + 3000000,
A: [6]byte{'a', 'b'},
}
if !reflect.DeepEqual(event, expect) {
fmt.Fprintf(os.Stderr, "want %v but got %v\n", expect, event)
os.Exit(1)
}

rb.Stop()
rb.Close()
}
1 change: 1 addition & 0 deletions selftest/global-variable/run.sh