2

Recently, I am trying to use bpf ringbuf in uprobe example of libbpf. But when running, error occurred which is "libbpf: load bpf program failed: Invalid argument". I have no idea why this happened. Could anyone help? Below is my test code. Kernel space code: uprobe.bpf.c, define a rb struct, and use bpf_ringbuf_reserve in uprobe code block.

#include <linux/bpf.h>
#include <linux/ptrace.h>
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_tracing.h>

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

struct {
    __uint(type, BPF_MAP_TYPE_RINGBUF);
    __uint(max_entries, 256 * 1024);
} rb SEC(".maps");


SEC("uprobe/func")
int BPF_KPROBE(uprobe, int a, int b)
{
    __u64* e = bpf_ringbuf_reserve(&rb, sizeof(__u64), 0);
    if (!e)
        return 0;
    bpf_printk("UPROBE ENTRY: a = %d, b = %d\n", a, b);
    return 0;
}

SEC("uretprobe/func")
int BPF_KRETPROBE(uretprobe, int ret)
{
    bpf_printk("UPROBE EXIT: return = %d\n", ret);
    return 0;
}

User space code: uprobe.c

#include <errno.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/resource.h>
#include <bpf/libbpf.h>
#include "uprobe.skel.h"

static int libbpf_print_fn(enum libbpf_print_level level, const char *format, va_list args)
{
    return vfprintf(stderr, format, args);
}

static void bump_memlock_rlimit(void)
{
    struct rlimit rlim_new = {
        .rlim_cur   = RLIM_INFINITY,
        .rlim_max   = RLIM_INFINITY,
    };

    if (setrlimit(RLIMIT_MEMLOCK, &rlim_new)) {
        fprintf(stderr, "Failed to increase RLIMIT_MEMLOCK limit!\n");
        exit(1);
    }
}

/* Find process's base load address. We use /proc/self/maps for that,
 * searching for the first executable (r-xp) memory mapping:
 *
 * 5574fd254000-5574fd258000 r-xp 00002000 fd:01 668759                     /usr/bin/cat
 * ^^^^^^^^^^^^                   ^^^^^^^^
 *
 * Subtracting that region's offset (4th column) from its absolute start
 * memory address (1st column) gives us the process's base load address.
 */
static long get_base_addr() {
    size_t start, offset;
    char buf[256];
    FILE *f;

    f = fopen("/proc/self/maps", "r");
    if (!f)
        return -errno;

    while (fscanf(f, "%zx-%*x %s %zx %*[^\n]\n", &start, buf, &offset) == 3) {
        if (strcmp(buf, "r-xp") == 0) {
            fclose(f);
            return start - offset;
        }
    }

    fclose(f);
    return -1;
}

static int handle_event(void *ctx, void *data, size_t data_sz)
{
    return 0;
}

/* It's a global function to make sure compiler doesn't inline it. */
int uprobed_function(int a, int b)
{
    return a + b;
}

int main(int argc, char **argv)
{
    struct ring_buffer *rb = NULL;
    struct uprobe_bpf *skel;
    long base_addr, uprobe_offset;
    int err, i;

    /* Set up libbpf errors and debug info callback */
    libbpf_set_print(libbpf_print_fn);

    /* Bump RLIMIT_MEMLOCK to allow BPF sub-system to do anything */
    bump_memlock_rlimit();

    /* Load and verify BPF application */
    skel = uprobe_bpf__open_and_load();
    if (!skel) {
        fprintf(stderr, "Failed to open and load BPF skeleton\n");
        return 1;
    }

    base_addr = get_base_addr();
    if (base_addr < 0) {
        fprintf(stderr, "Failed to determine process's load address\n");
        err = base_addr;
        goto cleanup;
    }

    /* uprobe/uretprobe expects relative offset of the function to attach
     * to. This offset is relateve to the process's base load address. So
     * easy way to do this is to take an absolute address of the desired
     * function and substract base load address from it.  If we were to
     * parse ELF to calculate this function, we'd need to add .text
     * section offset and function's offset within .text ELF section.
     */
    uprobe_offset = (long)&uprobed_function - base_addr;

    /* Attach tracepoint handler */
    skel->links.uprobe = bpf_program__attach_uprobe(skel->progs.uprobe,
                            false /* not uretprobe */,
                            0 /* self pid */,
                            "/proc/self/exe",
                            uprobe_offset);
    err = libbpf_get_error(skel->links.uprobe);
    if (err) {
        fprintf(stderr, "Failed to attach uprobe: %d\n", err);
        goto cleanup;
    }

    /* we can also attach uprobe/uretprobe to any existing or future
     * processes that use the same binary executable; to do that we need
     * to specify -1 as PID, as we do here
     */
    skel->links.uretprobe = bpf_program__attach_uprobe(skel->progs.uretprobe,
                               true /* uretprobe */,
                               -1 /* any pid */,
                               "/proc/self/exe",
                               uprobe_offset);
    err = libbpf_get_error(skel->links.uretprobe);
    if (err) {
        fprintf(stderr, "Failed to attach uprobe: %d\n", err);
        goto cleanup;
    }

    /* Set up ring buffer polling */
    rb = ring_buffer__new(bpf_map__fd(skel->maps.rb), handle_event, NULL, NULL);
    if (!rb) {
        err = -1;
        fprintf(stderr, "Failed to create ring buffer\n");
        goto cleanup;
    }

    printf("Successfully started! Please run `sudo cat /sys/kernel/debug/tracing/trace_pipe` "
           "to see output of the BPF programs.\n");

    for (i = 0; ; i++) {
        err = ring_buffer__poll(rb, 100 /* timeout, ms */);
        /* trigger our BPF programs */
        fprintf(stderr, ".");
        uprobed_function(i, i + 1);
        sleep(1);
    }

cleanup:
    ring_buffer__free(rb);
    uprobe_bpf__destroy(skel);
    return -err;
}
Hankin
  • 45
  • 4
  • 1
    You are executing a fair number of libbpf calls, which function caused the error? That might help us narrow it down. "Invalid argument" means an argument to a syscall is bad, it doesn't seem to be program type specific. – Dylan Reimerink Feb 09 '22 at 15:46
  • I have solved this problem. The reason is that it's illegal to use bpf_ringbuf_reserve() without corresponding bpf_ringbuf_submit(). when I use bpf_ringbuf_submit() and bpf_ringbuf_discard() to make sure that no resources(rb) are unreleased, the program works! BPF program has its own rules. – Hankin Mar 25 '22 at 08:39
  • 1
    Right, glad you figured it out. The verifier can be a real pain sometimes. Including the verifier log might help others help you figure things out faster next time. @Hankin you can submit an answer yourself and mark it as accepted, so that others might find the answer in the future, SO will remove this question after a while if it stays unanswered – Dylan Reimerink Mar 25 '22 at 12:45

0 Answers0