← Documents Documentation/bpf/classic_vs_extended.rst GitHub 원문 ↗

Linux 6.18.37 · BPF

Classic BPF vs eBPF

classic BPF에서 eBPF로 확장된 register·호출 규약·instruction format과 opcode encoding을 설명합니다.

Source pathDocumentation/bpf/classic_vs_extended.rst
Source versionLinux v6.18.37
TranslationDUJINLABS 전문 번역 + 해설

요약·해설과 원문, 전문 번역을 서로 분리했습니다. API 이름, symbol, source path는 원문 표기를 사용합니다.

1. 요약·해설

원문의 핵심 논리와 kernel programming 관점의 보충 설명입니다. 아래의 전문 번역과는 별도로 작성했습니다.

요약과 해설

classic_vs_extended.rst:1-376

eBPF는 10개의 64-bit register와 kernel ABI에 맞춘 calling convention을 사용해 JIT compiler가 eBPF instruction과 hardware instruction을 효율적으로 일대일 mapping하도록 설계됐습니다. `R1-R5`는 argument, `R0`는 return value, `R6-R9`는 callee-saved, `R10`은 read-only frame pointer 역할을 합니다.

8-bit opcode는 instruction 종류에 따라 operation/source/class 또는 mode/size/class field로 나뉩니다. classic BPF encoding을 최대한 재사용하면서 `BPF_ALU64`, `BPF_JMP32`, `BPF_CALL`, `BPF_EXIT`, `BPF_DW`, `BPF_ATOMIC` 같은 eBPF 확장을 수용합니다.

안전성은 `verifier.rst`가 CFG와 모든 실행 path의 register·stack 상태를 분석해 보장합니다. 이 구조 덕분에 eBPF는 socket filter, seccomp, tracing과 kernel 내부 최적화에 사용할 수 있는 deterministic한 general-purpose RISC instruction set이 됩니다.

2. 영어 원문 전체

번역 기준이 된 Linux v6.18.37 원문입니다. 줄 번호는 이 버전의 파일 좌표입니다.

원문 전체 펼치기
1
2 ===================
3 Classic BPF vs eBPF
4 ===================
5
6 eBPF is designed to be JITed with one to one mapping, which can also open up
7 the possibility for GCC/LLVM compilers to generate optimized eBPF code through
8 an eBPF backend that performs almost as fast as natively compiled code.
9
10 Some core changes of the eBPF format from classic BPF:
11
12 - Number of registers increase from 2 to 10:
13
14 The old format had two registers A and X, and a hidden frame pointer. The
15 new layout extends this to be 10 internal registers and a read-only frame
16 pointer. Since 64-bit CPUs are passing arguments to functions via registers
17 the number of args from eBPF program to in-kernel function is restricted
18 to 5 and one register is used to accept return value from an in-kernel
19 function. Natively, x86_64 passes first 6 arguments in registers, aarch64/
20 sparcv9/mips64 have 7 - 8 registers for arguments; x86_64 has 6 callee saved
21 registers, and aarch64/sparcv9/mips64 have 11 or more callee saved registers.
22
23 Thus, all eBPF registers map one to one to HW registers on x86_64, aarch64,
24 etc, and eBPF calling convention maps directly to ABIs used by the kernel on
25 64-bit architectures.
26
27 On 32-bit architectures JIT may map programs that use only 32-bit arithmetic
28 and may let more complex programs to be interpreted.
29
30 R0 - R5 are scratch registers and eBPF program needs spill/fill them if
31 necessary across calls. Note that there is only one eBPF program (== one
32 eBPF main routine) and it cannot call other eBPF functions, it can only
33 call predefined in-kernel functions, though.
34
35 - Register width increases from 32-bit to 64-bit:
36
37 Still, the semantics of the original 32-bit ALU operations are preserved
38 via 32-bit subregisters. All eBPF registers are 64-bit with 32-bit lower
39 subregisters that zero-extend into 64-bit if they are being written to.
40 That behavior maps directly to x86_64 and arm64 subregister definition, but
41 makes other JITs more difficult.
42
43 32-bit architectures run 64-bit eBPF programs via interpreter.
44 Their JITs may convert BPF programs that only use 32-bit subregisters into
45 native instruction set and let the rest being interpreted.
46
47 Operation is 64-bit, because on 64-bit architectures, pointers are also
48 64-bit wide, and we want to pass 64-bit values in/out of kernel functions,
49 so 32-bit eBPF registers would otherwise require to define register-pair
50 ABI, thus, there won't be able to use a direct eBPF register to HW register
51 mapping and JIT would need to do combine/split/move operations for every
52 register in and out of the function, which is complex, bug prone and slow.
53 Another reason is the use of atomic 64-bit counters.
54
55 - Conditional jt/jf targets replaced with jt/fall-through:
56
57 While the original design has constructs such as ``if (cond) jump_true;
58 else jump_false;``, they are being replaced into alternative constructs like
59 ``if (cond) jump_true; /* else fall-through */``.
60
61 - Introduces bpf_call insn and register passing convention for zero overhead
62 calls from/to other kernel functions:
63
64 Before an in-kernel function call, the eBPF program needs to
65 place function arguments into R1 to R5 registers to satisfy calling
66 convention, then the interpreter will take them from registers and pass
67 to in-kernel function. If R1 - R5 registers are mapped to CPU registers
68 that are used for argument passing on given architecture, the JIT compiler
69 doesn't need to emit extra moves. Function arguments will be in the correct
70 registers and BPF_CALL instruction will be JITed as single 'call' HW
71 instruction. This calling convention was picked to cover common call
72 situations without performance penalty.
73
74 After an in-kernel function call, R1 - R5 are reset to unreadable and R0 has
75 a return value of the function. Since R6 - R9 are callee saved, their state
76 is preserved across the call.
77
78 For example, consider three C functions::
79
80 u64 f1() { return (*_f2)(1); }
81 u64 f2(u64 a) { return f3(a + 1, a); }
82 u64 f3(u64 a, u64 b) { return a - b; }
83
84 GCC can compile f1, f3 into x86_64::
85
86 f1:
87 movl $1, %edi
88 movq _f2(%rip), %rax
89 jmp *%rax
90 f3:
91 movq %rdi, %rax
92 subq %rsi, %rax
93 ret
94
95 Function f2 in eBPF may look like::
96
97 f2:
98 bpf_mov R2, R1
99 bpf_add R1, 1
100 bpf_call f3
101 bpf_exit
102
103 If f2 is JITed and the pointer stored to ``_f2``. The calls f1 -> f2 -> f3 and
104 returns will be seamless. Without JIT, __bpf_prog_run() interpreter needs to
105 be used to call into f2.
106
107 For practical reasons all eBPF programs have only one argument 'ctx' which is
108 already placed into R1 (e.g. on __bpf_prog_run() startup) and the programs
109 can call kernel functions with up to 5 arguments. Calls with 6 or more arguments
110 are currently not supported, but these restrictions can be lifted if necessary
111 in the future.
112
113 On 64-bit architectures all register map to HW registers one to one. For
114 example, x86_64 JIT compiler can map them as ...
115
116 ::
117
118 R0 - rax
119 R1 - rdi
120 R2 - rsi
121 R3 - rdx
122 R4 - rcx
123 R5 - r8
124 R6 - rbx
125 R7 - r13
126 R8 - r14
127 R9 - r15
128 R10 - rbp
129
130 ... since x86_64 ABI mandates rdi, rsi, rdx, rcx, r8, r9 for argument passing
131 and rbx, r12 - r15 are callee saved.
132
133 Then the following eBPF pseudo-program::
134
135 bpf_mov R6, R1 /* save ctx */
136 bpf_mov R2, 2
137 bpf_mov R3, 3
138 bpf_mov R4, 4
139 bpf_mov R5, 5
140 bpf_call foo
141 bpf_mov R7, R0 /* save foo() return value */
142 bpf_mov R1, R6 /* restore ctx for next call */
143 bpf_mov R2, 6
144 bpf_mov R3, 7
145 bpf_mov R4, 8
146 bpf_mov R5, 9
147 bpf_call bar
148 bpf_add R0, R7
149 bpf_exit
150
151 After JIT to x86_64 may look like::
152
153 push %rbp
154 mov %rsp,%rbp
155 sub $0x228,%rsp
156 mov %rbx,-0x228(%rbp)
157 mov %r13,-0x220(%rbp)
158 mov %rdi,%rbx
159 mov $0x2,%esi
160 mov $0x3,%edx
161 mov $0x4,%ecx
162 mov $0x5,%r8d
163 callq foo
164 mov %rax,%r13
165 mov %rbx,%rdi
166 mov $0x6,%esi
167 mov $0x7,%edx
168 mov $0x8,%ecx
169 mov $0x9,%r8d
170 callq bar
171 add %r13,%rax
172 mov -0x228(%rbp),%rbx
173 mov -0x220(%rbp),%r13
174 leaveq
175 retq
176
177 Which is in this example equivalent in C to::
178
179 u64 bpf_filter(u64 ctx)
180 {
181 return foo(ctx, 2, 3, 4, 5) + bar(ctx, 6, 7, 8, 9);
182 }
183
184 In-kernel functions foo() and bar() with prototype: u64 (*)(u64 arg1, u64
185 arg2, u64 arg3, u64 arg4, u64 arg5); will receive arguments in proper
186 registers and place their return value into ``%rax`` which is R0 in eBPF.
187 Prologue and epilogue are emitted by JIT and are implicit in the
188 interpreter. R0-R5 are scratch registers, so eBPF program needs to preserve
189 them across the calls as defined by calling convention.
190
191 For example the following program is invalid::
192
193 bpf_mov R1, 1
194 bpf_call foo
195 bpf_mov R0, R1
196 bpf_exit
197
198 After the call the registers R1-R5 contain junk values and cannot be read.
199 An in-kernel verifier.rst is used to validate eBPF programs.
200
201 Also in the new design, eBPF is limited to 4096 insns, which means that any
202 program will terminate quickly and will only call a fixed number of kernel
203 functions. Original BPF and eBPF are two operand instructions,
204 which helps to do one-to-one mapping between eBPF insn and x86 insn during JIT.
205
206 The input context pointer for invoking the interpreter function is generic,
207 its content is defined by a specific use case. For seccomp register R1 points
208 to seccomp_data, for converted BPF filters R1 points to a skb.
209
210 A program, that is translated internally consists of the following elements::
211
212 op:16, jt:8, jf:8, k:32 ==> op:8, dst_reg:4, src_reg:4, off:16, imm:32
213
214 So far 87 eBPF instructions were implemented. 8-bit 'op' opcode field
215 has room for new instructions. Some of them may use 16/24/32 byte encoding. New
216 instructions must be multiple of 8 bytes to preserve backward compatibility.
217
218 eBPF is a general purpose RISC instruction set. Not every register and
219 every instruction are used during translation from original BPF to eBPF.
220 For example, socket filters are not using ``exclusive add`` instruction, but
221 tracing filters may do to maintain counters of events, for example. Register R9
222 is not used by socket filters either, but more complex filters may be running
223 out of registers and would have to resort to spill/fill to stack.
224
225 eBPF can be used as a generic assembler for last step performance
226 optimizations, socket filters and seccomp are using it as assembler. Tracing
227 filters may use it as assembler to generate code from kernel. In kernel usage
228 may not be bounded by security considerations, since generated eBPF code
229 may be optimizing internal code path and not being exposed to the user space.
230 Safety of eBPF can come from the verifier.rst. In such use cases as
231 described, it may be used as safe instruction set.
232
233 Just like the original BPF, eBPF runs within a controlled environment,
234 is deterministic and the kernel can easily prove that. The safety of the program
235 can be determined in two steps: first step does depth-first-search to disallow
236 loops and other CFG validation; second step starts from the first insn and
237 descends all possible paths. It simulates execution of every insn and observes
238 the state change of registers and stack.
239
240 opcode encoding
241 ===============
242
243 eBPF is reusing most of the opcode encoding from classic to simplify conversion
244 of classic BPF to eBPF.
245
246 For arithmetic and jump instructions the 8-bit 'code' field is divided into three
247 parts::
248
249 +----------------+--------+--------------------+
250 | 4 bits | 1 bit | 3 bits |
251 | operation code | source | instruction class |
252 +----------------+--------+--------------------+
253 (MSB) (LSB)
254
255 Three LSB bits store instruction class which is one of:
256
257 =================== ===============
258 Classic BPF classes eBPF classes
259 =================== ===============
260 BPF_LD 0x00 BPF_LD 0x00
261 BPF_LDX 0x01 BPF_LDX 0x01
262 BPF_ST 0x02 BPF_ST 0x02
263 BPF_STX 0x03 BPF_STX 0x03
264 BPF_ALU 0x04 BPF_ALU 0x04
265 BPF_JMP 0x05 BPF_JMP 0x05
266 BPF_RET 0x06 BPF_JMP32 0x06
267 BPF_MISC 0x07 BPF_ALU64 0x07
268 =================== ===============
269
270 The 4th bit encodes the source operand ...
271
272 ::
273
274 BPF_K 0x00
275 BPF_X 0x08
276
277 * in classic BPF, this means::
278
279 BPF_SRC(code) == BPF_X - use register X as source operand
280 BPF_SRC(code) == BPF_K - use 32-bit immediate as source operand
281
282 * in eBPF, this means::
283
284 BPF_SRC(code) == BPF_X - use 'src_reg' register as source operand
285 BPF_SRC(code) == BPF_K - use 32-bit immediate as source operand
286
287 ... and four MSB bits store operation code.
288
289 If BPF_CLASS(code) == BPF_ALU or BPF_ALU64 [ in eBPF ], BPF_OP(code) is one of::
290
291 BPF_ADD 0x00
292 BPF_SUB 0x10
293 BPF_MUL 0x20
294 BPF_DIV 0x30
295 BPF_OR 0x40
296 BPF_AND 0x50
297 BPF_LSH 0x60
298 BPF_RSH 0x70
299 BPF_NEG 0x80
300 BPF_MOD 0x90
301 BPF_XOR 0xa0
302 BPF_MOV 0xb0 /* eBPF only: mov reg to reg */
303 BPF_ARSH 0xc0 /* eBPF only: sign extending shift right */
304 BPF_END 0xd0 /* eBPF only: endianness conversion */
305
306 If BPF_CLASS(code) == BPF_JMP or BPF_JMP32 [ in eBPF ], BPF_OP(code) is one of::
307
308 BPF_JA 0x00 /* BPF_JMP only */
309 BPF_JEQ 0x10
310 BPF_JGT 0x20
311 BPF_JGE 0x30
312 BPF_JSET 0x40
313 BPF_JNE 0x50 /* eBPF only: jump != */
314 BPF_JSGT 0x60 /* eBPF only: signed '>' */
315 BPF_JSGE 0x70 /* eBPF only: signed '>=' */
316 BPF_CALL 0x80 /* eBPF BPF_JMP only: function call */
317 BPF_EXIT 0x90 /* eBPF BPF_JMP only: function return */
318 BPF_JLT 0xa0 /* eBPF only: unsigned '<' */
319 BPF_JLE 0xb0 /* eBPF only: unsigned '<=' */
320 BPF_JSLT 0xc0 /* eBPF only: signed '<' */
321 BPF_JSLE 0xd0 /* eBPF only: signed '<=' */
322
323 So BPF_ADD | BPF_X | BPF_ALU means 32-bit addition in both classic BPF
324 and eBPF. There are only two registers in classic BPF, so it means A += X.
325 In eBPF it means dst_reg = (u32) dst_reg + (u32) src_reg; similarly,
326 BPF_XOR | BPF_K | BPF_ALU means A ^= imm32 in classic BPF and analogous
327 src_reg = (u32) src_reg ^ (u32) imm32 in eBPF.
328
329 Classic BPF is using BPF_MISC class to represent A = X and X = A moves.
330 eBPF is using BPF_MOV | BPF_X | BPF_ALU code instead. Since there are no
331 BPF_MISC operations in eBPF, the class 7 is used as BPF_ALU64 to mean
332 exactly the same operations as BPF_ALU, but with 64-bit wide operands
333 instead. So BPF_ADD | BPF_X | BPF_ALU64 means 64-bit addition, i.e.:
334 dst_reg = dst_reg + src_reg
335
336 Classic BPF wastes the whole BPF_RET class to represent a single ``ret``
337 operation. Classic BPF_RET | BPF_K means copy imm32 into return register
338 and perform function exit. eBPF is modeled to match CPU, so BPF_JMP | BPF_EXIT
339 in eBPF means function exit only. The eBPF program needs to store return
340 value into register R0 before doing a BPF_EXIT. Class 6 in eBPF is used as
341 BPF_JMP32 to mean exactly the same operations as BPF_JMP, but with 32-bit wide
342 operands for the comparisons instead.
343
344 For load and store instructions the 8-bit 'code' field is divided as::
345
346 +--------+--------+-------------------+
347 | 3 bits | 2 bits | 3 bits |
348 | mode | size | instruction class |
349 +--------+--------+-------------------+
350 (MSB) (LSB)
351
352 Size modifier is one of ...
353
354 ::
355
356 BPF_W 0x00 /* word */
357 BPF_H 0x08 /* half word */
358 BPF_B 0x10 /* byte */
359 BPF_DW 0x18 /* eBPF only, double word */
360
361 ... which encodes size of load/store operation::
362
363 B - 1 byte
364 H - 2 byte
365 W - 4 byte
366 DW - 8 byte (eBPF only)
367
368 Mode modifier is one of::
369
370 BPF_IMM 0x00 /* used for 32-bit mov in classic BPF and 64-bit in eBPF */
371 BPF_ABS 0x20
372 BPF_IND 0x40
373 BPF_MEM 0x60
374 BPF_LEN 0x80 /* classic BPF only, reserved in eBPF */
375 BPF_MSH 0xa0 /* classic BPF only, reserved in eBPF */
376 BPF_ATOMIC 0xc0 /* eBPF only, atomic operations */
377

