I wrote a Go code that I want to trace function argument values in eBPF code.
My host machine is M1 MacOS and I running an Ubuntu machine with Lima. I compiled Go program with the following arguments:
CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build main.go
Here is the Go code:
package main
import (
"fmt"
"net/http"
"strconv"
)
//go:noinline
func testFn(arg int32) {
fmt.Println(arg)
}
func main() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
qParam := r.URL.Query().Get("param")
i, _ := strconv.Atoi(qParam)
fmt.Println(&i)
testFn(int32(i))
w.Write([]byte("test"))
})
http.ListenAndServe(":8080", nil)
}
Here is the eBPF code:
#include "vmlinux.h"
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_tracing.h>
#include <bpf/bpf_core_read.h>
char __license[] SEC("license") = "Dual MIT/GPL";
struct event {
u32 param;
};
struct {
__uint(type, BPF_MAP_TYPE_RINGBUF);
__uint(max_entries, 1 << 24);
} events SEC(".maps");
// Force emitting struct event into the ELF.
const struct event *unused __attribute__((unused));
SEC("uprobe/http.HandleFunc")
int uprobe_HttpHandleFunc(struct pt_regs *ctx) {
struct event *task_info;
task_info = bpf_ringbuf_reserve(&events, sizeof(struct event), 0);
if (!task_info) {
return 0;
}
bpf_probe_read(&task_info->param, sizeof(task_info->param), (void*)ctx->sp + 8);
bpf_printk("param --->>>> %d\n", task_info->param);
bpf_ringbuf_submit(task_info, 0);
return 0;
}
Here is eBPF Go code:
package main
import (
"bytes"
"encoding/binary"
"errors"
"log"
"os"
"os/signal"
"syscall"
"github.com/cilium/ebpf/link"
"github.com/cilium/ebpf/ringbuf"
"github.com/cilium/ebpf/rlimit"
)
// $BPF_CLANG and $BPF_CFLAGS are set by the Makefile.
//go:generate go run github.com/cilium/ebpf/cmd/bpf2go -cc $BPF_CLANG -cflags $BPF_CFLAGS -target arm64 -type event bpf ../test.bpf.c -- -I/bpf/usr/include
func main() {
// Name of the kernel function to trace.
//fn := "net/http.(*conn).serve"
fn := "main.testFn"
// Subscribe to signals for terminating the program.
stopper := make(chan os.Signal, 1)
signal.Notify(stopper, os.Interrupt, syscall.SIGTERM)
// Allow the current process to lock memory for eBPF resources.
if err := rlimit.RemoveMemlock(); err != nil {
log.Fatal(err)
}
// Load pre-compiled programs and maps into the kernel.
objs := bpfObjects{}
if err := loadBpfObjects(&objs, nil); err != nil {
log.Fatalf("loading objects: %v", err)
}
defer objs.Close()
ex, err := link.OpenExecutable("/Users/emre.savci/Desktop/my-projects/ebpf-test/main")
if err != nil {
log.Fatalf("opening uprobe executable: %s", err)
}
uprobe, err := ex.Uprobe(fn, objs.UprobeHttpHandleFunc, nil)
if err != nil {
log.Fatalf("creating uprobe: %s", err)
}
defer uprobe.Close()
// Open a ringbuf reader from userspace RINGBUF map described in the
// eBPF C program.
rd, err := ringbuf.NewReader(objs.Events)
if err != nil {
log.Fatalf("opening ringbuf reader: %s", err)
}
defer rd.Close()
// Close the reader when the process receives a signal, which will exit
// the read loop.
go func() {
<-stopper
if err := rd.Close(); err != nil {
log.Fatalf("closing ringbuf reader: %s", err)
}
}()
log.Println("Waiting for events..")
// bpfEvent is generated by bpf2go.
var event bpfEvent
for {
record, err := rd.Read()
if err != nil {
if errors.Is(err, ringbuf.ErrClosed) {
log.Println("Received signal, exiting..")
return
}
log.Printf("reading from reader: %s", err)
continue
}
// Parse the ringbuf event entry into a bpfEvent structure.
if err := binary.Read(bytes.NewBuffer(record.RawSample), binary.LittleEndian, &event); err != nil {
log.Printf("parsing ringbuf event: %s", err)
continue
}
log.Printf("event struct %+v\n", event)
}
}
When I run those projects it give me the following outputs:
(top left side comes from eBPF ring_buffer, so eBPF code somehow not reads the right value)
I expected eBPF code to print my url query parameter every time. But instead it shows an another same value for every function call.
BTW, I got couple of times correct value in the eBPF code.
I manageed to get the function parameter using PT_REGS_PARM1(ctx) as follow:
But somehow I did not understand why the following did not work: