ebpf_note

ebpf入门系列

来源:https://www.ebpf.top/post/bpf_intro_blog/

一些基础知识

ebpf最早在3.18版本中,也就是说3.18以前的无法使用ebpf方案,但是可能可以使用基本已经废弃的支持2.1.75以后版本的cbpf。

ebpf架构图:

也就是说,内核可以hook的程序类型有:

  • kprobes:内核函数动态追踪
  • uprobes:用户态函数动态追踪
  • tracepoints:内核中的静态追踪(估计用不到正常)
  • perf_events:定时采样和 PMC(需要注释一下)

BCC helloworld

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#!/usr/bin/python3

from bcc import BPF

# This may not work for 4.17 on x64, you need replace kprobe__sys_clone with kprobe____x64_sys_clone
prog = """
int kprobe__sys_execve(void *ctx) {
bpf_trace_printk("Hello, World!\\n");
return 0;
}
"""

b = BPF(text=prog, debug=0x04)
b.trace_print()

每次调用内核函数execve就会打印:“Hello, World!”,同时因为是debug模式所以会同时打印进程名称等信息。

在BPF程序中按照PID过滤进程

Linux系统中进程/线程在内核空间以结构体task_struct进行进程/线程维度的资源隔离。创建方式上Linux线程通过clone函数实现,进程/线程底层都是do_fork函数创建。

POSIX Thread是以一个定义Thread相关函数的API集,NPTL是对这个API集的实现,举个例子,可以使用用户态函数pthread_create()创建一个线程,此时内核就创建了一个task_struct结构体。

用户态和内核态线程的区别:

  • 用户态:负责执行线程的创建、销毁等操作;
  • 内核态:作为调度单元;

进程中第一个创建的线程称作主线程作为线程组的Leader,线程组的id使用tgid标识,主线程的pid与tgid相同。

也就是说,用户态的pid其实就是线程组里主线程的tgid,那么要在BPF中获取pid,获取当前线程组里任意线程task_struct结构体的tgid就行了。

bpf_get_current_pid_tgid的返回值为current->tgid << 32 | current->pid

current->tgid current->pid
用户态pid(占据32位) 用户态tid(占据32位)

因此要获取用户态pid,我们只需要获得bpf_get_current_pid_tgid返回值的高32位即可,即

1
u32 pid = bpf_get_current_pid_tgid()>>32;

若要获取tid,则是:

1
u32 pid = bpf_get_current_pid_tgid();

举个BCC的例子获取所有执行execve函数用户态pid则是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#!/usr/bin/python3

from bcc import BPF

prog = """
int kprobe__sys_execve(void *ctx) {
u32 pid = bpf_get_current_pid_tgid()>>32;
bpf_trace_printk("current pid: %d",pid);
return 0;
}
"""

b = BPF(text=prog, debug=0x04)
b.trace_print()

使用eBPF实时持续跟踪进程文件记录