libbpfgo 使用示例:在 ebpf 程序中获取进程信息

前言

大多数基于 ebpf 技术的程序都有需要在 ebpf 程序中获取相应事件发生时触发该事件的进程信息的需求, 本文记录一下如何在 ebpf 程序中获取常见的进程信息。

获取进程信息

在 linux 中, task_struct 结构体包含了进程相关的信息,所以我们可以从 bpf_get_current_task() 获取到的 task 实例中获取想要的进程信息:比如 pid、ppid、进程名称、进程 namespace 信息等信息。

同时, bpf-helpers 中也提供了一些辅助版本我们获取相关信息的辅助函数,比如前面所说的 bpf_get_current_task() 函数。

获取 host 层面的 pid 信息

首先是如何获取 host 层面的 pid 信息,之所以加个 host 层面是因为在类似容器的场景,进程有两个 pid 信息,一个是 host 上看到的 pid,另一个是容器中特定 pid namespace 下看到的 pid。

可以通过 bpf-helpers 提供的 bpf_get_current_pid_tgid() 函数(封装了对 task->tgidtask->pid 的调用)获取对应的 host 层面的 pid 信息:

u32 host_pid = bpf_get_current_pid_tgid() >> 32;

有了 pid,一般也会需要 ppid 即父进程的 pid。ppid 我们就只能从 task 从获取了。 首先是需要通过 task->real_parent 拿到父进程的 task 信息,然后再通过 task->tgid 获取对应的 pid 信息:

struct task_struct *task = (struct task_struct *)bpf_get_current_task();
u32 host_ppid = task->real_parent->tgid;

获取 userspace(用户态) 层面的 pid 信息

如上面所说,在容器等使用了独立的 pid namspace 的场景下,会出现对应 pid namespace 下看到的的 pid 跟 host 上的 pid 不一样的情况,所以我们也需要获取一下这个 userspace(用户态) 层面的 pid 信息。

主要是通过 task->nsproxy 拿到 nsproxy 信息, nsproxy 的结构体定义如下:

/*
 * A structure to contain pointers to all per-process
 * namespaces - fs (mount), uts, network, sysvipc, etc.
 *
 * The pid namespace is an exception -- it's accessed using
 * task_active_pid_ns.  The pid namespace here is the
 * namespace that children will use.
 *
 * 'count' is the number of tasks holding a reference.
 * The count for each namespace, then, will be the number
 * of nsproxies pointing to it, not the number of tasks.
 *
 * The nsproxy is shared by tasks which share all namespaces.
 * As soon as a single namespace is cloned or unshared, the
 * nsproxy is copied.
 */
struct nsproxy {
    atomic_t count;
    struct uts_namespace *uts_ns;
    struct ipc_namespace *ipc_ns;
    struct mnt_namespace *mnt_ns;
    struct pid_namespace *pid_ns_for_children;
    struct net           *net_ns;
    struct time_namespace *time_ns;
    struct time_namespace *time_ns_for_children;
    struct cgroup_namespace *cgroup_ns;
};

可以看到 nsproxy 中包含了进程相关的各种 namespace 信息。

可以通过下面的方法获取到所需要的 userspace 层面的 pid 信息:

unsigned int level = task->nsproxy->pid_ns_for_children->level;
u32 pid = task->group_leader->thread_pid->numbers[level].nr;

获取对应的 ppid 的方法也是类似的:

unsigned int p_level = task->real_parent->nsproxy->pid_ns_for_children->level;
u32 ppid = task->real_parent->group_leader->thread_pid->numbers[p_level].nr;

获取 namespace 信息

前面已经看到了 nsproxy 中包含了各种 namespace 信息,所以可以直接通过它就拿到 namspace 相关的信息。 比如获取 pid namespace 的 id:

u32 pid_ns_id = task->nsproxy->pid_ns_for_children->ns.ium

获取进程信息的完整代码详见:https://github.com/mozillazg/hello-libbpfgo/tree/master/05-get-process-info


Comments