4

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

 2 years ago
source link: https://mozillazg.com/2022/05/ebpf-libbpfgo-get-process-info.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

前言

大多数基于 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->tgid 和 task->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


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK