bpf, x86_32: add eBPF JIT compiler for ia32
Subsystems (7)
Wang YanQing
maintainerPATCH v2 1/1 bpf, x86_32: add eBPF JIT compiler for ia32
The JIT compiler emits ia32 bit instructions. Currently, It supports eBPF only. Classic BPF is supported because of the conversion by BPF core.
Almost all instructions from eBPF ISA supported except the following:
BPF_ALU64 | BPF_DIV | BPF_K BPF_ALU64 | BPF_DIV | BPF_X BPF_ALU64 | BPF_MOD | BPF_K BPF_ALU64 | BPF_MOD | BPF_X BPF_STX | BPF_XADD | BPF_W BPF_STX | BPF_XADD | BPF_DW
It doesn't support BPF_JMP|BPF_CALL with BPF_PSEUDO_CALL too.
ia32 has few general purpose registers, EAX|EDX|ECX|EBX|ESI|EDI, and in these six registers, we can't treat all of them as real general purpose registers in jit:
MUL instructions need EAX:EDX, shift instructions need ECX.
So I decide to use stack to emulate all eBPF 64 registers, this will simplify the implementation a lot, because we don't need to face the flexible memory address modes on ia32, for example, we don't need to write below code pattern for one BPF_ADD instruction:
if (src is a register && dst is a register)
{
//one instruction encoding for ADD instruction
} else if (only src is a register)
{
//another different instruction encoding for ADD instruction
} else if (only dst is a register)
{
//another different instruction encoding for ADD instruction
} else
{
//src and dst are all on stack.
//move src or dst to temporary registers
}If the above example if-else-else-else isn't so painful, try to think it for BPF_ALU64|BPF_*SHIFT* instruction which we need to use many native instructions to emulate.
Tested on my PC (Intel(R) Core(TM) i5-5200U CPU) and virtualbox.
Testing results on i5-5200U:
1) test_bpf: Summary: 349 PASSED, 0 FAILED, [319/341 JIT'ed]
2) test_progs: Summary: 81 PASSED, 2 FAILED.
test_progs report "libbpf: incorrect bpf_call opcode" for test_l4lb_noinline and test_xdp_noinline, because there is no llvm-6.0 on my machine, and current implementation doesn't support BPF_PSEUDO_CALL, so I think we can ignore the two failed testcases.
3) test_lpm: OK
4) test_lru_map: OK
5) test_verifier: Summary: 823 PASSED, 5 FAILED test_verifier report "invalid bpf_context access off=68 size=1/2/4/8" for all the 5 FAILED testcases with/without jit, we need to fix the failed testcases themself instead of this jit.
Above tests are all done with following flags settings discretely:
1:bpf_jit_enable=1 and bpf_jit_harden=0 2:bpf_jit_enable=1 and bpf_jit_harden=2
Below are some numbers for this jit implementation:
Note:
I run test_progs in kselftest 100 times continuously for every testcase, the numbers are in format: total/times=avg.
The numbers that test_bpf reports show almost the same relation.
a:jit_enable=0 and jit_harden=0 b:jit_enable=1 and jit_harden=0 test_pkt_access:PASS:ipv4:15622/100=156 test_pkt_access:PASS:ipv4:10057/100=100 test_pkt_access:PASS:ipv6:9130/100=91 test_pkt_access:PASS:ipv6:5055/100=50 test_xdp:PASS:ipv4:240198/100=2401 test_xdp:PASS:ipv4:145945/100=1459 test_xdp:PASS:ipv6:137326/100=1373 test_xdp:PASS:ipv6:67337/100=673 test_l4lb:PASS:ipv4:61100/100=611 test_l4lb:PASS:ipv4:38137/100=381 test_l4lb:PASS:ipv6:101000/100=1010 test_l4lb:PASS:ipv6:57779/100=577
c:jit_enable=1 and jit_harden=2
test_pkt_access:PASS:ipv4:12650/100=126
test_pkt_access:PASS:ipv6:7074/100=70
test_xdp:PASS:ipv4:147211/100=1472
test_xdp:PASS:ipv6:85783/100=857
test_l4lb:PASS:ipv4:53222/100=532
test_l4lb:PASS:ipv6:76322/100=763
Yes, the numbers are pretty when turn off jit_harden, if we want to speedup jit_harden, then we need to move BPF_REG_AX to *real* register instead of stack emulation, but when we do it, we need to face all the pain I describe above. We can do it in next step.
See Documentation/networking/filter.txt for more information.
---
Changes v1-v2:
1:Fix bug in emit_ia32_neg64.
2:Fix bug in emit_ia32_arsh_r64.
3:Delete filename in top level comment, suggested by Thomas Gleixner. 4:Delete unnecessary boiler plate text, suggested by Thomas Gleixner. 5:Rewrite some words in changelog.
6:CodingSytle improvement and a little more comments.
Thanks.
arch/x86/Kconfig | 2 +- arch/x86/include/asm/nospec-branch.h | 26 +- arch/x86/net/Makefile | 10 +- arch/x86/net/bpf_jit32.S | 143 +++ arch/x86/net/bpf_jit_comp32.c | 2258 ++++++++++++++++++++++++++++++++++ 5 files changed, 2434 insertions(+), 5 deletions(-) create mode 100644 arch/x86/net/bpf_jit32.S create mode 100644 arch/x86/net/bpf_jit_comp32.c
arch/x86/Kconfig +1 -1
@@ -137,7 +137,7 @@ config X86 select HAVE_DMA_CONTIGUOUS select HAVE_DYNAMIC_FTRACE select HAVE_DYNAMIC_FTRACE_WITH_REGS - select HAVE_EBPF_JIT if X86_64 + select HAVE_EBPF_JIT select HAVE_EFFICIENT_UNALIGNED_ACCESS select HAVE_EXIT_THREAD select HAVE_FENTRY if X86_64 || DYNAMIC_FTRACE
arch/x86/include/asm/nospec-branch.h +24 -2
@@ -291,14 +291,17 @@ static inline void indirect_branch_prediction_barrier(void)* lfence * jmp spec_trap * do_rop: -* mov %rax,(%rsp) +* mov %rax,(%rsp) for x86_64 +* mov %edx,(%esp) for x86_32 * retq * * Without retpolines configured: * -* jmp *%rax +* jmp *%rax for x86_64 +* jmp *%edx for x86_32 */ #ifdef CONFIG_RETPOLINE +#ifdef CONFIG_X86_64 # define RETPOLINE_RAX_BPF_JIT_SIZE 17# define RETPOLINE_RAX_BPF_JIT () \ EMIT1_off32(0xE8, 7);/* callq do_rop */ \
@@ -310,9 +313,28 @@ static inline void indirect_branch_prediction_barrier(void)EMIT4 (0x48 ,0x89 ,0x04 ,0x24 );/* mov %rax,(%rsp) */ \EMIT1 (0xC3 );/* retq */ #else +# define RETPOLINE_EDX_BPF_JIT () \ +do { \ + EMIT1_off32(0xE8, 7);/* call do_rop */ \ +/* spec_trap: */ \ +EMIT2 (0xF3 ,0x90 );/* pause */ \ +EMIT3 (0x0F ,0xAE ,0xE8 );/* lfence */ \ +EMIT2 (0xEB ,0xF9 );/* jmp spec_trap */ \ +/* do_rop: */ \ +EMIT3 (0x89 ,0x14 ,0x24 );/* mov %edx,(%esp) */ \ +EMIT1 (0xC3 );/* ret */ \ +} while (0) +#endif +#else /* !CONFIG_RETPOLINE */ + +#ifdef CONFIG_X86_64 # define RETPOLINE_RAX_BPF_JIT_SIZE 2# define RETPOLINE_RAX_BPF_JIT () \ EMIT2(0xFF, 0xE0);/* jmp *%rax */ +#else +# define RETPOLINE_EDX_BPF_JIT () \ + EMIT2(0xFF, 0xE2)/* jmp *%edx */ +#endif #endif #endif /* _ASM_X86_NOSPEC_BRANCH_H_ */
arch/x86/net/Makefile +8 -2
@@ -1,6 +1,12 @@ # # Arch-specific network modules # -OBJECT_FILES_NON_STANDARD_bpf_jit.o += y -obj-$(CONFIG_BPF_JIT) += bpf_jit.o bpf_jit_comp.o + +ifeq ($(CONFIG_X86_32),y) + OBJECT_FILES_NON_STANDARD_bpf_jit32.o += y + obj-$(CONFIG_BPF_JIT) += bpf_jit32.o bpf_jit_comp32.o +else + OBJECT_FILES_NON_STANDARD_bpf_jit.o += y + obj-$(CONFIG_BPF_JIT) += bpf_jit.o bpf_jit_comp.o +endif
arch/x86/net/bpf_jit32.S new file +143 -0
143 lines not on this page · raw message
arch/x86/net/bpf_jit_comp32.c new file +2258 -0
2,258 lines not on this page · raw message
Signature · 1 line
1.8.5.6.2.g3d8a54e.dirty
Daniel Borkmann
maintainerQuoted 38 lines · Wang YanQing
The JIT compiler emits ia32 bit instructions. Currently, It supports eBPF only. Classic BPF is supported because of the conversion by BPF core.
Almost all instructions from eBPF ISA supported except the following:
BPF_ALU64 | BPF_DIV | BPF_K BPF_ALU64 | BPF_DIV | BPF_X BPF_ALU64 | BPF_MOD | BPF_K BPF_ALU64 | BPF_MOD | BPF_X BPF_STX | BPF_XADD | BPF_W BPF_STX | BPF_XADD | BPF_DWIt doesn't support BPF_JMP|BPF_CALL with
BPF_PSEUDO_CALLtoo.ia32 has few general purpose registers, EAX|EDX|ECX|EBX|ESI|EDI, and in these six registers, we can't treat all of them as real general purpose registers in jit:
MUL instructions need EAX:EDX, shift instructions need ECX.So I decide to use stack to emulate all eBPF 64 registers, this will simplify the implementation a lot, because we don't need to face the flexible memory address modes on ia32, for example, we don't need to write below code pattern for one
BPF_ADDinstruction:if (src is a register && dst is a register) { //one instruction encoding for ADD instruction } else if (only src is a register) { //another different instruction encoding for ADD instruction } else if (only dst is a register) { //another different instruction encoding for ADD instruction } else { //src and dst are all on stack. //move src or dst to temporary registers }
Quoted 5 lines
If the above example if-else-else-else isn't so painful, try to think it for BPF_ALU64|BPF_*SHIFT* instruction which we need to use many native instructions to emulate.
Tested on my PC (Intel(R) Core(TM) i5-5200U CPU) and virtualbox.
Just out of plain curiosity, do you have a specific use case on the JIT for x86_32? Are you targeting this to be used for e.g. Atom or the like (sorry, don't really have a good overview where x86_32 is still in use these days)?
Quoted 15 lines
Testing results on i5-5200U:
1)
test_bpf: Summary: 349 PASSED, 0 FAILED, [319/341 JIT'ed]
2)test_progs: Summary: 81 PASSED, 2 FAILED.
test_progsreport "libbpf: incorrectbpf_callopcode" fortest_l4lb_noinlineandtest_xdp_noinline, because there is no llvm-6.0 on my machine, and current implementation doesn't supportBPF_PSEUDO_CALL, so I think we can ignore the two failed testcases.
3)test_lpm: OK
4)test_lru_map: OK
5)test_verifier: Summary: 823 PASSED, 5 FAILEDtest_verifierreport "invalidbpf_contextaccess off=68 size=1/2/4/8" for all the 5 FAILED testcases with/without jit, we need to fix the failed testcases themself instead of this jit.
Can you elaborate further on these? Looks like this definitely needs fixing on 32 bit. Would be great to get a better understanding of the underlying bug(s) and properly fix them.
Quoted 34 lines
Above tests are all done with following flags settings discretely:
1:bpf_jit_enable=1 and bpf_jit_harden=0 2:bpf_jit_enable=1 and bpf_jit_harden=2Below are some numbers for this jit implementation:
Note:
I runtest_progsin kselftest 100 times continuously for every testcase, the numbers are in format: total/times=avg.
The numbers thattest_bpfreports show almost the same relation.a:jit_enable=0 and jit_harden=0 b:jit_enable=1 and jit_harden=0 test_pkt_access:PASS:ipv4:15622/100=156 test_pkt_access:PASS:ipv4:10057/100=100 test_pkt_access:PASS:ipv6:9130/100=91 test_pkt_access:PASS:ipv6:5055/100=50 test_xdp:PASS:ipv4:240198/100=2401 test_xdp:PASS:ipv4:145945/100=1459 test_xdp:PASS:ipv6:137326/100=1373 test_xdp:PASS:ipv6:67337/100=673 test_l4lb:PASS:ipv4:61100/100=611 test_l4lb:PASS:ipv4:38137/100=381 test_l4lb:PASS:ipv6:101000/100=1010 test_l4lb:PASS:ipv6:57779/100=577c:jit_enable=1 and jit_harden=2
test_pkt_access:PASS:ipv4:12650/100=126
test_pkt_access:PASS:ipv6:7074/100=70
test_xdp:PASS:ipv4:147211/100=1472
test_xdp:PASS:ipv6:85783/100=857
test_l4lb:PASS:ipv4:53222/100=532
test_l4lb:PASS:ipv6:76322/100=763Yes, the numbers are pretty when turn off
jit_harden, if we want to speedupjit_harden, then we need to moveBPF_REG_AXto *real* register instead of stack emulation, but when we do it, we need to face all the pain I describe above. We can do it in next step.See
Documentation/networking/filter.txtfor more information.Signed-off-by Wang YanQing <[email protected]>
[...]
arch/x86/net/bpf_jit_comp32.c line 1823
Quoted 11 lines
arch/x86/net/bpf_jit_comp32.c new file +11
-0+/* call */ +case BPF_JMP |BPF_CALL : + { +const u8 *r1 = bpf2ia32[BPF_REG_1 ]; +const u8 *r2 = bpf2ia32[BPF_REG_2 ]; +const u8 *r3 = bpf2ia32[BPF_REG_3 ]; +const u8 *r4 = bpf2ia32[BPF_REG_4 ]; +const u8 *r5 = bpf2ia32[BPF_REG_5 ]; + +if (insn->src_reg ==BPF_PSEUDO_CALL ) + goto notyet;
Here you bail out on BPF_PSEUDO_CALL, but in below bpf_int_jit_compile() you implement half of e.g. 1c2a088a6626 ("bpf: x64: add JIT support for multi-function programs"), which is rather confusing to leave it in this half complete state; either remove altogether or implement everything.
Quoted 219 lines
arch/x86/net/bpf_jit_comp32.c new file +219
-0+ func = (u8 *) __bpf_call_base + imm32; + jmp_offset = func - (image + addrs[i]); + +if (!imm32 || !is_simm32 (jmp_offset)) { +pr_err ("unsupported bpf func %d addr %p image %p\n" , + imm32, func, image); +return -EINVAL ; + } + +/* mov eax,dword ptr [ebp+off] */ +EMIT3 (0x8B ,add_2reg (0x40 ,IA32_EBP , tmp2[0 ]), +STACK_VAR (r1[0 ])); +/* mov edx,dword ptr [ebp+off] */ +EMIT3 (0x8B ,add_2reg (0x40 ,IA32_EBP , tmp2[1 ]), +STACK_VAR (r1[1 ])); + +emit_push_r64 (r5, &prog); +emit_push_r64 (r4, &prog); +emit_push_r64 (r3, &prog); +emit_push_r64 (r2, &prog); + +EMIT1_off32 (0xE8 , jmp_offset +9 ); + +/* mov dword ptr [ebp+off],eax */ +EMIT3 (0x89 ,add_2reg (0x40 ,IA32_EBP , tmp2[0 ]), +STACK_VAR (r0[0 ])); +/* mov dword ptr [ebp+off],edx */ +EMIT3 (0x89 ,add_2reg (0x40 ,IA32_EBP , tmp2[1 ]), +STACK_VAR (r0[1 ])); + +/* add esp,32 */ +EMIT3 (0x83 ,add_1reg (0xC0 ,IA32_ESP ),32 ); +break ; + } +case BPF_JMP |BPF_TAIL_CALL : +emit_bpf_tail_call (&prog); +break ; + +/* cond jump */ +case BPF_JMP |BPF_JEQ |BPF_X : +case BPF_JMP |BPF_JNE |BPF_X : +case BPF_JMP |BPF_JGT |BPF_X : +case BPF_JMP |BPF_JLT |BPF_X : +case BPF_JMP |BPF_JGE |BPF_X : +case BPF_JMP |BPF_JLE |BPF_X : +case BPF_JMP |BPF_JSGT |BPF_X : +case BPF_JMP |BPF_JSLE |BPF_X : +case BPF_JMP |BPF_JSLT |BPF_X : +case BPF_JMP |BPF_JSGE |BPF_X : +/* mov esi,dword ptr [ebp+off] */ +EMIT3 (0x8B ,add_2reg (0x40 ,IA32_EBP , tmp[0 ]), +STACK_VAR (src_hi)); +/* cmp dword ptr [ebp+off], esi */ +EMIT3 (0x39 ,add_2reg (0x40 ,IA32_EBP , tmp[0 ]), +STACK_VAR (dst_hi)); + +EMIT2 (IA32_JNE ,6 ); +/* mov esi,dword ptr [ebp+off] */ +EMIT3 (0x8B ,add_2reg (0x40 ,IA32_EBP , tmp[0 ]), +STACK_VAR (src_lo)); +/* cmp dword ptr [ebp+off], esi */ +EMIT3 (0x39 ,add_2reg (0x40 ,IA32_EBP , tmp[0 ]), +STACK_VAR (dst_lo)); + goto emit_cond_jmp; + +case BPF_JMP |BPF_JSET |BPF_X : +/* mov esi,dword ptr [ebp+off] */ +EMIT3 (0x8B ,add_2reg (0x40 ,IA32_EBP , tmp[0 ]), +STACK_VAR (dst_lo)); +/* and esi,dword ptr [ebp+off]*/ +EMIT3 (0x23 ,add_2reg (0x40 ,IA32_EBP , tmp[0 ]), +STACK_VAR (src_lo)); + +/* mov edi,dword ptr [ebp+off] */ +EMIT3 (0x8B ,add_2reg (0x40 ,IA32_EBP , tmp[1 ]), +STACK_VAR (dst_hi)); +/* and edi,dword ptr [ebp+off] */ +EMIT3 (0x23 ,add_2reg (0x40 ,IA32_EBP , tmp[1 ]), +STACK_VAR (src_hi)); +/* or esi,edi */ +EMIT2 (0x09 ,add_2reg (0xC0 , tmp[0 ], tmp[1 ])); + goto emit_cond_jmp; + +case BPF_JMP |BPF_JSET |BPF_K : { +u32 hi; + + hi = imm32 & (1 <<31 ) ? (u32 )~0 :0 ; +/* mov esi,imm32 */ +EMIT2_off32 (0xC7 ,add_1reg (0xC0 , tmp[0 ]), imm32); +/* and esi,dword ptr [ebp+off]*/ +EMIT3 (0x23 ,add_2reg (0x40 ,IA32_EBP , tmp[0 ]), +STACK_VAR (dst_lo)); + +/* mov esi,imm32 */ +EMIT2_off32 (0xC7 ,add_1reg (0xC0 , tmp[1 ]), hi); +/* and esi,dword ptr [ebp+off] */ +EMIT3 (0x23 ,add_2reg (0x40 ,IA32_EBP , tmp[1 ]), +STACK_VAR (dst_hi)); +/* or esi,edi */ +EMIT2 (0x09 ,add_2reg (0xC0 , tmp[0 ], tmp[1 ])); + goto emit_cond_jmp; + } +case BPF_JMP |BPF_JEQ |BPF_K : +case BPF_JMP |BPF_JNE |BPF_K : +case BPF_JMP |BPF_JGT |BPF_K : +case BPF_JMP |BPF_JLT |BPF_K : +case BPF_JMP |BPF_JGE |BPF_K : +case BPF_JMP |BPF_JLE |BPF_K : +case BPF_JMP |BPF_JSGT |BPF_K : +case BPF_JMP |BPF_JSLE |BPF_K : +case BPF_JMP |BPF_JSLT |BPF_K : +case BPF_JMP |BPF_JSGE |BPF_K : { +u32 hi; + + hi = imm32 & (1 <<31 ) ? (u32 )~0 :0 ; +/* mov esi,imm32 */ +EMIT2_off32 (0xC7 ,add_1reg (0xC0 , tmp[0 ]), hi); +/* cmp dword ptr [ebp+off],esi */ +EMIT3 (0x39 ,add_2reg (0x40 ,IA32_EBP , tmp[0 ]), +STACK_VAR (dst_hi)); + +EMIT2 (IA32_JNE ,6 ); +/* mov esi,imm32 */ +EMIT2_off32 (0xC7 ,add_1reg (0xC0 , tmp[0 ]), imm32); +/* cmp dword ptr [ebp+off],esi */ +EMIT3 (0x39 ,add_2reg (0x40 ,IA32_EBP , tmp[0 ]), +STACK_VAR (dst_lo)); + +emit_cond_jmp:/* convert BPF opcode to x86 */ +switch (BPF_OP (code)) { +case BPF_JEQ : + jmp_cond =IA32_JE ; +break ; +case BPF_JSET : +case BPF_JNE : + jmp_cond =IA32_JNE ; +break ; +case BPF_JGT : +/* GT is unsigned '>', JA in x86 */ + jmp_cond =IA32_JA ; +break ; +case BPF_JLT : +/* LT is unsigned '<', JB in x86 */ + jmp_cond =IA32_JB ; +break ; +case BPF_JGE : +/* GE is unsigned '>=', JAE in x86 */ + jmp_cond =IA32_JAE ; +break ; +case BPF_JLE : +/* LE is unsigned '<=', JBE in x86 */ + jmp_cond =IA32_JBE ; +break ; +case BPF_JSGT : +/* signed '>', GT in x86 */ + jmp_cond =IA32_JG ; +break ; +case BPF_JSLT : +/* signed '<', LT in x86 */ + jmp_cond =IA32_JL ; +break ; +case BPF_JSGE : +/* signed '>=', GE in x86 */ + jmp_cond =IA32_JGE ; +break ; +case BPF_JSLE : +/* signed '<=', LE in x86 */ + jmp_cond =IA32_JLE ; +break ; +default :/* to silence gcc warning */ +return -EFAULT ; + } + jmp_offset = addrs[i + insn->off] - addrs[i]; +if (is_imm8 (jmp_offset)) { +EMIT2 (jmp_cond, jmp_offset); + }else if (is_simm32 (jmp_offset)) { +EMIT2_off32 (0x0F , jmp_cond +0x10 , jmp_offset); + }else { +pr_err ("cond_jmp gen bug %llx\n" , jmp_offset); +return -EFAULT ; + } + +break ; + } +case BPF_JMP |BPF_JA : + jmp_offset = addrs[i + insn->off] - addrs[i]; +if (!jmp_offset) +/* optimize out nop jumps */ +break ; +emit_jmp: +if (is_imm8 (jmp_offset)) { +EMIT2 (0xEB , jmp_offset); + }else if (is_simm32 (jmp_offset)) { +EMIT1_off32 (0xE9 , jmp_offset); + }else { +pr_err ("jmp gen bug %llx\n" , jmp_offset); +return -EFAULT ; + } +break ; + +case BPF_LD |BPF_IND |BPF_W : + func = sk_load_word; + goto common_load; +case BPF_LD |BPF_ABS |BPF_W : + func =CHOOSE_LOAD_FUNC (imm32, sk_load_word); +common_load: + jmp_offset = func - (image + addrs[i]); +if (!func || !is_simm32 (jmp_offset)) { +pr_err ("unsupported bpf func %d addr %p image %p\n" , + imm32, func, image); +return -EINVAL ; + } +if (BPF_MODE (code) ==BPF_ABS ) { +/* mov %edx, imm32 */ +EMIT1_off32 (0xBA , imm32); + }else { +/* mov edx,dword ptr [ebp+off] */ +EMIT3 (0x8B ,add_2reg (0x40 ,IA32_EBP ,IA32_EDX ), +STACK_VAR (src_lo));
arch/x86/net/bpf_jit_comp32.c line 2053
Quoted 10 lines
arch/x86/net/bpf_jit_comp32.c new file +10
-0+if (imm32) { +if (is_imm8 (imm32)) +/* add %edx, imm8 */ +EMIT3 (0x83 ,0xC2 , imm32); +else +/* add %edx, imm32 */ +EMIT2_off32 (0x81 ,0xC2 , imm32); + } + } +emit_load_skb_data_hlen (&prog);
The only place where you call the emit_load_skb_data_hlen() is here, so it effectively doesn't cache anything as with the other JITs. I would suggest to keep the complexity of the BPF_ABS/IND rather very low, remove the arch/x86/net/bpf_jit32.S handling altogether and just follow the model the way arm64 implemented this in the arch/arm64/net/bpf_jit_comp.c JIT. For eBPF these instructions are legacy and have been source of many subtle bugs in the various BPF JITs in the past, plus nobody complained about the current interpreter speed for LD_ABS/IND on x86_32 anyway so far. ;) We're also very close to have a rewrite of LD_ABS/IND out of native eBPF with similar performance to be able to finally remove them from the core.
Quoted 32 lines
arch/x86/net/bpf_jit_comp32.c new file +32
-0+EMIT1_off32 (0xE8 , jmp_offset +10 );/* call */ + +/* mov dword ptr [ebp+off],eax */ +EMIT3 (0x89 ,add_2reg (0x40 ,IA32_EBP ,IA32_EAX ), +STACK_VAR (r0[0 ])); +EMIT3 (0xC7 ,add_1reg (0x40 ,IA32_EBP ),STACK_VAR (r0[1 ])); +EMIT (0x0 ,4 ); +break ; + +case BPF_LD |BPF_IND |BPF_H : + func = sk_load_half; + goto common_load; +case BPF_LD |BPF_ABS |BPF_H : + func =CHOOSE_LOAD_FUNC (imm32, sk_load_half); + goto common_load; +case BPF_LD |BPF_IND |BPF_B : + func = sk_load_byte; + goto common_load; +case BPF_LD |BPF_ABS |BPF_B : + func =CHOOSE_LOAD_FUNC (imm32, sk_load_byte); + goto common_load; +/* STX XADD: lock *(u32 *)(dst + off) += src */ +case BPF_STX |BPF_XADD |BPF_W : +/* STX XADD: lock *(u64 *)(dst + off) += src */ +case BPF_STX |BPF_XADD |BPF_DW : + goto notyet; +case BPF_JMP |BPF_EXIT : +if (seen_exit) { + jmp_offset = ctx->cleanup_addr - addrs[i]; + goto emit_jmp; + } + seen_exit = true;
arch/x86/net/bpf_jit_comp32.c line 2095
Quoted 10 lines
arch/x86/net/bpf_jit_comp32.c new file +10
-0+/* update cleanup_addr */ + ctx->cleanup_addr = proglen; +emit_epilogue (&prog, bpf_prog->aux->stack_depth); +break ; +notyet: +pr_info_once ("*** NOT YET: opcode %02x ***\n" , code); +return -EFAULT ; +default : +/* This error will be seen if new instruction was added +* to interpreter, but not to JIT
[...]
Thanks,
Daniel
Wang YanQing
maintainerQuoted 43 lines · Daniel Borkmann
Quoted 42 lines · Wang YanQing
The JIT compiler emits ia32 bit instructions. Currently, It supports eBPF only. Classic BPF is supported because of the conversion by BPF core.
Almost all instructions from eBPF ISA supported except the following:
BPF_ALU64 | BPF_DIV | BPF_K BPF_ALU64 | BPF_DIV | BPF_X BPF_ALU64 | BPF_MOD | BPF_K BPF_ALU64 | BPF_MOD | BPF_X BPF_STX | BPF_XADD | BPF_W BPF_STX | BPF_XADD | BPF_DWIt doesn't support BPF_JMP|BPF_CALL with
BPF_PSEUDO_CALLtoo.ia32 has few general purpose registers, EAX|EDX|ECX|EBX|ESI|EDI, and in these six registers, we can't treat all of them as real general purpose registers in jit:
MUL instructions need EAX:EDX, shift instructions need ECX.So I decide to use stack to emulate all eBPF 64 registers, this will simplify the implementation a lot, because we don't need to face the flexible memory address modes on ia32, for example, we don't need to write below code pattern for one
BPF_ADDinstruction:if (src is a register && dst is a register) { //one instruction encoding for ADD instruction } else if (only src is a register) { //another different instruction encoding for ADD instruction } else if (only dst is a register) { //another different instruction encoding for ADD instruction } else { //src and dst are all on stack. //move src or dst to temporary registers }If the above example if-else-else-else isn't so painful, try to think it for BPF_ALU64|BPF_*SHIFT* instruction which we need to use many native instructions to emulate.
Quoted 6 lines
Quoted 1 line
Tested on my PC (Intel(R) Core(TM) i5-5200U CPU) and virtualbox.
Just out of plain curiosity, do you have a specific use case on the JIT for
x86_32? Are you targeting this to be used for e.g. Atom or the like (sorry, don't really have a good overview wherex86_32is still in use these days)?
Yes, you are right that x86_32 isn't the BIG DEAL anymore, but they willn't disappear completely in near future, the same as 32-bit linux kernel on 64bit machine.
For me, because I use 32-bit linux on development/hacking notebook for many years, I try to trace/perf the kernel, then I meet eBPF, it is strange that there isn't a jit for it, so I try to fill the empty.
Quoted 17 lines
Quoted 16 lines
Testing results on i5-5200U:
1)
test_bpf: Summary: 349 PASSED, 0 FAILED, [319/341 JIT'ed]
2)test_progs: Summary: 81 PASSED, 2 FAILED.
test_progsreport "libbpf: incorrectbpf_callopcode" fortest_l4lb_noinlineandtest_xdp_noinline, because there is no llvm-6.0 on my machine, and current implementation doesn't supportBPF_PSEUDO_CALL, so I think we can ignore the two failed testcases.
3)test_lpm: OK
4)test_lru_map: OK
5)test_verifier: Summary: 823 PASSED, 5 FAILEDtest_verifierreport "invalidbpf_contextaccess off=68 size=1/2/4/8" for all the 5 FAILED testcases with/without jit, we need to fix the failed testcases themself instead of this jit.
Quoted 3 lines
Can you elaborate further on these? Looks like this definitely needs fixing on 32 bit. Would be great to get a better understanding of the underlying bug(s) and properly fix them.
Ok! I will try to dig them out.
Quoted 36 lines
Quoted 35 lines
Above tests are all done with following flags settings discretely:
1:bpf_jit_enable=1 and bpf_jit_harden=0 2:bpf_jit_enable=1 and bpf_jit_harden=2Below are some numbers for this jit implementation:
Note:
I runtest_progsin kselftest 100 times continuously for every testcase, the numbers are in format: total/times=avg.
The numbers thattest_bpfreports show almost the same relation.a:jit_enable=0 and jit_harden=0 b:jit_enable=1 and jit_harden=0 test_pkt_access:PASS:ipv4:15622/100=156 test_pkt_access:PASS:ipv4:10057/100=100 test_pkt_access:PASS:ipv6:9130/100=91 test_pkt_access:PASS:ipv6:5055/100=50 test_xdp:PASS:ipv4:240198/100=2401 test_xdp:PASS:ipv4:145945/100=1459 test_xdp:PASS:ipv6:137326/100=1373 test_xdp:PASS:ipv6:67337/100=673 test_l4lb:PASS:ipv4:61100/100=611 test_l4lb:PASS:ipv4:38137/100=381 test_l4lb:PASS:ipv6:101000/100=1010 test_l4lb:PASS:ipv6:57779/100=577c:jit_enable=1 and jit_harden=2
test_pkt_access:PASS:ipv4:12650/100=126
test_pkt_access:PASS:ipv6:7074/100=70
test_xdp:PASS:ipv4:147211/100=1472
test_xdp:PASS:ipv6:85783/100=857
test_l4lb:PASS:ipv4:53222/100=532
test_l4lb:PASS:ipv6:76322/100=763Yes, the numbers are pretty when turn off
jit_harden, if we want to speedupjit_harden, then we need to moveBPF_REG_AXto *real* register instead of stack emulation, but when we do it, we need to face all the pain I describe above. We can do it in next step.See
Documentation/networking/filter.txtfor more information.Signed-off-by Wang YanQing <[email protected]>[...]
Quoted 16 lines
arch/x86/net/bpf_jit_comp32.c line 1823
Quoted 11 lines
arch/x86/net/bpf_jit_comp32.c new file +11
-0+/* call */ +case BPF_JMP |BPF_CALL : + { +const u8 *r1 = bpf2ia32[BPF_REG_1 ]; +const u8 *r2 = bpf2ia32[BPF_REG_2 ]; +const u8 *r3 = bpf2ia32[BPF_REG_3 ]; +const u8 *r4 = bpf2ia32[BPF_REG_4 ]; +const u8 *r5 = bpf2ia32[BPF_REG_5 ]; + +if (insn->src_reg ==BPF_PSEUDO_CALL ) + goto notyet;Here you bail out on
BPF_PSEUDO_CALL, but in belowbpf_int_jit_compile()you implement half of e.g.1c2a088a6626("bpf: x64: add JIT support for multi-function programs"), which is rather confusing to leave it in this half complete state; either remove altogether or implement everything.
Done.
Quoted 233 lines
Quoted 220 lines
arch/x86/net/bpf_jit_comp32.c new file +219
-0+ func = (u8 *) __bpf_call_base + imm32; + jmp_offset = func - (image + addrs[i]); + +if (!imm32 || !is_simm32 (jmp_offset)) { +pr_err ("unsupported bpf func %d addr %p image %p\n" , + imm32, func, image); +return -EINVAL ; + } + +/* mov eax,dword ptr [ebp+off] */ +EMIT3 (0x8B ,add_2reg (0x40 ,IA32_EBP , tmp2[0 ]), +STACK_VAR (r1[0 ])); +/* mov edx,dword ptr [ebp+off] */ +EMIT3 (0x8B ,add_2reg (0x40 ,IA32_EBP , tmp2[1 ]), +STACK_VAR (r1[1 ])); + +emit_push_r64 (r5, &prog); +emit_push_r64 (r4, &prog); +emit_push_r64 (r3, &prog); +emit_push_r64 (r2, &prog); + +EMIT1_off32 (0xE8 , jmp_offset +9 ); + +/* mov dword ptr [ebp+off],eax */ +EMIT3 (0x89 ,add_2reg (0x40 ,IA32_EBP , tmp2[0 ]), +STACK_VAR (r0[0 ])); +/* mov dword ptr [ebp+off],edx */ +EMIT3 (0x89 ,add_2reg (0x40 ,IA32_EBP , tmp2[1 ]), +STACK_VAR (r0[1 ])); + +/* add esp,32 */ +EMIT3 (0x83 ,add_1reg (0xC0 ,IA32_ESP ),32 ); +break ; + } +case BPF_JMP |BPF_TAIL_CALL : +emit_bpf_tail_call (&prog); +break ; + +/* cond jump */ +case BPF_JMP |BPF_JEQ |BPF_X : +case BPF_JMP |BPF_JNE |BPF_X : +case BPF_JMP |BPF_JGT |BPF_X : +case BPF_JMP |BPF_JLT |BPF_X : +case BPF_JMP |BPF_JGE |BPF_X : +case BPF_JMP |BPF_JLE |BPF_X : +case BPF_JMP |BPF_JSGT |BPF_X : +case BPF_JMP |BPF_JSLE |BPF_X : +case BPF_JMP |BPF_JSLT |BPF_X : +case BPF_JMP |BPF_JSGE |BPF_X : +/* mov esi,dword ptr [ebp+off] */ +EMIT3 (0x8B ,add_2reg (0x40 ,IA32_EBP , tmp[0 ]), +STACK_VAR (src_hi)); +/* cmp dword ptr [ebp+off], esi */ +EMIT3 (0x39 ,add_2reg (0x40 ,IA32_EBP , tmp[0 ]), +STACK_VAR (dst_hi)); + +EMIT2 (IA32_JNE ,6 ); +/* mov esi,dword ptr [ebp+off] */ +EMIT3 (0x8B ,add_2reg (0x40 ,IA32_EBP , tmp[0 ]), +STACK_VAR (src_lo)); +/* cmp dword ptr [ebp+off], esi */ +EMIT3 (0x39 ,add_2reg (0x40 ,IA32_EBP , tmp[0 ]), +STACK_VAR (dst_lo)); + goto emit_cond_jmp; + +case BPF_JMP |BPF_JSET |BPF_X : +/* mov esi,dword ptr [ebp+off] */ +EMIT3 (0x8B ,add_2reg (0x40 ,IA32_EBP , tmp[0 ]), +STACK_VAR (dst_lo)); +/* and esi,dword ptr [ebp+off]*/ +EMIT3 (0x23 ,add_2reg (0x40 ,IA32_EBP , tmp[0 ]), +STACK_VAR (src_lo)); + +/* mov edi,dword ptr [ebp+off] */ +EMIT3 (0x8B ,add_2reg (0x40 ,IA32_EBP , tmp[1 ]), +STACK_VAR (dst_hi)); +/* and edi,dword ptr [ebp+off] */ +EMIT3 (0x23 ,add_2reg (0x40 ,IA32_EBP , tmp[1 ]), +STACK_VAR (src_hi)); +/* or esi,edi */ +EMIT2 (0x09 ,add_2reg (0xC0 , tmp[0 ], tmp[1 ])); + goto emit_cond_jmp; + +case BPF_JMP |BPF_JSET |BPF_K : { +u32 hi; + + hi = imm32 & (1 <<31 ) ? (u32 )~0 :0 ; +/* mov esi,imm32 */ +EMIT2_off32 (0xC7 ,add_1reg (0xC0 , tmp[0 ]), imm32); +/* and esi,dword ptr [ebp+off]*/ +EMIT3 (0x23 ,add_2reg (0x40 ,IA32_EBP , tmp[0 ]), +STACK_VAR (dst_lo)); + +/* mov esi,imm32 */ +EMIT2_off32 (0xC7 ,add_1reg (0xC0 , tmp[1 ]), hi); +/* and esi,dword ptr [ebp+off] */ +EMIT3 (0x23 ,add_2reg (0x40 ,IA32_EBP , tmp[1 ]), +STACK_VAR (dst_hi)); +/* or esi,edi */ +EMIT2 (0x09 ,add_2reg (0xC0 , tmp[0 ], tmp[1 ])); + goto emit_cond_jmp; + } +case BPF_JMP |BPF_JEQ |BPF_K : +case BPF_JMP |BPF_JNE |BPF_K : +case BPF_JMP |BPF_JGT |BPF_K : +case BPF_JMP |BPF_JLT |BPF_K : +case BPF_JMP |BPF_JGE |BPF_K : +case BPF_JMP |BPF_JLE |BPF_K : +case BPF_JMP |BPF_JSGT |BPF_K : +case BPF_JMP |BPF_JSLE |BPF_K : +case BPF_JMP |BPF_JSLT |BPF_K : +case BPF_JMP |BPF_JSGE |BPF_K : { +u32 hi; + + hi = imm32 & (1 <<31 ) ? (u32 )~0 :0 ; +/* mov esi,imm32 */ +EMIT2_off32 (0xC7 ,add_1reg (0xC0 , tmp[0 ]), hi); +/* cmp dword ptr [ebp+off],esi */ +EMIT3 (0x39 ,add_2reg (0x40 ,IA32_EBP , tmp[0 ]), +STACK_VAR (dst_hi)); + +EMIT2 (IA32_JNE ,6 ); +/* mov esi,imm32 */ +EMIT2_off32 (0xC7 ,add_1reg (0xC0 , tmp[0 ]), imm32); +/* cmp dword ptr [ebp+off],esi */ +EMIT3 (0x39 ,add_2reg (0x40 ,IA32_EBP , tmp[0 ]), +STACK_VAR (dst_lo)); + +emit_cond_jmp:/* convert BPF opcode to x86 */ +switch (BPF_OP (code)) { +case BPF_JEQ : + jmp_cond =IA32_JE ; +break ; +case BPF_JSET : +case BPF_JNE : + jmp_cond =IA32_JNE ; +break ; +case BPF_JGT : +/* GT is unsigned '>', JA in x86 */ + jmp_cond =IA32_JA ; +break ; +case BPF_JLT : +/* LT is unsigned '<', JB in x86 */ + jmp_cond =IA32_JB ; +break ; +case BPF_JGE : +/* GE is unsigned '>=', JAE in x86 */ + jmp_cond =IA32_JAE ; +break ; +case BPF_JLE : +/* LE is unsigned '<=', JBE in x86 */ + jmp_cond =IA32_JBE ; +break ; +case BPF_JSGT : +/* signed '>', GT in x86 */ + jmp_cond =IA32_JG ; +break ; +case BPF_JSLT : +/* signed '<', LT in x86 */ + jmp_cond =IA32_JL ; +break ; +case BPF_JSGE : +/* signed '>=', GE in x86 */ + jmp_cond =IA32_JGE ; +break ; +case BPF_JSLE : +/* signed '<=', LE in x86 */ + jmp_cond =IA32_JLE ; +break ; +default :/* to silence gcc warning */ +return -EFAULT ; + } + jmp_offset = addrs[i + insn->off] - addrs[i]; +if (is_imm8 (jmp_offset)) { +EMIT2 (jmp_cond, jmp_offset); + }else if (is_simm32 (jmp_offset)) { +EMIT2_off32 (0x0F , jmp_cond +0x10 , jmp_offset); + }else { +pr_err ("cond_jmp gen bug %llx\n" , jmp_offset); +return -EFAULT ; + } + +break ; + } +case BPF_JMP |BPF_JA : + jmp_offset = addrs[i + insn->off] - addrs[i]; +if (!jmp_offset) +/* optimize out nop jumps */ +break ; +emit_jmp: +if (is_imm8 (jmp_offset)) { +EMIT2 (0xEB , jmp_offset); + }else if (is_simm32 (jmp_offset)) { +EMIT1_off32 (0xE9 , jmp_offset); + }else { +pr_err ("jmp gen bug %llx\n" , jmp_offset); +return -EFAULT ; + } +break ; + +case BPF_LD |BPF_IND |BPF_W : + func = sk_load_word; + goto common_load; +case BPF_LD |BPF_ABS |BPF_W : + func =CHOOSE_LOAD_FUNC (imm32, sk_load_word); +common_load: + jmp_offset = func - (image + addrs[i]); +if (!func || !is_simm32 (jmp_offset)) { +pr_err ("unsupported bpf func %d addr %p image %p\n" , + imm32, func, image); +return -EINVAL ; + } +if (BPF_MODE (code) ==BPF_ABS ) { +/* mov %edx, imm32 */ +EMIT1_off32 (0xBA , imm32); + }else { +/* mov edx,dword ptr [ebp+off] */ +EMIT3 (0x8B ,add_2reg (0x40 ,IA32_EBP ,IA32_EDX ), +STACK_VAR (src_lo));arch/x86/net/bpf_jit_comp32.c line 2053
Quoted 10 lines
arch/x86/net/bpf_jit_comp32.c new file +10
-0+if (imm32) { +if (is_imm8 (imm32)) +/* add %edx, imm8 */ +EMIT3 (0x83 ,0xC2 , imm32); +else +/* add %edx, imm32 */ +EMIT2_off32 (0x81 ,0xC2 , imm32); + } + } +emit_load_skb_data_hlen (&prog);The only place where you call the
emit_load_skb_data_hlen()is here, so it effectively doesn't cache anything as with the other JITs. I would suggest
Quoted 8 lines
to keep the complexity of the BPF_ABS/IND rather very low, remove the
arch/x86/net/bpf_jit32.Shandling altogether and just follow the model the way arm64 implemented this in thearch/arm64/net/bpf_jit_comp.cJIT. For eBPF these instructions are legacy and have been source of many subtle bugs in the various BPF JITs in the past, plus nobody complained about the current interpreter speed for LD_ABS/IND onx86_32anyway so far. ;) We're also very close to have a rewrite of LD_ABS/IND out of native eBPF with similar performance to be able to finally remove them from the core.
Done.
Thanks.
Wang YanQing
maintainerQuoted 17 lines · Daniel Borkmann
Quoted 15 lines · Wang YanQing
Testing results on i5-5200U:
1)
test_bpf: Summary: 349 PASSED, 0 FAILED, [319/341 JIT'ed]
2)test_progs: Summary: 81 PASSED, 2 FAILED.
test_progsreport "libbpf: incorrectbpf_callopcode" fortest_l4lb_noinlineandtest_xdp_noinline, because there is no llvm-6.0 on my machine, and current implementation doesn't supportBPF_PSEUDO_CALL, so I think we can ignore the two failed testcases.
3)test_lpm: OK
4)test_lru_map: OK
5)test_verifier: Summary: 823 PASSED, 5 FAILEDtest_verifierreport "invalidbpf_contextaccess off=68 size=1/2/4/8" for all the 5 FAILED testcases with/without jit, we need to fix the failed testcases themself instead of this jit.
Quoted 4 lines
Can you elaborate further on these? Looks like this definitely needs fixing on 32 bit. Would be great to get a better understanding of the underlying bug(s) and properly fix them.
Hi Daniel Borkmann, here is the detailed log for failed testcases.
linux: Gentoo 32 bit
llvm:
~ # llc --version
LLVM (http://llvm.org/):
LLVM version 4.0.1
Optimized build.
Default target: i686-pc-linux-gnu
Host CPU: broadwell
Registered Targets:
amdgcn - AMD GCN GPUs
bpf - BPF (host endian)
bpfeb - BPF (big endian)
bpfel - BPF (little endian)
nvptx - NVIDIA PTX 32-bit
nvptx64 - NVIDIA PTX 64-bit
r600 - AMD GPUs HD2XXX-HD6XXX
x86 - 32-bit X86: Pentium-Pro and above
x86-64 - 64-bit X86: EM64T and AMD64
~ # clang --version
clang version 4.0.1 (tags/RELEASE_401/final)
Target: i686-pc-linux-gnu
Thread model: posix
InstalledDir: /usr/lib/llvm/4/bin
kernel version:4.16.2
test program:test_verifier in kselftest
condition:bpf_jit_enable=0,bpf_jit_harden=0
log:
#172/p unpriv: spill/fill of different pointers ldx FAIL
Unexpected error message!
0: (bf) r6 = r10
1: (07) r6 += -8
2: (15) if r1 == 0x0 goto pc+3
R1=ctx(id=0,off=0,imm=0) R6=fp-8,call_-1 R10=fp0,call_-1
3: (bf) r2 = r10
4: (07) r2 += -76
5: (7b) *(u64 *)(r6 +0) = r2
6: (55) if r1 != 0x0 goto pc+1
R1=ctx(id=0,off=0,imm=0) R2=fp-76,call_-1 R6=fp-8,call_-1 R10=fp0,call_-1 fp-8=fp 7: (7b) *(u64 *)(r6 +0) = r1
8: (79) r1 = *(u64 *)(r6 +0)
9: (79) r1 = *(u64 *)(r1 +68)
invalid bpf_context access off=68 size=8
#378/p check bpf_perf_event_data->sample_period byte load permitted FAIL Failed to load prog 'Permission denied'!
0: (b7) r0 = 0
1: (71) r0 = *(u8 *)(r1 +68)
invalid bpf_context access off=68 size=1
#379/p check bpf_perf_event_data->sample_period half load permitted FAIL Failed to load prog 'Permission denied'!
0: (b7) r0 = 0
1: (69) r0 = *(u16 *)(r1 +68)
invalid bpf_context access off=68 size=2
#380/p check bpf_perf_event_data->sample_period word load permitted FAIL Failed to load prog 'Permission denied'!
0: (b7) r0 = 0
1: (61) r0 = *(u32 *)(r1 +68)
invalid bpf_context access off=68 size=4
#381/p check bpf_perf_event_data->sample_period dword load permitted FAIL Failed to load prog 'Permission denied'!
0: (b7) r0 = 0
1: (79) r0 = *(u64 *)(r1 +68)
invalid bpf_context access off=68 size=8
test program:test_progs
condition:bpf_jit_enable=0,bpf_jit_harden=0
bpf # ./test_progs
test_pkt_access:PASS:ipv4 53 nsec
test_pkt_access:PASS:ipv6 47 nsec
test_xdp:PASS:ipv4 1281 nsec
test_xdp:PASS:ipv6 749 nsec
test_l4lb:PASS:ipv4 427 nsec
test_l4lb:PASS:ipv6 562 nsec
libbpf: incorrect bpf_call opcode <= caused by ./test_l4lb_noinline.o in function test_l4lb_all
libbpf: incorrect bpf_call opcode <= caused by ././test_xdp_noinline.o in function test_xdp_noinline
Thanks.