3. 한국어 전문 번역

영어 원문의 문단 순서와 의미를 유지한 전체 번역입니다. 코드, 함수명, symbol과 URL은 원문 표기를 유지합니다.

Classic BPF와 eBPF의 register 구성

1-34

eBPF는 instruction을 hardware instruction에 일대일로 대응시켜 JIT compile하도록 설계됐습니다. 이 구조는 GCC/LLVM compiler가 eBPF backend를 통해 native compile code에 가까운 속도의 최적화된 eBPF code를 생성할 가능성도 열어 줍니다.

classic BPF에서 eBPF format으로 바뀐 핵심 사항은 다음과 같습니다.

  • register 수가 2개에서 10개로 증가합니다.
  • register 폭이 32-bit에서 64-bit로 증가합니다.
  • 조건 분기의 `jt/jf` target이 `jt/fall-through`로 바뀝니다.
  • zero-overhead kernel function call을 위한 `bpf_call` instruction과 register passing convention이 도입됩니다.

기존 format에는 A와 X라는 두 register와 숨겨진 frame pointer가 있었습니다. 새 layout은 이를 10개의 내부 register와 read-only frame pointer로 확장합니다. 64-bit CPU는 register로 function argument를 전달하므로 eBPF program이 in-kernel function에 전달할 수 있는 argument는 5개로 제한되고, register 하나는 in-kernel function의 return value를 받는 데 사용됩니다.

