[转帖]eBPF阅读笔记

ebpf,阅读,笔记 · 浏览次数 : 0

小编点评

生成内容时需要带简单的排版,例如: ```c++ if (head && hlist_empty(head)) return;unsigned int trace_call_bpf(struct trace_event_call *call, void *ctx){ ret = BPF_PROG_RUN(prog, ctx); [...]}。归纳总结以上内容,生成内容时需要带简单的排版。 ```

正文

https://zhuanlan.zhihu.com/p/264922917

 

eBPF指令集

eBPF是位于内核的一个解释器, 实现了自己的RISC指令集.

  • 寄存器
* R0        - return value from in-kernel function, and exit value for eBPF program
* R1 - R5   - arguments from eBPF program to in-kernel function
* R6 - R9   - callee saved registers that in-kernel function will preserve
* R10       - read-only frame pointer to access stack
  • 指令集
struct bpf_insn {
        __u8    code;           /* opcode */
        __u8    dst_reg:4;      /* dest register */
        __u8    src_reg:4;      /* source register */
        __s16   off;            /* signed offset */
        __s32   imm;            /* signed immediate constant */
};
#define MAX_BPF_STACK   512
  • 解释执行. 以比较早的版本f1bca824dab为例:
#define DST     regs[insn->dst_reg]
#define SRC     regs[insn->src_reg]

static unsigned int __bpf_prog_run(void *ctx, const struct bpf_insn *insn)
{
        u64 stack[MAX_BPF_STACK / sizeof(u64)];
        u64 regs[MAX_BPF_REG], tmp;    
        static const void *jumptable[256] = {  
                [0 ... 255] = &&default_label, 
                /* Now overwrite non-defaults ... */
                /* 32 bit ALU operations */
                [BPF_ALU | BPF_ADD | BPF_X] = &&ALU_ADD_X,
                [BPF_ALU | BPF_ADD | BPF_K] = &&ALU_ADD_K,

                [...]
        };

#define CONT     ({ insn++; goto select_insn; })
#define CONT_JMP ({ insn++; goto select_insn; })

        /* ALU */
#define ALU(OPCODE, OP)                 \
        ALU64_##OPCODE##_X:             \
                DST = DST OP SRC;       \
                CONT;                   \
        ALU_##OPCODE##_X:               \
                DST = (u32) DST OP (u32) SRC;   \
                CONT;                   \
        ALU64_##OPCODE##_K:             \
                DST = DST OP IMM;               \
                CONT;                   \
        ALU_##OPCODE##_K:               \
                DST = (u32) DST OP (u32) IMM;   \
                CONT;

        ALU(ADD,  +)
        ALU(SUB,  -)
        ALU(AND,  &)
        ALU(OR,   |)
        ALU(LSH, <<)
        ALU(RSH, >>)
        ALU(XOR,  ^)

        [...]
}
  • JIT执行. 在load的时候全部编译一遍, 虽然eBPF努力做到和x86指令集一一对应, 但是指令的长度还是不一样的, 下面的循环主要是处理跳转指令的偏移.
void bpf_int_jit_compile(struct bpf_prog *prog)
{
        for (pass = 0; pass < 10; pass++) {
                proglen = do_jit(prog, addrs, image, oldproglen, &ctx);
                [...]
        }
}

eBPF Verifier

Verifier主要分2步:

  1. check_cfg. 通过depth-first-search确保没有循环, check_cfg
  2. do_check. 模拟执行所有path, 确保访问安全. eBPF主要访问以下几类数据:
    1. context
    2. map
    3. 512B stack
    4. packet...
  • 寄存器类型.
* Corresponding eBPF program may look like:
 *    BPF_MOV64_REG(BPF_REG_2, BPF_REG_10),  // after this insn R2 type is FRAME_PTR
 *    BPF_ALU64_IMM(BPF_ADD, BPF_REG_2, -4), // after this insn R2 type is PTR_TO_STACK
 *    BPF_LD_MAP_FD(BPF_REG_1, map_fd),      // after this insn R1 type is CONST_PTR_TO_MAP
  • state. eBPF会模拟执行所有的分支, state里面包括了所有的regs和stack的内容 (regs里面并不会/需要记录所有的计算结果/ valid when type == CONST_IMM | PTR_TO_STACK /)
/* types of values stored in eBPF registers */
enum bpf_reg_type {
        NOT_INIT = 0,            /* nothing was written into register */
        UNKNOWN_VALUE,           /* reg doesn't contain a valid pointer */
        PTR_TO_CTX,              /* reg points to bpf_context */
        CONST_PTR_TO_MAP,        /* reg points to struct bpf_map */
        PTR_TO_MAP_VALUE,        /* reg points to map element value */
        PTR_TO_MAP_VALUE_OR_NULL,/* points to map elem value or NULL */
        FRAME_PTR,               /* reg == frame_pointer */
        PTR_TO_STACK,            /* reg == frame_pointer + imm */
        CONST_IMM,               /* constant integer value */
};

struct reg_state {
        enum bpf_reg_type type;
        union { 
                /* valid when type == CONST_IMM | PTR_TO_STACK */
                int imm;

                /* valid when type == CONST_PTR_TO_MAP | PTR_TO_MAP_VALUE |
                 *   PTR_TO_MAP_VALUE_OR_NULL
                 */
                struct bpf_map *map_ptr;
        };
};

enum bpf_stack_slot_type {
        STACK_INVALID,    /* nothing was stored in this stack slot */
        STACK_SPILL,      /* 1st byte of register spilled into stack */
        STACK_SPILL_PART, /* other 7 bytes of register spill */
        STACK_MISC        /* BPF program wrote some data into this slot */
};

struct bpf_stack_slot {
        enum bpf_stack_slot_type stype;
        struct reg_state reg_st;
};

/* state of the program:
 * type of all registers and stack info
 */
struct verifier_state {
        struct reg_state regs[MAX_BPF_REG];
        struct bpf_stack_slot stack[MAX_BPF_STACK];
};
  • register spill. 因为寄存器个数有限, 有的时候需要把寄存器转移到stack中, 对于ptr类型的寄存器, 在写到stack时会记录成STACK_SPILL状态, verifier很重要的一点就是防止指针越界, 所以需要保留这些状态, 对于非ptr类型的寄存器, 则直接设置为UNKNOWN_VALUE, 不会影响安全验证 (不用去检查具体的值).
static int check_stack_write(struct verifier_state *state, int off, int size,
                             int value_regno)
{
        struct bpf_stack_slot *slot;
        int i;

        if (value_regno >= 0 && 
            (state->regs[value_regno].type == PTR_TO_MAP_VALUE ||
             state->regs[value_regno].type == PTR_TO_STACK ||
             state->regs[value_regno].type == PTR_TO_CTX)) {

                /* register containing pointer is being spilled into stack */
                if (size != 8) { 
                        verbose("invalid size of register spill\n");
                        return -EACCES;
                }    

                slot = &state->stack[MAX_BPF_STACK + off];
                slot->stype = STACK_SPILL;
                /* save register state */
                slot->reg_st = state->regs[value_regno];
                for (i = 1; i < 8; i++) {
                        slot = &state->stack[MAX_BPF_STACK + off + i];
                        slot->stype = STACK_SPILL_PART;
                        slot->reg_st.type = UNKNOWN_VALUE;
                        slot->reg_st.map_ptr = NULL;
                }    
        } else {

                /* regular write of data into stack */
                for (i = 0; i < size; i++) {
                        slot = &state->stack[MAX_BPF_STACK + off + i];
                        slot->stype = STACK_MISC;
                        slot->reg_st.type = UNKNOWN_VALUE;
                        slot->reg_st.map_ptr = NULL;
                }    
        }    
        return 0;    
}
  • prune. eBPF要验证所有分支都是安全的, 这样验证会花费很多时间. 对于同一条指令, 很可能出现在多个分支上面, 每个已经验证国的分支都会记录该条指令对应的state (old), 如果当前state (cur)比old更严格, 比如在old的值是NOT_INIT都能通过验证, 那么cur可能也能通过验证, 所以当前分支就可以裁剪掉了.
