Search code examples
cbpfebpf

eBPF, track values longer than stack size?


I'm extending a program which take arguments of traced function and print it. Everything works fine with numeric arguments and short strings. But it's not clear how to handle with long string that longer than stack size in eBPF (which is limited with 512 byte).

In example below, string is limited by 80 bytes, of course it can be increased up to 512, but how to track more longer strings?

Example of C program with traced function which called "ameba"

#include <stdio.h>
#include <strings.h>
#include <stdlib.h>
#include <time.h>
#include <unistd.h>

char * ameba(char * s1);

int main(void) {
    printf("%s\n", ameba("verylonglongstring...1111111111111111111111111111111111111111111111111111111111111111111111111111"));
}

char * ameba(char * s1) {
    char *s;
    s = (char *) malloc(128);
    sleep(1);
    snprintf(s, 128, "ameba: %s", s1);
    return s;
}

Example of Go code

package main

import "C"
import (
    "bytes"
    "encoding/binary"
    "fmt"
    "os"
    "os/signal"
    "unsafe"

    bpf "github.com/iovisor/gobpf/bcc"
)

const source string = `
#include <uapi/linux/ptrace.h>
#include <linux/sched.h>

struct ameba_event_t {
        u32 pid;
        char comm[TASK_COMM_LEN];
        char arg1[80];
} __attribute__((packed));

BPF_PERF_OUTPUT(ameba_events);

int get_input_args(struct pt_regs *ctx) {
        struct ameba_event_t event = {};
        if (!PT_REGS_PARM1(ctx))
                return 0;
        event.pid = bpf_get_current_pid_tgid();
        bpf_get_current_comm(&event.comm, sizeof(event.comm));
        bpf_probe_read(&event.arg1, sizeof(event.arg1), (void *)PT_REGS_PARM1(ctx));
        ameba_events.perf_submit(ctx, &event, sizeof(event));

        return 0;
}
`

type amebaEvent struct {
    Pid uint32
    Comm [16]byte
    Arg1 [80]byte
}

func main() {
    m := bpf.NewModule(source, []string{})
    defer m.Close()

    amebaUprobe, err := m.LoadUprobe("get_input_args")
    if err != nil {
        fmt.Fprintf(os.Stderr, "Failed to load get_input_args: %s\n", err)
        os.Exit(1)
    }

    err = m.AttachUprobe("/home/lesovsky/Git/sandbox/ameba", "ameba", amebaUprobe, -1)
    if err != nil {
        fmt.Fprintf(os.Stderr, "Failed to attach input_args: %s\n", err)
        os.Exit(1)
    }

    table := bpf.NewTable(m.TableId("ameba_events"), m)

    channel := make(chan []byte)

    perfMap, err := bpf.InitPerfMap(table, channel)
    if err != nil {
        fmt.Fprintf(os.Stderr, "Failed to init perf map: %s\n", err)
        os.Exit(1)
    }

    sig := make(chan os.Signal, 1)
    signal.Notify(sig, os.Interrupt, os.Kill)

    fmt.Printf("%10s\t%s\t%s\n", "PID", "COMMAND", "ARG1")
    go func() {
        var event amebaEvent
        for {
            data := <-channel
            err := binary.Read(bytes.NewBuffer(data), binary.LittleEndian, &event)
            if err != nil {
                fmt.Printf("failed to decode received data: %s\n", err)
                continue
            }

            comm := (*C.char)(unsafe.Pointer(&event.Comm))
            query := (*C.char)(unsafe.Pointer(&event.Query))
            fmt.Printf("%10d\t%s\t%s\n", event.Pid, C.GoString(comm), C.GoString(query))
        }
    }()

    perfMap.Start()
    <-sig
    perfMap.Stop()
}

I read about BPF_MAP_TYPE_PERCPU_ARRAY could help in such case, but it's not clear for me how to use it.

EDIT: Go program has been modified to using "C" and "unsafe".


Solution

  • I read about BPF_MAP_TYPE_PERCPU_ARRAY could help in such case, but it's not clear for me how to use it.

    You're right. Peeps usually rely on per-cpu arrays to overcome stack size limitations. The following implements that sort of solution, whereby I'm using a per-cpu array to store struct ameba_event_t instead of storing it on the stack.

    I think you're going to need Linux v4.18 to be able to do this (you need commit d71962f). I haven't tested the code as I don't have the right setup at hand, but can later if you run into any issue.

    #include <uapi/linux/ptrace.h>
    #include <linux/sched.h>
    
    struct ameba_event_t {
        u32 pid;
        char comm[TASK_COMM_LEN];
        char arg1[512];
    } __attribute__((packed));
    
    BPF_PERF_OUTPUT(ameba_events);
    BPF_PERCPU_ARRAY(ameba_struct, struct ameba_event_t, 1);
    
    int get_input_args(struct pt_regs *ctx) {
        int zero = 0;
        if (!PT_REGS_PARM1(ctx))
            return 0;
        struct ameba_event_t* event = ameba_struct.lookup(&zero);
        if (!event)
            return 0;
        event->pid = bpf_get_current_pid_tgid();
        bpf_get_current_comm(&event->comm, sizeof(event->comm));
        bpf_probe_read(&event->arg1, sizeof(event->arg1), (void *)PT_REGS_PARM1(ctx));
        ameba_events.perf_submit(ctx, event, sizeof(*event));
        return 0;
    }