native ABI에서 x86_64는 처음 6개 argument를 register로 전달하고 aarch64/sparcv9/mips64는 argument용 register가 7~8개입니다. x86_64에는 callee-saved register가 6개, aarch64/sparcv9/mips64에는 11개 이상 있습니다.

따라서 x86_64, aarch64 등의 환경에서는 모든 eBPF register를 hardware register에 일대일로 mapping할 수 있고, eBPF calling convention도 64-bit architecture의 kernel ABI에 직접 대응합니다.

32-bit architecture의 JIT는 32-bit arithmetic만 사용하는 program을 mapping하고, 더 복잡한 program은 interpreter로 실행하게 할 수 있습니다.

`R0 - R5`는 scratch register이므로 eBPF program은 call을 사이에 두고 필요하면 이들을 spill/fill해야 합니다. eBPF program은 하나의 eBPF main routine뿐이며 다른 eBPF function을 호출할 수 없고, 미리 정의된 in-kernel function만 호출할 수 있습니다.

64-bit register와 32-bit subregister

35-54

register 폭은 32-bit에서 64-bit로 증가하지만 원래 32-bit ALU operation의 semantics는 32-bit subregister를 통해 보존됩니다. 모든 eBPF register는 64-bit이고, 아래쪽 32-bit subregister에 값을 쓰면 그 값이 64-bit로 zero-extend됩니다.

