[英]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中修复它,但这里有一些可能有用的指针:
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.