eBPF uprobe Go function argument wrong output

103 views Asked by At

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)

enter image description here

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.

1

There are 1 answers

0
Emre Savcı On

I manageed to get the function parameter using PT_REGS_PARM1(ctx) as follow:

int arg1 = PT_REGS_PARM1(ctx);

But somehow I did not understand why the following did not work:

bpf_probe_read(&task_info->param, sizeof(task_info->param), (void*)ctx->sp + 8);