이 동작은 x86_64와 arm64의 subregister 정의에 직접 대응하지만 다른 JIT의 구현은 더 어렵게 만듭니다. 32-bit architecture는 64-bit eBPF program을 interpreter로 실행합니다. 해당 JIT는 32-bit subregister만 사용하는 BPF program을 native instruction set으로 변환하고 나머지는 interpreter에 맡길 수 있습니다.

operation이 64-bit인 이유는 64-bit architecture에서 pointer도 64-bit이고 kernel function에 64-bit value를 전달하거나 돌려받아야 하기 때문입니다. 32-bit eBPF register를 사용하면 register-pair ABI를 별도로 정의해야 하므로 eBPF register와 hardware register를 직접 mapping할 수 없습니다.

그 경우 JIT는 function에 드나드는 모든 register마다 combine/split/move operation을 수행해야 하며, 이는 복잡하고 bug가 생기기 쉬우며 느립니다. atomic 64-bit counter를 사용한다는 점도 64-bit register를 채택한 또 다른 이유입니다.

Fall-through 분기와 bpf_call 호출 규약

55-77

기존의 `if (cond) jump_true; else jump_false;` 같은 conditional `jt/jf` 구조는 `if (cond) jump_true; /* else fall-through */` 형태의 `jt/fall-through` 구조로 대체됩니다.

eBPF는 다른 kernel function과의 zero-overhead call을 위해 `bpf_call` instruction과 register passing convention을 도입합니다. in-kernel function을 호출하기 전에 eBPF program은 calling convention에 맞춰 argument를 `R1`부터 `R5`에 둡니다. interpreter는 이 값을 register에서 꺼내 in-kernel function에 전달합니다.

