Слишком длинный список аргументов при загрузке программы eBPF через системный вызов bpf

Я пытаюсь загрузить программу eBPF через bpf системный вызов в Go, но я вижу ошибку, возвращенную из системного вызова. Чтобы ограничить проблему, я использую следующую минимальную программу eBPF, которая ничего не делает:

struct task_group {};    

Важными частями программы Go являются следующие:

b, err := ioutil.ReadFile("bpf/bbf_tty.o")
if err != nil {
    fmt.Print(err)
}

progType := BPF_PROG_TYPE_KPROBE
insns := unsafe.Pointer(&b)
insnCnt := len(b)

lba := struct {
    progType    uint32
    pad0        [4]byte
    insnCnt     uint32
    pad1        [4]byte
    insns       uint64
    license     uint64
    logLevel    uint32
    pad2        [4]byte
    logSize     uint32
    pad3        [4]byte
    logBuf      uint64
    kernVersion uint32
    pad4        [4]byte
}{
    progType:    uint32(progType),
    insns:       uint64(uintptr(insns)),
    insnCnt:     uint32(insnCnt),
    license:     uint64(uintptr(0)),
    logBuf:      uint64(uintptr(0)),
    logSize:     uint32(0),
    logLevel:    uint32(0),
    kernVersion: uint32(4),
}

ret, _, err := unix.Syscall(
    unix.SYS_BPF,
    bpf.BPF_PROG_LOAD,
    uintptr(unsafe.Pointer(&lba)),
    unsafe.Sizeof(lba),
)

if ret != 0 || err != 0 {
    return fmt.Errorf("Unable to load program: %s", err)
}

Однако возвращаемая ошибка Unable to load program: argument list too long, Почему это? Или еще лучше, как я могу получить более подробный вывод, чтобы выяснить причину проблемы?

Отсюда есть только три места, которые E2BIG (список аргументов слишком длинный) возвращается из bpf системный вызов, но ни один из них не подходит.

Я могу предоставить более полную версию своего кода, если это необходимо, я просто попытался вырезать несущественные части для краткости.

Чтобы помочь воссоздать эту проблему, я включил мою полную программу BPF ниже. Полный репо здесь:

#include <node_config.h>
#include <netdev_config.h>
#include <filter_config.h>

#include <bpf/api.h>

#include <stdint.h>
#include <stdio.h>

#include <linux/bpf.h>
#include <linux/if_ether.h>

#include "lib/utils.h"
#include "lib/common.h"
#include "lib/maps.h"
#include "lib/xdp.h"
#include "lib/eps.h"
#include "lib/events.h"

// define structures
enum pid_type
{
    PIDTYPE_PID,
    PIDTYPE_PGID,
    PIDTYPE_SID,
    PIDTYPE_MAX,
    // only valid to __task_pid_nr_ns() 
    __PIDTYPE_TGID
};
struct upid {
  int nr;
};
struct pid
{
  struct upid numbers[1];
};
struct pid_link
{
  struct pid *pid;
};
struct task_group {
};
struct task_struct {
  struct task_struct *group_leader;
  struct pid_link           pids[PIDTYPE_MAX];
};
struct sid_t {
    int sid;
};

#define BUFSIZE 256
struct tty_write_t {
    int count;
    char buf[BUFSIZE];
    unsigned int sessionid;
};

// define maps
struct bpf_elf_map __section_maps active_sids = {
    .type       = BPF_MAP_TYPE_HASH,
    .size_key   = sizeof(struct sid_t),
    .size_value = sizeof(uint64_t),
};

struct bpf_elf_map __section_maps tty_writes = {
    .type       = BPF_MAP_TYPE_PERF_EVENT_ARRAY,
};

// save_sid saves a sessionid generated from a call
// to setsid to the active_sids map
int save_sid(struct pt_regs *ctx) {

    struct sid_t sid_struct = {};
    int sid = PT_REGS_RC(ctx);
    uint64_t time_ns = bpf_ktime_get_ns();

    sid_struct.sid = sid;

    bpf_map_update(&sid_struct, &time_ns);

    return 0;

}

