首页 > 其他分享 >ebpf-1——ebpf初探

ebpf-1——ebpf初探

时间:2022-11-22 17:22:21浏览次数:52  
标签:bpf ebpf BPF bpftrace comm 内核 初探 TYPE

一、简介

1. eBPF 提供可基于系统或程序事件高效安全执行一段特定代码的通用能力,涵盖了性能分析、系统追踪、网络优化等方面。

2. eBPF 有以下优势

强安全:BPF 验证器(verifier)通过沙盒机制检查每个程序是否能够安全运行在内核中,拒绝不安全的代码。
高性能:JIT编译保证程序本地运行的高性能。
持续交付:程序可以被无缝替换(运行后Ctrl+C就取消了一段代码的附加),并且不影响正在运行的任务。

3. 内核中的 eBPF 虚拟机有10个寄存器,R0-R9 和一个只读的栈指针寄存器R10。


二、BPF Hooks

1. BPF钩子也就是在内核中哪些地方可以加载BPF程序,目前Linux内核中已经有了近10余种的钩子,如下:

(1) kernel functions (kprobes)
(2) userspace functions (uprobes)
(3) system calls
(4) fentry/fexit
(5) tracepoints
(6) network devices (tc/xdp)
(7) network routes
(8) TCP congestion algorithms
(9) sockets (data level)

 

三、BPF Map

用于存储状态数据,是与用户空间交换信息的桥梁。BPF Map是可以被用户空间访问及操作,而且可以与BPF程序分离,即当创建一个BPF Map的BPF程序运行结束后,该BPF Map还能存在,而不是随着程序一起消亡。
可以利用BPF Map持久化数据,在不丢失重要数据的同时,更新BPF程序逻辑,实现在不同程序之间共享和收集统计信息。

用户空间通过map的文件句柄访问,内核空间(bpf kernel程序)通过指针 struct bpf_map * 类型的指针访问、


四、BPF程序类型

1. BPF程序类型定义在 bpf_prog_type 结构中。

enum bpf_prog_type { //include/uapi/linux/bpf.h
    BPF_PROG_TYPE_UNSPEC,
    BPF_PROG_TYPE_SOCKET_FILTER,
    BPF_PROG_TYPE_KPROBE, //基于内核动态插桩点kprobes
    BPF_PROG_TYPE_TRACEPOINT, //基于内核静态跟踪点kprobes
    BPF_PROG_TYPE_PERF_EVENT, //用于 perf_events
    BPF_PROG_TYPE_SCHED_CLS, //用于流量控制分类
    BPF_PROG_TYPE_CGROUP_SKB,
    BPF_PROG_TYPE_CGROUP_SOCK,
    BPF_PROG_TYPE_RAW_TRACEPOINT,
    BPF_PROG_TYPE_CGROUP_SYSCTL,
    BPF_PROG_TYPE_SYSCALL, /* a program that can execute syscalls */
    ...
};

 

五、BPF存储类型

1. BPF程序类型定义在 bpf_map_type 结构中。

enum bpf_map_type {
    BPF_MAP_TYPE_UNSPEC,
    BPF_MAP_TYPE_HASH, //用于hash表的Map类型,保存的是key/val键值对
    BPF_MAP_TYPE_ARRAY, //数组类型
    BPF_MAP_TYPE_PERF_EVENT_ARRAY, //到perf_event环形缓冲区的接口,用于将记录发送到用户空间
    BPF_MAP_TYPE_PERCPU_HASH, //每CPU单独维护的更快的hash表
    BPF_MAP_TYPE_PERCPU_ARRAY, //每CPU单独维护的更快的数组
    BPF_MAP_TYPE_STACK_TRACE, //用于栈存储,使用栈ID进行索引
    BPF_MAP_TYPE_STACK, //调用栈存储
    BPF_MAP_TYPE_RINGBUF,
    ...
};

 

六、eBPF的限制

1. eBPF 程序不能调用任意的内核函数,只限于内核模块中列出的 BPF Helper 函数,如 bpf_get_current_comm() bpf_trace_printk() 等,内核中有相关头文件导出,见 include/uapi/linux/bpf.h
和 external/libbpf/src/bpf_helper_defs.h。这里面的函数支持列表也随着内核的演进在不断增加。