주어진 architecture에서 `R1 - R5`가 argument 전달용 CPU register에 mapping돼 있다면 JIT compiler는 별도 move를 생성할 필요가 없습니다. argument는 이미 올바른 register에 있고 `BPF_CALL`은 hardware의 단일 `call` instruction으로 JIT compile됩니다. 이 convention은 일반적인 호출 상황을 performance penalty 없이 처리하도록 선택됐습니다.

in-kernel function call이 끝나면 `R1 - R5`는 읽을 수 없는 상태로 reset되고 `R0`에 function return value가 들어갑니다. `R6 - R9`는 callee-saved이므로 call 전후에 상태가 보존됩니다.

C·x86_64·eBPF 호출 연결 예제

78-105

다음 세 C function을 예로 듭니다.

u64 f1() { return (*_f2)(1); }
u64 f2(u64 a) { return f3(a + 1, a); }
u64 f3(u64 a, u64 b) { return a - b; }

GCC는 `f1`과 `f3`를 다음 x86_64 code로 compile할 수 있습니다.

f1:
    movl $1, %edi
    movq _f2(%rip), %rax
    jmp  *%rax
f3:
    movq %rdi, %rax
    subq %rsi, %rax
    ret

eBPF로 작성한 `f2`는 다음과 같은 형태가 될 수 있습니다.

f2:
    bpf_mov R2, R1
    bpf_add R1, 1
    bpf_call f3
    bpf_exit

`f2`를 JIT compile하고 그 pointer를 `_f2`에 저장하면 `f1 -> f2 -> f3` call과 return이 끊김 없이 이어집니다. JIT를 사용하지 않을 때는 `__bpf_prog_run()` interpreter로 `f2`를 호출해야 합니다.

Program argument 제한과 x86_64 register mapping

106-132

실용적인 이유로 모든 eBPF program에는 `ctx`라는 argument 하나만 있습니다. `ctx`는 `__bpf_prog_run()` 시작 시점 등의 경로에서 이미 `R1`에 놓이며, program은 최대 5개 argument를 받는 kernel function을 호출할 수 있습니다. 6개 이상 argument를 받는 call은 현재 지원하지 않지만 필요하다면 향후 제한을 완화할 수 있습니다.

64-bit architecture에서는 모든 eBPF register를 hardware register에 일대일로 mapping합니다. 예를 들어 x86_64 JIT compiler는 다음처럼 mapping할 수 있습니다.

R0 - rax
R1 - rdi
R2 - rsi
R3 - rdx
R4 - rcx
R5 - r8
R6 - rbx
R7 - r13
R8 - r14
R9 - r15
R10 - rbp

x86_64 ABI는 argument 전달에 `rdi`, `rsi`, `rdx`, `rcx`, `r8`, `r9`를 사용하고 `rbx`, `r12 - r15`를 callee-saved로 규정하므로 이 mapping이 성립합니다.

eBPF pseudo-program과 x86_64 JIT 결과

133-190

다음 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

이를 x86_64로 JIT compile한 결과는 다음과 같은 형태가 될 수 있습니다.

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

이 예제는 C로 표현하면 다음과 같습니다.

u64 bpf_filter(u64 ctx)
{
    return foo(ctx, 2, 3, 4, 5) + bar(ctx, 6, 7, 8, 9);
}

`u64 (*)(u64 arg1, u64 arg2, u64 arg3, u64 arg4, u64 arg5)` prototype을 가진 in-kernel function `foo()`와 `bar()`는 올바른 register에서 argument를 받고, return value를 eBPF의 `R0`에 해당하는 `%rax`에 둡니다.

prologue와 epilogue는 JIT가 생성하며 interpreter에서는 암시적으로 처리됩니다. `R0-R5`는 scratch register이므로 eBPF program은 calling convention에 정의된 대로 call 사이에서 필요한 값을 보존해야 합니다.

Call 이후 register 상태와 program context

191-208

예를 들어 다음 program은 유효하지 않습니다.

bpf_mov R1, 1
bpf_call foo
bpf_mov R0, R1
bpf_exit

call 이후 `R1-R5`에는 의미 없는 값이 들어 있으므로 읽을 수 없습니다. in-kernel `verifier.rst`가 eBPF program을 검증합니다.

새 설계에서 eBPF program은 4096 insns로 제한됩니다. 따라서 모든 program은 빠르게 종료하며 고정된 횟수만큼만 kernel function을 호출합니다. Original BPF와 eBPF는 모두 two-operand instruction이므로 JIT 과정에서 eBPF instruction과 x86 instruction을 일대일로 mapping하기 쉽습니다.

interpreter function을 호출할 때 전달하는 input context pointer는 generic pointer이며 내용은 use case에 따라 정해집니다. seccomp에서는 `R1`이 `seccomp_data`를 가리키고, 변환된 BPF filter에서는 `R1`이 `skb`를 가리킵니다.

