Как получить путь к задаче cgroup в программе eBPF?
Я пытался поиграть с инструментом tcptop BCC от Брендана Грегга, чтобы узнать больше о том, как работают программы eBPF. Я пытаюсь заставить его распечатать путь CGROUP задач.
С моими ржавыми знаниями системного программирования Linux я подумал, что могу использовать функции из linux/cgroup.h
, особенно task_cgroup_path()
выглядело многообещающим, так как я могу передатьtask_struct *
(получен из bpf_get_current_task()
) к нему. Я использую машину CentOS7 с4.19.59
ядро.
Однако, когда я пытаюсь выполнить модифицированный tcptop, верификатор не работает с last insn is not an exit or jmp
сообщение об ошибке. Я пытаюсь понять, почему это происходит.
Вот разница модифицированного tcptop
: patch
Вот верификатор выход
2 ответа
I thought I could use functions from
linux/cgroup.h
No, you cannot.
The only functions that may be called from an eBPF program are:
- Other functions defined in your eBPF code (they can be inlined but this is no longer necessary, eBPF supports function calls),
- eBPF helpers, which are kernel functions specifically exposed to eBPF programs (see their documentation in the source or as a man page),
- Compiler built-ins, such as
__builtin_memcpy()
for example.
Other kernel functions, even if the symbols are exported and exposed to kernel modules, or user functions from the standard library for that matter, are not callable from an eBPF program (they would be traceable with eBPF, but this is different).
Regarding your use case, I am not sure what the best way to get the cgroup path from its id is. You could do it in user space, although I don't know how to get a path from the id. I know the reverse is possible (getting the id from the path, this can be done with the name_to_handle_at()
syscall), so worst case you could iterate on all existing paths and get the ids. If you have a strong use case and some time ahead, a longer-term solution would be to submit a new eBPF helper that would call into task_cgroup_path()
.
[Edit] Regarding the error message returned by the verifier (last insn is not an exit or jmp
): BPF programs support function calls, so you can have several functions (“subprogramsâ€) in your program. Each of those subprograms is a list of instructions, possibly containing forward or backward (under certain conditions) jumps. From the verifier's point of view, the execution of the program must end with an exit
or unconditional backward jump instruction (a “return†from the subprogram), this is to avoid fall-through from one subprog into another
. It wouldn't make sense to do nothing specific (return or exit from whole program) at the end of a function, and it would be dangerous (once JIT-ed, programs are not necessarily contiguous in memory so you couldn't fall through safely, even if that made sense).
Now if we look at how clang compiled your program, we see this for the call to task_cgroup_path()
:
; task_cgroup_path(t, (char *) &cgpath, sizeof(cgpath)); // Line 50
20: bf 01 00 00 00 00 00 00 r1 = r0
21: b7 03 00 00 10 00 00 00 r3 = 16
22: 85 10 00 00 ff ff ff ff call -1
The call -1
is a call to a BPF function (not a kernel helper, but a function expected to be found in your program, you can tell with the source register being set at 1 (BPF_PSEUDO_CALL
) on the second byte of the instruction). So the verifier considers that the target for this jump is a subprogram. Except that because task_cgroup_path()
is not a BPF subprogram, clang could not find it to set a relevant offset and used -1
instead, marking the call -1
as the first instruction of this would-be subprogram. But the last instruction before the call -1
is neither an exit
nor a jump
, so the verifier eventually catches that something is wrong with the program, and rejects it. All this logic happens in function check_subprogs()
.
ниже коды показывают, как получить идентификатор контейнера:
static __always_inline int get_cgroup_name(char *buf, size_t sz) {
struct task_struct *cur_tsk = (struct task_struct *)bpf_get_current_task();
if (cur_tsk == NULL) {
bpf_printk("failed to get cur task\n");
return -1;
}
int cgrp_id = memory_cgrp_id;
// failed when use BPF_PROBE_READ
const char *name = BPF_CORE_READ(cur_tsk, cgroups, subsys[cgrp_id], cgroup, kn, name);
bpf_printk("name: %s\n", name);
if (bpf_probe_read_kernel_str(buf, sz, name) < 0) {
bpf_printk("failed to get kernfs node name: %s\n", buf);
return -1;
}
bpf_printk("cgroup name: %s\n", buf);
return 0;
}
если вы хотите получить cgroupid, возможно, вам следует получить доступtask_struct->cgroups->subsys[cgrp_id]->cgroup->kn->id
.
Затем используйтеfind /sys/fs/cgroup -inum {id}
получим путь к группе