2. eBPF 程序不允许包含无法到达的指令,防止加载无效代码,延迟程序的终止。

3. eBPF 程序中循环次数限制且必须在有限时间内结束,这主要是用来防止在 kprobes 中插入任意的循环,导致锁住整个系统;

4. eBPF 堆栈大小被限制在 MAX_BPF_STACK,截止到内核 Linux 5.15 还是 512 字节。

5. eBPF 字节码大小最初被限制为 4096 条指令,目前已将放宽至 100 万指令,由 BPF_COMPLEXITY_LIMIT_INSNS 宏指定。

 

七、eBPF 加载执行流程

eBPF 程序按上下层来分的话,可分为用户空间和内核两部分。用户空间程序负责加载 BPF 字节码至内核,如需要也会负责读取内核回传的统计信息或者事件详情;加载到内核中的 BPF 代码负责在内核中跟踪执行特定事件,
如需要也会将执行的结果通过 maps 句柄文件或者 perf-event 事件发送至用户空间;

eBPF 程序按功能分的话,可以分为bpf功能程序和bpf加载程序。bpf功能程序就是实现追踪功能的bpf代码部分(prog和map),加载程序会将bpf字节码attach到相关事件,然后激活对事件的采集功能。


八、BCC/eBPF tools

1. BCC(BPF Compiler Collection)是一套基于eBPF的Linux内核分析、跟踪、网络监控工具集。网址: https://github.com/iovisor/bcc 里面有对各个工具的使用进行介绍。

2. 帮助信息

root@localhost:/# bpftool prog --help
Usage: bpftool [OPTIONS] OBJECT { COMMAND | help }
       bpftool batch file FILE
       bpftool version

       OBJECT := { prog | map | link | cgroup | perf | net | feature | btf | gen | struct_ops | iter }
       OPTIONS := { {-j|--json} [{-p|--pretty}] | {-d|--debug} | {-l|--legacy} |
                    {-V|--version} }

3. 使用举例

bpftool map list //遍历所有map信息 查看每个map对应的id
bpftool prog list  //遍历所有prog信息 查看每个prog对应的id
bpftool cgroup help //查看cgroup相关帮助
bpftool prog help

4. 查看gpu_mem 这个bpftrace插入的一段代码

# bpftool prog | grep gpu
15: tracepoint  name tracepoint_gpu_  tag 37955a3ec8581e93
# bpftool prog dump xlated id 15
   0: (61) r2 = *(u32 *)(r1 +8)
   1: (67) r2 <<= 32
   ...

 

九、bpftrace

1. 相关网址

https://github.com/iovisor/bpftrace //里面有对很多 tool/*.bt 的工具进行介绍
https://github.com/iovisor/bpftrace/blob/master/docs/reference_guide.md //会讲述bpftrace的语法和使用示例,重要
https://github.com/iovisor/bpftrace/blob/master/docs/tutorial_one_liners.md //介绍了一些 bpftrace oneline 的使用例子
https://github.com/iovisor/bpftrace/blob/master/docs/tutorial_one_liners_chinese.md //上面文档的中文翻译

2. bpftrace -help 查看其用法

root@localhost:/# bpftrace -help
USAGE:
    bpftrace [options] filename
    bpftrace [options] - <stdin input>
    bpftrace [options] -e 'program'

OPTIONS:
    -B MODE 输出缓冲模式('full', 'none')
    -f FORMAT 输出格式('text', 'json')
    -o file 将 bpftrace 输出重定向到文件
    -e 'program' 执行这个程序
    -h, --help 显示此帮助信息
    -I DIR 将此目录添加到包含搜索路径
    --include FILE 在预处理之前添加一个 #include 文件
    -l [search] 列出探针
    -p PID 在 PID 上启用 USDT 探测
    -c 'CMD' 运行 CMD 并在结果进程上启用 USDT 探测 //命令也写在同一行
    --usdt-file-activation 根据文件路径激活usdt信号量
    --unsafe 允许不安全的内置函数
    -q 保持消息安静
    --info 打印有关内核 BPF 支持的信息
    -k 当 bpf helper 返回错误时发出警告(读取函数除外)
    -kk 检查所有 bpf 辅助函数
    -V, --version bpftrace 版本
    --no-warnings 禁用所有警告信息