Instruction format과 일반 목적 RISC 특성

209-231

내부에서 변환되는 program element의 format은 다음과 같습니다.

op:16, jt:8, jf:8, k:32    ==>    op:8, dst_reg:4, src_reg:4, off:16, imm:32

현재까지 eBPF instruction은 87개가 구현됐습니다. 8-bit `op` opcode field에는 새 instruction을 추가할 여유가 있습니다. 일부 새 instruction은 16/24/32-byte encoding을 사용할 수 있지만 backward compatibility를 보존하려면 길이가 8 bytes의 배수여야 합니다.

eBPF는 general-purpose RISC instruction set입니다. original BPF에서 eBPF로 변환할 때 모든 register와 instruction을 사용하는 것은 아닙니다. 예를 들어 socket filter는 event counter를 유지하는 tracing filter와 달리 `exclusive add` instruction을 사용하지 않습니다.

socket filter는 `R9`도 사용하지 않지만 더 복잡한 filter는 register가 부족해 stack으로 spill/fill해야 할 수 있습니다.

eBPF는 마지막 단계의 performance optimization을 위한 generic assembler로 사용할 수 있습니다. socket filter와 seccomp는 eBPF를 assembler로 사용하고, tracing filter는 kernel에서 code를 생성하는 assembler로 사용할 수 있습니다.

kernel 내부에서 생성한 eBPF code가 userspace에 노출되지 않고 내부 code path를 최적화한다면 security consideration으로 제한할 필요가 없을 수 있습니다. eBPF의 safety는 `verifier.rst`에서 얻을 수 있으며, 이런 use case에서는 safe instruction set으로 활용할 수 있습니다.

Controlled execution과 verifier의 두 단계 검증

232-239

original BPF와 마찬가지로 eBPF는 controlled environment에서 deterministic하게 실행되므로 kernel이 그 특성을 쉽게 증명할 수 있습니다.

program safety는 두 단계로 판정합니다. 첫 단계는 depth-first-search를 수행해 loop를 금지하고 기타 CFG validation을 수행합니다. 두 번째 단계는 첫 instruction에서 시작해 가능한 모든 path를 내려가며 각 instruction의 실행을 simulate하고 register와 stack의 상태 변화를 관찰합니다.

Arithmetic·jump opcode encoding

240-254

eBPF는 classic BPF에서 eBPF로 쉽게 변환할 수 있도록 classic opcode encoding의 대부분을 재사용합니다.

arithmetic와 jump instruction의 8-bit `code` field는 operation code 4 bits, source 1 bit, instruction class 3 bits로 나뉩니다.

Arithmetic·jump instruction의 8-bit code field
Bit 위치Field
7..4 (MSB)4 bitsoperation code
31 bitsource
2..0 (LSB)3 bitsinstruction class
전체 폭8 bits
방향왼쪽이 MSB, 오른쪽이 LSB

bit 7의 MSB에서 bit 0의 LSB 방향으로 operation code, source, instruction class가 배치됩니다.

Classic BPF와 eBPF instruction class

255-268

하위 3 bits에는 다음 instruction class 중 하나를 저장합니다.

