377 строки
14 KiB
ReStructuredText
377 строки
14 KiB
ReStructuredText
|
|
===================
|
|
Classic BPF vs eBPF
|
|
===================
|
|
|
|
eBPF is designed to be JITed with one to one mapping, which can also open up
|
|
the possibility for GCC/LLVM compilers to generate optimized eBPF code through
|
|
an eBPF backend that performs almost as fast as natively compiled code.
|
|
|
|
Some core changes of the eBPF format from classic BPF:
|
|
|
|
- Number of registers increase from 2 to 10:
|
|
|
|
The old format had two registers A and X, and a hidden frame pointer. The
|
|
new layout extends this to be 10 internal registers and a read-only frame
|
|
pointer. Since 64-bit CPUs are passing arguments to functions via registers
|
|
the number of args from eBPF program to in-kernel function is restricted
|
|
to 5 and one register is used to accept return value from an in-kernel
|
|
function. Natively, x86_64 passes first 6 arguments in registers, aarch64/
|
|
sparcv9/mips64 have 7 - 8 registers for arguments; x86_64 has 6 callee saved
|
|
registers, and aarch64/sparcv9/mips64 have 11 or more callee saved registers.
|
|
|
|
Thus, all eBPF registers map one to one to HW registers on x86_64, aarch64,
|
|
etc, and eBPF calling convention maps directly to ABIs used by the kernel on
|
|
64-bit architectures.
|
|
|
|
On 32-bit architectures JIT may map programs that use only 32-bit arithmetic
|
|
and may let more complex programs to be interpreted.
|
|
|
|
R0 - R5 are scratch registers and eBPF program needs spill/fill them if
|
|
necessary across calls. Note that there is only one eBPF program (== one
|
|
eBPF main routine) and it cannot call other eBPF functions, it can only
|
|
call predefined in-kernel functions, though.
|
|
|
|
- Register width increases from 32-bit to 64-bit:
|
|
|
|
Still, the semantics of the original 32-bit ALU operations are preserved
|
|
via 32-bit subregisters. All eBPF registers are 64-bit with 32-bit lower
|
|
subregisters that zero-extend into 64-bit if they are being written to.
|
|
That behavior maps directly to x86_64 and arm64 subregister definition, but
|
|
makes other JITs more difficult.
|
|
|
|
32-bit architectures run 64-bit eBPF programs via interpreter.
|
|
Their JITs may convert BPF programs that only use 32-bit subregisters into
|
|
native instruction set and let the rest being interpreted.
|
|
|
|
Operation is 64-bit, because on 64-bit architectures, pointers are also
|
|
64-bit wide, and we want to pass 64-bit values in/out of kernel functions,
|
|
so 32-bit eBPF registers would otherwise require to define register-pair
|
|
ABI, thus, there won't be able to use a direct eBPF register to HW register
|
|
mapping and JIT would need to do combine/split/move operations for every
|
|
register in and out of the function, which is complex, bug prone and slow.
|
|
Another reason is the use of atomic 64-bit counters.
|
|
|
|
- Conditional jt/jf targets replaced with jt/fall-through:
|
|
|
|
While the original design has constructs such as ``if (cond) jump_true;
|
|
else jump_false;``, they are being replaced into alternative constructs like
|
|
``if (cond) jump_true; /* else fall-through */``.
|
|
|
|
- Introduces bpf_call insn and register passing convention for zero overhead
|
|
calls from/to other kernel functions:
|
|
|
|
Before an in-kernel function call, the eBPF program needs to
|
|
place function arguments into R1 to R5 registers to satisfy calling
|
|
convention, then the interpreter will take them from registers and pass
|
|
to in-kernel function. If R1 - R5 registers are mapped to CPU registers
|
|
that are used for argument passing on given architecture, the JIT compiler
|
|
doesn't need to emit extra moves. Function arguments will be in the correct
|
|
registers and BPF_CALL instruction will be JITed as single 'call' HW
|
|
instruction. This calling convention was picked to cover common call
|
|
situations without performance penalty.
|
|
|
|
After an in-kernel function call, R1 - R5 are reset to unreadable and R0 has
|
|
a return value of the function. Since R6 - R9 are callee saved, their state
|
|
is preserved across the call.
|
|
|
|
For example, consider three C functions::
|
|
|
|
u64 f1() { return (*_f2)(1); }
|
|
u64 f2(u64 a) { return f3(a + 1, a); }
|
|
u64 f3(u64 a, u64 b) { return a - b; }
|
|
|
|
GCC can compile f1, f3 into x86_64::
|
|
|
|
f1:
|
|
movl $1, %edi
|
|
movq _f2(%rip), %rax
|
|
jmp *%rax
|
|
f3:
|
|
movq %rdi, %rax
|
|
subq %rsi, %rax
|
|
ret
|
|
|
|
Function f2 in eBPF may look like::
|
|
|
|
f2:
|
|
bpf_mov R2, R1
|
|
bpf_add R1, 1
|
|
bpf_call f3
|
|
bpf_exit
|
|
|
|
If f2 is JITed and the pointer stored to ``_f2``. The calls f1 -> f2 -> f3 and
|
|
returns will be seamless. Without JIT, __bpf_prog_run() interpreter needs to
|
|
be used to call into f2.
|
|
|
|
For practical reasons all eBPF programs have only one argument 'ctx' which is
|
|
already placed into R1 (e.g. on __bpf_prog_run() startup) and the programs
|
|
can call kernel functions with up to 5 arguments. Calls with 6 or more arguments
|
|
are currently not supported, but these restrictions can be lifted if necessary
|
|
in the future.
|
|
|
|
On 64-bit architectures all register map to HW registers one to one. For
|
|
example, x86_64 JIT compiler can map them as ...
|
|
|
|
::
|
|
|
|
R0 - rax
|
|
R1 - rdi
|
|
R2 - rsi
|
|
R3 - rdx
|
|
R4 - rcx
|
|
R5 - r8
|
|
R6 - rbx
|
|
R7 - r13
|
|
R8 - r14
|
|
R9 - r15
|
|
R10 - rbp
|
|
|
|
... since x86_64 ABI mandates rdi, rsi, rdx, rcx, r8, r9 for argument passing
|
|
and rbx, r12 - r15 are callee saved.
|
|
|
|
Then the following eBPF pseudo-program::
|
|
|
|
bpf_mov R6, R1 /* save ctx */
|
|
bpf_mov R2, 2
|
|
bpf_mov R3, 3
|
|
bpf_mov R4, 4
|
|
bpf_mov R5, 5
|
|
bpf_call foo
|
|
bpf_mov R7, R0 /* save foo() return value */
|
|
bpf_mov R1, R6 /* restore ctx for next call */
|
|
bpf_mov R2, 6
|
|
bpf_mov R3, 7
|
|
bpf_mov R4, 8
|
|
bpf_mov R5, 9
|
|
bpf_call bar
|
|
bpf_add R0, R7
|
|
bpf_exit
|
|
|
|
After JIT to x86_64 may look like::
|
|
|
|
push %rbp
|
|
mov %rsp,%rbp
|
|
sub $0x228,%rsp
|
|
mov %rbx,-0x228(%rbp)
|
|
mov %r13,-0x220(%rbp)
|
|
mov %rdi,%rbx
|
|
mov $0x2,%esi
|
|
mov $0x3,%edx
|
|
mov $0x4,%ecx
|
|
mov $0x5,%r8d
|
|
callq foo
|
|
mov %rax,%r13
|
|
mov %rbx,%rdi
|
|
mov $0x6,%esi
|
|
mov $0x7,%edx
|
|
mov $0x8,%ecx
|
|
mov $0x9,%r8d
|
|
callq bar
|
|
add %r13,%rax
|
|
mov -0x228(%rbp),%rbx
|
|
mov -0x220(%rbp),%r13
|
|
leaveq
|
|
retq
|
|
|
|
Which is in this example equivalent in C to::
|
|
|
|
u64 bpf_filter(u64 ctx)
|
|
{
|
|
return foo(ctx, 2, 3, 4, 5) + bar(ctx, 6, 7, 8, 9);
|
|
}
|
|
|
|
In-kernel functions foo() and bar() with prototype: u64 (*)(u64 arg1, u64
|
|
arg2, u64 arg3, u64 arg4, u64 arg5); will receive arguments in proper
|
|
registers and place their return value into ``%rax`` which is R0 in eBPF.
|
|
Prologue and epilogue are emitted by JIT and are implicit in the
|
|
interpreter. R0-R5 are scratch registers, so eBPF program needs to preserve
|
|
them across the calls as defined by calling convention.
|
|
|
|
For example the following program is invalid::
|
|
|
|
bpf_mov R1, 1
|
|
bpf_call foo
|
|
bpf_mov R0, R1
|
|
bpf_exit
|
|
|
|
After the call the registers R1-R5 contain junk values and cannot be read.
|
|
An in-kernel verifier.rst is used to validate eBPF programs.
|
|
|
|
Also in the new design, eBPF is limited to 4096 insns, which means that any
|
|
program will terminate quickly and will only call a fixed number of kernel
|
|
functions. Original BPF and eBPF are two operand instructions,
|
|
which helps to do one-to-one mapping between eBPF insn and x86 insn during JIT.
|
|
|
|
The input context pointer for invoking the interpreter function is generic,
|
|
its content is defined by a specific use case. For seccomp register R1 points
|
|
to seccomp_data, for converted BPF filters R1 points to a skb.
|
|
|
|
A program, that is translated internally consists of the following elements::
|
|
|
|
op:16, jt:8, jf:8, k:32 ==> op:8, dst_reg:4, src_reg:4, off:16, imm:32
|
|
|
|
So far 87 eBPF instructions were implemented. 8-bit 'op' opcode field
|
|
has room for new instructions. Some of them may use 16/24/32 byte encoding. New
|
|
instructions must be multiple of 8 bytes to preserve backward compatibility.
|
|
|
|
eBPF is a general purpose RISC instruction set. Not every register and
|
|
every instruction are used during translation from original BPF to eBPF.
|
|
For example, socket filters are not using ``exclusive add`` instruction, but
|
|
tracing filters may do to maintain counters of events, for example. Register R9
|
|
is not used by socket filters either, but more complex filters may be running
|
|
out of registers and would have to resort to spill/fill to stack.
|
|
|
|
eBPF can be used as a generic assembler for last step performance
|
|
optimizations, socket filters and seccomp are using it as assembler. Tracing
|
|
filters may use it as assembler to generate code from kernel. In kernel usage
|
|
may not be bounded by security considerations, since generated eBPF code
|
|
may be optimizing internal code path and not being exposed to the user space.
|
|
Safety of eBPF can come from the verifier.rst. In such use cases as
|
|
described, it may be used as safe instruction set.
|
|
|
|
Just like the original BPF, eBPF runs within a controlled environment,
|
|
is deterministic and the kernel can easily prove that. The safety of the program
|
|
can be determined in two steps: first step does depth-first-search to disallow
|
|
loops and other CFG validation; second step starts from the first insn and
|
|
descends all possible paths. It simulates execution of every insn and observes
|
|
the state change of registers and stack.
|
|
|
|
opcode encoding
|
|
===============
|
|
|
|
eBPF is reusing most of the opcode encoding from classic to simplify conversion
|
|
of classic BPF to eBPF.
|
|
|
|
For arithmetic and jump instructions the 8-bit 'code' field is divided into three
|
|
parts::
|
|
|
|
+----------------+--------+--------------------+
|
|
| 4 bits | 1 bit | 3 bits |
|
|
| operation code | source | instruction class |
|
|
+----------------+--------+--------------------+
|
|
(MSB) (LSB)
|
|
|
|
Three LSB bits store instruction class which is one of:
|
|
|
|
=================== ===============
|
|
Classic BPF classes eBPF classes
|
|
=================== ===============
|
|
BPF_LD 0x00 BPF_LD 0x00
|
|
BPF_LDX 0x01 BPF_LDX 0x01
|
|
BPF_ST 0x02 BPF_ST 0x02
|
|
BPF_STX 0x03 BPF_STX 0x03
|
|
BPF_ALU 0x04 BPF_ALU 0x04
|
|
BPF_JMP 0x05 BPF_JMP 0x05
|
|
BPF_RET 0x06 BPF_JMP32 0x06
|
|
BPF_MISC 0x07 BPF_ALU64 0x07
|
|
=================== ===============
|
|
|
|
The 4th bit encodes the source operand ...
|
|
|
|
::
|
|
|
|
BPF_K 0x00
|
|
BPF_X 0x08
|
|
|
|
* in classic BPF, this means::
|
|
|
|
BPF_SRC(code) == BPF_X - use register X as source operand
|
|
BPF_SRC(code) == BPF_K - use 32-bit immediate as source operand
|
|
|
|
* in eBPF, this means::
|
|
|
|
BPF_SRC(code) == BPF_X - use 'src_reg' register as source operand
|
|
BPF_SRC(code) == BPF_K - use 32-bit immediate as source operand
|
|
|
|
... and four MSB bits store operation code.
|
|
|
|
If BPF_CLASS(code) == BPF_ALU or BPF_ALU64 [ in eBPF ], BPF_OP(code) is one of::
|
|
|
|
BPF_ADD 0x00
|
|
BPF_SUB 0x10
|
|
BPF_MUL 0x20
|
|
BPF_DIV 0x30
|
|
BPF_OR 0x40
|
|
BPF_AND 0x50
|
|
BPF_LSH 0x60
|
|
BPF_RSH 0x70
|
|
BPF_NEG 0x80
|
|
BPF_MOD 0x90
|
|
BPF_XOR 0xa0
|
|
BPF_MOV 0xb0 /* eBPF only: mov reg to reg */
|
|
BPF_ARSH 0xc0 /* eBPF only: sign extending shift right */
|
|
BPF_END 0xd0 /* eBPF only: endianness conversion */
|
|
|
|
If BPF_CLASS(code) == BPF_JMP or BPF_JMP32 [ in eBPF ], BPF_OP(code) is one of::
|
|
|
|
BPF_JA 0x00 /* BPF_JMP only */
|
|
BPF_JEQ 0x10
|
|
BPF_JGT 0x20
|
|
BPF_JGE 0x30
|
|
BPF_JSET 0x40
|
|
BPF_JNE 0x50 /* eBPF only: jump != */
|
|
BPF_JSGT 0x60 /* eBPF only: signed '>' */
|
|
BPF_JSGE 0x70 /* eBPF only: signed '>=' */
|
|
BPF_CALL 0x80 /* eBPF BPF_JMP only: function call */
|
|
BPF_EXIT 0x90 /* eBPF BPF_JMP only: function return */
|
|
BPF_JLT 0xa0 /* eBPF only: unsigned '<' */
|
|
BPF_JLE 0xb0 /* eBPF only: unsigned '<=' */
|
|
BPF_JSLT 0xc0 /* eBPF only: signed '<' */
|
|
BPF_JSLE 0xd0 /* eBPF only: signed '<=' */
|
|
|
|
So BPF_ADD | BPF_X | BPF_ALU means 32-bit addition in both classic BPF
|
|
and eBPF. There are only two registers in classic BPF, so it means A += X.
|
|
In eBPF it means dst_reg = (u32) dst_reg + (u32) src_reg; similarly,
|
|
BPF_XOR | BPF_K | BPF_ALU means A ^= imm32 in classic BPF and analogous
|
|
src_reg = (u32) src_reg ^ (u32) imm32 in eBPF.
|
|
|
|
Classic BPF is using BPF_MISC class to represent A = X and X = A moves.
|
|
eBPF is using BPF_MOV | BPF_X | BPF_ALU code instead. Since there are no
|
|
BPF_MISC operations in eBPF, the class 7 is used as BPF_ALU64 to mean
|
|
exactly the same operations as BPF_ALU, but with 64-bit wide operands
|
|
instead. So BPF_ADD | BPF_X | BPF_ALU64 means 64-bit addition, i.e.:
|
|
dst_reg = dst_reg + src_reg
|
|
|
|
Classic BPF wastes the whole BPF_RET class to represent a single ``ret``
|
|
operation. Classic BPF_RET | BPF_K means copy imm32 into return register
|
|
and perform function exit. eBPF is modeled to match CPU, so BPF_JMP | BPF_EXIT
|
|
in eBPF means function exit only. The eBPF program needs to store return
|
|
value into register R0 before doing a BPF_EXIT. Class 6 in eBPF is used as
|
|
BPF_JMP32 to mean exactly the same operations as BPF_JMP, but with 32-bit wide
|
|
operands for the comparisons instead.
|
|
|
|
For load and store instructions the 8-bit 'code' field is divided as::
|
|
|
|
+--------+--------+-------------------+
|
|
| 3 bits | 2 bits | 3 bits |
|
|
| mode | size | instruction class |
|
|
+--------+--------+-------------------+
|
|
(MSB) (LSB)
|
|
|
|
Size modifier is one of ...
|
|
|
|
::
|
|
|
|
BPF_W 0x00 /* word */
|
|
BPF_H 0x08 /* half word */
|
|
BPF_B 0x10 /* byte */
|
|
BPF_DW 0x18 /* eBPF only, double word */
|
|
|
|
... which encodes size of load/store operation::
|
|
|
|
B - 1 byte
|
|
H - 2 byte
|
|
W - 4 byte
|
|
DW - 8 byte (eBPF only)
|
|
|
|
Mode modifier is one of::
|
|
|
|
BPF_IMM 0x00 /* used for 32-bit mov in classic BPF and 64-bit in eBPF */
|
|
BPF_ABS 0x20
|
|
BPF_IND 0x40
|
|
BPF_MEM 0x60
|
|
BPF_LEN 0x80 /* classic BPF only, reserved in eBPF */
|
|
BPF_MSH 0xa0 /* classic BPF only, reserved in eBPF */
|
|
BPF_ATOMIC 0xc0 /* eBPF only, atomic operations */
|