static bool states_equal(struct verifier_state *old, struct verifier_state *cur)
{
        int i;

        for (i = 0; i < MAX_BPF_REG; i++) {
                if (memcmp(&old->regs[i], &cur->regs[i],
                           sizeof(old->regs[0])) != 0) { 
                        if (old->regs[i].type == NOT_INIT ||
                            old->regs[i].type == UNKNOWN_VALUE)
                                continue;
                        return false;
                }
        }

        for (i = 0; i < MAX_BPF_STACK; i++) {
                if (memcmp(&old->stack[i], &cur->stack[i],
                           sizeof(old->stack[0])) != 0) { 
                        if (old->stack[i].stype == STACK_INVALID)
                                continue;
                        return false;
                }
        }
        return true;
}
  • PTR_TO_CTX. 当eBPF程序需要访问context时, 会调用具体context对应的is_valid_access, 以kprobe为例:
/* bpf+kprobe programs can access fields of 'struct pt_regs' */
static bool kprobe_prog_is_valid_access(int off, int size, enum bpf_access_type type,
                                        const struct bpf_prog *prog,
                                        struct bpf_insn_access_aux *info)
{
        if (off < 0 || off >= sizeof(struct pt_regs))
                return false;
        if (type != BPF_READ)
                return false;
        if (off % size != 0)
                return false;
        /*
         * Assertion for 32 bit to make sure last 8 byte access
         * (BPF_DW) to the last 4 byte member is disallowed.
         */
        if (off + size > sizeof(struct pt_regs))
                return false;

        return true;
}

Helper Function

  • Verifier会检查helper function的参数, commit af42d3466
static int check_helper_call(struct bpf_verifier_env *env, int func_id, int insn_idx)
{
        /* check args */
        err = check_func_arg(env, BPF_REG_1, fn->arg1_type, &meta);
        if (err)
                return err;
        err = check_func_arg(env, BPF_REG_2, fn->arg2_type, &meta);
        if (err)
                return err;
        [...]
}
  • Helper定义
static const struct bpf_func_proto bpf_trace_printk_proto = {
        .func           = bpf_trace_printk,
        .gpl_only       = true,
        .ret_type       = RET_INTEGER,
        .arg1_type      = ARG_PTR_TO_MEM,
        .arg2_type      = ARG_CONST_SIZE,
};
  • 读写权限. 调试的时候权限总是越多越方便, 比如systemtap可以写内核的变量, 但是eBPF没有放开写内核权限
static const struct bpf_func_proto *
tracing_func_proto(enum bpf_func_id func_id, const struct bpf_prog *prog)
{
        switch (func_id) {    
        case BPF_FUNC_perf_event_read: 
                return &bpf_perf_event_read_proto;
        case BPF_FUNC_probe_write_user:
                return bpf_get_probe_write_proto();
        [...]
        }
}
  • bpf_get_current_task. 返回current task指针, 但是也不能写:
struct task_struct* t = (struct task_struct*)bpf_get_current_task();
t->cpu = 111;

({ typeof(unsigned int) _val; __builtin_memset(&_val, 0, sizeof(_val)); bpf_probe_read(&_val, sizeof(_val), (u64)&t->cpu); _val; }) = 111;
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ^

Attach to kprobe

commit 2541517c32b