Classic BPF classeBPF class
`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

Source operand bit

269-288

4번째 bit는 source operand를 다음처럼 encode합니다.

BPF_K     0x00
BPF_X     0x08

classic BPF에서 의미는 다음과 같습니다.

BPF_SRC(code) == BPF_X - use register X as source operand
BPF_SRC(code) == BPF_K - use 32-bit immediate as source operand

eBPF에서 의미는 다음과 같습니다.

BPF_SRC(code) == BPF_X - use 'src_reg' register as source operand
BPF_SRC(code) == BPF_K - use 32-bit immediate as source operand

상위 4 bits에는 operation code를 저장합니다.

BPF_ALU와 BPF_ALU64 operation code

289-305

`BPF_CLASS(code) == BPF_ALU`이거나 eBPF에서 `BPF_ALU64`이면 `BPF_OP(code)`는 다음 중 하나입니다.

Operation비고
`BPF_ADD`0x00addition
`BPF_SUB`0x10subtraction
`BPF_MUL`0x20multiplication
`BPF_DIV`0x30division
`BPF_OR`0x40bitwise OR
`BPF_AND`0x50bitwise AND
`BPF_LSH`0x60left shift
`BPF_RSH`0x70logical right shift
`BPF_NEG`0x80negation
`BPF_MOD`0x90modulo
`BPF_XOR`0xa0bitwise XOR
`BPF_MOV`0xb0eBPF only: register-to-register move
`BPF_ARSH`0xc0eBPF only: sign-extending right shift
`BPF_END`0xd0eBPF only: endianness conversion

BPF_JMP와 BPF_JMP32 operation code

306-322

`BPF_CLASS(code) == BPF_JMP`이거나 eBPF에서 `BPF_JMP32`이면 `BPF_OP(code)`는 다음 중 하나입니다.

Operation비고
`BPF_JA`0x00`BPF_JMP` only
`BPF_JEQ`0x10equal
`BPF_JGT`0x20unsigned greater than
`BPF_JGE`0x30unsigned greater than or equal
`BPF_JSET`0x40bit test
`BPF_JNE`0x50eBPF only: not equal
`BPF_JSGT`0x60eBPF only: signed `>`
`BPF_JSGE`0x70eBPF only: signed `>=`
`BPF_CALL`0x80eBPF `BPF_JMP` only: function call
`BPF_EXIT`0x90eBPF `BPF_JMP` only: function return
`BPF_JLT`0xa0eBPF only: unsigned `<`
`BPF_JLE`0xb0eBPF only: unsigned `<=`
`BPF_JSLT`0xc0eBPF only: signed `<`
`BPF_JSLE`0xd0eBPF only: signed `<=`

32-bit·64-bit ALU와 return semantics

323-343

`BPF_ADD | BPF_X | BPF_ALU`는 classic BPF와 eBPF 모두에서 32-bit addition을 뜻합니다. register가 A와 X 둘뿐인 classic BPF에서는 `A += X`이고, eBPF에서는 `dst_reg = (u32) dst_reg + (u32) src_reg`입니다.

마찬가지로 `BPF_XOR | BPF_K | BPF_ALU`는 classic BPF에서 `A ^= imm32`이고 eBPF에서는 이에 대응하는 `src_reg = (u32) src_reg ^ (u32) imm32`입니다.

classic BPF는 `A = X`와 `X = A` move를 표현하는 데 `BPF_MISC` class를 사용합니다. eBPF는 대신 `BPF_MOV | BPF_X | BPF_ALU` code를 사용합니다.

eBPF에는 `BPF_MISC` operation이 없으므로 class 7을 `BPF_ALU64`로 사용합니다. operation 자체는 `BPF_ALU`와 같지만 operand 폭이 64-bit입니다. 따라서 `BPF_ADD | BPF_X | BPF_ALU64`는 64-bit addition, 즉 `dst_reg = dst_reg + src_reg`를 뜻합니다.

classic BPF는 단일 `ret` operation을 표현하기 위해 `BPF_RET` class 전체를 사용합니다. classic `BPF_RET | BPF_K`는 `imm32`를 return register에 복사하고 function을 종료합니다.

eBPF는 CPU 동작에 맞춰 설계됐으므로 `BPF_JMP | BPF_EXIT`는 function exit만 의미합니다. eBPF program은 `BPF_EXIT` 전에 return value를 `R0`에 저장해야 합니다. eBPF의 class 6은 `BPF_JMP32`이며, `BPF_JMP`와 같은 비교 operation을 32-bit operand 폭으로 수행합니다.

Load/store code의 mode와 size encoding

344-376

load와 store instruction의 8-bit `code` field는 mode 3 bits, size 2 bits, instruction class 3 bits로 나뉩니다.

Load/store instruction의 8-bit code field
Bit 위치Field
7..5 (MSB)3 bitsmode
4..32 bitssize
2..0 (LSB)3 bitsinstruction class
전체 폭8 bits
방향왼쪽이 MSB, 오른쪽이 LSB

bit 7의 MSB에서 bit 0의 LSB 방향으로 mode, size, instruction class가 배치됩니다.

size modifier는 다음 중 하나입니다.

Modifier의미
`BPF_W`0x00word
`BPF_H`0x08half word
`BPF_B`0x10byte
`BPF_DW`0x18eBPF only, double word

각 modifier가 encode하는 load/store operation size는 다음과 같습니다.

표기크기비고
`B`1 bytebyte
`H`2 byteshalf word
`W`4 bytesword
`DW`8 byteseBPF only

mode modifier는 다음 중 하나입니다.

Modifier비고
`BPF_IMM`0x00classic BPF의 32-bit move와 eBPF의 64-bit move에 사용
`BPF_ABS`0x20absolute packet access
`BPF_IND`0x40indirect packet access
`BPF_MEM`0x60memory access
`BPF_LEN`0x80classic BPF only, eBPF에서 reserved
`BPF_MSH`0xa0classic BPF only, eBPF에서 reserved
`BPF_ATOMIC`0xc0eBPF only, atomic operation