一、简介
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