故障排除选项:
    -v 详细信息
    -d(试运行)调试信息
    -dd(试运行)详细的调试信息
    --emit-elf FILE (dry run) 用 bpf 程序生成 ELF 文件并写入 FILE
    --emit-llvm FILE 将 LLVM IR 写入 FILE.original.ll 和 FILE.optimized.ll

环境变量:
    BPFTRACE_STRLEN [default: 64] 每个 str() BPF 堆栈上的字节
    BPFTRACE_NO_CPP_DEMANGLE [default: 0] 禁用 C++ 符号分解
    BPFTRACE_MAP_KEYS_MAX [default: 4096] map中的最大keys
    BPFTRACE_CAT_BYTES_MAX [default: 10k] 内置 cat 读取的最大字节数
    BPFTRACE_MAX_PROBES [default: 512] 最大 probes 个数
    BPFTRACE_MAX_BPF_PROGS [default: 512] 生成的 BPF 程序的最大数量
    BPFTRACE_LOG_SIZE [default: 1000000] 以字节为单位的日志大小
    BPFTRACE_PERF_RB_PAGES [default: 64] 每个 CPU 分配给环形缓冲区的页面数
    BPFTRACE_NO_USER_SYMBOLS [default: 0] 禁用用户符号解析
    BPFTRACE_CACHE_USER_SYMBOLS [default: auto] 启用用户符号缓存
    BPFTRACE_VMLINUX [default: none] 用于内核符号解析的 vmlinux 路径
    BPFTRACE_BTF [default: none] BTF 文件

例子:
bpftrace -l '*sleep*' //列出包含"sleep"的探测器,但实测一直报"Segmentation fault",bpftrace -l 'u:/system/framework/arm64/boot-framework.oat:*' 看java可以
bpftrace -e 'kprobe:do_nanosleep { printf("PID %d sleeping...\n", pid); }' //跟踪调用睡眠的进程
bpftrace -e 'tracepoint:raw_syscalls:sys_enter { @[comm] = count(); }' //按进程名称计算系统调用次数

3. 一些oneline的例子

//https://github.com/iovisor/bpftrace

# Files opened by process
bpftrace -e 'tracepoint:syscalls:sys_enter_open { printf("%s %s\n", comm, str(args->filename)); }'

# Syscall count by program
bpftrace -e 'tracepoint:raw_syscalls:sys_enter { @[comm] = count(); }'

# Read bytes by process:
bpftrace -e 'tracepoint:syscalls:sys_exit_read /args->ret/ { @[comm] = sum(args->ret); }'

# Read size distribution by process:
bpftrace -e 'tracepoint:syscalls:sys_exit_read { @[comm] = hist(args->ret); }'

# Show per-second syscall rates:
bpftrace -e 'tracepoint:raw_syscalls:sys_enter { @ = count(); } interval:s:1 { print(@); clear(@); }'

# Trace disk size by process
bpftrace -e 'tracepoint:block:block_rq_issue { printf("%d %s %d\n", pid, comm, args->bytes); }'

# Count page faults by process
bpftrace -e 'software:faults:1 { @[comm] = count(); }'

# Count LLC cache misses by process name and PID (uses PMCs):
bpftrace -e 'hardware:cache-misses:1000000 { @[comm, pid] = count(); }'

# Profile user-level stacks at 99 Hertz, for PID 189:
bpftrace -e 'profile:hz:99 /pid == 189/ { @[ustack] = count(); }'

# Files opened, for processes in the root cgroup-v2
bpftrace -e 'tracepoint:syscalls:sys_enter_openat /cgroup == cgroupid("/sys/fs/cgroup/unified/mycg")/ { printf("%s\n", str(args->filename)); }'

# 内核中确认函数调用路径
bpftrace -e 'kprobe:do_nanosleep { printf("stack: %s\n", kstack(perf)); }'
bpftrace -e 'kretprobe:do_sys_open { printf("returned: %d\n", retval); }'

# 对于此类型的监控参数和返回值
bpftrace -e 'kprobe:proc_file_write { printf("pid=%d, comm=%s, str=%s ", pid, comm, str(uptr(arg1))); } kretprobe:proc_file_write { printf("ret=%d\n", retval);}'

static ssize_t proc_file_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
{
    ...
    return count;
}

# uprobe use demo
bpftrace -e 'uprobe:/bin/bash:0x2ec00 { printf("in here\n"); }'
bpftrace -e 'uretprobe:/bin/bash:readline { printf("read a line\n"); }'

4. bpftrace 常用的内置变量及函数

常见内嵌变量    功能描述
-----------------------------
pid tid            进程组id 线程id
nsecs            时间戳ns
cpu                当前的core id
comm            进程名
kstack()        内核栈
ustack()        用户栈
str()            字符串转换
argX            函数传参,X=0,1,2,3... //内核中arg1是第二个参数
retval            返回值
hist()            直方图显示(power of 2), @histogram_name[optional_key] = hist(value)
lhist()            线性显示, @histogram_name[optional_key] = lhist(value, min, max, step)

5. 一个bpftrace脚本

#include <linux/path.h>
#include <linux/dcache.h>

kprobe:chmod_common
{
    printf("comm=%s, file=%s\n", comm, str(((struct path*)arg0)->dentry->d_name.name));
}

root@localhost:/# bpftrace path.bt
Attaching 1 probe...
comm=queued-work-loo, file=predict_background.xml
comm=usap64, file=0
comm=usap64, file=cgroup.events
...

 

十、/d/kprobes 文件

1. cat /d/kprobes/list 可以看哪些内核函数被kprobe钩住了

root@localhost:/# cat /d/kprobes/list | grep do_nanosleep
root@localhost:/# bpftrace -e 'kprobe:do_nanosleep { printf("stack: %s\n", kstack(perf)); }' &
root@localhost:/# cat /d/kprobes/list | grep do_nanosleep
ffffffe7f0a0ae18  k  do_nanosleep+0x0

2. cat /d/kprobes/blacklist 黑名单,可以看内核中哪些函数是不允许被钩住的

root@localhost:/# cat /d/kprobes/blacklist
...
el0_sync

root@localhost:/# bpftrace -e 'kprobe:el0_sync { printf("stack: %s\n", kstack(perf)); }'
Attaching 1 probe...
cannot attach kprobe, Invalid argument
ERROR: Error attaching probe: 'kprobe:el0_sync'

3. /d/kprobes/enabled 可以动态开关kprobe功能,若是echo 0后,是没有响应的。

root@localhost:/# echo 0 > /d/kprobes/enabled
root@localhost:/# bpftrace -e 'kprobe:do_nanosleep { printf("stack: %s\n", kstack(perf)); }'
Attaching 1 probe...
/* 然后没有任何反应 */

 

十一、实验总结

1. 若是 kprobe 钩不住函数,可以试下 cat /proc/kallsyms 看是否在里面,以防编译器优化了,加上 oninline 修饰。

2. 函数中的任意一行也可以probe的,语法 kprobe:function_name[+offset], 其中offset是汇编后的偏移,但是这是不安全的,默认没有开启这个功能,需要重新编译内核才行。

3. 可以直接在Android设备上编译出 ARM aarch64 的elf文件,但是编译出来的elf文件只能在bpf虚拟机中运行,不能在Android设备中运行!

root@localhost:/testprogs# gcc array_access.c -o array_access
root@localhost:/testprogs# file array_access
array_access: ELF 64-bit LSB pie executable, ARM aarch64, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux-aarch64.so.1, BuildID[sha1]=95930fc79b98ed26f95f7af48e414a41899434a4, 
for GNU/Linux 3.7.0, not stripped /data/local/tmp # ./array_access /system/bin/sh: ./array_access: No such file or directory

 

标签:bpf,ebpf,BPF,bpftrace,comm,内核,初探,TYPE
From: https://www.cnblogs.com/hellokitty2/p/16915788.html

相关文章