Search code examples
linux-kernelcgroupsebpfbcc-bpf

How to get cgroup path of task in an eBPF program?


I have been trying to play with tcptop BCC tool by Brendan Gregg to learn more about how eBPF programs work. I am trying to get it to print the CGROUP path of the tasks.

With my rusty knowledge of Linux systems programming, I thought I could use functions from linux/cgroup.h, especially task_cgroup_path() looked promising as I can pass current task_struct * (obtained from bpf_get_current_task()) to it. I am using a CentOS7 machine with 4.19.59 kernel.

However, when I try to executed modified tcptop, the verifier fails with last insn is not an exit or jmp error message. I am trying to understand why this is happening.

Here's the diff of modified tcptop: patch

Here's the verifier output


Solution

  • 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().