//int kprobe__tty_write(struct pt_regs *ctx, struct file *file, const char __user *buf, size_t count)
int kprobe__tty_write(struct pt_regs *ctx, struct file *file, const char *buf, size_t count)
{
    struct task_struct *task;
    struct pid_link pid_link;
    struct pid pid;
    int sessionid;

    // get current sessionid
    task = (struct task_struct *)bpf_get_current_task();
    bpf_probe_read(&pid_link, sizeof(pid_link), (void *)&task->group_leader->pids[PIDTYPE_SID]);
    bpf_probe_read(&pid, sizeof(pid), (void *)pid_link.pid);
    sessionid = pid.numbers[0].nr;

    // build session struct key
    struct sid_t sid_key;
    sid_key.sid = sessionid;

    // if sid does not exist in our map then return
    //u64 *time_ns = active_sids.lookup(&sid_key);
    //if (!time_ns) {
    //    return 0;
    //}

    // bpf_probe_read() can only use a fixed size, so truncate to count
    // in user space:
    struct tty_write_t tty_write = {};
    bpf_probe_read(&tty_write.buf, BUFSIZE, (void *)buf);
    if (count > BUFSIZE) {
        tty_write.count = BUFSIZE;
    } else {
        tty_write.count = count;
    }

    // add sessionid to tty_write structure and submit
    tty_write.sessionid = sessionid;
    bpf_perf_event_output(ctx, &tty_write, sizeof(tty_write));

    return 0;
}

2 ответа

Решение

Ваша проблема здесь в том, как вы пытаетесь загрузить байт-код BPF.

b, err := ioutil.ReadFile("bpf/bbf_tty.o")

Я никогда не использовал Go, но, насколько я понимаю, он считывает все байты из объектного файла ELF без какой-либо специальной обработки и передает их в bpf() Системный вызов позже в вашем коде.

Дело в том, что это не так: когда компилируется в e BPF, clang помещает вашу программу в один отдельный раздел (по умолчанию .text, но вы можете указать другое имя). Кроме того, если вы используете карты e BPF, происходит какое-то волшебство ("перемещение карты"), так что ваш файл ELF может встраивать информацию о карте, а ваша программа пользовательского пространства вызывает bpf() может получить его и отправить ядру.

Таким образом, когда вы загружаете весь файл, чтобы отправить его bpf(), вы загружаете свой фактический байт-код, а также все разделы ELF и заголовок. Ядру наверное не очень нравится. Я не знаю, как это исправить в Go, но вот несколько советов, которые могут быть полезны:

  • libbpf, библиотека C, которая может загружать программы e BPF из файлов ELF: расположена в дереве ядра.
  • Gobpf, некоторые рамки для использования программ e BPF с Go ( ссылка). Я никогда не использовал его, но наверняка у них был бы некоторый код для загрузки программ из объектных файлов?

См. Ответ @Qeole для фактической причины этого сообщения об ошибке.

Вам нужна непустая программа BPF. В противном случае вы не сможете выполнить следующее предварительное условие в bpf_prog_load:

if (attr->insn_cnt == 0 || attr->insn_cnt > BPF_MAXINSNS)
    return -E2BIG;

Ваша текущая скомпилированная программа BPF кажется пустой, так как она не содержит никакой функции. Следовательно, attr->insn_cnt нулевой.


Подробности я проверил, что attr->insn_cnt на самом деле нуль:

$ cat tmp.c 
struct task_group {};
$ clang -O2 -target bpf -c tmp.c -o tmp.o
$ ls -lh tmp.o 
-rw-rw-r-- 1 paul paul 368 févr.  7 11:21 tmp.o
$ readelf -x .text tmp.o

Section '.text' has no data to dump.

Объектный файл не пустой, но его раздел.text, который должен содержать инструкции BPF, есть. Если я бегу readelf -x .text tmp.o в одной из моих собственных программ я получаю hexdump, как и ожидалось.

Другие вопросы по тегам