--- a/kernel/trace/trace_kprobe.c
+++ b/kernel/trace/trace_kprobe.c
@@ -1134,11 +1134,15 @@ static void
 kprobe_perf_func(struct trace_kprobe *tk, struct pt_regs *regs)
 {
        struct ftrace_event_call *call = &tk->tp.call;
+       struct bpf_prog *prog = call->prog;
        struct kprobe_trace_entry_head *entry;
        struct hlist_head *head;
        int size, __size, dsize;
        int rctx;

+       if (prog && !trace_call_bpf(prog, regs))
+               return;
+
        head = this_cpu_ptr(call->perf_events);
        if (hlist_empty(head))
                return;
unsigned int trace_call_bpf(struct trace_event_call *call, void *ctx)
{
        ret = BPF_PROG_RUN(prog, ctx);
        [...]
}

与[转帖]eBPF阅读笔记相似的内容:

[转帖]eBPF阅读笔记

https://zhuanlan.zhihu.com/p/264922917 eBPF指令集 eBPF是位于内核的一个解释器, 实现了自己的RISC指令集. 寄存器 * R0 - return value from in-kernel function, and exit value for eBP

[转帖]宋宝华:用eBPF/bcc分析系统性能的一个简单案例

原创 宋宝华 Linux阅码场 3月8日 bcc是eBPF的一种前端,当然这个前端特别地简单好用。可以直接在python里面嵌入通过C语言写的BPF程序,并帮忙产生BPF bytecode和load进入kernel挂载kprobe、tracepoints等上面执行。之后,还可以从python取出来C

[转帖]eBPF 技术实践:加速容器网络转发,耗时降低 60%+

https://my.oschina.net/u/6150560/blog/5587717 背景 Linux 具有功能丰富的网络协议栈,并且兼顾了非常优秀的性能。但是,这是相对的。单纯从网络协议栈各个子系统的角度来说,确实做到了功能与性能的平衡。不过,当把多个子系统组合起来,去满足实际的业务需求,功

[转帖]eBPF 技术实践:加速容器网络转发,耗时降低60%+

https://new.qq.com/rain/a/20221103A03ZHE00 作者 | 王栋栋 背 景 Linux 具有功能丰富的网络协议栈,并且兼顾了非常优秀的性能。但是,这是相对的。单纯从网络协议栈各个子系统的角度来说,确实做到了功能与性能的平衡。不过,当把多个子系统组合起来,去满足实际

[转帖]eBPF监控工具bcc系列一启航

https://blog.51cto.com/u_15333820/3453313 在eBPF篇中,我们知道虽然可用 C 来实现 BPF,但编译出来的却仍然是 ELF 文件,开发者需要手动析出真正可以注入内核的代码。工作有些麻烦,于是就有人设计了 BPF Compiler Collection(BC

[转帖]eBPF介绍

https://blog.51cto.com/u_15155099/2767325 1.BPF起源BPF源头起源于一篇1992年的论文,这篇论文主要提出一种新的网络数据包的过滤的框架,如下图所示。提出bpf的原因其实也很简单,早期我们从网卡中接收到很多的数据包,我们要想从中过滤出我们想要的数据包,我

[转帖]eBPF 完全入门指南.pdf(万字长文)

https://zhuanlan.zhihu.com/p/492185920 图片 eBPF 源于 BPF[1],本质上是处于内核中的一个高效与灵活的虚类虚拟机组件,以一种安全的方式在许多内核 hook 点执行字节码。BPF 最初的目的是用于高效网络报文过滤,经过重新设计,eBPF 不再局限于网络协

[转帖]ebpf的未来

[转帖]eBPF系列学习(4)了解libbpf、CO-RE (Compile Once – Run Everywhe) | 使用go开发ebpf程序(云原生利器cilium ebpf )

文章目录 一、了解libbpf1. BPF的可移植性CO-RE (Compile Once – Run Everywhere)BPF 可移植性面临的问题BPF的可移植性CO-RE (Compile Once – Run Everywhere) 2. libbpf和bcc性能对比3. 了解libbpf

[转帖]eBPF文章翻译(2)——BCC介绍(附实验环境)

nevermosby eBPF学习计划可以看这里。 该篇为入门文章翻译系列第二篇,第一篇看这里。 原文名称:An introduction to the BPF Compiler Collection,原文地址:https://lwn.net/Articles/742082/ 目录 BCC是什么 一