Слишком длинный список аргументов при загрузке программы 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, как и ожидалось.