eBPF代碼流程分析
0x1:應(yīng)用層流程
基于Linux kernel source v5.13
1.加載bpf.o文件并處理elf section信息
1.int bpf_object__open(char *path) //參數(shù)是bpf.o文件路徑
-- __bpf_object__open(const char *path, const void *obj_buf, size_t obj_buf_sz, const struct bpf_object_open_opts *opts)//讀取obj文件,解析elf中section信息。
-- obj = bpf_object__new(path, obj_buf, obj_buf_sz, obj_name); //創(chuàng)建并初始化obj結(jié)構(gòu)體
err = bpf_object__elf_init(obj); //讀取elf文件
err = err ? : bpf_object__check_endianness(obj); //判斷大小端
err = err ? : bpf_object__elf_collect(obj); //讀取elf節(jié)信息(license / version / maps / .reloc / .text)
err = err ? : bpf_object__collect_externs(obj); //讀取btf section
err = err ? : bpf_object__finalize_btf(obj); //讀取需要 btf處理的data section
err = err ? : bpf_object__init_maps(obj, opts); //讀取map信息(user map / global data map / btf map / kconfig map)
err = err ? : bpf_object__collect_relos(obj); //讀取重定位信息
2.加載obj文件到內(nèi)核
2.int bpf_object__load(struct bpf_object *obj) //加載第一步生成的obj結(jié)構(gòu)體
-- bpf_object__load_xattr(struct bpf_object_load_attr *attr)
-- err = bpf_object__probe_loading(obj); //加載bpf prog到內(nèi)核(這里加載的是未經(jīng)過修改的bpf代碼)
err = err ? : bpf_object__load_vmlinux_btf(obj, false); //讀取內(nèi)核vmlinux信息
err = err ? : bpf_object__resolve_externs(obj, obj->kconfig); //讀取內(nèi)核kconfig /vmlinux / kallsysm信息
err = err ? : bpf_object__sanitize_and_load_btf(obj); // BPF_BTF_LOAD 加載btf信息
err = err ? : bpf_object__sanitize_maps(obj); // 判斷內(nèi)核支持的map種類
err = err ? : bpf_object__init_kern_struct_ops_maps(obj);
err = err ? : bpf_object__create_maps(obj); //BPF_MAP_CREATE 創(chuàng)建map
err = err ? : bpf_object__relocate(obj, attr->target_btf_path); //處理bpf代碼重定位信息
err = err ? : bpf_object__load_progs(obj, attr->log_level); //這里加載經(jīng)過重定位 btf修改的bpf代碼 ****
-- libbpf__bpf_prog_load(const struct bpf_prog_load_params *load_attr)
-- sys_bpf_prog_load(union bpf_attr *attr, unsigned int size)
//調(diào)用sys_bpf(BPF_PROG_LOAD, attr, size) 完成bpf prog的加載
union bpf_attr attr; 是一個(gè)union結(jié)構(gòu),根據(jù)bpf_type的不同,產(chǎn)生不同的結(jié)構(gòu),具體可以在kernel source/include/uapi/linux/bpf.h中查看
0x2:內(nèi)核流程
define __NR_bpf 321 //調(diào)用號在x64下為321
static inline int sys_bpf(enum bpf_cmd cmd, union bpf_attr *attr, unsigned int size)
{
return syscall(__NR_bpf, cmd, attr, size);
}
sys_bpf()
-- __SYS_CALL(_NR_bpf, cmd, attr, size)
-- SYSCALL_DEFINE3(bpf, cmd, uattr, size)
/kernel/bpf/syscall.c/
SYSCALL_DEFINE3(bpf, int, cmd, union bpf_attr __user *, uattr, unsigned int, size) {
//這個(gè)函數(shù)就是內(nèi)核處理應(yīng)用層bpf相關(guān)操作的總?cè)肟冢鶕?jù)cmd參數(shù)的不同,產(chǎn)生不同結(jié)構(gòu)的struct bpf_attr
... ...
copy_from_user(&attr, uattr, size); //拷貝虛擬地址內(nèi)容到內(nèi)核中
security_bpf(cmd, &attr, size); //LSM 框架支持 截止目前v5.13,只實(shí)現(xiàn)了幾個(gè)函數(shù),和selinux/appamor相差甚遠(yuǎn)
switch (cmd) {
case BPF_MAP_CREATE:
err = map_create(&attr); //創(chuàng)建map
break;
case BPF_PROG_LOAD:
err = bpf_prog_load(&attr, uattr); //加載bpf程序
break;
default:
err = -EINVAL;
break;
}
... ...
}
重點(diǎn)看看bpf prog加載流程,熟悉verfiy機(jī)制和jit機(jī)制
static int bpf_prog_load(union bpf_attr *attr, union bpf_attr __user *uattr)
{
... ...
license_is_gpl_compatible(license); // 開源許可證判斷
if (is_net_admin_prog_type(type) && !capable(CAP_NET_ADMIN) && !capable(CAP_SYS_ADMIN)) //如果是net相關(guān)類型,判斷所需權(quán)限是否滿足
if (is_perfmon_prog_type(type) && !perfmon_capable()) //判斷是追蹤相關(guān)類型,判斷所需權(quán)限是否滿足
bpf_prog_alloc(bpf_prog_size(attr->insn_cnt), GFP_USER); //給 struct bpf_prog 申請內(nèi)存,該結(jié)構(gòu)是bpf在內(nèi)核中的實(shí)例
copy_from_user(prog->insns, u64_to_user_ptr(attr->insns),bpf_prog_insn_size(prog)) //拷貝bpf字節(jié)碼到內(nèi)核
bpf_check(&prog, attr, uattr); //bpf verify機(jī)制核心
-- 1.調(diào)用replace_map_fd_with_map_ptr將eBPF匯編中的fd替換為對應(yīng)的map結(jié)構(gòu)體地址。
-- 2.check_subprogs檢查所有條件跳轉(zhuǎn)指令都位于相應(yīng)subprog內(nèi)(本eBPF函數(shù)內(nèi))
-- 3.check_cfg采用深度優(yōu)先算法確保函數(shù)分支不存在循環(huán)和存在執(zhí)行不到的指令。
-- 4.do_check函數(shù)檢查寄存器和參數(shù)的合法性。
-- 5.調(diào)用fix_call_args函數(shù)對多bpf函數(shù)的prog進(jìn)行jit (多sub prog在這里jit,單prog的在下面bpf_prog_select_runtime進(jìn)行jit)
bpf_prog_select_runtime(prog, &err); //bpf jit機(jī)制核心,將bpf字節(jié)碼編譯為目標(biāo)平臺匯編代碼
bpf_audit_prog(prog, BPF_AUDIT_LOAD); //打印一條prog load 的 audit信息
perf_event_bpf_event(prog, PERF_BPF_EVENT_PROG_LOAD, 0); //通過perf機(jī)制加載到對應(yīng)的hook api中
err = bpf_prog_new_fd(prog);//返回給應(yīng)用層bpf prog的fd信息,后續(xù)應(yīng)用層用該fd進(jìn)行操作(詳細(xì)可以看libbpf如何通過fd操作map)
... ...
}
struct bpf_prog {
u16 pages; /* 分配page數(shù) */
u16 jited:1, /* prog是否已經(jīng)jit過*/
jit_requested:1,/* 是否需要jit */
undo_set_mem:1, /* Passed set_memory_ro() checkpoint */
gpl_compatible:1, /* Is filter GPL compatible? */
cb_access:1, /* Is control block accessed? */
dst_needed:1, /* Do we need dst entry? */
blinded:1, /* 常量致盲 */
is_func:1, /* eBPF func? 大多數(shù)情況是 */
kprobe_override:1, /* 是否是overrided kprobe */
has_callchain_buf:1; /* callchain buffer allocated? */
enum bpf_prog_type type; /* prog類型,eg kprobe 、tracepoint*/
enum bpf_attach_type expected_attach_type; /* For some prog types */
u32 len; /* eBPF指令個(gè)數(shù) */
u32 jited_len; /* eBPF匯編指令代碼總長度 */
u8 tag[BPF_TAG_SIZE];
struct bpf_prog_aux *aux; /* Auxiliary fields */
struct sock_fprog_kern *orig_prog; /* Original BPF program */
unsigned int (*bpf_func)(const void *ctx,
const struct bpf_insn *insn);/* 存放jit后的可執(zhí)行匯編 */
/* 不支持jit,需要模擬,x64支持jit,不需要模擬 */
union {
struct sock_filter insns[0]; /* 從用戶態(tài)拷貝來的eBPF原程序 */
struct bpf_insn insnsi[0];
};
};
第一參數(shù)cmd
enum bpf_cmd {
BPF_MAP_CREATE, //前五個(gè)是操作Map的
BPF_MAP_LOOKUP_ELEM,
BPF_MAP_UPDATE_ELEM,
BPF_MAP_DELETE_ELEM,
BPF_MAP_GET_NEXT_KEY,
BPF_PROG_LOAD, //eBPF字節(jié)碼加載
BPF_OBJ_PIN,
BPF_OBJ_GET,
BPF_PROG_ATTACH,
BPF_PROG_DETACH,
BPF_PROG_TEST_RUN,
BPF_PROG_GET_NEXT_ID,
BPF_MAP_GET_NEXT_ID,
BPF_PROG_GET_FD_BY_ID,
BPF_MAP_GET_FD_BY_ID,
BPF_OBJ_GET_INFO_BY_FD,
BPF_PROG_QUERY,
BPF_RAW_TRACEPOINT_OPEN,
BPF_BTF_LOAD, //加載btf信息
BPF_BTF_GET_FD_BY_ID,
BPF_TASK_FD_QUERY,
BPF_MAP_LOOKUP_AND_DELETE_ELEM,
BPF_MAP_FREEZE,
BPF_BTF_GET_NEXT_ID,
BPF_MAP_LOOKUP_BATCH,
BPF_MAP_LOOKUP_AND_DELETE_BATCH,
BPF_MAP_UPDATE_BATCH,
BPF_MAP_DELETE_BATCH,
BPF_LINK_CREATE,
BPF_LINK_UPDATE,
BPF_LINK_GET_FD_BY_ID,
BPF_LINK_GET_NEXT_ID,
BPF_ENABLE_STATS,
BPF_ITER_CREATE,
};
BPF MAP 類型
enum bpf_map_type {
BPF_MAP_TYPE_UNSPEC = 0,
BPF_MAP_TYPE_HASH = 1, //哈希表
BPF_MAP_TYPE_ARRAY = 2, //數(shù)組映射,已針對快速查找速度進(jìn)行了優(yōu)化,通常用于計(jì)數(shù)器
BPF_MAP_TYPE_PROG_ARRAY = 3, //對應(yīng)eBPF程序的文件描述符數(shù)組;用于實(shí)現(xiàn)跳轉(zhuǎn)表和子程序以處理特定的數(shù)據(jù)包協(xié)議
BPF_MAP_TYPE_PERF_EVENT_ARRAY = 4, // linux kernel 4.4 存儲指向struct perf_event的指針,用于讀取和存儲perf事件計(jì)數(shù)器
BPF_MAP_TYPE_PERCPU_HASH = 5, //每個(gè)CPU的哈希表
BPF_MAP_TYPE_PERCPU_ARRAY = 6, //每個(gè)CPU的數(shù)組
BPF_MAP_TYPE_STACK_TRACE = 7, //存儲堆棧跟蹤
BPF_MAP_TYPE_CGROUP_ARRAY = 8, //存儲指向控制組的指針
BPF_MAP_TYPE_LRU_HASH = 9, //僅保留最近使用項(xiàng)目的哈希表
BPF_MAP_TYPE_LRU_PERCPU_HASH = 10, //每個(gè)CPU的哈希表,僅保留最近使用的項(xiàng)目
BPF_MAP_TYPE_LPM_TRIE = 11, //最長前綴匹配樹,適用于將IP地址匹配到某個(gè)范圍
BPF_MAP_TYPE_ARRAY_OF_MAPS = 12,
BPF_MAP_TYPE_HASH_OF_MAPS = 13,
BPF_MAP_TYPE_DEVMAP = 14, //用于存儲和查找網(wǎng)絡(luò)設(shè)備引用
BPF_MAP_TYPE_SOCKMAP = 15, //存儲和查找套接字,并允許使用BPF輔助函數(shù)進(jìn)行套接字重定向
BPF_MAP_TYPE_CPUMAP = 16,
BPF_MAP_TYPE_XSKMAP = 17,
BPF_MAP_TYPE_SOCKHASH = 18,
BPF_MAP_TYPE_CGROUP_STORAGE = 19,
BPF_MAP_TYPE_REUSEPORT_SOCKARRAY = 20,
BPF_MAP_TYPE_PERCPU_CGROUP_STORAGE = 21,
BPF_MAP_TYPE_QUEUE = 22,
BPF_MAP_TYPE_STACK = 23,
BPF_MAP_TYPE_SK_STORAGE = 24,
BPF_MAP_TYPE_DEVMAP_HASH = 25,
BPF_MAP_TYPE_STRUCT_OPS = 26,
BPF_MAP_TYPE_RINGBUF = 27, // linux kernel 5.8 Perf Buffer增強(qiáng)版
BPF_MAP_TYPE_INODE_STORAGE = 28,
};
詳細(xì)介紹: BFP MAP介紹
BPF PROG 類型
【helper函數(shù)使用范圍】不同類型eBPF程序可以使用的eBPF helper函數(shù)范圍
enum bpf_prog_type {
BPF_PROG_TYPE_UNSPEC,
BPF_PROG_TYPE_SOCKET_FILTER, //網(wǎng)絡(luò)數(shù)據(jù)包過濾器
BPF_PROG_TYPE_KPROBE, //確定是否應(yīng)觸發(fā)kprobe
BPF_PROG_TYPE_SCHED_CLS, //網(wǎng)絡(luò)流量控制分類器
BPF_PROG_TYPE_SCHED_ACT, //網(wǎng)絡(luò)流量控制操作
BPF_PROG_TYPE_TRACEPOINT, //確定是否應(yīng)觸發(fā)跟蹤點(diǎn)
BPF_PROG_TYPE_XDP, //從設(shè)備驅(qū)動(dòng)程序接收路徑運(yùn)行的網(wǎng)絡(luò)數(shù)據(jù)包篩選器
BPF_PROG_TYPE_PERF_EVENT, //確定是否應(yīng)該觸發(fā)性能事件處理程序
BPF_PROG_TYPE_CGROUP_SKB, //用于控制組的網(wǎng)絡(luò)數(shù)據(jù)包過濾器
BPF_PROG_TYPE_CGROUP_SOCK, //用于控制組的網(wǎng)絡(luò)數(shù)據(jù)包篩選器,允許修改套接字選項(xiàng)
BPF_PROG_TYPE_LWT_IN,
BPF_PROG_TYPE_LWT_OUT,
BPF_PROG_TYPE_LWT_XMIT,
BPF_PROG_TYPE_SOCK_OPS,
BPF_PROG_TYPE_SK_SKB,
BPF_PROG_TYPE_CGROUP_DEVICE,
BPF_PROG_TYPE_SK_MSG,
BPF_PROG_TYPE_RAW_TRACEPOINT,
BPF_PROG_TYPE_CGROUP_SOCK_ADDR,
BPF_PROG_TYPE_LWT_SEG6LOCAL,
BPF_PROG_TYPE_LIRC_MODE2,
BPF_PROG_TYPE_SK_REUSEPORT,
BPF_PROG_TYPE_FLOW_DISSECTOR,
BPF_PROG_TYPE_CGROUP_SYSCTL,
BPF_PROG_TYPE_RAW_TRACEPOINT_WRITABLE,
BPF_PROG_TYPE_CGROUP_SOCKOPT,
BPF_PROG_TYPE_TRACING,
BPF_PROG_TYPE_STRUCT_OPS,
BPF_PROG_TYPE_EXT,
BPF_PROG_TYPE_LSM,
};
0x3:BPF寄存器
eBPF從bpf的兩個(gè)32位寄存器擴(kuò)展到10個(gè)64位寄存器R0~R9和一個(gè)只讀棧幀寄存器,并支持call指令,更加貼近現(xiàn)代64位處理器硬件
R0對應(yīng)rax, 函數(shù)返回值
R1對應(yīng)rdi, 函數(shù)參數(shù)1
R2對應(yīng)rsi, 函數(shù)參數(shù)2
R3對應(yīng)rdx, 函數(shù)參數(shù)3
R4對應(yīng)rcx, 函數(shù)參數(shù)4
R5對應(yīng)r8, 函數(shù)參數(shù)5
R6對應(yīng)rbx, callee保存
R7對應(yīng)r13, callee保存
R8對應(yīng)r14, callee保存
R9對應(yīng)r15, callee保存
R10對應(yīng)rbp,只讀棧幀寄存器
0x4:內(nèi)核路徑
/Documentation/bpf/btf.rst
/include/uapi/linux/bpf_common.h 和 /include/uapi/linux/bpf.h 定義了指令集
/samples/bpf 相關(guān)的樣例
/tools/bpf/bpftool 工具,用來調(diào)試bpf
/tools/testing/selftests/bpf 測試代碼
本文由博客一文多發(fā)平臺 OpenWrite 發(fā)布!

浙公網(wǎng)安備 33010602011771號