简体   繁体   English

参数列表太长,无法通过bpf系统调用加载eBPF程序

[英]Argument list too long to when loading an eBPF program via the bpf syscall

I am trying to load an eBPF program via the bpf syscall in Go but am seeing an error returned from the syscall. 我试图通过Go中的bpf系统调用加载一个eBPF程序,但是我看到系统调用返回了一个错误。 In order to restrict the problem I am using the following minimal eBPF program, which does nothing: 为了限制问题,我使用以下最小的eBPF程序,它什么都不做:

struct task_group {};    

The important parts of the Go program are as follows: 围棋计划的重要部分如下:

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)
}

However the error that's getting returned is Unable to load program: argument list too long . 但是,返回的错误是Unable to load program: argument list too long Why is this? 为什么是这样? Or better yet, how can I get a more verbose output to find out the root cause of the issue? 或者更好的是,我如何获得更详细的输出以找出问题的根本原因?

From here there are only three places that E2BIG (argument list too long) gets returned from the bpf syscall, but none of them seem to fit. 这里只有三个地方E2BIG (参数列表太长)从bpf系统调用返回,但它们似乎都不适合。

I can provide a more complete version of my code if needed, I just tried to strip out the irrelevant parts for brevity. 如果需要,我可以提供更完整的代码版本,我只是为了简洁而试图删除不相关的部分。

To help with recreating this issue, I have included my full BPF program below. 为了帮助重新创建此问题,我在下面提供了完整的BPF计划。 The full repo is here : 完整的回购在这里

#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;
}

Your problem here is the way you try to load the BPF bytecode. 这里的问题是您尝试加载BPF字节码的方式。

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

I have never used Go, but from what I understand this reads all the bytes from the ELF object file, without any specific processing, and feed them to the bpf() syscall later in your code. 我从来没有使用过Go,但据我所知,这将读取ELF目标文件中的所有字节,无需任何特定处理,并在代码中稍后将它们提供给bpf()系统调用。

The thing is, this is not how things work: when it compiles into eBPF, clang puts your program into one particular section (by default, .text , but you could specify another name). 事情是,这不是事情的工作方式:当它编译成eBPF时,clang将你的程序放入一个特定的部分(默认情况下, .text ,但你可以指定另一个名字)。 In addition, if you use eBPF maps, some magic happens (“map relocation”) so that your ELF file can embed map info, and your userspace program calling to bpf() can retrieve it and send it to the kernel. 此外,如果您使用eBPF地图,会发生一些神奇的事情(“地图重定位”),以便您的ELF文件可以嵌入地图信息,而调用bpf()用户空间程序可以检索它并将其发送到内核。

So when you load the whole file to send it to bpf() , you load your actual bytecode, plus all ELF sections and header. 因此,当您加载整个文件以将其发送到bpf() ,您将加载实际的字节码,以及所有ELF部分和标题。 The kernel probably does not like it much. 内核可能不喜欢它。 I don't know how to fix it in Go, but here are some pointers that might be helpful: 我不知道如何在Go中修复它,但这里有一些可能有用的指针:

  • libbpf, a C library that can load eBPF programs from the ELF files: located in the kernel tree . libbpf,一个可以从ELF文件加载eBPF程序的C库:位于内核树中
  • Gobpf, some framework to use eBPF programs with Go ( link ). Gobpf,一些使用Go( 链接 )的eBPF程序的框架。 I've never used it, but surely they would have some code to load programs from object files? 我从来没有使用它,但他们肯定会有一些代码从目标文件加载程序?

See @Qeole's answer for the actual cause of this error message. 有关此错误消息的实际原因,请参阅@ Qeole的答案。

You need a non-empty BPF program. 您需要一个非空的BPF程序。 Otherwise, you will fail the following precondition in bpf_prog_load : 否则,您将在bpf_prog_load失败以下前提条件:

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

Your current compiled BPF program appears to be empty since it does not contain any function. 您当前编译的BPF程序似乎是空的,因为它不包含任何功能。 Therefore, attr->insn_cnt is null. 因此, attr->insn_cnt为null。


Details I've checked that attr->insn_cnt is actually null: 细节我已经检查过attr->insn_cnt实际上是null:

$ 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.

The object file is not empty but its .text section, which should contains the BPF instructions, is. 目标文件不为空,但其.text部分应该包含BPF指令。 If I run readelf -x .text tmp.o on one of my own programs I get a hexdump, as expected. 如果我在我自己的一个程序上运行readelf -x .text tmp.o ,我会得到一个hexdump,正如预期的那样。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM