├── test ├── test_framework │ ├── ubpf │ │ ├── __init__.py │ │ ├── asm_parser.py │ │ ├── assembler.py │ │ └── disassembler.py │ ├── test_disassembler.py │ ├── test_assembler.py │ ├── test_roundtrip.py │ ├── expand-testcase.py │ ├── testdata.py │ ├── test_vm.py │ └── test_jit.py ├── test-cases │ ├── tcp-sack │ │ ├── .gitignore │ │ ├── match.data │ │ ├── nomatch.data │ │ ├── pkt-nosack.hex │ │ ├── Makefile │ │ ├── pkt-sack.hex │ │ ├── tcp-sack.asm │ │ └── tcp-sack.c │ ├── exit.data │ ├── add64.data │ ├── ja.data │ ├── mov.data │ ├── mul32-imm.data │ ├── neg.data │ ├── div-by-zero-imm.data │ ├── mov64-sign-extend.data │ ├── unload_reload.data │ ├── div32-imm.data │ ├── div64-by-zero-imm.data │ ├── early-exit.data │ ├── mod-by-zero-imm.data │ ├── mod32.data │ ├── mod64-by-zero-imm.data │ ├── neg64.data │ ├── lddw2.data │ ├── ldxb.data │ ├── lsh-reg.data │ ├── mul32-reg.data │ ├── mul64-imm.data │ ├── rsh-reg.data │ ├── be16.data │ ├── ldxh.data │ ├── le16.data │ ├── rsh32.data │ ├── div-by-zero-reg.data │ ├── div32-reg.data │ ├── div64-by-zero-reg.data │ ├── div64-imm.data │ ├── div64-negative-imm.data │ ├── err-infinite-loop.data │ ├── err-jmp-out.data │ ├── mod-by-zero-reg.data │ ├── mod64-by-zero-reg.data │ ├── add.data │ ├── be32.data │ ├── ldxw.data │ ├── le32.data │ ├── mul64-reg.data │ ├── arsh32-high-shift.data │ ├── div32-high-divisor.data │ ├── mul32-reg-overflow.data │ ├── stb.data │ ├── div64-reg.data │ ├── err-write-r10.dst │ ├── be16-high.data │ ├── err-invalid-reg-src.data │ ├── exit-not-last.data │ ├── sth.data │ ├── be32-high.data │ ├── be64.data │ ├── div64-negative-reg.data │ ├── err-invalid-reg-dst.data │ ├── le64.data │ ├── ldxdw.data │ ├── err-incomplete-lddw.data │ ├── err-incomplete-lddw2.data │ ├── err-unknown-opcode.data │ ├── stw.data │ ├── arsh.data │ ├── err-jmp-lddw.data │ ├── ldxh-same-reg.data │ ├── mem-len.data │ ├── stxb.data │ ├── arsh64.data │ ├── call_unwind.data.unused │ ├── call_unwind_fail.data │ ├── stxh.data │ ├── arsh-reg.data │ ├── stdw.data │ ├── err-endian-size.data │ ├── mod.data │ ├── stxw.data │ ├── jit-bounce.data │ ├── lddw.data │ ├── call.data │ ├── reload.data.unused │ ├── err-call-bad-imm.data │ ├── err-call-unreg.data │ ├── jgt-imm.data │ ├── jlt-imm.data │ ├── stxb-all2.data │ ├── jle-imm.data │ ├── stack3.data │ ├── jeq-imm.data │ ├── stxdw.data │ ├── jgt-reg.data │ ├── jlt-reg.data │ ├── err-stack-oob.data │ ├── jge-imm.data │ ├── jle-reg.data │ ├── jset-imm.data │ ├── jslt-imm.data │ ├── jeq-reg.data │ ├── jne-reg.data │ ├── call-memfrob.data │ ├── jset-reg.data │ ├── jsgt-imm.data │ ├── jsle-imm.data │ ├── jsgt-reg.data │ ├── stx.data │ ├── jslt-reg.data │ ├── st.data │ ├── call-save.data │ ├── stack.data │ ├── jsge-imm.data │ ├── jsle-reg.data │ ├── jsge-reg.data │ ├── mod64.data │ ├── tcp-port-80 │ │ ├── tcp-port-80.asm │ │ ├── match.data │ │ ├── nomatch-ethertype.data │ │ ├── nomatch-proto.data │ │ └── nomatch.data │ ├── ldx.data │ ├── alu64-arith.data │ ├── stxb-all.data │ ├── alu-arith.data │ ├── mul-loop.data │ ├── stack2.data │ ├── alu64-bit.data │ ├── alu-bit.data │ ├── stxb-chain.data │ ├── jmp.data │ ├── prime.data │ ├── alu64.data │ ├── ldxb-all.data │ ├── ldxh-all2.data │ ├── ldxw-all.data │ ├── ldxh-all.data │ ├── alu.data │ ├── string-stack.data │ └── subnet.data ├── .gitignore ├── bpf_conformance_runner │ ├── README.md │ ├── CMakeLists.txt │ └── main-bpf-conformance.cpp ├── requirements.txt ├── include │ ├── test_defs.h │ ├── test_bpf_progs.h │ └── test_minimal_bpf_host_ufunc.h ├── unit-test │ ├── bpf_prog_test.cpp │ └── CMakeLists.txt ├── src │ ├── test_vm.c │ ├── test_core_minimal_ffi.c │ ├── test_jit.c │ └── test.c ├── CMakeLists.txt └── README.md ├── .clangd ├── .github ├── assets │ └── sum.bpf.o ├── dependabot.yml ├── FUNDING.yml └── workflows │ ├── unit-test.yml │ ├── test-aot-cli.yml │ ├── test-vm.yml │ ├── test-spirv-opencl.yml │ ├── bpf_conformance.yml │ └── codeql.yml ├── example ├── load-llvm-ir │ ├── Makefile │ └── bpf_module.c ├── ptx │ ├── example_trampoline │ │ ├── .gitignore │ │ └── README.md │ ├── CMakeLists.txt │ └── README.md ├── standalone │ ├── Makefile │ └── main.c ├── inline │ ├── Makefile │ ├── main.c │ ├── libmap.c │ └── libmap.ll ├── CMakeLists.txt ├── spirv │ └── CMakeLists.txt ├── xdp-counter.json ├── basic.cpp └── maps.cpp ├── .gitignore ├── codecov.yaml ├── cli ├── README.md ├── CMakeLists.txt └── main.cpp ├── .devcontainer └── devcontainer.json ├── LICENSE ├── CITATION.cff ├── src ├── llvm_jit_context.hpp ├── compiler_utils.hpp └── vm.cpp ├── include └── llvmbpf.hpp ├── CMakeLists.txt └── CLAUDE.md /test/test_framework/ubpf/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/test-cases/tcp-sack/.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | *.bin 3 | -------------------------------------------------------------------------------- /test/.gitignore: -------------------------------------------------------------------------------- 1 | myenv 2 | bin 3 | lib 4 | lib64 5 | pyvenv.cfg 6 | /man 7 | -------------------------------------------------------------------------------- /test/test-cases/exit.data: -------------------------------------------------------------------------------- 1 | -- asm 2 | mov r0, 0 3 | exit 4 | -- result 5 | 0x0 6 | -------------------------------------------------------------------------------- /.clangd: -------------------------------------------------------------------------------- 1 | CompileFlags: 2 | Add: 3 | - "-I/usr/local/cuda/extras/Debugger/include" 4 | -------------------------------------------------------------------------------- /test/test-cases/add64.data: -------------------------------------------------------------------------------- 1 | -- asm 2 | mov r0, 1 3 | add r0, -1 4 | exit 5 | -- result 6 | 0x0 7 | -------------------------------------------------------------------------------- /.github/assets/sum.bpf.o: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eunomia-bpf/llvmbpf/HEAD/.github/assets/sum.bpf.o -------------------------------------------------------------------------------- /test/test-cases/ja.data: -------------------------------------------------------------------------------- 1 | -- asm 2 | mov r0, 1 3 | ja +1 4 | mov r0, 2 5 | exit 6 | -- result 7 | 0x1 8 | -------------------------------------------------------------------------------- /test/test-cases/mov.data: -------------------------------------------------------------------------------- 1 | -- asm 2 | mov32 r1, 1 3 | mov32 r0, r1 4 | exit 5 | -- result 6 | 0x1 7 | -------------------------------------------------------------------------------- /test/test-cases/mul32-imm.data: -------------------------------------------------------------------------------- 1 | -- asm 2 | mov r0, 3 3 | mul32 r0, 4 4 | exit 5 | -- result 6 | 0xc 7 | -------------------------------------------------------------------------------- /test/test-cases/neg.data: -------------------------------------------------------------------------------- 1 | -- asm 2 | mov32 r0, 2 3 | neg32 r0 4 | exit 5 | -- result 6 | 0xfffffffe 7 | -------------------------------------------------------------------------------- /test/test-cases/div-by-zero-imm.data: -------------------------------------------------------------------------------- 1 | -- asm 2 | mov32 r0, 1 3 | div32 r0, 0 4 | exit 5 | -- result 6 | 0x0 -------------------------------------------------------------------------------- /test/test-cases/mov64-sign-extend.data: -------------------------------------------------------------------------------- 1 | -- asm 2 | mov r0, -10 3 | exit 4 | -- result 5 | 0xFFFFFFFFFFFFFFF6 -------------------------------------------------------------------------------- /test/test-cases/unload_reload.data: -------------------------------------------------------------------------------- 1 | -- asm 2 | mov r0, 0 3 | exit 4 | -- unload 5 | -- result 6 | 0x0 7 | -------------------------------------------------------------------------------- /example/load-llvm-ir/Makefile: -------------------------------------------------------------------------------- 1 | bpf_module: bpf_module.o 2 | clang -g -c bpf_module.c -o bpf_module.o 3 | 4 | -------------------------------------------------------------------------------- /example/ptx/example_trampoline/.gitignore: -------------------------------------------------------------------------------- 1 | /default_trampoline.s 2 | /test-cuda-nvptx64-nvidia-cuda-sm_60.s 3 | -------------------------------------------------------------------------------- /test/test-cases/div32-imm.data: -------------------------------------------------------------------------------- 1 | -- asm 2 | lddw r0, 0x10000000c 3 | div32 r0, 4 4 | exit 5 | -- result 6 | 0x3 7 | -------------------------------------------------------------------------------- /test/test-cases/div64-by-zero-imm.data: -------------------------------------------------------------------------------- 1 | -- asm 2 | mov32 r0, 1 3 | div r0, 0 4 | exit 5 | -- result 6 | 0x0 7 | -------------------------------------------------------------------------------- /test/test-cases/early-exit.data: -------------------------------------------------------------------------------- 1 | -- asm 2 | mov r0, 3 3 | exit 4 | mov r0, 4 5 | exit 6 | -- result 7 | 0x3 8 | -------------------------------------------------------------------------------- /test/test-cases/mod-by-zero-imm.data: -------------------------------------------------------------------------------- 1 | -- asm 2 | mov32 r0, 1 3 | mod32 r0, 0 4 | exit 5 | -- result 6 | 0x1 7 | -------------------------------------------------------------------------------- /test/test-cases/mod32.data: -------------------------------------------------------------------------------- 1 | -- asm 2 | lddw r0, 0x100000003 3 | mod32 r0, 3 4 | exit 5 | -- result 6 | 0x0 7 | -------------------------------------------------------------------------------- /test/test-cases/mod64-by-zero-imm.data: -------------------------------------------------------------------------------- 1 | -- asm 2 | mov32 r0, 1 3 | mod r0, 0 4 | exit 5 | -- result 6 | 0x1 7 | -------------------------------------------------------------------------------- /test/test-cases/neg64.data: -------------------------------------------------------------------------------- 1 | -- asm 2 | mov32 r0, 2 3 | neg r0 4 | exit 5 | -- result 6 | 0xfffffffffffffffe 7 | -------------------------------------------------------------------------------- /test/test-cases/tcp-sack/match.data: -------------------------------------------------------------------------------- 1 | -- asm @ tcp-sack.asm 2 | -- mem @ pkt-sack.hex 3 | -- result 4 | 0x1 5 | -------------------------------------------------------------------------------- /test/test-cases/tcp-sack/nomatch.data: -------------------------------------------------------------------------------- 1 | -- asm @ tcp-sack.asm 2 | -- mem @ pkt-nosack.hex 3 | -- result 4 | 0x0 5 | -------------------------------------------------------------------------------- /test/test-cases/lddw2.data: -------------------------------------------------------------------------------- 1 | -- asm 2 | lddw r0, 0x0000000080000000 3 | exit 4 | -- result 5 | 0x0000000080000000 6 | -------------------------------------------------------------------------------- /test/test-cases/ldxb.data: -------------------------------------------------------------------------------- 1 | -- asm 2 | ldxb r0, [r1+2] 3 | exit 4 | -- mem 5 | aa bb 11 cc dd 6 | -- result 7 | 0x11 8 | -------------------------------------------------------------------------------- /test/test-cases/lsh-reg.data: -------------------------------------------------------------------------------- 1 | -- asm 2 | mov r0, 0x1 3 | mov r7, 4 4 | lsh r0, r7 5 | exit 6 | -- result 7 | 0x10 8 | -------------------------------------------------------------------------------- /test/test-cases/mul32-reg.data: -------------------------------------------------------------------------------- 1 | -- asm 2 | mov r0, 3 3 | mov r1, 4 4 | mul32 r0, r1 5 | exit 6 | -- result 7 | 0xc 8 | -------------------------------------------------------------------------------- /test/test-cases/mul64-imm.data: -------------------------------------------------------------------------------- 1 | -- asm 2 | mov r0, 0x40000001 3 | mul r0, 4 4 | exit 5 | -- result 6 | 0x100000004 7 | -------------------------------------------------------------------------------- /test/test-cases/rsh-reg.data: -------------------------------------------------------------------------------- 1 | -- asm 2 | mov r0, 0x10 3 | mov r7, 4 4 | rsh r0, r7 5 | exit 6 | -- result 7 | 0x1 8 | -------------------------------------------------------------------------------- /test/test-cases/be16.data: -------------------------------------------------------------------------------- 1 | -- asm 2 | ldxh r0, [r1] 3 | be16 r0 4 | exit 5 | -- mem 6 | 11 22 7 | -- result 8 | 0x1122 9 | -------------------------------------------------------------------------------- /test/test-cases/ldxh.data: -------------------------------------------------------------------------------- 1 | -- asm 2 | ldxh r0, [r1+2] 3 | exit 4 | -- mem 5 | aa bb 11 22 cc dd 6 | -- result 7 | 0x2211 8 | -------------------------------------------------------------------------------- /test/test-cases/le16.data: -------------------------------------------------------------------------------- 1 | -- asm 2 | ldxh r0, [r1] 3 | le16 r0 4 | exit 5 | -- mem 6 | 22 11 7 | -- result 8 | 0x1122 9 | -------------------------------------------------------------------------------- /test/test-cases/rsh32.data: -------------------------------------------------------------------------------- 1 | -- asm 2 | mov r0, 0 3 | sub r0, 1 4 | rsh32 r0, 8 5 | exit 6 | -- result 7 | 0x00ffffff 8 | -------------------------------------------------------------------------------- /test/test-cases/div-by-zero-reg.data: -------------------------------------------------------------------------------- 1 | -- asm 2 | mov32 r0, 1 3 | mov32 r1, 0 4 | div32 r0, r1 5 | exit 6 | -- result 7 | 0x0 8 | -------------------------------------------------------------------------------- /test/test-cases/div32-reg.data: -------------------------------------------------------------------------------- 1 | -- asm 2 | lddw r0, 0x10000000c 3 | mov r1, 4 4 | div32 r0, r1 5 | exit 6 | -- result 7 | 0x3 8 | -------------------------------------------------------------------------------- /test/test-cases/div64-by-zero-reg.data: -------------------------------------------------------------------------------- 1 | -- asm 2 | mov32 r0, 1 3 | mov32 r1, 0 4 | div r0, r1 5 | exit 6 | -- result 7 | 0x0 8 | -------------------------------------------------------------------------------- /test/test-cases/div64-imm.data: -------------------------------------------------------------------------------- 1 | -- asm 2 | mov r0, 0xc 3 | lsh r0, 32 4 | div r0, 4 5 | exit 6 | -- result 7 | 0x300000000 8 | -------------------------------------------------------------------------------- /test/test-cases/div64-negative-imm.data: -------------------------------------------------------------------------------- 1 | -- asm 2 | lddw r0, 0xFFFFFFFFFFFFFFFF 3 | div r0, -10 4 | exit 5 | -- result 6 | 0x1 -------------------------------------------------------------------------------- /test/test-cases/err-infinite-loop.data: -------------------------------------------------------------------------------- 1 | -- asm 2 | ja -1 3 | exit 4 | -- error 5 | Failed to load code: infinite loop at PC 0 6 | -------------------------------------------------------------------------------- /test/test-cases/err-jmp-out.data: -------------------------------------------------------------------------------- 1 | -- asm 2 | ja +2 3 | exit 4 | -- error 5 | Failed to load code: jump out of bounds at PC 0 6 | -------------------------------------------------------------------------------- /test/test-cases/mod-by-zero-reg.data: -------------------------------------------------------------------------------- 1 | -- asm 2 | mov32 r0, 1 3 | mov32 r1, 0 4 | mod32 r0, r1 5 | exit 6 | -- result 7 | 0x1 8 | -------------------------------------------------------------------------------- /test/test-cases/mod64-by-zero-reg.data: -------------------------------------------------------------------------------- 1 | -- asm 2 | mov32 r0, 1 3 | mov32 r1, 0 4 | mod r0, r1 5 | exit 6 | -- result 7 | 0x1 8 | -------------------------------------------------------------------------------- /example/standalone/Makefile: -------------------------------------------------------------------------------- 1 | standalone: main.c xdp-counter.ll 2 | clang -opaque-pointers -g main.c xdp-counter.ll -o standalone 3 | -------------------------------------------------------------------------------- /test/test-cases/add.data: -------------------------------------------------------------------------------- 1 | -- asm 2 | mov32 r0, 0 3 | mov32 r1, 2 4 | add32 r0, 1 5 | add32 r0, r1 6 | exit 7 | -- result 8 | 0x3 9 | -------------------------------------------------------------------------------- /test/test-cases/be32.data: -------------------------------------------------------------------------------- 1 | -- asm 2 | ldxw r0, [r1] 3 | be32 r0 4 | exit 5 | -- mem 6 | 11 22 33 44 7 | -- result 8 | 0x11223344 9 | -------------------------------------------------------------------------------- /test/test-cases/ldxw.data: -------------------------------------------------------------------------------- 1 | -- asm 2 | ldxw r0, [r1+2] 3 | exit 4 | -- mem 5 | aa bb 11 22 33 44 cc dd 6 | -- result 7 | 0x44332211 8 | -------------------------------------------------------------------------------- /test/test-cases/le32.data: -------------------------------------------------------------------------------- 1 | -- asm 2 | ldxw r0, [r1] 3 | le32 r0 4 | exit 5 | -- mem 6 | 44 33 22 11 7 | -- result 8 | 0x11223344 9 | -------------------------------------------------------------------------------- /test/test-cases/mul64-reg.data: -------------------------------------------------------------------------------- 1 | -- asm 2 | mov r0, 0x40000001 3 | mov r1, 4 4 | mul r0, r1 5 | exit 6 | -- result 7 | 0x100000004 8 | -------------------------------------------------------------------------------- /test/test-cases/arsh32-high-shift.data: -------------------------------------------------------------------------------- 1 | -- asm 2 | mov r0, 8 3 | lddw r1, 0x100000001 4 | arsh32 r0, r1 5 | exit 6 | -- result 7 | 0x4 8 | -------------------------------------------------------------------------------- /test/test-cases/div32-high-divisor.data: -------------------------------------------------------------------------------- 1 | -- asm 2 | mov r0, 12 3 | lddw r1, 0x100000004 4 | div32 r0, r1 5 | exit 6 | -- result 7 | 0x3 8 | -------------------------------------------------------------------------------- /test/test-cases/mul32-reg-overflow.data: -------------------------------------------------------------------------------- 1 | -- asm 2 | mov r0, 0x40000001 3 | mov r1, 4 4 | mul32 r0, r1 5 | exit 6 | -- result 7 | 0x4 8 | -------------------------------------------------------------------------------- /test/test-cases/stb.data: -------------------------------------------------------------------------------- 1 | -- asm 2 | stb [r1+2], 0x11 3 | ldxb r0, [r1+2] 4 | exit 5 | -- mem 6 | aa bb ff cc dd 7 | -- result 8 | 0x11 9 | -------------------------------------------------------------------------------- /test/test-cases/div64-reg.data: -------------------------------------------------------------------------------- 1 | -- asm 2 | mov r0, 0xc 3 | lsh r0, 32 4 | mov r1, 4 5 | div r0, r1 6 | exit 7 | -- result 8 | 0x300000000 9 | -------------------------------------------------------------------------------- /test/test-cases/err-write-r10.dst: -------------------------------------------------------------------------------- 1 | -- asm 2 | mov r10, 1 3 | exit 4 | -- error 5 | Failed to load code: invalid destination register at PC 0 6 | -------------------------------------------------------------------------------- /test/test-cases/be16-high.data: -------------------------------------------------------------------------------- 1 | -- asm 2 | ldxdw r0, [r1] 3 | be16 r0 4 | exit 5 | -- mem 6 | 11 22 33 44 55 66 77 88 7 | -- result 8 | 0x1122 9 | -------------------------------------------------------------------------------- /test/test-cases/err-invalid-reg-src.data: -------------------------------------------------------------------------------- 1 | -- asm 2 | mov r0, r11 3 | exit 4 | -- error 5 | Failed to load code: invalid source register at PC 0 6 | -------------------------------------------------------------------------------- /test/test-cases/exit-not-last.data: -------------------------------------------------------------------------------- 1 | -- asm 2 | mov r1, 0 3 | ja +2 4 | mov r2, 0 5 | exit 6 | mov r0, 0 7 | ja -4 8 | -- result 9 | 0x0 10 | -------------------------------------------------------------------------------- /test/test-cases/sth.data: -------------------------------------------------------------------------------- 1 | -- asm 2 | sth [r1+2], 0x2211 3 | ldxh r0, [r1+2] 4 | exit 5 | -- mem 6 | aa bb ff ff cc dd 7 | -- result 8 | 0x2211 9 | -------------------------------------------------------------------------------- /test/test-cases/be32-high.data: -------------------------------------------------------------------------------- 1 | -- asm 2 | ldxdw r0, [r1] 3 | be32 r0 4 | exit 5 | -- mem 6 | 11 22 33 44 55 66 77 88 7 | -- result 8 | 0x11223344 9 | -------------------------------------------------------------------------------- /test/test-cases/be64.data: -------------------------------------------------------------------------------- 1 | -- asm 2 | ldxdw r0, [r1] 3 | be64 r0 4 | exit 5 | -- mem 6 | 11 22 33 44 55 66 77 88 7 | -- result 8 | 0x1122334455667788 9 | -------------------------------------------------------------------------------- /test/test-cases/div64-negative-reg.data: -------------------------------------------------------------------------------- 1 | -- asm 2 | lddw r0, 0xFFFFFFFFFFFFFFFF 3 | mov32 r1, -10 4 | div r0, r1 5 | exit 6 | -- result 7 | 0x10000000A -------------------------------------------------------------------------------- /test/test-cases/err-invalid-reg-dst.data: -------------------------------------------------------------------------------- 1 | -- asm 2 | mov r11, 1 3 | exit 4 | -- error 5 | Failed to load code: invalid destination register at PC 0 6 | -------------------------------------------------------------------------------- /test/test-cases/le64.data: -------------------------------------------------------------------------------- 1 | -- asm 2 | ldxdw r0, [r1] 3 | le64 r0 4 | exit 5 | -- mem 6 | 88 77 66 55 44 33 22 11 7 | -- result 8 | 0x1122334455667788 9 | -------------------------------------------------------------------------------- /test/test-cases/ldxdw.data: -------------------------------------------------------------------------------- 1 | -- asm 2 | ldxdw r0, [r1+2] 3 | exit 4 | -- mem 5 | aa bb 11 22 33 44 55 66 77 88 cc dd 6 | -- result 7 | 0x8877665544332211 8 | -------------------------------------------------------------------------------- /test/test-cases/err-incomplete-lddw.data: -------------------------------------------------------------------------------- 1 | -- raw 2 | 0x5566778800000018 3 | 0x0000000000000095 4 | -- error 5 | Failed to load code: incomplete lddw at PC 0 6 | -------------------------------------------------------------------------------- /test/test-cases/err-incomplete-lddw2.data: -------------------------------------------------------------------------------- 1 | -- raw 2 | 0x5566778800000018 3 | 0x0000000000000095 4 | -- error 5 | Failed to load code: incomplete lddw at PC 0 6 | -------------------------------------------------------------------------------- /test/test-cases/err-unknown-opcode.data: -------------------------------------------------------------------------------- 1 | -- raw 2 | 0x0000000000000006 3 | 0x0000000000000095 4 | -- error 5 | Failed to load code: unknown opcode 0x06 at PC 0 6 | -------------------------------------------------------------------------------- /test/test-cases/stw.data: -------------------------------------------------------------------------------- 1 | -- asm 2 | stw [r1+2], 0x44332211 3 | ldxw r0, [r1+2] 4 | exit 5 | -- mem 6 | aa bb ff ff ff ff cc dd 7 | -- result 8 | 0x44332211 9 | -------------------------------------------------------------------------------- /test/bpf_conformance_runner/README.md: -------------------------------------------------------------------------------- 1 | # bpf_conformance_runner 2 | 3 | This is an executable used for testing the vm implementation with bpf_conformance_runner. 4 | -------------------------------------------------------------------------------- /test/test-cases/arsh.data: -------------------------------------------------------------------------------- 1 | -- asm 2 | mov32 r0, 0xf8 3 | lsh32 r0, 28 4 | # r0 == 0x80000000 5 | arsh32 r0, 16 6 | exit 7 | -- result 8 | 0xffff8000 9 | 10 | -------------------------------------------------------------------------------- /test/test-cases/err-jmp-lddw.data: -------------------------------------------------------------------------------- 1 | -- asm 2 | ja +1 3 | lddw r0, 0x1122334455667788 4 | exit 5 | -- error 6 | Failed to load code: jump to middle of lddw at PC 0 7 | -------------------------------------------------------------------------------- /test/test-cases/ldxh-same-reg.data: -------------------------------------------------------------------------------- 1 | -- asm 2 | mov r0, r1 3 | sth [r0], 0x1234 4 | ldxh r0, [r0] 5 | exit 6 | -- mem 7 | ff ff 8 | -- result 9 | 0x1234 10 | -------------------------------------------------------------------------------- /test/test-cases/mem-len.data: -------------------------------------------------------------------------------- 1 | -- asm 2 | mov r0, r2 3 | exit 4 | -- mem 5 | 00 00 00 01 6 | 00 00 00 02 7 | -- no register offset 8 | -- result 9 | 0x8 10 | -------------------------------------------------------------------------------- /test/test-cases/stxb.data: -------------------------------------------------------------------------------- 1 | -- asm 2 | mov32 r2, 0x11 3 | stxb [r1+2], r2 4 | ldxb r0, [r1+2] 5 | exit 6 | -- mem 7 | aa bb ff cc dd 8 | -- result 9 | 0x11 10 | -------------------------------------------------------------------------------- /test/test-cases/arsh64.data: -------------------------------------------------------------------------------- 1 | -- asm 2 | mov32 r0, 1 3 | lsh r0, 63 4 | arsh r0, 55 5 | mov32 r1, 5 6 | arsh r0, r1 7 | exit 8 | -- result 9 | 0xfffffffffffffff8 10 | -------------------------------------------------------------------------------- /test/test-cases/call_unwind.data.unused: -------------------------------------------------------------------------------- 1 | -- asm 2 | mov r1, 0 3 | call 5 4 | mov r0, 2 5 | exit 6 | -- result 7 | 0x0 8 | -- no register offset 9 | call instruction 10 | -------------------------------------------------------------------------------- /test/test-cases/call_unwind_fail.data: -------------------------------------------------------------------------------- 1 | -- asm 2 | mov r1, -1 3 | call 5 4 | mov r0, 2 5 | exit 6 | -- result 7 | 0x2 8 | -- no register offset 9 | call instruction 10 | -------------------------------------------------------------------------------- /test/test-cases/stxh.data: -------------------------------------------------------------------------------- 1 | -- asm 2 | mov32 r2, 0x2211 3 | stxh [r1+2], r2 4 | ldxh r0, [r1+2] 5 | exit 6 | -- mem 7 | aa bb ff ff cc dd 8 | -- result 9 | 0x2211 10 | -------------------------------------------------------------------------------- /test/test-cases/arsh-reg.data: -------------------------------------------------------------------------------- 1 | -- asm 2 | mov32 r0, 0xf8 3 | mov32 r1, 16 4 | lsh32 r0, 28 5 | # r0 == 0x80000000 6 | arsh32 r0, r1 7 | exit 8 | -- result 9 | 0xffff8000 10 | -------------------------------------------------------------------------------- /test/test-cases/stdw.data: -------------------------------------------------------------------------------- 1 | -- asm 2 | stdw [r1+2], 0x44332211 3 | ldxdw r0, [r1+2] 4 | exit 5 | -- mem 6 | aa bb ff ff ff ff ff ff ff ff cc dd 7 | -- result 8 | 0x0000000044332211 9 | -------------------------------------------------------------------------------- /test/requirements.txt: -------------------------------------------------------------------------------- 1 | # pypi version of parcon does not support python3 2 | git+https://github.com/javawizard/parcon 3 | nose ~= 1.3.7 4 | pyelftools ~= 0.29 5 | pytest 6 | colorama 7 | -------------------------------------------------------------------------------- /test/test-cases/err-endian-size.data: -------------------------------------------------------------------------------- 1 | -- raw 2 | 0x00000030000001dc 3 | 0x00000000000010b7 4 | 0x0000000000000095 5 | -- error 6 | Failed to load code: invalid endian immediate at PC 0 7 | -------------------------------------------------------------------------------- /test/test-cases/mod.data: -------------------------------------------------------------------------------- 1 | -- asm 2 | mov32 r0, 5748 3 | mod32 r0, 92 4 | # r0 == 44 5 | 6 | mov32 r1, 13 7 | mod32 r0, r1 8 | # r0 == 5 9 | 10 | exit 11 | -- result 12 | 0x5 13 | -------------------------------------------------------------------------------- /test/test-cases/stxw.data: -------------------------------------------------------------------------------- 1 | -- asm 2 | mov32 r2, 0x44332211 3 | stxw [r1+2], r2 4 | ldxw r0, [r1+2] 5 | exit 6 | -- mem 7 | aa bb ff ff ff ff cc dd 8 | -- result 9 | 0x44332211 10 | -------------------------------------------------------------------------------- /test/test-cases/jit-bounce.data: -------------------------------------------------------------------------------- 1 | -- asm 2 | mov r0, 1 3 | mov r6, r0 4 | mov r7, r6 5 | mov r8, r7 6 | mov r9, r8 7 | # TODO r10 8 | mov r0, r9 9 | exit 10 | -- result 11 | 0x1 12 | -------------------------------------------------------------------------------- /test/test-cases/lddw.data: -------------------------------------------------------------------------------- 1 | -- asm 2 | lddw r0, 0x1122334455667788 3 | exit 4 | -- raw 5 | 0x5566778800000018 6 | 0x1122334400000000 7 | 0x0000000000000095 8 | -- result 9 | 0x1122334455667788 10 | -------------------------------------------------------------------------------- /example/load-llvm-ir/bpf_module.c: -------------------------------------------------------------------------------- 1 | int _bpf_helper_ext_0006(const char *fmt, ... ); 2 | 3 | int bpf_main(void* ctx, int size) { 4 | _bpf_helper_ext_0006("hello world: %d\n", size); 5 | return 0; 6 | } 7 | -------------------------------------------------------------------------------- /test/test-cases/call.data: -------------------------------------------------------------------------------- 1 | -- asm 2 | mov r1, 1 3 | mov r2, 2 4 | mov r3, 3 5 | mov r4, 4 6 | mov r5, 5 7 | call 0 8 | exit 9 | -- result 10 | 0x0102030405 11 | -- no register offset 12 | call instruction 13 | -------------------------------------------------------------------------------- /test/test-cases/reload.data.unused: -------------------------------------------------------------------------------- 1 | -- asm 2 | mov r0, 0 3 | exit 4 | -- reload 5 | -- error 6 | Failed to load code: code has already been loaded into this VM. Use ebpf_unload_code() if you need to reuse this VM 7 | -------------------------------------------------------------------------------- /test/test-cases/err-call-bad-imm.data: -------------------------------------------------------------------------------- 1 | -- asm 2 | mov r1, 1 3 | mov r2, 2 4 | mov r3, 3 5 | mov r4, 4 6 | mov r5, 5 7 | call 10000 8 | exit 9 | -- error 10 | Failed to load code: invalid call immediate at PC 5 11 | -------------------------------------------------------------------------------- /test/test-cases/err-call-unreg.data: -------------------------------------------------------------------------------- 1 | -- asm 2 | mov r1, 1 3 | mov r2, 2 4 | mov r3, 3 5 | mov r4, 4 6 | mov r5, 5 7 | call 63 8 | exit 9 | -- error 10 | Failed to load code: call to nonexistent function 63 at PC 5 11 | -------------------------------------------------------------------------------- /test/test-cases/jgt-imm.data: -------------------------------------------------------------------------------- 1 | -- asm 2 | mov32 r0, 0 3 | mov32 r1, 5 4 | jgt r1, 6, +2 # Not taken 5 | jgt r1, 5, +1 # Not taken 6 | jgt r1, 4, +1 # Taken 7 | exit 8 | mov32 r0, 1 9 | exit 10 | -- result 11 | 0x1 12 | -------------------------------------------------------------------------------- /test/test-cases/jlt-imm.data: -------------------------------------------------------------------------------- 1 | -- asm 2 | mov32 r0, 0 3 | mov32 r1, 5 4 | jlt r1, 4, +2 # Not taken 5 | jlt r1, 5, +1 # Not taken 6 | jlt r1, 6, +1 # Taken 7 | exit 8 | mov32 r0, 1 9 | exit 10 | -- result 11 | 0x1 12 | -------------------------------------------------------------------------------- /test/test-cases/stxb-all2.data: -------------------------------------------------------------------------------- 1 | -- asm 2 | mov r0, r1 3 | mov r1, 0xf1 4 | mov r9, 0xf9 5 | stxb [r0], r1 6 | stxb [r0+1], r9 7 | ldxh r0, [r0] 8 | be16 r0 9 | exit 10 | -- mem 11 | ff ff 12 | -- result 13 | 0xf1f9 14 | -------------------------------------------------------------------------------- /test/test-cases/jle-imm.data: -------------------------------------------------------------------------------- 1 | -- asm 2 | mov32 r0, 0 3 | mov32 r1, 5 4 | jle r1, 4, +1 # Not taken 5 | jle r1, 6, +1 # Taken 6 | exit 7 | jle r1, 5, +1 # Taken 8 | exit 9 | mov32 r0, 1 10 | exit 11 | -- result 12 | 0x1 13 | -------------------------------------------------------------------------------- /test/test-cases/stack3.data: -------------------------------------------------------------------------------- 1 | -- asm 2 | stdw [r10-256], 123 3 | stdw [r10-512], 456 4 | mov r2, r10 5 | sub r2, 256 6 | ldxdw r0, [r2] 7 | sub r2, 256 8 | ldxdw r1, [r2] 9 | add r0, r1 10 | 11 | exit 12 | -- result 13 | 0x243 14 | -------------------------------------------------------------------------------- /test/test-cases/jeq-imm.data: -------------------------------------------------------------------------------- 1 | -- asm 2 | mov32 r0, 0 3 | mov32 r1, 0xa 4 | jeq r1, 0xb, +4 # Not taken 5 | 6 | mov32 r0, 1 7 | mov32 r1, 0xb 8 | jeq r1, 0xb, +1 # Taken 9 | 10 | mov32 r0, 2 # Skipped 11 | exit 12 | -- result 13 | 0x1 14 | -------------------------------------------------------------------------------- /test/test-cases/stxdw.data: -------------------------------------------------------------------------------- 1 | -- asm 2 | mov r2, 0x88776655 3 | lsh r2, 32 4 | or r2, 0x44332211 5 | stxdw [r1+2], r2 6 | ldxdw r0, [r1+2] 7 | exit 8 | -- mem 9 | aa bb ff ff ff ff ff ff ff ff cc dd 10 | -- result 11 | 0x8877665544332211 12 | -------------------------------------------------------------------------------- /test/test-cases/jgt-reg.data: -------------------------------------------------------------------------------- 1 | -- asm 2 | mov r0, 0 3 | mov r1, 5 4 | mov r2, 6 5 | mov r3, 4 6 | jgt r1, r2, +2 # Not taken 7 | jgt r1, r1, +1 # Not taken 8 | jgt r1, r3, +1 # Taken 9 | exit 10 | mov r0, 1 11 | exit 12 | -- result 13 | 0x1 14 | -------------------------------------------------------------------------------- /test/test-cases/jlt-reg.data: -------------------------------------------------------------------------------- 1 | -- asm 2 | mov r0, 0 3 | mov r1, 5 4 | mov r2, 4 5 | mov r3, 6 6 | jlt r1, r2, +2 # Not taken 7 | jlt r1, r1, +1 # Not taken 8 | jlt r1, r3, +1 # Taken 9 | exit 10 | mov r0, 1 11 | exit 12 | -- result 13 | 0x1 14 | -------------------------------------------------------------------------------- /test/test-cases/err-stack-oob.data: -------------------------------------------------------------------------------- 1 | -- asm 2 | stb [r10], 0 3 | exit 4 | -- error pattern 5 | .{4} error: out of bounds memory store at PC 0, addr .*, size 1 6 | -- result 7 | 0xffffffffffffffff 8 | -- no jit 9 | stack oob check not implemented 10 | -------------------------------------------------------------------------------- /test/test-cases/jge-imm.data: -------------------------------------------------------------------------------- 1 | -- asm 2 | mov32 r0, 0 3 | mov32 r1, 0xa 4 | jge r1, 0xb, +4 # Not taken 5 | 6 | mov32 r0, 1 7 | mov32 r1, 0xc 8 | jge r1, 0xb, +1 # Taken 9 | 10 | mov32 r0, 2 # Skipped 11 | 12 | exit 13 | -- result 14 | 0x1 15 | -------------------------------------------------------------------------------- /test/test-cases/jle-reg.data: -------------------------------------------------------------------------------- 1 | -- asm 2 | mov r0, 0 3 | mov r1, 5 4 | mov r2, 4 5 | mov r3, 6 6 | jle r1, r2, +2 # Not taken 7 | jle r1, r1, +1 # Taken 8 | exit 9 | jle r1, r3, +1 # Taken 10 | exit 11 | mov r0, 1 12 | exit 13 | -- result 14 | 0x1 15 | -------------------------------------------------------------------------------- /test/test-cases/jset-imm.data: -------------------------------------------------------------------------------- 1 | -- asm 2 | mov32 r0, 0 3 | mov32 r1, 0x7 4 | jset r1, 0x8, +4 # Not taken 5 | 6 | mov32 r0, 1 7 | mov32 r1, 0x9 8 | jset r1, 0x8, +1 # Taken 9 | 10 | mov32 r0, 2 # Skipped 11 | 12 | exit 13 | -- result 14 | 0x1 15 | -------------------------------------------------------------------------------- /test/test-cases/jslt-imm.data: -------------------------------------------------------------------------------- 1 | -- asm 2 | mov32 r0, 0 3 | mov r1, 0xfffffffe 4 | jslt r1, 0xfffffffd, +2 # Not taken 5 | jslt r1, 0xfffffffe, +1 # Not taken 6 | jslt r1, 0xffffffff, +1 # Taken 7 | exit 8 | mov32 r0, 1 9 | exit 10 | -- result 11 | 0x1 12 | -------------------------------------------------------------------------------- /test/test-cases/jeq-reg.data: -------------------------------------------------------------------------------- 1 | -- asm 2 | mov32 r0, 0 3 | mov32 r1, 0xa 4 | mov32 r2, 0xb 5 | jeq r1, r2, +4 # Not taken 6 | 7 | mov32 r0, 1 8 | mov32 r1, 0xb 9 | jeq r1, r2, +1 # Taken 10 | 11 | mov32 r0, 2 # Skipped 12 | exit 13 | -- result 14 | 0x1 15 | -------------------------------------------------------------------------------- /test/test-cases/jne-reg.data: -------------------------------------------------------------------------------- 1 | -- asm 2 | mov32 r0, 0 3 | mov32 r1, 0xb 4 | mov32 r2, 0xb 5 | jne r1, r2, +4 # Not taken 6 | 7 | mov32 r0, 1 8 | mov32 r1, 0xa 9 | jne r1, r2, +1 # Taken 10 | 11 | mov32 r0, 2 # Skipped 12 | exit 13 | -- result 14 | 0x1 15 | -------------------------------------------------------------------------------- /test/test-cases/tcp-sack/pkt-nosack.hex: -------------------------------------------------------------------------------- 1 | 0x0000: 0026 622f 4787 001d 60b3 0184 0800 4500 2 | 0x0010: 0040 a8de 4000 4006 9d58 c0a8 0103 3f74 3 | 0x0020: f361 e5c0 0050 e594 3f77 a3c4 c480 8010 4 | 0x0030: 013e 34b6 0000 0101 080a 0017 956f 8d9d 5 | 0x0040: 9e27 6 | -------------------------------------------------------------------------------- /test/test-cases/call-memfrob.data: -------------------------------------------------------------------------------- 1 | -- asm 2 | mov r6, r1 3 | add r1, 2 4 | mov r2, 4 5 | call 1 6 | ldxdw r0, [r6] 7 | be64 r0 8 | exit 9 | -- mem 10 | 01 02 03 04 05 06 07 08 11 | -- result 12 | 0x102292e2f2c0708 13 | -- no register offset 14 | call instruction 15 | -------------------------------------------------------------------------------- /test/test-cases/jset-reg.data: -------------------------------------------------------------------------------- 1 | -- asm 2 | mov32 r0, 0 3 | mov32 r1, 0x7 4 | mov32 r2, 0x8 5 | jset r1, r2, +4 # Not taken 6 | 7 | mov32 r0, 1 8 | mov32 r1, 0x9 9 | jset r1, r2, +1 # Taken 10 | 11 | mov32 r0, 2 # Skipped 12 | 13 | exit 14 | -- result 15 | 0x1 16 | -------------------------------------------------------------------------------- /test/test-cases/jsgt-imm.data: -------------------------------------------------------------------------------- 1 | -- asm 2 | mov32 r0, 0 3 | mov r1, 0xfffffffe 4 | jsgt r1, 0xffffffff, +4 # Not taken 5 | 6 | mov32 r0, 1 7 | mov32 r1, 0 8 | jsgt r1, 0xffffffff, +1 # Taken 9 | 10 | mov32 r0, 2 # Skipped 11 | 12 | exit 13 | -- result 14 | 0x1 15 | -------------------------------------------------------------------------------- /test/test-cases/jsle-imm.data: -------------------------------------------------------------------------------- 1 | -- asm 2 | mov32 r0, 0 3 | mov r1, 0xfffffffe 4 | jsle r1, 0xfffffffd, +1 # Not taken 5 | jsle r1, 0xffffffff, +1 # Taken 6 | exit 7 | mov32 r0, 1 8 | jsle r1, 0xfffffffe, +1 # Taken 9 | mov32 r0, 2 10 | exit 11 | -- result 12 | 0x1 13 | -------------------------------------------------------------------------------- /test/test-cases/jsgt-reg.data: -------------------------------------------------------------------------------- 1 | -- asm 2 | mov32 r0, 0 3 | mov r1, 0xfffffffe 4 | mov r2, 0xffffffff 5 | jsgt r1, r2, +4 # Not taken 6 | 7 | mov32 r0, 1 8 | mov32 r1, 0 9 | jsgt r1, r2, +1 # Taken 10 | 11 | mov32 r0, 2 # Skipped 12 | 13 | exit 14 | -- result 15 | 0x1 16 | -------------------------------------------------------------------------------- /test/test-cases/stx.data: -------------------------------------------------------------------------------- 1 | -- asm 2 | stxw [r1], r2 3 | stxw [r1+1], r2 4 | stxw [r1+32767], r2 5 | stxw [r1-1], r2 6 | stxw [r1-32768], r2 7 | -- raw 8 | 0x0000000000002163 9 | 0x0000000000012163 10 | 0x000000007fff2163 11 | 0x00000000ffff2163 12 | 0x0000000080002163 13 | -------------------------------------------------------------------------------- /test/test-cases/jslt-reg.data: -------------------------------------------------------------------------------- 1 | -- asm 2 | mov32 r0, 0 3 | mov r1, 0xfffffffe 4 | mov r2, 0xfffffffd 5 | mov r3, 0xffffffff 6 | jslt r1, r1, +2 # Not taken 7 | jslt r1, r2, +1 # Not taken 8 | jslt r1, r3, +1 # Taken 9 | exit 10 | mov32 r0, 1 11 | exit 12 | -- result 13 | 0x1 14 | -------------------------------------------------------------------------------- /test/test-cases/st.data: -------------------------------------------------------------------------------- 1 | -- asm 2 | stw [r1], 0x33 3 | stw [r1+1], 0x33 4 | stw [r1+32767], 0x33 5 | stw [r1-1], 0x33 6 | stw [r1-32768], 0x33 7 | -- raw 8 | 0x0000003300000162 9 | 0x0000003300010162 10 | 0x000000337fff0162 11 | 0x00000033ffff0162 12 | 0x0000003380000162 13 | -------------------------------------------------------------------------------- /test/test-cases/tcp-sack/Makefile: -------------------------------------------------------------------------------- 1 | PROG := tcp-sack 2 | 3 | $(PROG).asm: $(PROG).bin 4 | ../../bin/ubpf-disassembler $^ $@ 5 | 6 | $(PROG).bin: $(PROG).o 7 | objcopy -I elf64-little -O binary $^ $@ 8 | 9 | $(PROG).o: $(PROG).c 10 | clang-3.7 -O2 -target bpf -c $^ -o $@ 11 | -------------------------------------------------------------------------------- /test/test-cases/call-save.data: -------------------------------------------------------------------------------- 1 | -- asm 2 | mov r6, 0x0001 3 | mov r7, 0x0020 4 | mov r8, 0x0300 5 | mov r9, 0x4000 6 | call 2 7 | mov r0, 0 8 | or r0, r6 9 | or r0, r7 10 | or r0, r8 11 | or r0, r9 12 | exit 13 | -- result 14 | 0x4321 15 | -- no register offset 16 | call instruction 17 | -------------------------------------------------------------------------------- /test/test-cases/stack.data: -------------------------------------------------------------------------------- 1 | -- asm 2 | mov r1, 51 3 | 4 | # Create lookup table 5 | stdw [r10-16], 0xab 6 | stdw [r10-8], 0xcd 7 | 8 | # Load lookup[r1 % 2] 9 | and r1, 1 10 | lsh r1, 3 11 | mov r2, r10 12 | add r2, r1 13 | ldxdw r0, [r2-16] 14 | 15 | exit 16 | -- result 17 | 0xcd 18 | -------------------------------------------------------------------------------- /test/test-cases/tcp-sack/pkt-sack.hex: -------------------------------------------------------------------------------- 1 | 0x0000: 0026 622f 4787 001d 60b3 0184 0800 4500 2 | 0x0010: 0040 a8de 4000 4006 9d58 c0a8 0103 3f74 3 | 0x0020: f361 e5c0 0050 e594 3f77 a3c4 c480 b010 4 | 0x0030: 013e 34b6 0000 0101 080a 0017 956f 8d9d 5 | 0x0040: 9e27 0101 050a a3c4 ca28 a3c4 cfd0 6 | -------------------------------------------------------------------------------- /example/inline/Makefile: -------------------------------------------------------------------------------- 1 | inline.o: libmap.c 2 | clang -S -O3 -emit-llvm libmap.c -o libmap.ll 3 | llvm-link -S -o xdp-counter-inline.ll xdp-counter.ll libmap.ll 4 | opt -passes=always-inline -S xdp-counter-inline.ll -o xdp-counter-inline.ll 5 | clang -O3 -g -c xdp-counter-inline.ll -o inline.o 6 | -------------------------------------------------------------------------------- /test/test-cases/jsge-imm.data: -------------------------------------------------------------------------------- 1 | -- asm 2 | mov32 r0, 0 3 | mov r1, 0xfffffffe 4 | jsge r1, 0xffffffff, +5 # Not taken 5 | jsge r1, 0, +4 # Not taken 6 | 7 | mov32 r0, 1 8 | mov r1, 0xffffffff 9 | jsge r1, 0xffffffff, +1 # Taken 10 | 11 | mov32 r0, 2 # Skipped 12 | 13 | exit 14 | -- result 15 | 0x1 16 | -------------------------------------------------------------------------------- /test/test-cases/jsle-reg.data: -------------------------------------------------------------------------------- 1 | -- asm 2 | mov32 r0, 0 3 | mov r1, 0xffffffff 4 | mov r2, 0xfffffffe 5 | mov32 r3, 0 6 | jsle r1, r2, +1 # Not taken 7 | jsle r1, r3, +1 # Taken 8 | exit 9 | mov32 r0, 1 10 | mov r1, r2 11 | jsle r1, r2, +1 # Taken 12 | mov32 r0, 2 13 | exit 14 | -- result 15 | 0x1 16 | -------------------------------------------------------------------------------- /test/test-cases/jsge-reg.data: -------------------------------------------------------------------------------- 1 | -- asm 2 | mov32 r0, 0 3 | mov r1, 0xfffffffe 4 | mov r2, 0xffffffff 5 | mov32 r3, 0 6 | jsge r1, r2, +5 # Not taken 7 | jsge r1, r3, +4 # Not taken 8 | 9 | mov32 r0, 1 10 | mov r1, r2 11 | jsge r1, r2, +1 # Taken 12 | 13 | mov32 r0, 2 # Skipped 14 | 15 | exit 16 | -- result 17 | 0x1 18 | -------------------------------------------------------------------------------- /test/bpf_conformance_runner/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_executable(bpftime_vm_bpf_conformance_runner 2 | main-bpf-conformance.cpp 3 | ) 4 | 5 | add_dependencies(bpftime_vm_bpf_conformance_runner llvmbpf_vm) 6 | target_link_libraries(bpftime_vm_bpf_conformance_runner llvmbpf_vm) 7 | 8 | set_target_properties(bpftime_vm_bpf_conformance_runner PROPERTIES CXX_STANDARD 20) 9 | -------------------------------------------------------------------------------- /test/include/test_defs.h: -------------------------------------------------------------------------------- 1 | #ifndef TEST_DEF_BPFTIME_H 2 | #define TEST_DEF_BPFTIME_H 3 | 4 | #define CHECK_EXIT(ret) \ 5 | if (ret != 0) { \ 6 | fprintf(stderr, "Failed to load code: %s\n", errmsg); \ 7 | return -1; \ 8 | } 9 | 10 | #endif 11 | -------------------------------------------------------------------------------- /test/test-cases/mod64.data: -------------------------------------------------------------------------------- 1 | -- asm 2 | mov32 r0, 0xb1858436 3 | lsh r0, 32 4 | or r0, 0x100dc5c8 5 | # r0 == 0xb1858436100dc5c8 6 | 7 | mov32 r1, 0xdde263e 8 | lsh r1, 32 9 | or r1, 0x3cbef7f3 10 | # r1 == 0xdde263e3cbef7f3 11 | 12 | mod r0, r1 13 | # r0 == 0xb1bb94b371a2664 14 | 15 | mod r0, 0x658f1778 16 | # r0 == 0x30ba5a04 17 | 18 | exit 19 | -- result 20 | 0x30ba5a04 21 | -------------------------------------------------------------------------------- /test/test-cases/tcp-port-80/tcp-port-80.asm: -------------------------------------------------------------------------------- 1 | ldxb r2, [r1+12] 2 | ldxb r3, [r1+13] 3 | lsh r3, 0x8 4 | or r3, r2 5 | mov r0, 0x0 6 | jne r3, 0x8, +12 7 | ldxb r2, [r1+23] 8 | jne r2, 0x6, +10 9 | ldxb r2, [r1+14] 10 | add r1, 0xe 11 | and r2, 0xf 12 | lsh r2, 0x2 13 | add r1, r2 14 | ldxh r2, [r1+2] 15 | jeq r2, 0x5000, +2 16 | ldxh r1, [r1] 17 | jne r1, 0x5000, +1 18 | mov r0, 0x1 19 | exit 20 | -------------------------------------------------------------------------------- /test/test-cases/ldx.data: -------------------------------------------------------------------------------- 1 | -- asm 2 | ldxw r1, [r2] 3 | ldxh r1, [r2] 4 | ldxb r1, [r2] 5 | ldxdw r1, [r2] 6 | ldxw r1, [r2+1] 7 | ldxw r1, [r2+32767] 8 | ldxw r1, [r2-1] 9 | ldxw r1, [r2-32768] 10 | -- raw 11 | 0x0000000000002161 12 | 0x0000000000002169 13 | 0x0000000000002171 14 | 0x0000000000002179 15 | 0x0000000000012161 16 | 0x000000007fff2161 17 | 0x00000000ffff2161 18 | 0x0000000080002161 19 | -------------------------------------------------------------------------------- /example/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | bpftime_add_executable(vm-llvm-example 2 | ./basic.cpp 3 | ) 4 | 5 | add_dependencies(vm-llvm-example llvmbpf_vm spdlog::spdlog) 6 | target_link_libraries(vm-llvm-example llvmbpf_vm) 7 | 8 | bpftime_add_executable(maps-example 9 | ./maps.cpp 10 | ) 11 | 12 | add_dependencies(maps-example llvmbpf_vm spdlog::spdlog) 13 | target_link_libraries(maps-example llvmbpf_vm) 14 | -------------------------------------------------------------------------------- /example/inline/main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | int bpf_main(void* ctx, uint64_t size); 6 | 7 | unsigned char bpf_mem[] = { 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88 }; 8 | 9 | int main() { 10 | printf("calling ebpf program...\n"); 11 | int res = bpf_main(bpf_mem, sizeof(bpf_mem)); 12 | printf("return value = %d\n", res); 13 | return 0; 14 | } -------------------------------------------------------------------------------- /test/test-cases/tcp-port-80/match.data: -------------------------------------------------------------------------------- 1 | -- asm @ tcp-port-80.asm 2 | -- mem 3 | 00 01 02 03 04 05 00 06 07 08 09 0a 08 00 45 00 4 | 00 56 00 01 00 00 40 06 f9 4d c0 a8 00 01 c0 a8 5 | 00 02 27 10 00 50 00 00 00 00 00 00 00 00 50 02 6 | 20 00 c5 18 00 00 44 44 44 44 44 44 44 44 44 44 7 | 44 44 44 44 44 44 44 44 44 44 44 44 44 44 44 44 8 | 44 44 44 44 44 44 44 44 44 44 44 44 44 44 44 44 9 | 44 44 44 44 10 | -- result 11 | 0x1 12 | -------------------------------------------------------------------------------- /test/test-cases/alu64-arith.data: -------------------------------------------------------------------------------- 1 | -- asm 2 | mov r0, 0 3 | mov r1, 1 4 | mov r2, 2 5 | mov r3, 3 6 | mov r4, 4 7 | mov r5, 5 8 | mov r6, 6 9 | mov r7, 7 10 | mov r8, 8 11 | mov r9, 9 12 | # r0 == 0 13 | 14 | add r0, 23 15 | add r0, r7 16 | # r0 == 30 17 | 18 | sub r0, 13 19 | sub r0, r1 20 | # r0 == 16 21 | 22 | mul r0, 7 23 | mul r0, r3 24 | # r0 == 336 25 | 26 | div r0, 2 27 | div r0, r4 28 | # r0 == 42 29 | 30 | exit 31 | -- result 32 | 0x2a 33 | -------------------------------------------------------------------------------- /test/test-cases/stxb-all.data: -------------------------------------------------------------------------------- 1 | -- asm 2 | mov r0, 0xf0 3 | mov r2, 0xf2 4 | mov r3, 0xf3 5 | mov r4, 0xf4 6 | mov r5, 0xf5 7 | mov r6, 0xf6 8 | mov r7, 0xf7 9 | mov r8, 0xf8 10 | stxb [r1], r0 11 | stxb [r1+1], r2 12 | stxb [r1+2], r3 13 | stxb [r1+3], r4 14 | stxb [r1+4], r5 15 | stxb [r1+5], r6 16 | stxb [r1+6], r7 17 | stxb [r1+7], r8 18 | ldxdw r0, [r1] 19 | be64 r0 20 | exit 21 | -- mem 22 | ff ff ff ff ff ff ff ff 23 | -- result 24 | 0xf0f2f3f4f5f6f7f8 25 | -------------------------------------------------------------------------------- /test/test-cases/tcp-port-80/nomatch-ethertype.data: -------------------------------------------------------------------------------- 1 | -- description 2 | Wrong ethertype 3 | -- asm @ tcp-port-80.asm 4 | -- mem 5 | 00 01 02 03 04 05 00 06 07 08 09 0a 08 01 45 00 6 | 00 56 00 01 00 00 40 06 f9 4d c0 a8 00 01 c0 a8 7 | 00 02 27 10 00 50 00 00 00 00 00 00 00 00 50 02 8 | 20 00 c5 18 00 00 44 44 44 44 44 44 44 44 44 44 9 | 44 44 44 44 44 44 44 44 44 44 44 44 44 44 44 44 10 | 44 44 44 44 44 44 44 44 44 44 44 44 44 44 44 44 11 | 44 44 44 44 12 | -- result 13 | 0x0 14 | -------------------------------------------------------------------------------- /test/test-cases/tcp-port-80/nomatch-proto.data: -------------------------------------------------------------------------------- 1 | -- description 2 | Wrong IP protocol 3 | -- asm @ tcp-port-80.asm 4 | -- mem 5 | 00 01 02 03 04 05 00 06 07 08 09 0a 08 00 45 00 6 | 00 56 00 01 00 00 40 11 f9 4d c0 a8 00 01 c0 a8 7 | 00 02 27 10 00 50 00 00 00 00 00 00 00 00 50 02 8 | 20 00 c5 18 00 00 44 44 44 44 44 44 44 44 44 44 9 | 44 44 44 44 44 44 44 44 44 44 44 44 44 44 44 44 10 | 44 44 44 44 44 44 44 44 44 44 44 44 44 44 44 44 11 | 44 44 44 44 12 | -- result 13 | 0x0 14 | -------------------------------------------------------------------------------- /test/test-cases/tcp-port-80/nomatch.data: -------------------------------------------------------------------------------- 1 | -- description 2 | Incorrect TCP src/dst ports 3 | -- asm @ tcp-port-80.asm 4 | -- mem 5 | 00 01 02 03 04 05 00 06 07 08 09 0a 08 00 45 00 6 | 00 56 00 01 00 00 40 06 f9 4d c0 a8 00 01 c0 a8 7 | 00 02 00 16 27 10 00 00 00 00 00 00 00 00 51 02 8 | 20 00 c5 18 00 00 44 44 44 44 44 44 44 44 44 44 9 | 44 44 44 44 44 44 44 44 44 44 44 44 44 44 44 44 10 | 44 44 44 44 44 44 44 44 44 44 44 44 44 44 44 44 11 | 44 44 44 44 12 | -- result 13 | 0x0 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | build 3 | /.vscode 4 | /.cache 5 | libvm-bpf.a 6 | CMakeCache.txt 7 | CMakeFiles 8 | _deps 9 | cmake_install.cmake 10 | compile_commands.json 11 | libllvmbpf_vm.a 12 | test.o 13 | test.bin 14 | *.ll 15 | bpf_conformance 16 | coverage.info 17 | example/standalone/standalone 18 | example/inline/standalone 19 | example/inline/inline.o 20 | example/load-llvm-ir/bpf_module.o 21 | dump*.txt 22 | /a.out 23 | /out.ptx 24 | /test.* 25 | /bpf_program.spv 26 | /bpf_program.spvasm -------------------------------------------------------------------------------- /example/ptx/example_trampoline/README.md: -------------------------------------------------------------------------------- 1 | # trampoline for llvm-bpf PTX code 2 | 3 | - `default_trampoline.cu` is a template program, where the built-in trampoline for maps opertions comes from 4 | - `default_trampoline-cuda-nvptx64-nvidia-cuda-sm_60.s` is the PTX code generated by `test.cu`, by calling `clang++-17 -S ./default_trampoline.cu -Wall --cuda-gpu-arch=sm_60 -O2 -L/usr/local/cuda/lib64/ -lcudart `. PTX code here with `bpf_main` stripped out will be put in `../src/trampoline_ptx.h` 5 | -------------------------------------------------------------------------------- /test/test-cases/alu-arith.data: -------------------------------------------------------------------------------- 1 | -- asm 2 | mov32 r0, 0 3 | mov32 r1, 1 4 | mov32 r2, 2 5 | mov32 r3, 3 6 | mov32 r4, 4 7 | mov32 r5, 5 8 | mov32 r6, 6 9 | mov32 r7, 7 10 | mov32 r8, 8 11 | mov32 r9, 9 12 | # r0 == 0 13 | 14 | add32 r0, 23 15 | add32 r0, r7 16 | # r0 == 30 17 | 18 | sub32 r0, 13 19 | sub32 r0, r1 20 | # r0 == 16 21 | 22 | mul32 r0, 7 23 | mul32 r0, r3 24 | # r0 == 336 25 | 26 | div32 r0, 2 27 | div32 r0, r4 28 | # r0 == 42 29 | 30 | exit 31 | -- result 32 | 0x2a 33 | -------------------------------------------------------------------------------- /codecov.yaml: -------------------------------------------------------------------------------- 1 | ignore: 2 | - "build" 3 | - "test" 4 | - "example" 5 | 6 | coverage: 7 | status: 8 | project: 9 | default: 10 | target: auto 11 | # adjust accordingly based on how flaky your tests are 12 | # this allows a 0.5% drop from the previous base commit coverage 13 | threshold: 0.5% 14 | 15 | patch: 16 | default: 17 | target: auto 18 | base: auto 19 | only_pulls: false 20 | threshold: 25% 21 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for more information: 4 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | # https://containers.dev/guide/dependabot 6 | 7 | version: 2 8 | updates: 9 | - package-ecosystem: "devcontainers" 10 | directory: "/" 11 | schedule: 12 | interval: weekly 13 | -------------------------------------------------------------------------------- /test/test-cases/mul-loop.data: -------------------------------------------------------------------------------- 1 | # Testcase compiled by Clang 2 | -- c 3 | #include 4 | 5 | uint64_t entry(void *ctx) 6 | { 7 | uint64_t n = (uintptr_t)ctx + 10; 8 | uint64_t i; 9 | uint64_t a = 7llu; 10 | for (i = 0; i < n; i++) { 11 | a *= 7llu; 12 | } 13 | return a; 14 | } 15 | -- asm 16 | mov r0, 0x7 17 | add r1, 0xa 18 | lsh r1, 0x20 19 | rsh r1, 0x20 20 | jeq r1, 0x0, +4 21 | mov r0, 0x7 22 | mul r0, 0x7 23 | add r1, 0xffffffff 24 | jne r1, 0x0, -3 25 | exit 26 | -- result 27 | 0x75db9c97 28 | -------------------------------------------------------------------------------- /test/test-cases/stack2.data: -------------------------------------------------------------------------------- 1 | # Test that stack data is preserved across function calls 2 | -- asm 3 | stb [r10-4], 0x01 4 | stb [r10-3], 0x02 5 | stb [r10-2], 0x03 6 | stb [r10-1], 0x04 7 | 8 | # memfrob 9 | mov r1, r10 10 | mov r2, 0x4 11 | sub r1, r2 12 | call 1 13 | 14 | mov r1, 0 15 | ldxb r2, [r10-4] 16 | ldxb r3, [r10-3] 17 | ldxb r4, [r10-2] 18 | ldxb r5, [r10-1] 19 | 20 | call 0 # gather_bytes 21 | 22 | xor r0, 0x2a2a2a2a # undo memfrob 23 | 24 | exit 25 | -- result 26 | 0x01020304 27 | -- no register offset 28 | call instruction 29 | -------------------------------------------------------------------------------- /test/test-cases/alu64-bit.data: -------------------------------------------------------------------------------- 1 | -- asm 2 | mov r0, 0 3 | mov r1, 1 4 | mov r2, 2 5 | mov r3, 3 6 | mov r4, 4 7 | mov r5, 5 8 | mov r6, 6 9 | mov r7, 7 10 | mov r8, 8 11 | # r0 == 0 12 | 13 | or r0, r5 14 | or r0, 0xa0 15 | # r0 == 0xa5 16 | 17 | and r0, 0xa3 18 | mov r9, 0x91 19 | and r0, r9 20 | # r0 == 0x21 21 | 22 | lsh r0, 32 23 | lsh r0, 22 24 | lsh r0, r8 25 | # r0 == 0x40000000 26 | 27 | rsh r0, 32 28 | rsh r0, 19 29 | rsh r0, r7 30 | # r0 == 0x10 31 | 32 | xor r0, 0x03 33 | xor r0, r2 34 | # r0 == 0x11 35 | 36 | exit 37 | -- result 38 | 0x11 39 | -------------------------------------------------------------------------------- /test/test-cases/alu-bit.data: -------------------------------------------------------------------------------- 1 | -- asm 2 | mov32 r0, 0 3 | mov32 r1, 1 4 | mov32 r2, 2 5 | mov32 r3, 3 6 | mov32 r4, 4 7 | mov32 r5, 5 8 | mov32 r6, 6 9 | mov32 r7, 7 10 | mov32 r8, 8 11 | # r0 == 0 12 | 13 | or32 r0, r5 14 | or32 r0, 0xa0 15 | # r0 == 0xa5 16 | 17 | and32 r0, 0xa3 18 | mov32 r9, 0x91 19 | and32 r0, r9 20 | # r0 == 0x21 21 | 22 | lsh32 r0, 22 23 | lsh32 r0, r8 24 | # r0 == 0x40000000 25 | 26 | rsh32 r0, 19 27 | rsh32 r0, r7 28 | # r0 == 0x10 29 | 30 | xor32 r0, 0x03 31 | xor32 r0, r2 32 | # r0 == 0x11 33 | 34 | exit 35 | -- result 36 | 0x11 37 | -------------------------------------------------------------------------------- /test/test-cases/stxb-chain.data: -------------------------------------------------------------------------------- 1 | -- asm 2 | mov r0, r1 3 | 4 | ldxb r9, [r0+0] 5 | stxb [r0+1], r9 6 | 7 | ldxb r8, [r0+1] 8 | stxb [r0+2], r8 9 | 10 | ldxb r7, [r0+2] 11 | stxb [r0+3], r7 12 | 13 | ldxb r6, [r0+3] 14 | stxb [r0+4], r6 15 | 16 | ldxb r5, [r0+4] 17 | stxb [r0+5], r5 18 | 19 | ldxb r4, [r0+5] 20 | stxb [r0+6], r4 21 | 22 | ldxb r3, [r0+6] 23 | stxb [r0+7], r3 24 | 25 | ldxb r2, [r0+7] 26 | stxb [r0+8], r2 27 | 28 | ldxb r1, [r0+8] 29 | stxb [r0+9], r1 30 | 31 | ldxb r0, [r0+9] 32 | exit 33 | -- mem 34 | 2a 00 00 00 00 00 00 00 00 00 35 | -- result 36 | 0x2a 37 | -------------------------------------------------------------------------------- /test/test-cases/jmp.data: -------------------------------------------------------------------------------- 1 | -- asm 2 | ja +1 3 | ja +32767 4 | ja -1 5 | ja -32768 6 | jeq r1, 0x33, +1 7 | jeq r1, r2, +1 8 | jgt r1, r2, +1 9 | jge r1, r2, +1 10 | jset r1, r2, +1 11 | jne r1, r2, +1 12 | jsgt r1, r2, +1 13 | jsge r1, r2, +1 14 | call 0x1 15 | exit 16 | -- raw 17 | 0x0000000000010005 18 | 0x000000007fff0005 19 | 0x00000000ffff0005 20 | 0x0000000080000005 21 | 0x0000003300010115 22 | 0x000000000001211d 23 | 0x000000000001212d 24 | 0x000000000001213d 25 | 0x000000000001214d 26 | 0x000000000001215d 27 | 0x000000000001216d 28 | 0x000000000001217d 29 | 0x0000000100000085 30 | 0x0000000000000095 31 | -------------------------------------------------------------------------------- /test/test-cases/prime.data: -------------------------------------------------------------------------------- 1 | # Compiled by Clang 2 | -- c 3 | #include 4 | #include 5 | 6 | uint64_t entry(uint64_t arg) 7 | { 8 | int i = 0; 9 | for (i = 2; i < arg; i++) { 10 | if (arg % i == 0) { 11 | return false; 12 | } 13 | } 14 | return true; 15 | } 16 | -- asm 17 | mov r1, 67 18 | mov r0, 0x1 19 | mov r2, 0x2 20 | jgt r1, 0x2, +4 21 | ja +10 22 | add r2, 0x1 23 | mov r0, 0x1 24 | jge r2, r1, +7 25 | mov r3, r1 26 | div r3, r2 27 | mul r3, r2 28 | mov r4, r1 29 | sub r4, r3 30 | mov r0, 0x0 31 | jne r4, 0x0, -10 32 | exit 33 | -- result 34 | 0x1 35 | -------------------------------------------------------------------------------- /test/test-cases/alu64.data: -------------------------------------------------------------------------------- 1 | -- asm 2 | add r1, 0x2 3 | add r9, 0xffffffff 4 | add r1, r2 5 | sub r1, r2 6 | mul r1, r2 7 | div r1, r2 8 | or r1, r2 9 | and r1, r2 10 | lsh r1, r2 11 | rsh r1, r2 12 | neg r1 13 | mod r1, r2 14 | xor r1, r2 15 | mov r1, r2 16 | arsh r1, r2 17 | -- raw 18 | 0x0000000200000107 19 | 0xffffffff00000907 20 | 0x000000000000210f 21 | 0x000000000000211f 22 | 0x000000000000212f 23 | 0x000000000000213f 24 | 0x000000000000214f 25 | 0x000000000000215f 26 | 0x000000000000216f 27 | 0x000000000000217f 28 | 0x0000000000000187 29 | 0x000000000000219f 30 | 0x00000000000021af 31 | 0x00000000000021bf 32 | 0x00000000000021cf 33 | -------------------------------------------------------------------------------- /test/test-cases/ldxb-all.data: -------------------------------------------------------------------------------- 1 | -- asm 2 | mov r0, r1 3 | 4 | ldxb r9, [r0+0] 5 | lsh r9, 0 6 | 7 | ldxb r8, [r0+1] 8 | lsh r8, 4 9 | 10 | ldxb r7, [r0+2] 11 | lsh r7, 8 12 | 13 | ldxb r6, [r0+3] 14 | lsh r6, 12 15 | 16 | ldxb r5, [r0+4] 17 | lsh r5, 16 18 | 19 | ldxb r4, [r0+5] 20 | lsh r4, 20 21 | 22 | ldxb r3, [r0+6] 23 | lsh r3, 24 24 | 25 | ldxb r2, [r0+7] 26 | lsh r2, 28 27 | 28 | ldxb r1, [r0+8] 29 | lsh r1, 32 30 | 31 | ldxb r0, [r0+9] 32 | lsh r0, 36 33 | 34 | or r0, r1 35 | or r0, r2 36 | or r0, r3 37 | or r0, r4 38 | or r0, r5 39 | or r0, r6 40 | or r0, r7 41 | or r0, r8 42 | or r0, r9 43 | 44 | exit 45 | -- result 46 | 0x9876543210 47 | -- mem 48 | 00 01 02 03 04 05 06 07 08 09 49 | -------------------------------------------------------------------------------- /example/inline/libmap.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | uint32_t ctl_array[2] = { 0, 0 }; 6 | uint64_t cntrs_array[2] = { 0, 0 }; 7 | 8 | void *_bpf_helper_ext_0001(uint64_t map_fd, void *key) 9 | { 10 | if (map_fd == 5) { 11 | return &ctl_array[*(uint32_t *)key]; 12 | } else if (map_fd == 6) { 13 | return &cntrs_array[*(uint32_t *)key]; 14 | } else { 15 | return NULL; 16 | } 17 | return 0; 18 | } 19 | 20 | void* __lddw_helper_map_val(uint64_t val) 21 | { 22 | if (val == 5) { 23 | return (void *)ctl_array; 24 | } else if (val == 6) { 25 | return (void *)cntrs_array; 26 | } else { 27 | return NULL; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /test/unit-test/bpf_prog_test.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "llvmbpf.hpp" 5 | #include "bpf_progs.h" 6 | 7 | extern "C" uint64_t add_func(uint64_t a, uint64_t b, uint64_t, uint64_t, 8 | uint64_t) 9 | { 10 | return a + b; 11 | } 12 | 13 | TEST_CASE("Test aot compilation for extenal function") 14 | { 15 | bpftime::llvmbpf_vm vm; 16 | REQUIRE(vm.register_external_function(3, "add", (void *)add_func) == 0); 17 | REQUIRE(vm.load_code((const void *)bpf_function_call_add, 18 | sizeof(bpf_function_call_add) - 1) == 0); 19 | uint64_t ret = 0; 20 | uint64_t mem = 0; 21 | 22 | REQUIRE(vm.exec(&mem, sizeof(mem), ret) == 0); 23 | REQUIRE(ret == 4); 24 | } 25 | -------------------------------------------------------------------------------- /test/test-cases/ldxh-all2.data: -------------------------------------------------------------------------------- 1 | -- asm 2 | mov r0, r1 3 | 4 | ldxh r9, [r0+0] 5 | be16 r9 6 | 7 | ldxh r8, [r0+2] 8 | be16 r8 9 | 10 | ldxh r7, [r0+4] 11 | be16 r7 12 | 13 | ldxh r6, [r0+6] 14 | be16 r6 15 | 16 | ldxh r5, [r0+8] 17 | be16 r5 18 | 19 | ldxh r4, [r0+10] 20 | be16 r4 21 | 22 | ldxh r3, [r0+12] 23 | be16 r3 24 | 25 | ldxh r2, [r0+14] 26 | be16 r2 27 | 28 | ldxh r1, [r0+16] 29 | be16 r1 30 | 31 | ldxh r0, [r0+18] 32 | be16 r0 33 | 34 | or r0, r1 35 | or r0, r2 36 | or r0, r3 37 | or r0, r4 38 | or r0, r5 39 | or r0, r6 40 | or r0, r7 41 | or r0, r8 42 | or r0, r9 43 | 44 | exit 45 | -- result 46 | 0x3ff 47 | -- mem 48 | 00 01 49 | 00 02 50 | 00 04 51 | 00 08 52 | 00 10 53 | 00 20 54 | 00 40 55 | 00 80 56 | 01 00 57 | 02 00 58 | -------------------------------------------------------------------------------- /cli/README.md: -------------------------------------------------------------------------------- 1 | # bpftime-vm cli tool 2 | 3 | ```console 4 | # bpftime-vm 5 | Usage: bpftime-vm [--help] [--version] {build,run} 6 | 7 | Optional arguments: 8 | -h, --help shows help message and exits 9 | -v, --version prints version information and exits 10 | 11 | Subcommands: 12 | build Build native ELF(s) from eBPF ELF. Each program in the eBPF ELF will be built into a single native ELF 13 | run Run an native eBPF program 14 | ``` 15 | 16 | A CLI compiler for AOT of llvm-jit. 17 | 18 | It can build ebpf ELF into native elf, or execute compiled native ELF. **Helpers and relocations are not supported. For helpers and maps, please use bpftime aot cli in the [bpftime/tools](https://github.com/eunomia-bpf/bpftime)** 19 | 20 | -------------------------------------------------------------------------------- /test/test-cases/ldxw-all.data: -------------------------------------------------------------------------------- 1 | -- asm 2 | mov r0, r1 3 | 4 | ldxw r9, [r0+0] 5 | be32 r9 6 | 7 | ldxw r8, [r0+4] 8 | be32 r8 9 | 10 | ldxw r7, [r0+8] 11 | be32 r7 12 | 13 | ldxw r6, [r0+12] 14 | be32 r6 15 | 16 | ldxw r5, [r0+16] 17 | be32 r5 18 | 19 | ldxw r4, [r0+20] 20 | be32 r4 21 | 22 | ldxw r3, [r0+24] 23 | be32 r3 24 | 25 | ldxw r2, [r0+28] 26 | be32 r2 27 | 28 | ldxw r1, [r0+32] 29 | be32 r1 30 | 31 | ldxw r0, [r0+36] 32 | be32 r0 33 | 34 | or r0, r1 35 | or r0, r2 36 | or r0, r3 37 | or r0, r4 38 | or r0, r5 39 | or r0, r6 40 | or r0, r7 41 | or r0, r8 42 | or r0, r9 43 | 44 | exit 45 | -- result 46 | 0x030f0f 47 | -- mem 48 | 00 00 00 01 49 | 00 00 00 02 50 | 00 00 00 04 51 | 00 00 00 08 52 | 00 00 01 00 53 | 00 00 02 00 54 | 00 00 04 00 55 | 00 00 08 00 56 | 00 01 00 00 57 | 00 02 00 00 58 | -------------------------------------------------------------------------------- /test/test-cases/tcp-sack/tcp-sack.asm: -------------------------------------------------------------------------------- 1 | ldxb r2, [r1+12] 2 | ldxb r3, [r1+13] 3 | lsh r3, 0x8 4 | or r3, r2 5 | mov r0, 0x0 6 | jne r3, 0x8, +37 7 | ldxb r2, [r1+23] 8 | jne r2, 0x6, +35 9 | ldxb r2, [r1+14] 10 | add r1, 0xe 11 | and r2, 0xf 12 | lsh r2, 0x2 13 | add r1, r2 14 | mov r0, 0x0 15 | ldxh r4, [r1+12] 16 | add r1, 0x14 17 | rsh r4, 0x2 18 | and r4, 0x3c 19 | mov r2, r4 20 | add r2, 0xffffffec 21 | mov r5, 0x15 22 | mov r3, 0x0 23 | jgt r5, r4, +20 24 | mov r5, r3 25 | lsh r5, 0x20 26 | arsh r5, 0x20 27 | mov r4, r1 28 | add r4, r5 29 | ldxb r5, [r4] 30 | jeq r5, 0x1, +4 31 | jeq r5, 0x0, +12 32 | mov r6, r3 33 | jeq r5, 0x5, +9 34 | ja +2 35 | add r3, 0x1 36 | mov r6, r3 37 | ldxb r3, [r4+1] 38 | add r3, r6 39 | lsh r3, 0x20 40 | arsh r3, 0x20 41 | jsgt r2, r3, -18 42 | ja +1 43 | mov r0, 0x1 44 | exit 45 | -------------------------------------------------------------------------------- /test/test-cases/ldxh-all.data: -------------------------------------------------------------------------------- 1 | -- asm 2 | mov r0, r1 3 | 4 | ldxh r9, [r0+0] 5 | be16 r9 6 | lsh r9, 0 7 | 8 | ldxh r8, [r0+2] 9 | be16 r8 10 | lsh r8, 4 11 | 12 | ldxh r7, [r0+4] 13 | be16 r7 14 | lsh r7, 8 15 | 16 | ldxh r6, [r0+6] 17 | be16 r6 18 | lsh r6, 12 19 | 20 | ldxh r5, [r0+8] 21 | be16 r5 22 | lsh r5, 16 23 | 24 | ldxh r4, [r0+10] 25 | be16 r4 26 | lsh r4, 20 27 | 28 | ldxh r3, [r0+12] 29 | be16 r3 30 | lsh r3, 24 31 | 32 | ldxh r2, [r0+14] 33 | be16 r2 34 | lsh r2, 28 35 | 36 | ldxh r1, [r0+16] 37 | be16 r1 38 | lsh r1, 32 39 | 40 | ldxh r0, [r0+18] 41 | be16 r0 42 | lsh r0, 36 43 | 44 | or r0, r1 45 | or r0, r2 46 | or r0, r3 47 | or r0, r4 48 | or r0, r5 49 | or r0, r6 50 | or r0, r7 51 | or r0, r8 52 | or r0, r9 53 | 54 | exit 55 | -- result 56 | 0x9876543210 57 | -- mem 58 | 00 00 00 01 00 02 00 03 00 04 00 05 00 06 00 07 00 08 00 09 59 | -------------------------------------------------------------------------------- /test/test-cases/alu.data: -------------------------------------------------------------------------------- 1 | -- asm 2 | add32 r1, 0x2 3 | add32 r9, 0xffffffff 4 | add32 r1, r2 5 | sub32 r1, r2 6 | mul32 r1, r2 7 | div32 r1, r2 8 | or32 r1, r2 9 | and32 r1, r2 10 | lsh32 r1, r2 11 | rsh32 r1, r2 12 | neg32 r1 13 | mod32 r1, r2 14 | xor32 r1, r2 15 | mov32 r1, r2 16 | arsh32 r1, r2 17 | le16 r1 18 | le32 r1 19 | le64 r1 20 | be16 r1 21 | be32 r1 22 | be64 r1 23 | -- raw 24 | 0x0000000200000104 25 | 0xffffffff00000904 26 | 0x000000000000210c 27 | 0x000000000000211c 28 | 0x000000000000212c 29 | 0x000000000000213c 30 | 0x000000000000214c 31 | 0x000000000000215c 32 | 0x000000000000216c 33 | 0x000000000000217c 34 | 0x0000000000000184 35 | 0x000000000000219c 36 | 0x00000000000021ac 37 | 0x00000000000021bc 38 | 0x00000000000021cc 39 | 0x00000010000001d4 40 | 0x00000020000001d4 41 | 0x00000040000001d4 42 | 0x00000010000001dc 43 | 0x00000020000001dc 44 | 0x00000040000001dc 45 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [yunwei37, Officeyutong] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry 13 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 14 | -------------------------------------------------------------------------------- /test/test-cases/string-stack.data: -------------------------------------------------------------------------------- 1 | -- c 2 | extern int strcmp_ext(const char *a, const char *b); 3 | 4 | int entry(int *mem) 5 | { 6 | char a[] = "abcx"; 7 | char b[] = "abcy"; 8 | 9 | if (strcmp_ext(a, a) != 0) { 10 | return 1; 11 | } 12 | 13 | if (strcmp_ext(a, b) == 0) { 14 | return 1; 15 | } 16 | 17 | return 0; 18 | } 19 | -- asm 20 | mov r1, 0x78636261 21 | stxw [r10-8], r1 22 | mov r6, 0x0 23 | stxb [r10-4], r6 24 | stxb [r10-12], r6 25 | mov r1, 0x79636261 26 | stxw [r10-16], r1 27 | mov r1, r10 28 | add r1, 0xfffffff8 29 | mov r2, r1 30 | call 0x4 31 | mov r1, r0 32 | mov r0, 0x1 33 | lsh r1, 0x20 34 | rsh r1, 0x20 35 | jne r1, 0x0, +11 36 | mov r1, r10 37 | add r1, 0xfffffff8 38 | mov r2, r10 39 | add r2, 0xfffffff0 40 | call 0x4 41 | mov r1, r0 42 | lsh r1, 0x20 43 | rsh r1, 0x20 44 | mov r0, 0x1 45 | jeq r1, r6, +1 46 | mov r0, 0x0 47 | exit 48 | -- result 49 | 0x0 50 | -- no register offset 51 | call instruction 52 | -------------------------------------------------------------------------------- /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | // For format details, see https://aka.ms/devcontainer.json. For config options, see the 2 | // README at: https://github.com/devcontainers/templates/tree/main/src/ubuntu 3 | { 4 | "name": "Ubuntu", 5 | // Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile 6 | "image": "mcr.microsoft.com/devcontainers/base:noble" 7 | 8 | // Features to add to the dev container. More info: https://containers.dev/features. 9 | // "features": {}, 10 | 11 | // Use 'forwardPorts' to make a list of ports inside the container available locally. 12 | // "forwardPorts": [], 13 | 14 | // Use 'postCreateCommand' to run commands after the container is created. 15 | // "postCreateCommand": "uname -a", 16 | 17 | // Configure tool-specific properties. 18 | // "customizations": {}, 19 | 20 | // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root. 21 | // "remoteUser": "root" 22 | } 23 | -------------------------------------------------------------------------------- /test/unit-test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Have to fetch Catch2 2 | if(${BPFTIME_ENABLE_UNIT_TESTING} AND NOT TARGET Catch2) 3 | message(STATUS "Adding Catch2 by FetchContent at llvmbpf/test/unit-test") 4 | Include(FetchContent) 5 | FetchContent_Declare( 6 | Catch2 7 | GIT_REPOSITORY https://github.com/catchorg/Catch2.git 8 | GIT_TAG v3.4.0 9 | ) 10 | FetchContent_MakeAvailable(Catch2) 11 | endif() 12 | 13 | set(TEST_SOURCES 14 | bpf_prog_test.cpp 15 | vm_test.cpp 16 | ) 17 | 18 | add_executable(llvm_jit_tests ${TEST_SOURCES}) 19 | set_property(TARGET llvm_jit_tests PROPERTY CXX_STANDARD 20) 20 | add_dependencies(llvm_jit_tests llvmbpf_vm) 21 | target_link_libraries(llvm_jit_tests PRIVATE llvmbpf_vm Catch2::Catch2WithMain ${LIBBPF_LIBRARIES}) 22 | target_include_directories(llvm_jit_tests PRIVATE ${Catch2_INCLUDE} ${CMAKE_CURRENT_SOURCE_DIR}/../include ${CMAKE_CURRENT_SOURCE_DIR}/../src ${LIBBPF_INCLUDE_DIRS} ${LIBBPF_INCLUDE_DIRS}/uapi) 23 | add_test(NAME llvm_jit_tests COMMAND llvm_jit_tests) 24 | -------------------------------------------------------------------------------- /test/src/test_vm.c: -------------------------------------------------------------------------------- 1 | // test jit for vm 2 | #include 3 | #include "test_bpf_progs.h" 4 | #include "test_defs.h" 5 | #include "ebpf-vm.h" 6 | #include 7 | #include 8 | #include 9 | 10 | struct ebpf_inst; 11 | 12 | #define TEST_BPF_CODE bpf_add_mem_64_bit 13 | #define TEST_BPF_SIZE sizeof(bpf_add_mem_64_bit) 14 | 15 | typedef unsigned int (*kernel_fn)(const void *ctx, const struct ebpf_inst *insn); 16 | 17 | char *errmsg; 18 | struct mem { 19 | uint64_t val; 20 | }; 21 | 22 | int main() 23 | { 24 | struct mem m = { __LINE__ }; 25 | uint64_t res = 0; 26 | // using ubpf jit for x86_64 and arm64 27 | struct ebpf_vm *vm = ebpf_create("llvm"); 28 | 29 | ebpf_toggle_bounds_check(vm, false); 30 | 31 | // remove 0, in the end 32 | CHECK_EXIT(ebpf_load(vm, TEST_BPF_CODE, TEST_BPF_SIZE, &errmsg)); 33 | 34 | // EBPF_OP_CALL 35 | printf("code len: %zd\n", TEST_BPF_SIZE); 36 | 37 | int mem_len = 1024 * 1024; 38 | char *mem = malloc(mem_len); 39 | ebpf_exec(vm, mem, mem_len, &res); 40 | printf("res = %" PRIu64 "\n", res); 41 | return 0; 42 | } -------------------------------------------------------------------------------- /example/spirv/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_executable(spirv_opencl_test spirv_opencl_test.cpp) 2 | 3 | # spdlog is provided by parent CMakeLists.txt via FetchContent 4 | if(NOT TARGET spdlog::spdlog) 5 | find_package(spdlog REQUIRED) 6 | endif() 7 | 8 | # Find OpenCL 9 | find_package(OpenCL REQUIRED) 10 | if(NOT OpenCL_FOUND) 11 | message(FATAL_ERROR "OpenCL not found. Please install OpenCL development files (e.g., opencl-headers, ocl-icd-opencl-dev)") 12 | endif() 13 | 14 | message(STATUS "OpenCL found: ${OpenCL_INCLUDE_DIRS}") 15 | message(STATUS "OpenCL libraries: ${OpenCL_LIBRARIES}") 16 | 17 | set(LLVM_LINK_COMPONENTS 18 | ${LLVM_TARGETS_TO_BUILD} 19 | ) 20 | 21 | target_link_libraries(spirv_opencl_test 22 | PRIVATE 23 | ${LLVM_LIBS} 24 | llvmbpf_vm 25 | OpenCL::OpenCL 26 | spdlog::spdlog) 27 | 28 | add_dependencies(spirv_opencl_test llvmbpf_vm spdlog::spdlog) 29 | target_include_directories(spirv_opencl_test 30 | PRIVATE 31 | ${LLVM_INCLUDE_DIRS} 32 | include 33 | src 34 | ${OpenCL_INCLUDE_DIRS} 35 | ${SPDLOG_INCLUDES}) 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 eunomia-bpf org. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /test/src/test_core_minimal_ffi.c: -------------------------------------------------------------------------------- 1 | // test the ufunc function call with core ebpf vm 2 | #include 3 | #include "test_bpf_progs.h" 4 | #include "test_minimal_bpf_host_ufunc.h" 5 | #include 6 | #include 7 | #include "test_defs.h" 8 | 9 | struct ebpf_inst; 10 | 11 | #define TEST_BPF_CODE bpf_ufunc_code 12 | #define TEST_BPF_SIZE sizeof(bpf_ufunc_code) - 1 13 | 14 | typedef unsigned int (*kernel_fn)(const void *ctx, 15 | const struct ebpf_inst *insn); 16 | 17 | char *errmsg; 18 | struct mem { 19 | uint64_t val; 20 | }; 21 | 22 | int main() 23 | { 24 | struct mem m = { __LINE__ }; 25 | uint64_t res = 0; 26 | // using ubpf jit for x86_64 and arm64 27 | struct ebpf_vm *vm = ebpf_create("llvm"); 28 | 29 | register_ufunc_handler(vm); 30 | 31 | ebpf_toggle_bounds_check(vm, false); 32 | 33 | // remove 0, in the end 34 | CHECK_EXIT(ebpf_load(vm, TEST_BPF_CODE, TEST_BPF_SIZE, &errmsg)); 35 | 36 | // EBPF_OP_CALL 37 | printf("code len: %zd\n", TEST_BPF_SIZE); 38 | 39 | // int mem_len = 1024 * 1024; 40 | // char *mem = (char *) malloc(mem_len); 41 | CHECK_EXIT(ebpf_exec(vm, &m, sizeof(m), &res)); 42 | printf("res = %" PRIu64 "\n", res); 43 | return 0; 44 | } -------------------------------------------------------------------------------- /test/test-cases/tcp-sack/tcp-sack.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | struct tcp_option 7 | { 8 | uint8_t kind; 9 | uint8_t length; 10 | }; 11 | 12 | uint64_t 13 | entry(void* pkt) 14 | { 15 | struct ether_header* ether_header = (void*)pkt; 16 | 17 | if (ether_header->ether_type != __builtin_bswap16(0x0800)) { 18 | return 0; 19 | } 20 | 21 | struct iphdr* iphdr = (void*)(ether_header + 1); 22 | if (iphdr->protocol != 6) { 23 | return 0; 24 | } 25 | 26 | struct tcphdr* tcphdr = (void*)iphdr + iphdr->ihl * 4; 27 | 28 | void* options_start = (void*)(tcphdr + 1); 29 | int options_length = tcphdr->doff * 4 - sizeof(*tcphdr); 30 | int offset = 0; 31 | 32 | while (offset < options_length) { 33 | struct tcp_option* option = options_start + offset; 34 | if (option->kind == 0) { 35 | /* End of options */ 36 | break; 37 | } else if (option->kind == 1) { 38 | /* NOP */ 39 | offset++; 40 | } else if (option->kind == 5) { 41 | /* SACK */ 42 | return 1; 43 | } 44 | 45 | offset += option->length; 46 | } 47 | 48 | return 0; 49 | } 50 | -------------------------------------------------------------------------------- /test/src/test_jit.c: -------------------------------------------------------------------------------- 1 | // test jit for vm 2 | #include 3 | #include "test_bpf_progs.h" 4 | #include "test_defs.h" 5 | #include "ebpf-vm.h" 6 | #include 7 | #include 8 | #include 9 | 10 | struct ebpf_inst; 11 | 12 | #define TEST_BPF_CODE bpf_add_mem_64_bit 13 | #define TEST_BPF_SIZE sizeof(bpf_add_mem_64_bit) 14 | 15 | typedef unsigned int (*kernel_fn)(const void *ctx, const struct ebpf_inst *insn); 16 | 17 | char *errmsg; 18 | struct mem { 19 | uint64_t val; 20 | }; 21 | 22 | int main() 23 | { 24 | struct mem m = { __LINE__ }; 25 | uint64_t res = 0; 26 | // using ubpf jit for x86_64 and arm64 27 | struct ebpf_vm *vm = ebpf_create("llvm"); 28 | 29 | ebpf_toggle_bounds_check(vm, false); 30 | 31 | // remove 0, in the end 32 | CHECK_EXIT(ebpf_load(vm, TEST_BPF_CODE, TEST_BPF_SIZE, &errmsg)); 33 | 34 | // EBPF_OP_CALL 35 | printf("code len: %zd\n", TEST_BPF_SIZE); 36 | 37 | int mem_len = 1024 * 1024; 38 | char *mem = malloc(mem_len); 39 | printf("Use JIT Mode\n"); 40 | 41 | ebpf_jit_fn fn = ebpf_compile(vm, &errmsg); 42 | if (fn == NULL) { 43 | fprintf(stderr, "Failed to compile: %s\n", errmsg); 44 | free(mem); 45 | return 1; 46 | } 47 | 48 | // res = ((kernel_fn)(fn))(NULL, context->vm->insnsi); 49 | res = fn(&m, sizeof(m)); 50 | printf("res = %" PRIu64 "\n", res); 51 | return 0; 52 | } -------------------------------------------------------------------------------- /example/standalone/main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | int bpf_main(void* ctx, uint64_t size); 6 | 7 | uint32_t ctl_array[2] = { 0, 0 }; 8 | uint64_t cntrs_array[2] = { 0, 0 }; 9 | 10 | void *_bpf_helper_ext_0001(uint64_t map_fd, void *key) 11 | { 12 | printf("bpf_map_lookup_elem %lu\n", map_fd); 13 | if (map_fd == 5) { 14 | return &ctl_array[*(uint32_t *)key]; 15 | } else if (map_fd == 6) { 16 | return &cntrs_array[*(uint32_t *)key]; 17 | } else { 18 | return NULL; 19 | } 20 | return 0; 21 | } 22 | 23 | void* __lddw_helper_map_val(uint64_t val) 24 | { 25 | printf("map_val %lu\n", val); 26 | if (val == 5) { 27 | return (void *)ctl_array; 28 | } else if (val == 6) { 29 | return (void *)cntrs_array; 30 | } else { 31 | return NULL; 32 | } 33 | } 34 | 35 | uint8_t bpf_mem[] = { 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88 }; 36 | 37 | int main() { 38 | printf("The value of cntrs_array[0] is %" PRIu64 "\n", cntrs_array[0]); 39 | printf("calling ebpf program...\n"); 40 | bpf_main(bpf_mem, sizeof(bpf_mem)); 41 | printf("The value of cntrs_array[0] is %" PRIu64 "\n", cntrs_array[0]); 42 | printf("calling ebpf program...\n"); 43 | bpf_main(bpf_mem, sizeof(bpf_mem)); 44 | printf("The value of cntrs_array[0] is %" PRIu64 "\n", cntrs_array[0]); 45 | return 0; 46 | } 47 | -------------------------------------------------------------------------------- /test/test_framework/test_disassembler.py: -------------------------------------------------------------------------------- 1 | import struct 2 | import difflib 3 | from nose.plugins.skip import Skip, SkipTest 4 | import ubpf.disassembler 5 | import testdata 6 | import pytest 7 | import os 8 | _test_data_dir = os.path.join(os.path.dirname(os.path.realpath(__file__)), "../test-cases") 9 | 10 | def check_datafile(filename): 11 | """ 12 | Verify that the result of disassembling the 'raw' section matches the 13 | 'asm' section. 14 | """ 15 | data = testdata.read(_test_data_dir, filename) 16 | if 'asm' not in data: 17 | raise SkipTest("no asm section in datafile") 18 | if 'raw' not in data: 19 | raise SkipTest("no raw section in datafile") 20 | 21 | binary = b''.join(struct.pack("=Q", x) for x in data['raw']) 22 | result = ubpf.disassembler.disassemble(binary) 23 | 24 | # TODO strip whitespace and comments from asm 25 | if result.strip() != data['asm'].strip(): 26 | diff = difflib.unified_diff(data['asm'].splitlines(), result.splitlines(), lineterm="") 27 | formatted = ''.join(' %s\n' % x for x in diff) 28 | raise AssertionError("Assembly differs:\n%s" % formatted) 29 | 30 | @pytest.mark.parametrize("filename", testdata.list_files(_test_data_dir)) 31 | def test_datafiles(filename): 32 | # This is now a regular test function that will be called once for each filename 33 | check_datafile(filename) 34 | -------------------------------------------------------------------------------- /.github/workflows/unit-test.yml: -------------------------------------------------------------------------------- 1 | name: Run unit tests of llvm-jit 2 | 3 | on: 4 | push: 5 | branches: "main" 6 | pull_request: 7 | branches: "main" 8 | jobs: 9 | build: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v2 13 | with: 14 | submodules: 'recursive' 15 | 16 | - name: Install Ninja and lcov (Ubuntu) 17 | run: sudo apt install -y ninja-build lcov llvm-15-dev libzstd-dev 18 | 19 | - name: build 20 | run: 21 | | 22 | LLVM_DIR=/usr/lib/llvm-15/cmake cmake -B build -DCMAKE_BUILD_TYPE=Debug -DBPFTIME_ENABLE_UNIT_TESTING=1 -DBPFTIME_ENABLE_CODE_COVERAGE=1 -G Ninja 23 | cmake --build build --target all -j 24 | 25 | - name: Run example 26 | run: | 27 | ./build/vm-llvm-example 28 | ./build/maps-example 29 | 30 | - name: Run tests 31 | run: | 32 | ./build/test/unit-test/llvm_jit_tests 33 | 34 | - name: upload coverage 35 | run: | 36 | lcov --capture --directory . --output-file coverage.info 37 | lcov --remove coverage.info '/usr/*' --output-file coverage.info # filter system-files 38 | lcov --list coverage.info # debug info 39 | 40 | - uses: codecov/codecov-action@v4 41 | with: 42 | fail_ci_if_error: true # optional (default = false) 43 | files: ./coverage.info # optional 44 | flags: unittests 45 | token: ${{ secrets.CODECOV_TOKEN }} # required 46 | verbose: true # optional (default = false) 47 | -------------------------------------------------------------------------------- /cli/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_executable( 2 | bpftime-vm-cli 3 | main.cpp 4 | ) 5 | 6 | set_target_properties(bpftime-vm-cli PROPERTIES OUTPUT_NAME "bpftime-vm") 7 | 8 | target_include_directories(bpftime-vm-cli 9 | PRIVATE 10 | ${SPDLOG_INCLUDE} 11 | ${argparse_INCLUDE} 12 | ${CMAKE_CURRENT_SOURCE_DIR}/../llvm-jit/src 13 | ${CMAKE_CURRENT_SOURCE_DIR}/../llvm-jit/include 14 | ${CMAKE_CURRENT_SOURCE_DIR}/../include 15 | ${LIBBPF_INCLUDE_DIRS} 16 | ) 17 | 18 | set(LIBBPF_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/libbpf_project) 19 | 20 | include(ExternalProject) 21 | ExternalProject_Add( 22 | libbpf_project 23 | GIT_REPOSITORY https://github.com/libbpf/libbpf.git 24 | GIT_TAG v1.2.0 # Replace with the version you need 25 | CONFIGURE_COMMAND "" 26 | BUILD_COMMAND make -C src -j 27 | BUILD_IN_SOURCE TRUE 28 | INSTALL_COMMAND "" 29 | BUILD_BYPRODUCTS ${LIBBPF_SOURCE_DIR}/src/libbpf.a 30 | SOURCE_DIR ${LIBBPF_SOURCE_DIR} 31 | ) 32 | 33 | set(LIBBPF_INCLUDE_DIRS ${LIBBPF_SOURCE_DIR}/src) 34 | set(LIBBPF_LIBRARIES ${LIBBPF_SOURCE_DIR}/src/libbpf.a) 35 | # Ensure libbpf is built before your target 36 | 37 | include_directories(${LIBBPF_INCLUDE_DIRS}) 38 | 39 | add_dependencies(bpftime-vm-cli spdlog::spdlog llvmbpf_vm libbpf_project) 40 | target_link_libraries(bpftime-vm-cli 41 | PRIVATE spdlog::spdlog llvmbpf_vm 42 | ${LIBBPF_LIBRARIES} 43 | elf 44 | z 45 | ) 46 | set_property(TARGET bpftime-vm-cli PROPERTY CXX_STANDARD 20) 47 | 48 | target_compile_definitions(bpftime-vm-cli PRIVATE _GNU_SOURCE) 49 | install(TARGETS bpftime-vm-cli CONFIGURATIONS Release Debug RelWithDebInfo DESTINATION ~/.bpftime) 50 | -------------------------------------------------------------------------------- /test/test_framework/test_assembler.py: -------------------------------------------------------------------------------- 1 | import struct 2 | from nose.plugins.skip import Skip, SkipTest 3 | import ubpf.assembler 4 | import ubpf.disassembler 5 | import testdata 6 | import pytest 7 | import os 8 | _test_data_dir = os.path.join(os.path.dirname(os.path.realpath(__file__)), "../test-cases") 9 | 10 | try: 11 | xrange 12 | except NameError: 13 | xrange = range 14 | 15 | # Just for assertion messages 16 | def try_disassemble(inst): 17 | data = struct.pack("=Q", inst) 18 | try: 19 | return ubpf.disassembler.disassemble(data).strip() 20 | except ValueError: 21 | return "" 22 | 23 | def check_datafile(filename): 24 | """ 25 | Verify that the result of assembling the 'asm' section matches the 26 | 'raw' section. 27 | """ 28 | data = testdata.read(_test_data_dir, filename) 29 | if 'asm' not in data: 30 | raise SkipTest("no asm section in datafile") 31 | if 'raw' not in data: 32 | raise SkipTest("no raw section in datafile") 33 | 34 | bin_result = ubpf.assembler.assemble(data['asm']) 35 | assert len(bin_result) % 8 == 0 36 | assert len(bin_result) / 8 == len(data['raw']) 37 | 38 | for i in xrange(0, len(bin_result), 8): 39 | j = int(i/8) 40 | inst, = struct.unpack_from("=Q", bin_result[i:i+8]) 41 | exp = data['raw'][j] 42 | if exp != inst: 43 | raise AssertionError("Expected instruction %d to be %#x (%s), but was %#x (%s)" % 44 | (j, exp, try_disassemble(exp), inst, try_disassemble(inst))) 45 | 46 | @pytest.mark.parametrize("filename", testdata.list_files(_test_data_dir)) 47 | def test_datafiles(filename): 48 | # This is now a regular test function that will be called once for each filename 49 | check_datafile(filename) 50 | 51 | -------------------------------------------------------------------------------- /test/test_framework/test_roundtrip.py: -------------------------------------------------------------------------------- 1 | import struct 2 | import difflib 3 | from nose.plugins.skip import Skip, SkipTest 4 | import ubpf.assembler 5 | import ubpf.disassembler 6 | import testdata 7 | import pytest 8 | import os 9 | _test_data_dir = os.path.join(os.path.dirname(os.path.realpath(__file__)), "../test-cases") 10 | 11 | # Just for assertion messages 12 | def try_disassemble(inst): 13 | data = struct.pack("=Q", inst) 14 | try: 15 | return ubpf.disassembler.disassemble(data).strip() 16 | except ValueError: 17 | return "" 18 | 19 | def check_datafile(filename): 20 | """ 21 | Verify that the reassembling the output of the disassembler produces 22 | the same binary, and that disassembling the output of the assembler 23 | produces the same text. 24 | """ 25 | data = testdata.read(_test_data_dir, filename) 26 | 27 | if 'asm' not in data: 28 | raise SkipTest("no asm section in datafile") 29 | 30 | assembled = ubpf.assembler.assemble(data['asm']) 31 | disassembled = ubpf.disassembler.disassemble(assembled) 32 | reassembled = ubpf.assembler.assemble(disassembled) 33 | disassembled2 = ubpf.disassembler.disassemble(reassembled) 34 | 35 | if disassembled != disassembled2: 36 | diff = difflib.unified_diff(disassembled.splitlines(), disassembled2.splitlines(), lineterm="") 37 | formatted = ''.join(' %s\n' % x for x in diff) 38 | raise AssertionError("Assembly differs:\n%s" % formatted) 39 | 40 | if assembled != reassembled: 41 | raise AssertionError("binary differs") 42 | 43 | @pytest.mark.parametrize("filename", testdata.list_files(_test_data_dir)) 44 | def test_datafiles(filename): 45 | # This is now a regular test function that will be called once for each filename 46 | check_datafile(filename) 47 | -------------------------------------------------------------------------------- /.github/workflows/test-aot-cli.yml: -------------------------------------------------------------------------------- 1 | name: Build and test AOT cli 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | concurrency: 9 | group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event_name }} 10 | cancel-in-progress: true 11 | jobs: 12 | build: 13 | strategy: 14 | matrix: 15 | container: 16 | - ubuntu-2204 17 | - fedora-39 18 | runs-on: ubuntu-latest 19 | container: 20 | image: "manjusakalza/bpftime-base-image:${{matrix.container}}" 21 | options: --privileged 22 | steps: 23 | - uses: actions/checkout@v2 24 | with: 25 | submodules: 'recursive' 26 | 27 | - name: Install Ninja (Ubuntu) 28 | if: ${{matrix.container == 'ubuntu-2204'}} 29 | run: 30 | apt install ninja-build 31 | 32 | - name: Install Ninja (Fedora) 33 | if: ${{matrix.container == 'fedora-39'}} 34 | run: 35 | dnf install -y ninja-build 36 | 37 | 38 | - name: Build and install everything 39 | run: | 40 | cmake -B build -DCMAKE_BUILD_TYPE=Debug -DBPFTIME_ENABLE_UNIT_TESTING=1 -DBUILD_LLVM_AOT_CLI=1 -G Ninja 41 | cmake --build build --target all -j 42 | - name: test emit llvm 43 | run: | 44 | ./build/cli/bpftime-vm build .github/assets/sum.bpf.o -emit-llvm 45 | 46 | - name: Do compilation & run 47 | run: | 48 | export PATH=$PATH:~/.bpftime 49 | ./build/cli/bpftime-vm build .github/assets/sum.bpf.o 50 | echo "AwAAAAEAAAACAAAAAwAAAA==" | base64 -d > test.bin 51 | program_output=$(./build/cli/bpftime-vm run test.o test.bin) 52 | echo $program_output 53 | if echo $program_output | grep "Return value: 6"; then 54 | echo "Successful!" 55 | exit 0 56 | else 57 | echo "Not found!" 58 | exit 1 59 | fi 60 | -------------------------------------------------------------------------------- /test/test-cases/subnet.data: -------------------------------------------------------------------------------- 1 | # Compiled by Clang 2 | # Check that the ipv4_dst is in 192.168.1.0/24 3 | -- c 4 | #include 5 | 6 | #define NETMASK 0xffffff00 7 | #define SUBNET 0xc0a80100 8 | 9 | struct eth_hdr { 10 | uint8_t eth_src[6]; 11 | uint8_t eth_dst[6]; 12 | uint16_t eth_type; 13 | }; 14 | 15 | struct vlan_hdr { 16 | uint16_t vlan; 17 | uint16_t eth_type; 18 | }; 19 | 20 | struct ipv4_hdr { 21 | uint8_t ver_ihl; 22 | uint8_t tos; 23 | uint16_t total_length; 24 | uint16_t id; 25 | uint16_t frag; 26 | uint8_t ttl; 27 | uint8_t proto; 28 | uint16_t csum; 29 | uint32_t src; 30 | uint32_t dst; 31 | }; 32 | 33 | uint64_t entry(void *mem) 34 | { 35 | struct eth_hdr *eth_hdr = (void *)mem; 36 | uint16_t eth_type; 37 | void *next = eth_hdr; 38 | 39 | if (eth_hdr->eth_type == __builtin_bswap16(0x8100)) { 40 | struct vlan_hdr *vlan_hdr = (void *)(eth_hdr + 1); 41 | eth_type = vlan_hdr->eth_type; 42 | next = vlan_hdr + 1; 43 | } else { 44 | eth_type = eth_hdr->eth_type; 45 | next = eth_hdr + 1; 46 | } 47 | 48 | if (eth_type == __builtin_bswap16(0x0800)) { 49 | struct ipv4_hdr *ipv4_hdr = next; 50 | if ((ipv4_hdr->dst & __builtin_bswap32(NETMASK)) == __builtin_bswap32(SUBNET)) { 51 | return 1; 52 | } 53 | } 54 | 55 | return 0; 56 | } 57 | -- asm 58 | mov r2, 0xe 59 | ldxh r3, [r1+12] 60 | jne r3, 0x81, +2 61 | mov r2, 0x12 62 | ldxh r3, [r1+16] 63 | and r3, 0xffff 64 | jne r3, 0x8, +5 65 | add r1, r2 66 | mov r0, 0x1 67 | ldxw r1, [r1+16] 68 | and r1, 0xffffff 69 | jeq r1, 0x1a8c0, +1 70 | mov r0, 0x0 71 | exit 72 | -- mem 73 | 00 00 c0 9f a0 97 00 a0 74 | cc 3b bf fa 08 00 45 10 75 | 00 3c 46 3c 40 00 40 06 76 | 73 1c c0 a8 01 02 c0 a8 77 | 01 01 06 0e 00 17 99 c5 78 | a0 ec 00 00 00 00 a0 02 79 | 7d 78 e0 a3 00 00 02 04 80 | 05 b4 04 02 08 0a 00 9c 81 | 27 24 00 00 00 00 01 03 82 | 03 00 83 | -- result 84 | 0x1 85 | -------------------------------------------------------------------------------- /.github/workflows/test-vm.yml: -------------------------------------------------------------------------------- 1 | name: Build and Test VM 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | concurrency: 9 | group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event_name }} 10 | cancel-in-progress: true 11 | jobs: 12 | build: 13 | runs-on: ubuntu-latest 14 | strategy: 15 | matrix: 16 | container: 17 | - ubuntu-2204 18 | - fedora-39 19 | container: 20 | image: "manjusakalza/bpftime-base-image:${{matrix.container}}" 21 | options: --privileged 22 | steps: 23 | 24 | - name: cache dependencies 25 | uses: actions/cache@v4 26 | id: cache 27 | with: 28 | path: ${{ github.workspace }}/${{ env.INSTALL_LOCATION }} 29 | key: ${{ runner.os }}-dependencies 30 | 31 | - uses: actions/checkout@v2 32 | with: 33 | submodules: 'recursive' 34 | 35 | - name: Install Ninja (Ubuntu) 36 | if: ${{matrix.container == 'ubuntu-2204'}} 37 | run: 38 | apt install ninja-build 39 | 40 | - name: Install Ninja (Fedora) 41 | if: ${{matrix.container == 'fedora-39'}} 42 | run: 43 | dnf install -y ninja-build 44 | 45 | 46 | - uses: actions/setup-python@v4 47 | if: startsWith(matrix.container,'ubuntu') 48 | with: 49 | python-version: '3.8' 50 | 51 | - name: build debug 52 | run: 53 | | 54 | cmake -B build -DCMAKE_BUILD_TYPE=Debug -DBPFTIME_ENABLE_UNIT_TESTING=1 -G Ninja 55 | cmake --build build --target all -j 56 | 57 | - name: run testsuit x86 58 | shell: bash 59 | run: | 60 | python3.8 -m venv ./test 61 | source test/bin/activate 62 | pip install -r test/requirements.txt 63 | # make build # or build-arm32 build-arm64 64 | pytest -v -s test/test_framework 65 | 66 | - name: build llvm JIT/AOT release as a standalone library 67 | run: | 68 | cmake -B build -DCMAKE_BUILD_TYPE=Release -G Ninja &&\ 69 | cmake --build build --target all -j 70 | -------------------------------------------------------------------------------- /example/xdp-counter.json: -------------------------------------------------------------------------------- 1 | { 2 | "5": { 3 | "attr": { 4 | "btf_id": 2, 5 | "btf_key_type_id": 6, 6 | "btf_value_type_id": 6, 7 | "btf_vmlinux_value_type_id": 0, 8 | "flags": 0, 9 | "ifindex": 0, 10 | "kernel_bpf_map_id": 0, 11 | "key_size": 4, 12 | "map_extra": 0, 13 | "map_type": 2, 14 | "max_entries": 2, 15 | "value_size": 4 16 | }, 17 | "name": "ctl_array", 18 | "type": "bpf_map_handler" 19 | }, 20 | "6": { 21 | "attr": { 22 | "btf_id": 2, 23 | "btf_key_type_id": 0, 24 | "btf_value_type_id": 22, 25 | "btf_vmlinux_value_type_id": 0, 26 | "flags": 1024, 27 | "ifindex": 0, 28 | "kernel_bpf_map_id": 0, 29 | "key_size": 4, 30 | "map_extra": 0, 31 | "map_type": 2, 32 | "max_entries": 1, 33 | "value_size": 4096 34 | }, 35 | "name": "xdp_coun.bss", 36 | "type": "bpf_map_handler" 37 | }, 38 | "7": { 39 | "attr": { 40 | "cnt": 37, 41 | "insns": "79160000000000007917080000000000b701000000000000631afcff00000000bfa200000000000007020000fcffffff181100000500000000000000000000008500000001000000bf01000000000000b70000000200000015011800000000006111000000000000550116000000000018210000060000000000000000000000791200000000000007020000010000007b21000000000000b700000001000000bf61000000000000070100000e0000002d710d0000000000696100000000000069620600000000006b26000000000000696208000000000069630200000000006b360800000000006b2602000000000069620a000000000069630400000000006b360a00000000006b160600000000006b26040000000000b7000000030000009500000000000000", 42 | "type": -317813312 43 | }, 44 | "name": "xdp_pass", 45 | "type": "bpf_prog_handler" 46 | }, 47 | "8": { 48 | "attr": { 49 | "prog_fd": 7, 50 | "target_fd": 5 51 | }, 52 | "type": "bpf_link_handler" 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /CITATION.cff: -------------------------------------------------------------------------------- 1 | # This CITATION.cff file was generated with cffinit. 2 | # Visit https://bit.ly/cffinit to generate yours today! 3 | 4 | cff-version: 1.2.0 5 | title: bpftime 6 | message: >- 7 | If you use this software, please cite it using the 8 | metadata from this file. 9 | type: software 10 | authors: 11 | - given-names: Yusheng 12 | family-names: Zheng 13 | email: yunwei356@gmail.com 14 | affiliation: UC Santa Cruz 15 | - given-names: Tong 16 | family-names: Yu 17 | affiliation: eunomia-bpf Community 18 | - given-names: Yiwei 19 | family-names: Yang 20 | affiliation: UC Santa Cruz 21 | - given-names: Yanpeng 22 | family-names: Hu 23 | affiliation: ShanghaiTech University 24 | - given-names: Xiaozheng 25 | family-names: Lai 26 | affiliation: South China University of Technology 27 | - given-names: Dan 28 | family-names: Williams 29 | affiliation: Virginia Tech 30 | - given-names: Andi 31 | family-names: Quinn 32 | affiliation: UC Santa Cruz 33 | identifiers: 34 | - type: url 35 | value: 'https://www.usenix.org/conference/osdi25/presentation/zheng-yusheng' 36 | description: >- 37 | Extending Applications Safely and Efficiently 38 | repository-code: 'https://github.com/eunomia-bpf/bpftime' 39 | url: 'https://eunomia.dev/bpftime/' 40 | abstract: >- 41 | This paper introduces the Extension Interface Model (EIM) and 42 | bpftime, a framework for safer and more efficient application 43 | extension. EIM treats extension features as resources, allowing 44 | managers to specify exact resource needs. bpftime uses eBPF-style 45 | verification, hardware isolation, and dynamic binary rewriting 46 | to achieve efficiency and compatibility with the existing eBPF 47 | ecosystem. The system demonstrates the approach across 6 use 48 | cases involving security, performance monitoring, and configuration 49 | exploration. By operating in userspace, bpftime achieves significant 50 | performance improvements while maintaining safety guarantees and 51 | compatibility with existing eBPF toolchains. 52 | keywords: 53 | - userspace 54 | - plugin 55 | - eBPF 56 | license: MIT -------------------------------------------------------------------------------- /test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.15) 2 | 3 | # 4 | # Project details 5 | # 6 | project( 7 | ${CMAKE_PROJECT_NAME}Tests 8 | LANGUAGES C 9 | ) 10 | 11 | add_subdirectory(bpf_conformance_runner) 12 | add_subdirectory(unit-test) 13 | 14 | message(DEBUG "Adding tests under ${CMAKE_PROJECT_NAME}Tests...") 15 | 16 | foreach(file ${test_sources}) 17 | string(REGEX REPLACE "(.*/)([a-zA-Z0-9_ ]+)(\.c)" "\\2" test_name ${file}) 18 | add_executable(${test_name}_Tests ${file}) 19 | target_link_libraries( 20 | ${test_name}_Tests PRIVATE 21 | -lm) 22 | 23 | if(NOT BPFTIME_ENABLE_ASAN) 24 | # set the -static flag for static linking 25 | # set_target_properties(${test_name}_Tests PROPERTIES LINK_FLAGS "-static") 26 | # need on qemu-user 27 | endif() 28 | 29 | add_dependencies(${test_name}_Tests bpftime_vm) 30 | # 31 | # Set the compiler standard 32 | # 33 | 34 | # target_compile_features(${test_name}_Tests PUBLIC cxx_std_17) 35 | target_include_directories(${test_name}_Tests 36 | PRIVATE 37 | ${CMAKE_CURRENT_SOURCE_DIR}/../include 38 | ${CMAKE_CURRENT_SOURCE_DIR}/include 39 | ) 40 | 41 | # 42 | # Setup code coverage if enabled 43 | # 44 | if(BPFTIME_ENABLE_CODE_COVERAGE) 45 | message(DEBUG "Code coverage is enabled and provided with GCC.") 46 | endif() 47 | 48 | # 49 | # Load the desired unit testing framework 50 | # 51 | # Currently supported: GoogleTest (and GoogleMock), Catch2. 52 | if(${CMAKE_PROJECT_NAME}_USE_CATCH2) 53 | find_package(Catch2 REQUIRED) 54 | target_link_libraries( 55 | ${test_name}_Tests 56 | PUBLIC 57 | Catch2::Catch2 58 | bpftime_vm 59 | ) 60 | else() 61 | target_link_libraries( 62 | ${test_name}_Tests 63 | PUBLIC 64 | bpftime_vm 65 | ) 66 | # message("Unknown testing library ${test_name}_Tests. Please setup your desired unit testing library by using `target_link_libraries`.") 67 | endif() 68 | 69 | # 70 | # Add the unit tests 71 | # 72 | add_test( 73 | NAME 74 | ${test_name} 75 | COMMAND 76 | ${test_name}_Tests 77 | ) 78 | endforeach() 79 | 80 | message(DEBUG "Finished adding unit tests for ${CMAKE_PROJECT_NAME}.") 81 | -------------------------------------------------------------------------------- /test/test_framework/expand-testcase.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | Expand testcase into individual files 4 | """ 5 | import os 6 | import sys 7 | import struct 8 | import testdata 9 | import argparse 10 | 11 | ROOT_DIR = os.path.join(os.path.dirname(os.path.realpath(__file__)), "..") 12 | if os.path.exists(os.path.join(ROOT_DIR, "ubpf")): 13 | # Running from source tree 14 | sys.path.insert(0, ROOT_DIR) 15 | 16 | import ubpf.assembler 17 | import ubpf.disassembler 18 | 19 | def main(): 20 | parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) 21 | parser.add_argument('name') 22 | parser.add_argument('path') 23 | args = parser.parse_args() 24 | 25 | data = testdata.read(_test_data_dir, args.name + '.data') 26 | assert data 27 | 28 | if not os.path.isdir(args.path): 29 | os.makedirs(args.path) 30 | 31 | def writefile(name, contents): 32 | open("%s/%s" % (args.path, name), "wb").write(contents) 33 | 34 | if 'mem' in data: 35 | writefile('mem', data['mem']) 36 | 37 | # Probably a packet, so write out a pcap file 38 | writefile('pcap', 39 | struct.pack('=IHHIIIIIIII', 40 | 0xa1b2c3d4, # magic 41 | 2, 4, # version 42 | 0, # time zone offset 43 | 0, # time stamp accuracy 44 | 65535, # snapshot length 45 | 1, # link layer type 46 | 0, 0, # timestamp 47 | len(data['mem']), # length 48 | len(data['mem'])) # length 49 | + data['mem']) 50 | 51 | if 'raw' in data: 52 | code = b''.join(struct.pack("=Q", x) for x in data['raw']) 53 | elif 'asm' in data: 54 | code = ubpf.assembler.assemble(data['asm']) 55 | else: 56 | code = None 57 | 58 | if code: 59 | writefile('code', code) 60 | 61 | if 'asm' in data: 62 | writefile('asm', data['asm'].encode()) 63 | elif code: 64 | writefile('asm', ubpf.disassembler.disassemble(code)) 65 | 66 | if 'pyelf' in data: 67 | from test_elf import generate_elf 68 | elf = generate_elf(data['pyelf']) 69 | writefile('elf', elf) 70 | 71 | if __name__ == "__main__": 72 | main() 73 | -------------------------------------------------------------------------------- /test/test_framework/ubpf/asm_parser.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from __future__ import print_function 3 | from parcon import * 4 | from collections import namedtuple 5 | 6 | hexchars = '0123456789abcdefABCDEF' 7 | 8 | Reg = namedtuple("Reg", ["num"]) 9 | Imm = namedtuple("Imm", ["value"]) 10 | MemRef = namedtuple("MemRef", ["reg", "offset"]) 11 | 12 | def keywords(vs): 13 | return First(*[Keyword(SignificantLiteral(v)) for v in vs]) 14 | 15 | hexnum = SignificantLiteral('0x') + +CharIn(hexchars) 16 | decnum = +Digit() 17 | offset = (CharIn("+-") + Exact(hexnum | decnum))[flatten]["".join][lambda x: int(x, 0)] 18 | imm = (-CharIn("+-") + Exact(hexnum | decnum))[flatten]["".join][lambda x: int(x, 0)][Imm] 19 | 20 | reg = Literal('r') + integer[int][Reg] 21 | memref = (Literal('[') + reg + Optional(offset, 0) + Literal(']'))[lambda x: MemRef(*x)] 22 | 23 | unary_alu_ops = ['neg', 'neg32', 'le16', 'le32', 'le64', 'be16', 'be32', 'be64'] 24 | binary_alu_ops = ['add', 'sub', 'mul', 'div', 'or', 'and', 'lsh', 'rsh', 25 | 'mod', 'xor', 'mov', 'arsh'] 26 | binary_alu_ops.extend([x + '32' for x in binary_alu_ops]) 27 | 28 | alu_instruction = \ 29 | (keywords(unary_alu_ops) + reg) | \ 30 | (keywords(binary_alu_ops) + reg + "," + (reg | imm)) 31 | 32 | mem_sizes = ['w', 'h', 'b', 'dw'] 33 | mem_store_reg_ops = ['stx' + s for s in mem_sizes] 34 | mem_store_imm_ops = ['st' + s for s in mem_sizes] 35 | mem_load_ops = ['ldx' + s for s in mem_sizes] 36 | 37 | mem_instruction = \ 38 | (keywords(mem_store_reg_ops) + memref + "," + reg) | \ 39 | (keywords(mem_store_imm_ops) + memref + "," + imm) | \ 40 | (keywords(mem_load_ops) + reg + "," + memref) | \ 41 | (keywords(["lddw"]) + reg + "," + imm) 42 | 43 | jmp_cmp_ops = ['jeq', 'jgt', 'jge', 'jlt', 'jle', 'jset', 'jne', 'jsgt', 'jsge', 'jslt', 'jsle'] 44 | jmp_instruction = \ 45 | (keywords(jmp_cmp_ops) + reg + "," + (reg | imm) + "," + offset) | \ 46 | (keywords(['ja']) + offset) | \ 47 | (keywords(['call']) + imm) | \ 48 | (keywords(['exit'])[lambda x: (x, )]) 49 | 50 | instruction = alu_instruction | mem_instruction | jmp_instruction 51 | 52 | start = ZeroOrMore(instruction + Optional(Literal(';'))) + End() 53 | 54 | def parse(source): 55 | return start.parse_string(source) 56 | 57 | if __name__ == "__main__": 58 | import argparse 59 | parser = argparse.ArgumentParser(description="Assembly parser", formatter_class=argparse.RawDescriptionHelpFormatter) 60 | parser.add_argument('file', type=argparse.FileType('r'), default='-') 61 | args = parser.parse_args() 62 | result = parse(args.file.read()) 63 | for inst in result: 64 | print(repr(inst)) 65 | -------------------------------------------------------------------------------- /example/inline/libmap.ll: -------------------------------------------------------------------------------- 1 | ; ModuleID = 'libmap.c' 2 | source_filename = "libmap.c" 3 | target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128" 4 | target triple = "x86_64-pc-linux-gnu" 5 | 6 | @ctl_array = dso_local global [2 x i32] zeroinitializer, align 4 7 | @cntrs_array = dso_local global [2 x i64] zeroinitializer, align 16 8 | 9 | ; Function Attrs: mustprogress nofree norecurse nosync nounwind willreturn memory(argmem: read) uwtable 10 | define dso_local ptr @_bpf_helper_ext_0001(i64 noundef %0, ptr nocapture noundef readonly %1) local_unnamed_addr #0 { 11 | switch i64 %0, label %11 [ 12 | i64 5, label %3 13 | i64 6, label %7 14 | ] 15 | 16 | 3: ; preds = %2 17 | %4 = load i32, ptr %1, align 4, !tbaa !5 18 | %5 = zext i32 %4 to i64 19 | %6 = getelementptr inbounds [2 x i32], ptr @ctl_array, i64 0, i64 %5 20 | br label %11 21 | 22 | 7: ; preds = %2 23 | %8 = load i32, ptr %1, align 4, !tbaa !5 24 | %9 = zext i32 %8 to i64 25 | %10 = getelementptr inbounds [2 x i64], ptr @cntrs_array, i64 0, i64 %9 26 | br label %11 27 | 28 | 11: ; preds = %2, %7, %3 29 | %12 = phi ptr [ %6, %3 ], [ %10, %7 ], [ null, %2 ] 30 | ret ptr %12 31 | } 32 | 33 | ; Function Attrs: mustprogress nofree norecurse nosync nounwind willreturn memory(none) uwtable 34 | define dso_local noundef ptr @__lddw_helper_map_val(i64 noundef %0) local_unnamed_addr #1 { 35 | %2 = icmp eq i64 %0, 6 36 | %3 = select i1 %2, ptr @cntrs_array, ptr null 37 | %4 = icmp eq i64 %0, 5 38 | %5 = select i1 %4, ptr @ctl_array, ptr %3 39 | ret ptr %5 40 | } 41 | 42 | attributes #0 = { mustprogress nofree norecurse nosync nounwind willreturn memory(argmem: read) uwtable "min-legal-vector-width"="0" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+cmov,+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu"="generic" } 43 | attributes #1 = { mustprogress nofree norecurse nosync nounwind willreturn memory(none) uwtable "min-legal-vector-width"="0" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+cmov,+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu"="generic" } 44 | 45 | !llvm.module.flags = !{!0, !1, !2, !3} 46 | !llvm.ident = !{!4} 47 | 48 | !0 = !{i32 1, !"wchar_size", i32 4} 49 | !1 = !{i32 8, !"PIC Level", i32 2} 50 | !2 = !{i32 7, !"PIE Level", i32 2} 51 | !3 = !{i32 7, !"uwtable", i32 2} 52 | !4 = !{!"Ubuntu clang version 18.1.3 (1ubuntu1)"} 53 | !5 = !{!6, !6, i64 0} 54 | !6 = !{!"int", !7, i64 0} 55 | !7 = !{!"omnipotent char", !8, i64 0} 56 | !8 = !{!"Simple C/C++ TBAA"} 57 | -------------------------------------------------------------------------------- /src/llvm_jit_context.hpp: -------------------------------------------------------------------------------- 1 | #ifndef _LLVM_BPF_JIT_CONTEXT_H 2 | #define _LLVM_BPF_JIT_CONTEXT_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | namespace bpftime 19 | { 20 | 21 | class llvmbpf_vm; 22 | 23 | const static char *LDDW_HELPER_MAP_BY_FD = "__lddw_helper_map_by_fd"; 24 | const static char *LDDW_HELPER_MAP_BY_IDX = "__lddw_helper_map_by_idx"; 25 | const static char *LDDW_HELPER_MAP_VAL = "__lddw_helper_map_val"; 26 | const static char *LDDW_HELPER_VAR_ADDR = "__lddw_helper_var_addr"; 27 | const static char *LDDW_HELPER_CODE_ADDR = "__lddw_helper_code_addr"; 28 | 29 | #define IS_ALIGNED(x, a) (((uintptr_t)(x) & ((a)-1)) == 0) 30 | 31 | #ifndef EBPF_STACK_SIZE 32 | // Compatible to C headers 33 | #define EBPF_STACK_SIZE 512 34 | #endif 35 | 36 | class llvm_bpf_jit_context { 37 | llvmbpf_vm &vm; 38 | 39 | std::unique_ptr compiling; 40 | llvm::Expected 41 | 42 | generateModule(const std::vector &extFuncNames, 43 | const std::vector &lddwHelpers, 44 | bool patch_map_val_at_compile_time, 45 | bool main_func_with_arguments = true, 46 | const std::string &func_name = "bpf_main", 47 | bool is_gpu = false); 48 | std::vector 49 | do_aot_compile(const std::vector &extFuncNames, 50 | const std::vector &lddwHelpers, 51 | bool print_ir); 52 | // (JIT, extFuncs, definedLddwSymbols) 53 | std::tuple, std::vector, 54 | std::vector> 55 | create_and_initialize_lljit_instance(); 56 | 57 | public: 58 | std::optional> jit; 59 | llvm::Error do_jit_compile(); 60 | llvm_bpf_jit_context(llvmbpf_vm &vm); 61 | virtual ~llvm_bpf_jit_context(); 62 | precompiled_ebpf_function get_entry_address(); 63 | std::vector do_aot_compile(bool print_ir = false); 64 | llvm::Error load_aot_object(const std::vector &buf); 65 | std::optional 66 | generate_ptx(bool main_with_arguments = true, 67 | const std::string &func_name = "bpf_main", 68 | const char *target_cpu = "sm_60"); 69 | std::optional> 70 | generate_spirv(bool main_with_arguments = true, 71 | const std::string &func_name = "bpf_main", 72 | const char *target_env = ""); 73 | }; 74 | 75 | } // namespace bpftime 76 | 77 | #endif 78 | -------------------------------------------------------------------------------- /example/ptx/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_executable(ptx_test ptx_test.cpp) 2 | 3 | # spdlog is provided by parent CMakeLists.txt via FetchContent 4 | if(NOT TARGET spdlog::spdlog) 5 | find_package(spdlog REQUIRED) 6 | endif() 7 | 8 | option(LLVMBPF_CUDA_PATH "Path where CUDA installs") 9 | message(STATUS "LLVMBPF_CUDA_PATH=${LLVMBPF_CUDA_PATH}") 10 | if(NOT LLVMBPF_CUDA_PATH) 11 | message(FATAL_ERROR "Please set LLVMBPF_CUDA_PATH if you want to build ptx_test, otherwise set LLVMBPF_ENABLE_PTX to NO") 12 | endif() 13 | set(LLVM_LINK_COMPONENTS 14 | ${LLVM_TARGETS_TO_BUILD} 15 | ) 16 | target_link_directories(ptx_test PRIVATE ${LLVMBPF_CUDA_PATH}/lib64 ${LLVMBPF_CUDA_PATH}/lib64/stubs) 17 | 18 | # Detect CUDA version to determine if nvptxcompiler_static is available 19 | if(EXISTS "${LLVMBPF_CUDA_PATH}/version.json") 20 | file(READ "${LLVMBPF_CUDA_PATH}/version.json" CUDA_VERSION_JSON) 21 | string(REGEX MATCH "\"cuda\"[^{]*\\{[^}]*\"version\"[^\"]*\"([0-9]+)\\.([0-9]+)" _ "${CUDA_VERSION_JSON}") 22 | set(LLVMBPF_CUDA_VERSION_MAJOR ${CMAKE_MATCH_1}) 23 | elseif(EXISTS "${LLVMBPF_CUDA_PATH}/version.txt") 24 | file(READ "${LLVMBPF_CUDA_PATH}/version.txt" CUDA_VERSION_TXT) 25 | string(REGEX MATCH "CUDA Version ([0-9]+)\\.([0-9]+)" _ "${CUDA_VERSION_TXT}") 26 | set(LLVMBPF_CUDA_VERSION_MAJOR ${CMAKE_MATCH_1}) 27 | else() 28 | string(REGEX MATCH "cuda-([0-9]+)\\.([0-9]+)" _ "${LLVMBPF_CUDA_PATH}") 29 | set(LLVMBPF_CUDA_VERSION_MAJOR ${CMAKE_MATCH_1}) 30 | endif() 31 | 32 | # nvptxcompiler_static is only available in CUDA 12.x and earlier 33 | if(LLVMBPF_CUDA_VERSION_MAJOR LESS 13) 34 | find_library(NVPTXCOMPILER_LIB 35 | NAMES 36 | nvptxcompiler_static 37 | PATHS 38 | ${LLVMBPF_CUDA_PATH}/lib64 39 | ${LLVMBPF_CUDA_PATH}/lib64/stubs 40 | NO_DEFAULT_PATH) 41 | if(NOT NVPTXCOMPILER_LIB) 42 | message(FATAL_ERROR "nvptxcompiler_static library not found in ${LLVMBPF_CUDA_PATH}/lib64 or ${LLVMBPF_CUDA_PATH}/lib64/stubs") 43 | endif() 44 | message(STATUS "CUDA ${LLVMBPF_CUDA_VERSION_MAJOR}.x: Linking with nvptxcompiler_static") 45 | target_link_libraries(ptx_test 46 | PRIVATE 47 | ${LLVM_LIBS} 48 | llvmbpf_vm 49 | cuda 50 | ${NVPTXCOMPILER_LIB} 51 | spdlog::spdlog) 52 | else() 53 | message(STATUS "CUDA ${LLVMBPF_CUDA_VERSION_MAJOR}.x: Skipping nvptxcompiler_static (not available)") 54 | target_link_libraries(ptx_test 55 | PRIVATE 56 | ${LLVM_LIBS} 57 | llvmbpf_vm 58 | cuda 59 | spdlog::spdlog) 60 | endif() 61 | 62 | add_dependencies(ptx_test llvmbpf_vm spdlog::spdlog) 63 | target_include_directories(ptx_test PRIVATE ${LLVM_INCLUDE_DIRS} include src ${LLVMBPF_CUDA_PATH}/include ${SPDLOG_INCLUDES}) 64 | -------------------------------------------------------------------------------- /test/test_framework/testdata.py: -------------------------------------------------------------------------------- 1 | import os 2 | import re 3 | 4 | def list_files(test_data_dir): 5 | """ 6 | Return a list of data files under tests/data 7 | 8 | These strings are suitable to be passed to read(). 9 | """ 10 | 11 | result = [] 12 | for dirname, dirnames, filenames in os.walk(test_data_dir): 13 | dirname = (os.path.relpath(dirname, test_data_dir) + '/').replace('./', '') 14 | for filename in filenames: 15 | if filename.endswith('.data') and not filename.startswith('.'): 16 | result.append(dirname + filename) 17 | return sorted(result) 18 | 19 | def read(_test_data_dir, name): 20 | """ 21 | Read, parse, and return a test data file 22 | 23 | @param name Filename relative to the tests/data directory 24 | @returns A hash from section to the string contents 25 | """ 26 | 27 | section_lines = {} 28 | cur_section = None 29 | 30 | with open(os.path.join(_test_data_dir, name)) as f: 31 | for line in f: 32 | line = line.rstrip().partition('#')[0].rstrip() 33 | if line == '': 34 | continue 35 | elif line.startswith('--'): 36 | cur_section = line[2:].strip() 37 | if cur_section in section_lines: 38 | raise Exception("section %s already exists in the test data file") 39 | section_lines[cur_section] = [] 40 | elif cur_section: 41 | section_lines[cur_section].append(line) 42 | data = { section: '\n'.join(lines) for (section, lines) in list(section_lines.items()) } 43 | 44 | 45 | # Resolve links 46 | for k in list(data): 47 | if '@' in k: 48 | del data[k] 49 | section, path = k.split('@') 50 | section = section.strip() 51 | path = path.strip() 52 | fullpath = os.path.join(_test_data_dir, os.path.dirname(name), path) 53 | with open(fullpath) as f: 54 | data[section] = f.read() 55 | 56 | # Special case: convert 'raw' section into binary 57 | # Each line is parsed as an integer representing an instruction. 58 | if 'raw' in data: 59 | insts = [] 60 | for line in data['raw'].splitlines(): 61 | num, _, _ = line.partition("#") 62 | insts.append(int(num, 0)) 63 | data['raw'] = insts 64 | # 65 | # Special case: convert 'mem' section into binary 66 | # The string '00 11\n22 33' results in "\x00\x11\x22\x33" 67 | # Ignores hexdump prefix ending with a colon. 68 | if 'mem' in data: 69 | hex_strs = [] 70 | for line in data['mem'].splitlines(): 71 | if ':' in line: 72 | line = line[(line.rindex(':')+1):] 73 | hex_strs.extend(re.findall(r"[0-9A-Fa-f]{2}", line)) 74 | data['mem'] = bytes(bytearray([(int(x, 16)) for x in hex_strs])) 75 | 76 | return data 77 | -------------------------------------------------------------------------------- /.github/workflows/test-spirv-opencl.yml: -------------------------------------------------------------------------------- 1 | name: Build and Test SPIR-V with OpenCL 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | concurrency: 10 | group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event_name }} 11 | cancel-in-progress: true 12 | 13 | jobs: 14 | test-spirv: 15 | runs-on: ubuntu-24.04 16 | 17 | steps: 18 | - uses: actions/checkout@v3 19 | with: 20 | submodules: 'recursive' 21 | 22 | - name: Install dependencies 23 | run: | 24 | sudo apt-get update 25 | sudo apt-get install -y \ 26 | llvm-20-dev \ 27 | libzstd-dev \ 28 | ninja-build \ 29 | opencl-headers \ 30 | ocl-icd-opencl-dev \ 31 | spirv-tools \ 32 | lcov 33 | 34 | - name: Configure and build 35 | run: | 36 | env LLVM_DIR=/usr/lib/llvm-20/cmake \ 37 | cmake -B build -DCMAKE_BUILD_TYPE=Debug -DLLVMBPF_ENABLE_SPIRV=ON -DBPFTIME_ENABLE_UNIT_TESTING=1 -DBPFTIME_ENABLE_CODE_COVERAGE=1 -G Ninja 38 | cmake --build build --target all -j$(nproc) 39 | 40 | - name: Test JIT compilation (validates eBPF logic) 41 | run: | 42 | echo "Running JIT examples to validate eBPF program logic..." 43 | ./build/vm-llvm-example 44 | ./build/maps-example 45 | 46 | - name: Test SPIR-V generation 47 | run: | 48 | echo "Generating SPIR-V binary..." 49 | 50 | # Run the test - it will generate SPIR-V but may fail on OpenCL execution 51 | # PoCL's cpu-minimal doesn't support SPIR-V IL (CL_INVALID_BINARY -30) 52 | # This is expected - only real GPU drivers support SPIR-V properly 53 | ./build/example/spirv/spirv_opencl_test || true 54 | 55 | # Verify SPIR-V binary was generated 56 | if [ ! -f bpf_program.spv ]; then 57 | echo "❌ Error: SPIR-V binary not generated" 58 | exit 1 59 | fi 60 | 61 | echo "✅ SPIR-V binary generated successfully" 62 | ls -lh bpf_program.spv 63 | 64 | - name: Validate SPIR-V binary 65 | run: | 66 | echo "Validating SPIR-V binary structure with spirv-val..." 67 | spirv-val bpf_program.spv 68 | echo "✅ SPIR-V binary is valid" 69 | 70 | echo "" 71 | echo "SPIR-V disassembly (first 40 lines):" 72 | spirv-dis bpf_program.spv -o bpf_program.spvasm 73 | head -40 bpf_program.spvasm 74 | 75 | echo "" 76 | echo "✅ SPIR-V compilation test passed" 77 | 78 | - name: Upload coverage 79 | run: | 80 | lcov --capture --directory . --output-file coverage.info 81 | lcov --remove coverage.info '/usr/*' --output-file coverage.info # filter system-files 82 | lcov --list coverage.info # debug info 83 | 84 | - uses: codecov/codecov-action@v4 85 | with: 86 | fail_ci_if_error: true # optional (default = false) 87 | files: ./coverage.info # optional 88 | flags: spirv_opencl 89 | token: ${{ secrets.CODECOV_TOKEN }} # required 90 | verbose: true # optional (default = false) 91 | -------------------------------------------------------------------------------- /test/test_framework/test_vm.py: -------------------------------------------------------------------------------- 1 | import os 2 | import tempfile 3 | import struct 4 | import re 5 | from subprocess import Popen, PIPE 6 | from nose.plugins.skip import Skip, SkipTest 7 | import ubpf.assembler 8 | import testdata 9 | import pytest 10 | import os 11 | _test_data_dir = os.path.join(os.path.dirname(os.path.realpath(__file__)), "../test-cases") 12 | 13 | VM = os.path.join(os.path.dirname(os.path.realpath(__file__)), "../..", "build/test/test_Tests") 14 | 15 | def check_datafile(filename): 16 | """ 17 | Given assembly source code and an expected result, run the eBPF program and 18 | verify that the result matches. 19 | """ 20 | data = testdata.read(_test_data_dir, filename) 21 | if 'asm' not in data and 'raw' not in data: 22 | raise SkipTest("no asm or raw section in datafile") 23 | if 'result' not in data and 'error' not in data and 'error pattern' not in data: 24 | raise SkipTest("no result or error section in datafile") 25 | if not os.path.exists(VM): 26 | raise SkipTest("VM not found: ", VM) 27 | 28 | if 'raw' in data: 29 | code = b''.join(struct.pack("=Q", x) for x in data['raw']) 30 | else: 31 | code = ubpf.assembler.assemble(data['asm']) 32 | 33 | memfile = None 34 | 35 | cmd = [VM] 36 | if 'mem' in data: 37 | memfile = tempfile.NamedTemporaryFile() 38 | memfile.write(data['mem']) 39 | memfile.flush() 40 | cmd.extend(['-m', memfile.name]) 41 | if 'reload' in data: 42 | cmd.extend(['-R']) 43 | if 'unload' in data: 44 | cmd.extend(['-U']) 45 | 46 | cmd.append('-') 47 | 48 | # print(cmd) 49 | vm = Popen(cmd, stdin=PIPE, stdout=PIPE, stderr=PIPE) 50 | 51 | stdout, stderr = vm.communicate(code) 52 | stdout = stdout.decode("utf-8") 53 | stderr = stderr.decode("utf-8") 54 | stderr = stderr.strip() 55 | 56 | if memfile: 57 | memfile.close() 58 | 59 | if 'error' in data: 60 | if data['error'] != stderr: 61 | raise AssertionError("Expected error %r, got %r" % (data['error'], stderr)) 62 | elif 'error pattern' in data: 63 | if not re.search(data['error pattern'], stderr): 64 | raise AssertionError("Expected error matching %r, got %r" % (data['error pattern'], stderr)) 65 | else: 66 | if stderr: 67 | raise AssertionError("Unexpected error %r" % stderr) 68 | 69 | if 'result' in data: 70 | if vm.returncode != 0: 71 | raise AssertionError("VM exited with status %d, stderr=%r" % (vm.returncode, stderr)) 72 | expected = int(data['result'], 0) 73 | result = int(stdout, 0) 74 | if expected != result: 75 | raise AssertionError("Expected result 0x%x, got 0x%x, stderr=%r" % (expected, result, stderr)) 76 | else: 77 | if vm.returncode == 0: 78 | raise AssertionError("Expected VM to exit with an error code") 79 | 80 | @pytest.mark.parametrize("filename", testdata.list_files(_test_data_dir)) 81 | def test_datafiles(filename): 82 | # This is now a regular test function that will be called once for each filename 83 | check_datafile(filename) 84 | -------------------------------------------------------------------------------- /test/README.md: -------------------------------------------------------------------------------- 1 | # Tests 2 | 3 | ## How to run? 4 | 5 | - Build the target `test_Tests` in `PROJECT_ROOT/vm` with `-DBPFTIME_LLVM_JIT=YES` 6 | - Create a venv with Python 3.8, then install packages in `vm/test/requirements.txt` 7 | - Run `pytest -k "test_jit.py and not err-infinite"` in `vm/test/test_frameworks` 8 | 9 | ## What's expected 10 | 11 | Some tests are not expected to succeed. Some of them used features that we haven'e implemented. Some of them are too specilized for other runtimes. 12 | 13 | ```console 14 | FAILED test_jit.py::test_datafiles[call_unwind.data] - AssertionError: Expected result 0x0, got 0x2, stderr='' 15 | 16 | FAILED test_jit.py::test_datafiles[err-call-bad-imm.data] - AssertionError: Expected error 'Failed to load code: invalid call immediate at PC 5', got 'Ext func not found: ext_10000' 17 | 18 | FAILED test_jit.py::test_datafiles[err-call-unreg.data] - AssertionError: Expected error 'Failed to load code: call to nonexistent function 63 at PC 5', got 'Ext func not found: ext_0063' 19 | 20 | FAILED test_jit.py::test_datafiles[err-endian-size.data] - AssertionError: Expected error 'Failed to load code: invalid endian immediate at PC 0', got 'Unexpected endian size: 48' 21 | 22 | FAILED test_jit.py::test_datafiles[err-incomplete-lddw.data] - AssertionError: Expected error 'Failed to load code: incomplete lddw at PC 0', got 'Loaded LDDW at pc=0 which requires an extra pseudo instruction, but the next instruction is not a legal one' 23 | 24 | FAILED test_jit.py::test_datafiles[err-incomplete-lddw2.data] - AssertionError: Expected error 'Failed to load code: incomplete lddw at PC 0', got 'Loaded LDDW at pc=0 which requires an extra pseudo instruction, but the next instruction is not a legal one' 25 | 26 | FAILED test_jit.py::test_datafiles[err-invalid-reg-dst.data] - AssertionError: Expected error 'Failed to load code: invalid destination register at PC 0', got 'Illegal src reg/dst reg at pc 0' 27 | 28 | FAILED test_jit.py::test_datafiles[err-invalid-reg-src.data] - AssertionError: Expected error 'Failed to load code: invalid source register at PC 0', got 'Illegal src reg/dst reg at pc 0' 29 | 30 | FAILED test_jit.py::test_datafiles[err-jmp-lddw.data] - AssertionError: Expected error 'Failed to load code: jump to middle of lddw at PC 0', got "Basic Block in function 'bpf_main' does not have terminator!\nlabel %bb_inst_2\nInvalid module generated" 31 | 32 | FAILED test_jit.py::test_datafiles[err-jmp-out.data] - AssertionError: Expected error 'Failed to load code: jump out of bounds at PC 0', got 'Instruction at pc=0 is going to jump to an illegal position 3' 33 | 34 | FAILED test_jit.py::test_datafiles[err-lddw-invalid-src.data] - AssertionError: Expected error 'Failed to load code: invalid source register for LDDW at PC 0', got "Basic Block in function 'bpf_main' does not have terminator!\nlabel %bb_inst_0\nInvalid module generated" 35 | 36 | FAILED test_jit.py::test_datafiles[err-too-many-instructions.data] - AssertionError: Expected error 'Failed to load code: too many instructions (max 65536)', got "Basic Block in function 'bpf_main' does not have terminator!\nlabel %bb_inst_0\nInvalid module generated" 37 | 38 | FAILED test_jit.py::test_datafiles[err-unknown-opcode.data] - AssertionError: Expected error 'Failed to load code: unknown opcode 0x06 at PC 0', got 'Unsupported or illegal opcode: 6' 39 | ``` 40 | -------------------------------------------------------------------------------- /test/bpf_conformance_runner/main-bpf-conformance.cpp: -------------------------------------------------------------------------------- 1 | #include "llvmbpf.hpp" 2 | #include "ebpf_inst.h" 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | using namespace bpftime; 11 | 12 | /** 13 | * @brief Read in a string of hex bytes and return a vector of bytes. 14 | * 15 | * @param[in] input String containing hex bytes. 16 | * @return Vector of bytes. 17 | */ 18 | static inline std::vector base16_decode(const std::string &input) 19 | { 20 | std::vector output; 21 | std::stringstream ss(input); 22 | std::string value; 23 | while (std::getline(ss, value, ' ')) { 24 | try { 25 | output.push_back(std::stoi(value, nullptr, 16)); 26 | } catch (...) { 27 | // Ignore invalid values. 28 | } 29 | } 30 | return output; 31 | } 32 | 33 | /** 34 | * @brief Convert a vector of bytes to a vector of ebpf_inst. 35 | * 36 | * @param[in] bytes Vector of bytes. 37 | * @return Vector of ebpf_inst. 38 | */ 39 | std::vector bytes_to_ebpf_inst(std::vector bytes) 40 | { 41 | std::vector instructions(bytes.size() / sizeof(ebpf_inst)); 42 | memcpy(instructions.data(), bytes.data(), bytes.size()); 43 | return instructions; 44 | } 45 | 46 | uint64_t empty_helper_func(uint64_t _a, uint64_t _b, uint64_t _c, uint64_t _d, 47 | uint64_t _e) 48 | { 49 | return 0; 50 | } 51 | 52 | int main(int argc, char **argv) 53 | { 54 | // bool debug = false; 55 | // bool elf = false; 56 | std::vector args(argv, argv + argc); 57 | if (args.size() > 0) { 58 | args.erase(args.begin()); 59 | } 60 | std::string program_string; 61 | std::string memory_string; 62 | 63 | if (args.size() > 0 && args[0] == "--help") { 64 | std::cout 65 | << "usage: " << argv[0] 66 | << " [--program ] [] [--debug] [--elf]" 67 | << std::endl; 68 | return 1; 69 | } 70 | 71 | if (args.size() > 1 && args[0] == "--program") { 72 | args.erase(args.begin()); 73 | program_string = args[0]; 74 | args.erase(args.begin()); 75 | } else { 76 | std::getline(std::cin, program_string); 77 | } 78 | 79 | // Next parameter is optional memory contents. 80 | if (args.size() > 0 && !args[0].starts_with("--")) { 81 | memory_string = args[0]; 82 | args.erase(args.begin()); 83 | } 84 | 85 | if (args.size() > 0 && args[0] == "--debug") { 86 | // debug = true; 87 | args.erase(args.begin()); 88 | } 89 | 90 | if (args.size() > 0 && args[0] == "--elf") { 91 | // elf = true; 92 | args.erase(args.begin()); 93 | } 94 | 95 | if (args.size() > 0 && args[0].size() > 0) { 96 | std::cerr << "Unexpected arguments: " << args[0] << std::endl; 97 | return 1; 98 | } 99 | 100 | std::vector memory = base16_decode(memory_string); 101 | std::vector program = 102 | bytes_to_ebpf_inst(base16_decode(program_string)); 103 | std::string log; 104 | 105 | int err; 106 | auto vm = llvmbpf_vm(); 107 | err = vm.load_code(&program[0], program.size() * sizeof(ebpf_inst)); 108 | if (err < 0) { 109 | std::cerr << "Error: " << vm.get_error_message() << std::endl; 110 | return -1; 111 | } 112 | // add 1000 pesudo helpers so it can be tested with helpers 113 | for (int i = 0; i < 1000; i++) { 114 | vm.register_external_function(i, "helper_" + std::to_string(i), 115 | (void*)empty_helper_func); 116 | } 117 | auto func = vm.compile(); 118 | assert(func); 119 | uint64_t res; 120 | vm.exec(&memory[0], memory.size(), res); 121 | std::cout << std::hex << res << std::endl; 122 | return 0; 123 | } 124 | -------------------------------------------------------------------------------- /example/basic.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include "llvmbpf.hpp" 13 | 14 | using namespace bpftime; 15 | 16 | uint64_t ffi_print_func(uint64_t a, uint64_t _b, uint64_t _c, uint64_t _d, 17 | uint64_t _e) 18 | { 19 | std::cout << (const char *)a << std::endl; 20 | return 0; 21 | } 22 | 23 | uint8_t bpf_mem[] = { 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88 }; 24 | 25 | /* 26 | int add_test(struct data *d, int sz) { 27 | return d->a + d->b; 28 | } 29 | in 64 bit: 30 | */ 31 | const unsigned char bpf_add_mem_64_bit[] = { 32 | 0x7b, 0x1a, 0xf8, 0xff, 0x00, 0x00, 0x00, 0x00, 0x63, 0x2a, 0xf4, 0xff, 33 | 0x00, 0x00, 0x00, 0x00, 0x79, 0xa1, 0xf8, 0xff, 0x00, 0x00, 0x00, 0x00, 34 | 0x61, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x61, 0x11, 0x04, 0x00, 35 | 0x00, 0x00, 0x00, 0x00, 0x0f, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 36 | 0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 37 | }; 38 | 39 | /* 40 | int mul_test() { 41 | int a = 1; 42 | int b = 2; 43 | int c = a * b; 44 | return c; 45 | } 46 | in 64 bit: using clang -target bpf -c mul.bpf.c -o mul.bpf.o to compile 47 | */ 48 | const unsigned char bpf_mul_64_bit[] = { 49 | 0xb7, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x63, 0x1a, 0xfc, 0xff, 50 | 0x00, 0x00, 0x00, 0x00, 0xb7, 0x01, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 51 | 0x63, 0x1a, 0xf8, 0xff, 0x00, 0x00, 0x00, 0x00, 0x61, 0xa1, 0xfc, 0xff, 52 | 0x00, 0x00, 0x00, 0x00, 0x61, 0xa2, 0xf8, 0xff, 0x00, 0x00, 0x00, 0x00, 53 | 0x2f, 0x21, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x63, 0x1a, 0xf4, 0xff, 54 | 0x00, 0x00, 0x00, 0x00, 0x61, 0xa0, 0xf4, 0xff, 0x00, 0x00, 0x00, 0x00, 55 | 0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 56 | }; 57 | 58 | // static void (*print_bpf)(char *str) = (void *)0x2; 59 | // int print_and_add1(struct data *d, int sz) { 60 | // char a[] = "hello"; 61 | // print_bpf(a); 62 | // return 0; 63 | // } 64 | const unsigned char bpf_function_call_print[] = 65 | "\xb7\x01\x00\x00\x6f\x00\x00\x00" 66 | "\x6b\x1a\xfc\xff\x00\x00\x00\x00" 67 | "\xb7\x01\x00\x00\x68\x65\x6c\x6c" 68 | "\x63\x1a\xf8\xff\x00\x00\x00\x00" 69 | "\xbf\xa1\x00\x00\x00\x00\x00\x00" 70 | "\x07\x01\x00\x00\xf8\xff\xff\xff" 71 | "\x85\x00\x00\x00\x02\x00\x00\x00" 72 | "\xb7\x00\x00\x00\x00\x00\x00\x00" 73 | "\x95\x00\x00\x00\x00\x00\x00\x00"; 74 | 75 | uint64_t run_ebpf_prog(const void *code, size_t code_len) 76 | { 77 | uint64_t res = 0; 78 | llvmbpf_vm vm; 79 | printf("running ebpf prog, code len: %zd\n", code_len); 80 | 81 | res = vm.load_code(code, code_len); 82 | if (res) { 83 | fprintf(stderr, "Failed to load: %s\n", 84 | vm.get_error_message().c_str()); 85 | exit(1); 86 | } 87 | vm.register_external_function(2, "print", (void *)ffi_print_func); 88 | auto func = vm.compile(); 89 | if (!func) { 90 | fprintf(stderr, "Failed to compile: %s\n", 91 | vm.get_error_message().c_str()); 92 | exit(1); 93 | } 94 | int err = vm.exec(&bpf_mem, sizeof(bpf_mem), res); 95 | if (err != 0) { 96 | fprintf(stderr, "Failed to exec."); 97 | exit(1); 98 | } 99 | printf("res = %" PRIu64 "\n", res); 100 | return res; 101 | } 102 | 103 | int main(int argc, char *argv[]) 104 | { 105 | assert(run_ebpf_prog(bpf_add_mem_64_bit, sizeof(bpf_add_mem_64_bit)) == 106 | 3433728102); 107 | assert(run_ebpf_prog(bpf_mul_64_bit, sizeof(bpf_mul_64_bit)) == 2); 108 | // here we use string for the code 109 | assert(run_ebpf_prog(bpf_function_call_print, 110 | sizeof(bpf_function_call_print) - 1) == 0); 111 | return 0; 112 | } 113 | -------------------------------------------------------------------------------- /.github/workflows/bpf_conformance.yml: -------------------------------------------------------------------------------- 1 | name: Test bpf_conformance and publish results 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | concurrency: 9 | group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event_name }} 10 | cancel-in-progress: true 11 | jobs: 12 | build: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v2 16 | with: 17 | submodules: 'recursive' 18 | 19 | - name: Install lcov (Ubuntu) 20 | run: sudo apt install -y lcov llvm-15-dev libzstd-dev libboost-all-dev 21 | 22 | - name: build 23 | run: 24 | | 25 | LLVM_DIR=/usr/lib/llvm-15/cmake cmake -B build -DCMAKE_BUILD_TYPE=Debug -DBPFTIME_ENABLE_UNIT_TESTING=1 -DBPFTIME_ENABLE_CODE_COVERAGE=1 26 | cmake --build build --target all -j 27 | 28 | - name: get bpf_conformance for spec version 29 | run: | 30 | git clone https://github.com/Alan-Jowett/bpf_conformance --recursive 31 | cd bpf_conformance 32 | git checkout bb9f745ef53eb0568eb1ffc772d9a0e434b17755 33 | cmake . -B build -DCMAKE_BUILD_TYPE=Release 34 | cmake --build build --target bpf_conformance_runner 35 | 36 | - name: test bpf_conformance 37 | run: | 38 | ./bpf_conformance/build/bin/bpf_conformance_runner --cpu_version v4 --exclude_regex "lock_xchg+" --test_file_directory \ 39 | ./bpf_conformance/tests --plugin_path \ 40 | build/test/bpf_conformance_runner/bpftime_vm_bpf_conformance_runner 41 | 42 | - name: upload coverage 43 | run: | 44 | lcov --capture --directory . --output-file coverage.info 45 | lcov --remove coverage.info '/usr/*' --output-file coverage.info # filter system-files 46 | lcov --list coverage.info # debug info 47 | 48 | - uses: codecov/codecov-action@v4 49 | with: 50 | fail_ci_if_error: true # optional (default = false) 51 | files: ./coverage.info # optional 52 | flags: bpf_conformance 53 | token: ${{ secrets.CODECOV_TOKEN }} # required 54 | verbose: true # optional (default = false) 55 | 56 | - name: get bpf_conformance newest 57 | run: | 58 | cd bpf_conformance 59 | git checkout main 60 | cmake . -B build -DCMAKE_BUILD_TYPE=Release 61 | cmake --build build --target bpf_conformance_runner 62 | 63 | - name: test bpf_conformance newest 64 | run: | 65 | ./bpf_conformance/build/bin/bpf_conformance_runner --test_file_directory \ 66 | ./bpf_conformance/tests --plugin_path \ 67 | build/test/bpf_conformance_runner/bpftime_vm_bpf_conformance_runner \ 68 | > bpf_conformance_results.txt || echo "ok" 69 | 70 | - name: Upload artifact 71 | uses: actions/upload-pages-artifact@v3 72 | with: 73 | # Upload entire repository 74 | path: '.' 75 | deploy: 76 | if: github.event_name == 'push' 77 | # Add a dependency to the build job 78 | needs: build 79 | 80 | # Grant GITHUB_TOKEN the permissions required to make a Pages deployment 81 | permissions: 82 | pages: write # to deploy to Pages 83 | id-token: write # to verify the deployment originates from an appropriate source 84 | 85 | # Deploy to the github-pages environment 86 | environment: 87 | name: github-pages 88 | url: ${{ steps.deployment.outputs.page_url }} 89 | 90 | # Specify runner + deployment step 91 | runs-on: ubuntu-latest 92 | steps: 93 | - name: Deploy to GitHub Pages 94 | id: deployment 95 | uses: actions/deploy-pages@v4 # or specific "vX.X.X" version tag for this action 96 | 97 | 98 | -------------------------------------------------------------------------------- /include/llvmbpf.hpp: -------------------------------------------------------------------------------- 1 | #ifndef _BPFTIME_VM_LLVM_HPP 2 | #define _BPFTIME_VM_LLVM_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #ifndef MAX_EXT_FUNCS 11 | #define MAX_EXT_FUNCS 8192 12 | #endif 13 | 14 | namespace bpftime 15 | { 16 | 17 | struct external_function { 18 | std::string name; 19 | void *fn; 20 | }; 21 | 22 | class llvm_bpf_jit_context; 23 | 24 | // The JITed function signature. 25 | // The JITed function can be called with the memory and memory length directly. 26 | using precompiled_ebpf_function = uint64_t (*)(void *mem, size_t mem_len); 27 | 28 | class llvmbpf_vm { 29 | public: 30 | llvmbpf_vm(); 31 | ~llvmbpf_vm(); // Destructor declared 32 | std::string get_error_message() noexcept; 33 | 34 | // register external function, e.g. helper functions for eBPF 35 | // return 0 on success 36 | int register_external_function(size_t index, const std::string &name, 37 | void *fn) noexcept; 38 | 39 | // load the eBPF bytecode into the vm 40 | // The eBPF bytecode now can be JIT/AOT compiled 41 | // Or executed directly. 42 | // return 0 on success 43 | int load_code(const void *code, size_t code_len) noexcept; 44 | 45 | // unload the bytecode and remove the JIT/AOT compiled results 46 | void unload_code() noexcept; 47 | 48 | // execute the eBPF program 49 | // If the program is JIT compiled, it will be executed directly 50 | // If not, it will be JIT compiled, cached and executed 51 | // return 0 on success 52 | int exec(void *mem, size_t mem_len, 53 | uint64_t &bpf_return_value) noexcept; 54 | 55 | // Do AOT compile and generate the ELF object file 56 | // The external functions are required to be registered before 57 | // calling this function. The compile result can be linked with 58 | // other object files to generate the final executable. 59 | // return the ELF object file content 60 | std::optional > 61 | do_aot_compile(bool print_ir = false) noexcept; 62 | 63 | // Load the AOT object file into the vm and link it with the 64 | // external functions 65 | // return the JITed function if success 66 | std::optional 67 | load_aot_object(const std::vector &object) noexcept; 68 | 69 | // Compile the eBPF program into a JITed function 70 | // return the JITed function if success 71 | std::optional compile() noexcept; 72 | 73 | // See the spec for details. 74 | // If the code involve array map access, the map_val function 75 | // needs to be provided. 76 | // IF the map_by_fd, map_by_idx, var_addr, code_addr are not provided, 77 | // The are using imm as the address. 78 | void set_lddw_helpers(uint64_t (*map_by_fd)(uint32_t), 79 | uint64_t (*map_by_idx)(uint32_t), 80 | uint64_t (*map_val)(uint64_t), 81 | uint64_t (*var_addr)(uint32_t), 82 | uint64_t (*code_addr)(uint32_t)) noexcept; 83 | std::optional 84 | generate_ptx(const char *target_cpu = "sm_60"); 85 | std::optional> 86 | generate_spirv(const char *target_env = ""); 87 | 88 | private: 89 | // See spec for details 90 | uint64_t (*map_by_fd)(uint32_t) = nullptr; 91 | uint64_t (*map_by_idx)(uint32_t) = nullptr; 92 | uint64_t (*map_val)(uint64_t) = nullptr; 93 | uint64_t (*var_addr)(uint32_t) = nullptr; 94 | uint64_t (*code_addr)(uint32_t) = nullptr; 95 | 96 | std::vector instructions; 97 | 98 | std::vector > ext_funcs; 99 | 100 | std::unique_ptr jit_ctx; 101 | 102 | friend class llvm_bpf_jit_context; 103 | 104 | std::string error_msg; 105 | 106 | std::optional jitted_function = std::nullopt; 107 | }; 108 | 109 | } // namespace bpftime 110 | 111 | #endif // _BPFTIME_VM_LLVM_HPP 112 | -------------------------------------------------------------------------------- /test/test_framework/ubpf/assembler.py: -------------------------------------------------------------------------------- 1 | from .asm_parser import parse, Reg, Imm, MemRef 2 | import struct 3 | try: 4 | from StringIO import StringIO as io 5 | except ImportError: 6 | from io import BytesIO as io 7 | 8 | Inst = struct.Struct("BBHI") 9 | 10 | MEM_SIZES = { 11 | 'w': 0, 12 | 'h': 1, 13 | 'b': 2, 14 | 'dw': 3, 15 | } 16 | 17 | MEM_LOAD_OPS = { 'ldx' + k: (0x61 | (v << 3)) for k, v in list(MEM_SIZES.items()) } 18 | MEM_STORE_IMM_OPS = { 'st' + k: (0x62 | (v << 3)) for k, v in list(MEM_SIZES.items()) } 19 | MEM_STORE_REG_OPS = { 'stx' + k: (0x63 | (v << 3)) for k, v in list(MEM_SIZES.items()) } 20 | 21 | UNARY_ALU_OPS = { 22 | 'neg': 8, 23 | } 24 | 25 | BINARY_ALU_OPS = { 26 | 'add': 0, 27 | 'sub': 1, 28 | 'mul': 2, 29 | 'div': 3, 30 | 'or': 4, 31 | 'and': 5, 32 | 'lsh': 6, 33 | 'rsh': 7, 34 | 'mod': 9, 35 | 'xor': 10, 36 | 'mov': 11, 37 | 'arsh': 12, 38 | } 39 | 40 | UNARY_ALU32_OPS = { k + '32': v for k, v in list(UNARY_ALU_OPS.items()) } 41 | BINARY_ALU32_OPS = { k + '32': v for k, v in list(BINARY_ALU_OPS.items()) } 42 | 43 | END_OPS = { 44 | 'le16': (0xd4, 16), 45 | 'le32': (0xd4, 32), 46 | 'le64': (0xd4, 64), 47 | 'be16': (0xdc, 16), 48 | 'be32': (0xdc, 32), 49 | 'be64': (0xdc, 64), 50 | } 51 | 52 | JMP_CMP_OPS = { 53 | 'jeq': 1, 54 | 'jgt': 2, 55 | 'jge': 3, 56 | 'jset': 4, 57 | 'jne': 5, 58 | 'jsgt': 6, 59 | 'jsge': 7, 60 | 'jlt': 10, 61 | 'jle': 11, 62 | 'jslt': 12, 63 | 'jsle': 13, 64 | } 65 | 66 | JMP_MISC_OPS = { 67 | 'ja': 0, 68 | 'call': 8, 69 | 'exit': 9, 70 | } 71 | 72 | def pack(opcode, dst, src, offset, imm): 73 | return Inst.pack(opcode & 0xff, (dst | (src << 4)) & 0xff, offset & 0xffff, imm & 0xffffffff) 74 | 75 | def assemble_binop(op, cls, ops, dst, src, offset): 76 | opcode = cls | (ops[op] << 4) 77 | if isinstance(src, Imm): 78 | return pack(opcode, dst.num, 0, offset, src.value) 79 | else: 80 | return pack(opcode | 0x08, dst.num, src.num, offset, 0) 81 | 82 | def assemble_one(inst): 83 | op = inst[0] 84 | if op in MEM_LOAD_OPS: 85 | opcode = MEM_LOAD_OPS[op] 86 | return pack(opcode, inst[1].num, inst[2].reg.num, inst[2].offset, 0) 87 | elif op == "lddw": 88 | a = pack(0x18, inst[1].num, 0, 0, inst[2].value) 89 | b = pack(0, 0, 0, 0, inst[2].value >> 32) 90 | return a + b 91 | elif op in MEM_STORE_IMM_OPS: 92 | opcode = MEM_STORE_IMM_OPS[op] 93 | return pack(opcode, inst[1].reg.num, 0, inst[1].offset, inst[2].value) 94 | elif op in MEM_STORE_REG_OPS: 95 | opcode = MEM_STORE_REG_OPS[op] 96 | return pack(opcode, inst[1].reg.num, inst[2].num, inst[1].offset, 0) 97 | elif op in UNARY_ALU_OPS: 98 | opcode = 0x07 | (UNARY_ALU_OPS[op] << 4) 99 | return pack(opcode, inst[1].num, 0, 0, 0) 100 | elif op in UNARY_ALU32_OPS: 101 | opcode = 0x04 | (UNARY_ALU32_OPS[op] << 4) 102 | return pack(opcode, inst[1].num, 0, 0, 0) 103 | elif op in BINARY_ALU_OPS: 104 | return assemble_binop(op, 0x07, BINARY_ALU_OPS, inst[1], inst[2], 0) 105 | elif op in BINARY_ALU32_OPS: 106 | return assemble_binop(op, 0x04, BINARY_ALU32_OPS, inst[1], inst[2], 0) 107 | elif op in END_OPS: 108 | opcode, imm = END_OPS[op] 109 | return pack(opcode, inst[1].num, 0, 0, imm) 110 | elif op in JMP_CMP_OPS: 111 | return assemble_binop(op, 0x05, JMP_CMP_OPS, inst[1], inst[2], inst[3]) 112 | elif op in JMP_MISC_OPS: 113 | opcode = 0x05 | (JMP_MISC_OPS[op] << 4) 114 | if op == 'ja': 115 | return pack(opcode, 0, 0, inst[1], 0) 116 | elif op == 'call': 117 | return pack(opcode, 0, 0, 0, inst[1].value) 118 | elif op == 'exit': 119 | return pack(opcode, 0, 0, 0, 0) 120 | else: 121 | raise ValueError("unexpected instruction %r" % op) 122 | 123 | def assemble(source): 124 | insts = parse(source) 125 | output = io() 126 | for inst in insts: 127 | output.write(assemble_one(inst)) 128 | return output.getvalue() 129 | -------------------------------------------------------------------------------- /test/test_framework/test_jit.py: -------------------------------------------------------------------------------- 1 | import os 2 | import platform 3 | import tempfile 4 | import struct 5 | import re 6 | from subprocess import Popen, PIPE 7 | from nose.plugins.skip import Skip, SkipTest 8 | import ubpf.assembler 9 | import testdata 10 | import pytest 11 | import os 12 | _test_data_dir = os.path.join(os.path.dirname(os.path.realpath(__file__)), "../test-cases") 13 | VM = os.path.join(os.path.dirname(os.path.realpath(__file__)), "../..", "build/test/test_Tests") 14 | try: 15 | xrange 16 | except NameError: 17 | xrange = range 18 | 19 | def jit_supported_platform(): 20 | """Is the JIT supported on the current platform.""" 21 | return platform.machine() in ['amd64', 'x86_64', 'arm64', 'aarch64'] 22 | 23 | def check_datafile(filename): 24 | """ 25 | Given assembly source code and an expected result, run the eBPF program and 26 | verify that the result matches. Uses the JIT compiler. 27 | """ 28 | if not jit_supported_platform(): 29 | raise SkipTest("JIT is not supported on the current platform") 30 | 31 | data = testdata.read(_test_data_dir, filename) 32 | if 'asm' not in data and 'raw' not in data: 33 | raise SkipTest("no asm or raw section in datafile") 34 | if 'result' not in data and 'error' not in data and 'error pattern' not in data: 35 | raise SkipTest("no result or error section in datafile") 36 | if not os.path.exists(VM): 37 | raise SkipTest("VM not found") 38 | if 'no jit' in data: 39 | raise SkipTest("JIT disabled for this testcase (%s)" % data['no jit']) 40 | 41 | if 'raw' in data: 42 | code = b''.join(struct.pack("=Q", x) for x in data['raw']) 43 | else: 44 | code = ubpf.assembler.assemble(data['asm']) 45 | 46 | memfile = None 47 | 48 | if 'mem' in data: 49 | memfile = tempfile.NamedTemporaryFile() 50 | memfile.write(data['mem']) 51 | memfile.flush() 52 | 53 | num_register_offsets = 20 54 | if 'no register offset' in data: 55 | # The JIT relies on a fixed register mapping for the call instruction 56 | num_register_offsets = 1 57 | 58 | try: 59 | for register_offset in xrange(0, num_register_offsets): 60 | cmd = [VM] 61 | if memfile: 62 | cmd.extend(['-m', memfile.name]) 63 | if 'reload' in data: 64 | cmd.extend(['-R']) 65 | if 'unload' in data: 66 | cmd.extend(['-U']) 67 | cmd.extend(['-j', '-r', str(register_offset), '-']) 68 | 69 | vm = Popen(cmd, stdin=PIPE, stdout=PIPE, stderr=PIPE) 70 | 71 | stdout, stderr = vm.communicate(code) 72 | stdout = stdout.decode("utf-8") 73 | stderr = stderr.decode("utf-8") 74 | stderr = stderr.strip() 75 | 76 | if 'error' in data: 77 | if data['error'] != stderr: 78 | raise AssertionError("Expected error %r, got %r" % (data['error'], stderr)) 79 | elif 'error pattern' in data: 80 | if not re.search(data['error pattern'], stderr): 81 | raise AssertionError("Expected error matching %r, got %r" % (data['error pattern'], stderr)) 82 | else: 83 | if stderr: 84 | raise AssertionError("Unexpected error %r" % stderr) 85 | 86 | if 'result' in data: 87 | if vm.returncode != 0: 88 | raise AssertionError("VM exited with status %d, stderr=%r" % (vm.returncode, stderr)) 89 | expected = int(data['result'], 0) 90 | result = int(stdout, 0) 91 | if expected != result: 92 | raise AssertionError("Expected result 0x%x, got 0x%x, stderr=%r" % (expected, result, stderr)) 93 | else: 94 | if vm.returncode == 0: 95 | raise AssertionError("Expected VM to exit with an error code") 96 | finally: 97 | if memfile: 98 | memfile.close() 99 | 100 | @pytest.mark.parametrize("filename", testdata.list_files(_test_data_dir)) 101 | def test_datafiles(filename): 102 | # This is now a regular test function that will be called once for each filename 103 | check_datafile(filename) 104 | 105 | -------------------------------------------------------------------------------- /.github/workflows/codeql.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [ "main" ] 17 | pull_request: 18 | branches: [ "main" ] 19 | schedule: 20 | - cron: '17 12 * * 6' 21 | 22 | jobs: 23 | analyze: 24 | name: Analyze (${{ matrix.language }}) 25 | # Runner size impacts CodeQL analysis time. To learn more, please see: 26 | # - https://gh.io/recommended-hardware-resources-for-running-codeql 27 | # - https://gh.io/supported-runners-and-hardware-resources 28 | # - https://gh.io/using-larger-runners (GitHub.com only) 29 | # Consider using larger runners or machines with greater resources for possible analysis time improvements. 30 | runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }} 31 | permissions: 32 | # required for all workflows 33 | security-events: write 34 | 35 | # required to fetch internal or private CodeQL packs 36 | packages: read 37 | 38 | # only required for workflows in private repositories 39 | actions: read 40 | contents: read 41 | 42 | strategy: 43 | fail-fast: false 44 | matrix: 45 | include: 46 | - language: c-cpp 47 | build-mode: manual 48 | - language: python 49 | build-mode: none 50 | # CodeQL supports the following values keywords for 'language': 'c-cpp', 'csharp', 'go', 'java-kotlin', 'javascript-typescript', 'python', 'ruby', 'swift' 51 | # Use `c-cpp` to analyze code written in C, C++ or both 52 | # Use 'java-kotlin' to analyze code written in Java, Kotlin or both 53 | # Use 'javascript-typescript' to analyze code written in JavaScript, TypeScript or both 54 | # To learn more about changing the languages that are analyzed or customizing the build mode for your analysis, 55 | # see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/customizing-your-advanced-setup-for-code-scanning. 56 | # If you are analyzing a compiled language, you can modify the 'build-mode' for that language to customize how 57 | # your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages 58 | steps: 59 | - name: Checkout repository 60 | uses: actions/checkout@v4 61 | # Initializes the CodeQL tools for scanning. 62 | - name: Initialize CodeQL 63 | uses: github/codeql-action/init@v3 64 | with: 65 | languages: ${{ matrix.language }} 66 | build-mode: ${{ matrix.build-mode }} 67 | # If you wish to specify custom queries, you can do so here or in a config file. 68 | # By default, queries listed here will override any specified in a config file. 69 | # Prefix the list here with "+" to use these queries and those in the config file. 70 | 71 | # For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs 72 | # queries: security-extended,security-and-quality 73 | 74 | # If the analyze step fails for one of the languages you are analyzing with 75 | # "We were unable to automatically build your code", modify the matrix above 76 | # to set the build mode to "manual" for that language. Then modify this step 77 | # to build your code. 78 | # ℹ️ Command-line programs to run using the OS shell. 79 | # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun 80 | - if: matrix.build-mode == 'manual' 81 | shell: bash 82 | run: | 83 | sudo apt install llvm-15-dev libzstd-dev 84 | LLVM_DIR=/usr/lib/llvm-15/cmake cmake -B build -DCMAKE_BUILD_TYPE=Debug 85 | cmake --build build --target all -j 86 | 87 | - name: Perform CodeQL Analysis 88 | uses: github/codeql-action/analyze@v3 89 | with: 90 | category: "/language:${{matrix.language}}" 91 | -------------------------------------------------------------------------------- /src/compiler_utils.hpp: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: MIT 2 | * 3 | * Copyright (c) 2022, eunomia-bpf org 4 | * All rights reserved. 5 | */ 6 | #ifndef _LLVM_BPF_JIT_HELPER 7 | #define _LLVM_BPF_JIT_HELPER 8 | 9 | #include "llvm_jit_context.hpp" 10 | #include "ebpf_inst.h" 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | namespace bpftime 27 | { 28 | 29 | static inline bool is_jmp(const ebpf_inst &insn) 30 | { 31 | return (insn.opcode & 0x07) == EBPF_CLS_JMP || 32 | (insn.opcode & 0x07) == EBPF_CLS_JMP32; 33 | } 34 | 35 | static inline bool is_imm_jmp(const ebpf_inst &inst) 36 | { 37 | return inst.opcode == EBPF_OP_JA_IMM; 38 | } 39 | static inline std::string ext_func_sym(uint32_t idx) 40 | { 41 | char buf[32]; 42 | sprintf(buf, "_bpf_helper_ext_%04" PRIu32, idx); 43 | return buf; 44 | } 45 | 46 | static inline bool is_alu64(const ebpf_inst &insn) 47 | { 48 | return (insn.opcode & 0x07) == EBPF_CLS_ALU64; 49 | } 50 | 51 | /// Get the source representation of certain ALU operands 52 | llvm::Value *emitLoadALUSource(const ebpf_inst &inst, llvm::Value **regs, 53 | llvm::IRBuilder<> &builder); 54 | llvm::Value *emitLoadALUDest(const ebpf_inst &inst, llvm::Value **regs, 55 | llvm::IRBuilder<> &builder, bool dstAlways64); 56 | void emitStoreALUResult(const ebpf_inst &inst, llvm::Value **regs, 57 | llvm::IRBuilder<> &builder, llvm::Value *result); 58 | llvm::Expected 59 | emitALUEndianConversion(const ebpf_inst &inst, llvm::IRBuilder<> &builder, 60 | llvm::Value *dst_val); 61 | 62 | void emitALUWithDstAndSrc( 63 | const ebpf_inst &inst, llvm::IRBuilder<> &builder, llvm::Value **regs, 64 | std::function func); 65 | 66 | llvm::Value *emitStoreLoadingSrc(const ebpf_inst &inst, 67 | llvm::IRBuilder<> &builder, 68 | llvm::Value **regs); 69 | void emitStoreWritingResult(const ebpf_inst &inst, llvm::IRBuilder<> &builder, 70 | llvm::Value **regs, llvm::Value *result); 71 | 72 | void emitStore(const ebpf_inst &inst, llvm::IRBuilder<> &builder, 73 | llvm::Value **regs, llvm::IntegerType *destTy); 74 | 75 | std::tuple 76 | emitJmpLoadSrcAndDstAndZero(const ebpf_inst &inst, llvm::Value **regs, 77 | llvm::IRBuilder<> &builder); 78 | 79 | llvm::Expected 80 | loadJmpDstBlock(uint16_t pc, const ebpf_inst &inst, 81 | const std::map &instBlocks, 82 | bool useOffset); 83 | llvm::Expected 84 | loadCallDstBlock(uint16_t pc, const ebpf_inst &inst, 85 | const std::map &instBlocks); 86 | llvm::Expected 87 | loadJmpNextBlock(uint16_t pc, const ebpf_inst &inst, 88 | const std::map &instBlocks); 89 | llvm::Expected > 90 | localJmpDstAndNextBlk(uint16_t pc, const ebpf_inst &inst, 91 | const std::map &instBlocks); 92 | llvm::Value *emitLDXLoadingAddr(llvm::IRBuilder<> &builder, llvm::Value **regs, 93 | const ebpf_inst &inst); 94 | void emitLDXStoringResult(llvm::IRBuilder<> &builder, llvm::Value **regs, 95 | const ebpf_inst &inst, llvm::Value *result); 96 | void emitLoadX(llvm::IRBuilder<> &builder, llvm::Value **regs, 97 | const ebpf_inst &inst, llvm::IntegerType *srcTy); 98 | 99 | llvm::Expected emitCondJmpWithDstAndSrc( 100 | llvm::IRBuilder<> &builder, uint16_t pc, const ebpf_inst &inst, 101 | const std::map &instBlocks, 102 | llvm::Value **regs, 103 | std::function func); 104 | 105 | llvm::Expected 106 | emitExtFuncCall(llvm::IRBuilder<> &builder, const ebpf_inst &inst, 107 | const std::map &extFunc, 108 | llvm::Value **regs, llvm::FunctionType *helperFuncTy, 109 | uint16_t pc, llvm::BasicBlock *exitBlk); 110 | void emitAtomicBinOp(llvm::IRBuilder<> &builder, llvm::Value **regs, 111 | llvm::AtomicRMWInst::BinOp op, const ebpf_inst &inst, 112 | bool is64, bool is_fetch); 113 | } // namespace bpftime 114 | #endif 115 | -------------------------------------------------------------------------------- /test/include/test_bpf_progs.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef EBPF_TEST_CODE_H_ 3 | #define EBPF_TEST_CODE_H_ 4 | 5 | // original code from libebpf repo 6 | const unsigned char bpf_add_mem_64_bit_minimal[] = 7 | "" 8 | "\x61\x12\x00\x00\x00\x00\x00\x00" 9 | "\x61\x10\x04\x00\x00\x00\x00\x00" 10 | "\x0f\x20\x00\x00\x00\x00\x00\x00" 11 | "\x95\x00\x00\x00\x00\x00\x00\x00" 12 | ""; 13 | 14 | /// ebpf code generate by compile framework 15 | // static int (*add)(int a, int b) = (void *)0x3; 16 | // int print_and_add1(struct data *d, int sz) { 17 | // return add(1, 3); 18 | // } 19 | const unsigned char bpf_function_call_add[] = 20 | "" 21 | "\xb7\x01\x00\x00\x01\x00\x00\x00" 22 | "\xb7\x02\x00\x00\x03\x00\x00\x00" 23 | "\x85\x00\x00\x00\x03\x00\x00\x00" 24 | "\x95\x00\x00\x00\x00\x00\x00\x00"; 25 | 26 | // static void (*print_bpf)(char *str) = (void *)0x2; 27 | // int print_and_add1(struct data *d, int sz) { 28 | // char a[] = "hello"; 29 | // print_bpf(a); 30 | // return 0; 31 | // } 32 | const unsigned char bpf_function_call_print[] = 33 | "" 34 | "\xb7\x01\x00\x00\x6f\x00\x00\x00" 35 | "\x6b\x1a\xfc\xff\x00\x00\x00\x00" 36 | "\xb7\x01\x00\x00\x68\x65\x6c\x6c" 37 | "\x63\x1a\xf8\xff\x00\x00\x00\x00" 38 | "\xbf\xa1\x00\x00\x00\x00\x00\x00" 39 | "\x07\x01\x00\x00\xf8\xff\xff\xff" 40 | "\x85\x00\x00\x00\x02\x00\x00\x00" 41 | "\xb7\x00\x00\x00\x00\x00\x00\x00" 42 | "\x95\x00\x00\x00\x00\x00\x00\x00"; 43 | 44 | /* 45 | int add_test(struct data *d, int sz) { 46 | return d->a + d->b; 47 | } 48 | in 64 bit: 49 | */ 50 | const unsigned char bpf_add_mem_64_bit[] = { 51 | 0x7b, 0x1a, 0xf8, 0xff, 0x00, 0x00, 0x00, 0x00, 0x63, 0x2a, 0xf4, 0xff, 52 | 0x00, 0x00, 0x00, 0x00, 0x79, 0xa1, 0xf8, 0xff, 0x00, 0x00, 0x00, 0x00, 53 | 0x61, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x61, 0x11, 0x04, 0x00, 54 | 0x00, 0x00, 0x00, 0x00, 0x0f, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 55 | 0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 56 | }; 57 | 58 | /* 59 | int mul_test() { 60 | int a = 1; 61 | int b = 2; 62 | int c = a * b; 63 | return c; 64 | } 65 | in 64 bit: using clang -Xlinker --export-dynamic -target bpf -c mul.bpf.c -o mul.bpf.o to compile 66 | */ 67 | const unsigned char bpf_mul_64_bit[] = { 68 | 0xb7, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x63, 0x1a, 0xfc, 0xff, 69 | 0x00, 0x00, 0x00, 0x00, 0xb7, 0x01, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 70 | 0x63, 0x1a, 0xf8, 0xff, 0x00, 0x00, 0x00, 0x00, 0x61, 0xa1, 0xfc, 0xff, 71 | 0x00, 0x00, 0x00, 0x00, 0x61, 0xa2, 0xf8, 0xff, 0x00, 0x00, 0x00, 0x00, 72 | 0x2f, 0x21, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x63, 0x1a, 0xf4, 0xff, 73 | 0x00, 0x00, 0x00, 0x00, 0x61, 0xa0, 0xf4, 0xff, 0x00, 0x00, 0x00, 0x00, 74 | 0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 75 | }; 76 | 77 | /* 78 | a * b / 2 for 32 bit 79 | clang -Xlinker --export-dynamic -O2 -target bpf -m32 -c example/bpf/mul.bpf.c -o prog.o 80 | */ 81 | const unsigned char bpf_mul_optimized[] = { 0xb7, 0x00, 0x00, 0x00, 0x02, 0x00, 82 | 0x00, 0x00, 0x95, 0x00, 0x00, 0x00, 83 | 0x00, 0x00, 0x00, 0x00 }; 84 | 85 | const unsigned char bpf_ufunc_code[] = 86 | "" 87 | "\xbf\x16\x00\x00\x00\x00\x00\x00\xb7\x01\x00\x00\x6f\x00\x00\x00\x6b\x1a\xcc\xff\x00\x00\x00\x00\xb7" 88 | "\x01\x00\x00\x68\x65\x6c\x6c\x63\x1a\xc8\xff\x00\x00\x00\x00\xbf\xa1\x00\x00\x00\x00\x00\x00\x07\x01" 89 | "\x00\x00\xc8\xff\xff\xff\x7b\x1a\xd0\xff\x00\x00\x00\x00\xbf\xa2\x00\x00\x00\x00\x00\x00\x07\x02\x00" 90 | "\x00\xd0\xff\xff\xff\xb7\x01\x00\x00\x02\x00\x00\x00\x85\x00\x00\x00\x01\x00\x00\x00\x79\x61\x00\x00" 91 | "\x00\x00\x00\x00\x67\x01\x00\x00\x20\x00\x00\x00\xc7\x01\x00\x00\x20\x00\x00\x00\x7b\x1a\xd0\xff\x00" 92 | "\x00\x00\x00\xb7\x01\x00\x00\x01\x00\x00\x00\x7b\x1a\xd8\xff\x00\x00\x00\x00\xbf\xa2\x00\x00\x00\x00" 93 | "\x00\x00\x07\x02\x00\x00\xd0\xff\xff\xff\xb7\x01\x00\x00\x01\x00\x00\x00\x85\x00\x00\x00\x01\x00\x00" 94 | "\x00\x95\x00\x00\x00\x00\x00\x00\x00"; 95 | 96 | // disassemble: 97 | // 2 mov r1, 0x1 98 | // 3 mov r2, 0x5 99 | // 4 call 0x3 100 | // 5 stxdw [r10-8], r0 101 | // 6 ldxdw r1, [r10-8] 102 | // 7 mov r2, 0x8 103 | // 8 div r2, r1 104 | // 9 stxdw [r10-16], r2 105 | // 10 exit 106 | const unsigned char bpf_div64_code[] = "" 107 | "\x79\x13\x00\x00\x00\x00\x00\x00" 108 | "\xb7\x01\x00\x00\x01\x00\x00\x00" 109 | "\xb7\x02\x00\x00\x05\x00\x00\x00" 110 | "\x85\x00\x00\x00\x03\x00\x00\x00" 111 | "\x7b\x0a\xf8\xff\x00\x00\x00\x00" 112 | "\x79\xa1\xf8\xff\x00\x00\x00\x00" 113 | "\xb7\x02\x00\x00\x08\x00\x00\x00" 114 | "\x3f\x12\x00\x00\x00\x00\x00\x00" 115 | "\x7b\x2a\xf0\xff\x00\x00\x00\x00" 116 | "\x95\x00\x00\x00\x00\x00\x00\x00"; 117 | 118 | #endif 119 | -------------------------------------------------------------------------------- /src/vm.cpp: -------------------------------------------------------------------------------- 1 | #include "spdlog/spdlog.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include "llvm_jit_context.hpp" 7 | 8 | using namespace bpftime; 9 | 10 | llvmbpf_vm::llvmbpf_vm() 11 | : ext_funcs(MAX_EXT_FUNCS), 12 | jit_ctx(std::make_unique(*this)) 13 | { 14 | } 15 | 16 | llvmbpf_vm::~llvmbpf_vm() 17 | { 18 | } 19 | 20 | std::string llvmbpf_vm::get_error_message() noexcept 21 | { 22 | return error_msg; 23 | } 24 | 25 | int llvmbpf_vm::register_external_function(size_t index, 26 | const std::string &name, 27 | void *fn) noexcept 28 | { 29 | if (index >= ext_funcs.size()) { 30 | error_msg = "Index too large"; 31 | return -E2BIG; 32 | } 33 | if (ext_funcs[index].has_value()) { 34 | error_msg = "Already defined"; 35 | return -EEXIST; 36 | } 37 | ext_funcs[index] = external_function{ .name = name, .fn = fn }; 38 | return 0; 39 | } 40 | 41 | int llvmbpf_vm::load_code(const void *code, size_t code_len) noexcept 42 | { 43 | if (code_len % 8 != 0) { 44 | error_msg = "Code len must be a multiple of 8"; 45 | return -EINVAL; 46 | } 47 | instructions.assign((ebpf_inst *)code, 48 | (ebpf_inst *)code + code_len / 8); 49 | return 0; 50 | } 51 | 52 | void llvmbpf_vm::unload_code() noexcept 53 | { 54 | instructions.clear(); 55 | } 56 | 57 | int llvmbpf_vm::exec(void *mem, size_t mem_len, 58 | uint64_t &bpf_return_value) noexcept 59 | { 60 | if (jitted_function) { 61 | SPDLOG_TRACE("llvm-jit: Called jitted function {:x}", 62 | (uintptr_t)jitted_function.value()); 63 | auto ret = 64 | (*jitted_function)(mem, static_cast(mem_len)); 65 | SPDLOG_TRACE( 66 | "LLJIT: called from jitted function {:x} returned {}", 67 | (uintptr_t)jitted_function.value(), ret); 68 | bpf_return_value = ret; 69 | return 0; 70 | } 71 | try { 72 | auto func = compile(); 73 | if (!func) { 74 | SPDLOG_ERROR("Unable to compile eBPF program"); 75 | return -1; 76 | } 77 | jitted_function = func; 78 | // after compile, run 79 | return exec(mem, mem_len, bpf_return_value); 80 | } catch (const std::exception &e) { 81 | error_msg = e.what(); 82 | return -1; 83 | } 84 | } 85 | 86 | std::optional llvmbpf_vm::compile() noexcept 87 | { 88 | if (jitted_function) { 89 | error_msg = "Already compiled"; 90 | return jitted_function; 91 | } 92 | try { 93 | auto res = jit_ctx->do_jit_compile(); 94 | if (res) { 95 | LLVMErrorRef llvmError = llvm::wrap(std::move(res)); 96 | error_msg = LLVMGetErrorMessage(llvmError); 97 | SPDLOG_ERROR("LLVM-JIT: failed to compile: {}", 98 | error_msg); 99 | return {}; 100 | } 101 | auto func = jit_ctx->get_entry_address(); 102 | jitted_function = func; 103 | return func; 104 | } catch (const std::exception &e) { 105 | error_msg = e.what(); 106 | jitted_function = std::nullopt; 107 | return {}; 108 | } 109 | } 110 | 111 | void llvmbpf_vm::set_lddw_helpers(uint64_t (*map_by_fd)(uint32_t), 112 | uint64_t (*map_by_idx)(uint32_t), 113 | uint64_t (*map_val)(uint64_t), 114 | uint64_t (*var_addr)(uint32_t), 115 | uint64_t (*code_addr)(uint32_t)) noexcept 116 | { 117 | this->map_by_fd = map_by_fd; 118 | this->map_by_idx = map_by_idx; 119 | this->map_val = map_val; 120 | this->var_addr = var_addr; 121 | this->code_addr = code_addr; 122 | } 123 | 124 | std::optional> 125 | llvmbpf_vm::do_aot_compile(bool print_ir) noexcept 126 | { 127 | try { 128 | return jit_ctx->do_aot_compile(print_ir); 129 | } catch (const std::exception &e) { 130 | error_msg = e.what(); 131 | return {}; 132 | } 133 | } 134 | 135 | std::optional 136 | llvmbpf_vm::load_aot_object(const std::vector &object) noexcept 137 | { 138 | if (jitted_function) { 139 | error_msg = "Already compiled"; 140 | return {}; 141 | } 142 | try { 143 | if (auto res = this->jit_ctx->load_aot_object(object); res) { 144 | LLVMErrorRef llvmError = llvm::wrap(std::move(res)); 145 | error_msg = LLVMGetErrorMessage(llvmError); 146 | return {}; 147 | } 148 | jitted_function = this->jit_ctx->get_entry_address(); 149 | } catch (const std::exception &e) { 150 | error_msg = e.what(); 151 | return {}; 152 | } 153 | return jitted_function; 154 | } 155 | 156 | std::optional llvmbpf_vm::generate_ptx(const char *target_cpu) 157 | { 158 | return this->jit_ctx->generate_ptx(target_cpu); 159 | } 160 | 161 | std::optional> 162 | llvmbpf_vm::generate_spirv(const char *target_env) 163 | { 164 | return this->jit_ctx->generate_spirv(true, "bpf_main", target_env); 165 | } 166 | -------------------------------------------------------------------------------- /example/maps.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include "llvmbpf.hpp" 13 | 14 | using namespace bpftime; 15 | const unsigned char xdp_counter_bytecode[] = "\x79\x16\x00\x00\x00\x00\x00\x00" 16 | "\x79\x17\x08\x00\x00\x00\x00\x00" 17 | "\xb7\x01\x00\x00\x00\x00\x00\x00" 18 | "\x63\x1a\xfc\xff\x00\x00\x00\x00" 19 | "\xbf\xa2\x00\x00\x00\x00\x00\x00" 20 | "\x07\x02\x00\x00\xfc\xff\xff\xff" 21 | "\x18\x11\x00\x00\x05\x00\x00\x00" 22 | "\x00\x00\x00\x00\x00\x00\x00\x00" 23 | "\x85\x00\x00\x00\x01\x00\x00\x00" 24 | "\xbf\x01\x00\x00\x00\x00\x00\x00" 25 | "\xb7\x00\x00\x00\x02\x00\x00\x00" 26 | "\x15\x01\x18\x00\x00\x00\x00\x00" 27 | "\x61\x11\x00\x00\x00\x00\x00\x00" 28 | "\x55\x01\x16\x00\x00\x00\x00\x00" 29 | "\x18\x21\x00\x00\x06\x00\x00\x00" 30 | "\x00\x00\x00\x00\x00\x00\x00\x00" 31 | "\x79\x12\x00\x00\x00\x00\x00\x00" 32 | "\x07\x02\x00\x00\x01\x00\x00\x00" 33 | "\x7b\x21\x00\x00\x00\x00\x00\x00" 34 | "\xb7\x00\x00\x00\x01\x00\x00\x00" 35 | "\xbf\x61\x00\x00\x00\x00\x00\x00" 36 | "\x07\x01\x00\x00\x0e\x00\x00\x00" 37 | "\x2d\x71\x0d\x00\x00\x00\x00\x00" 38 | "\x69\x61\x00\x00\x00\x00\x00\x00" 39 | "\x69\x62\x06\x00\x00\x00\x00\x00" 40 | "\x6b\x26\x00\x00\x00\x00\x00\x00" 41 | "\x69\x62\x08\x00\x00\x00\x00\x00" 42 | "\x69\x63\x02\x00\x00\x00\x00\x00" 43 | "\x6b\x36\x08\x00\x00\x00\x00\x00" 44 | "\x6b\x26\x02\x00\x00\x00\x00\x00" 45 | "\x69\x62\x0a\x00\x00\x00\x00\x00" 46 | "\x69\x63\x04\x00\x00\x00\x00\x00" 47 | "\x6b\x36\x0a\x00\x00\x00\x00\x00" 48 | "\x6b\x16\x06\x00\x00\x00\x00\x00" 49 | "\x6b\x26\x04\x00\x00\x00\x00\x00" 50 | "\xb7\x00\x00\x00\x03\x00\x00\x00" 51 | "\x95\x00\x00\x00\x00\x00\x00\x00"; 52 | 53 | uint8_t bpf_mem[] = { 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88 }; 54 | 55 | uint32_t ctl_array[2] = { 0, 0 }; 56 | uint64_t cntrs_array[2] = { 0, 0 }; 57 | 58 | void *bpf_map_lookup_elem(uint64_t map_fd, void *key) 59 | { 60 | std::cout << "bpf_map_lookup_elem " << map_fd << std::endl; 61 | if (map_fd == 5) { 62 | return &ctl_array[*(uint32_t *)key]; 63 | } else if (map_fd == 6) { 64 | return &cntrs_array[*(uint32_t *)key]; 65 | } else { 66 | return nullptr; 67 | } 68 | return 0; 69 | } 70 | 71 | uint64_t map_by_fd(uint32_t fd) 72 | { 73 | std::cout << "map_by_fd " << fd << std::endl; 74 | return fd; 75 | } 76 | 77 | uint64_t map_val(uint64_t val) 78 | { 79 | std::cout << "map_val " << val << std::endl; 80 | if (val == 5) { 81 | return (uint64_t)(void *)ctl_array; 82 | } else if (val == 6) { 83 | return (uint64_t)(void *)cntrs_array; 84 | } else { 85 | return 0; 86 | } 87 | } 88 | 89 | int main(int argc, char *argv[]) 90 | { 91 | auto code = xdp_counter_bytecode; 92 | size_t code_len = sizeof(xdp_counter_bytecode) - 1; 93 | uint64_t res = 0; 94 | llvmbpf_vm vm; 95 | std::cout << "running ebpf prog, code len: " << code_len << std::endl; 96 | 97 | res = vm.load_code(code, code_len); 98 | if (res) { 99 | std::cout << vm.get_error_message() << std::endl; 100 | exit(1); 101 | } 102 | vm.register_external_function(1, "bpf_map_lookup_elem", 103 | (void *)bpf_map_lookup_elem); 104 | // set the lddw helpers for accessing maps 105 | vm.set_lddw_helpers(map_by_fd, nullptr, map_val, nullptr, nullptr); 106 | auto func = vm.compile(); 107 | if (!func) { 108 | std::cout << vm.get_error_message() << std::endl; 109 | exit(1); 110 | } 111 | // Map value (counter) should be 0 112 | std::cout << "cntrs_array[0] = " << cntrs_array[0] << std::endl; 113 | int err = vm.exec(&bpf_mem, sizeof(bpf_mem), res); 114 | assert(err == 0 && cntrs_array[0] == 1); 115 | std::cout << "\nreturn value = " << res << std::endl; 116 | // counter should be 1 117 | std::cout << "cntrs_array[0] = " << cntrs_array[0] << std::endl; 118 | 119 | err = vm.exec(&bpf_mem, sizeof(bpf_mem), res); 120 | assert(err == 0 && cntrs_array[0] == 2); 121 | std::cout << "\nreturn value = " << res << std::endl; 122 | // counter should be 2 123 | std::cout << "cntrs_array[0] = " << cntrs_array[0] << std::endl; 124 | 125 | // Change the value of the control array can change the return value 126 | ctl_array[0] = 1; 127 | err = vm.exec(&bpf_mem, sizeof(bpf_mem), res); 128 | assert(err == 0); 129 | std::cout << "\nreturn value = " << res << std::endl; 130 | return 0; 131 | } 132 | -------------------------------------------------------------------------------- /test/test_framework/ubpf/disassembler.py: -------------------------------------------------------------------------------- 1 | import struct 2 | try: 3 | from StringIO import StringIO as io 4 | except ImportError: 5 | from io import StringIO as io 6 | 7 | Inst = struct.Struct("BBHI") 8 | 9 | CLASSES = { 10 | 0: "ld", 11 | 1: "ldx", 12 | 2: "st", 13 | 3: "stx", 14 | 4: "alu", 15 | 5: "jmp", 16 | 7: "alu64", 17 | } 18 | 19 | ALU_OPCODES = { 20 | 0: 'add', 21 | 1: 'sub', 22 | 2: 'mul', 23 | 3: 'div', 24 | 4: 'or', 25 | 5: 'and', 26 | 6: 'lsh', 27 | 7: 'rsh', 28 | 8: 'neg', 29 | 9: 'mod', 30 | 10: 'xor', 31 | 11: 'mov', 32 | 12: 'arsh', 33 | 13: '(endian)', 34 | } 35 | 36 | JMP_OPCODES = { 37 | 0: 'ja', 38 | 1: 'jeq', 39 | 2: 'jgt', 40 | 3: 'jge', 41 | 4: 'jset', 42 | 5: 'jne', 43 | 6: 'jsgt', 44 | 7: 'jsge', 45 | 8: 'call', 46 | 9: 'exit', 47 | 10: 'jlt', 48 | 11: 'jle', 49 | 12: 'jslt', 50 | 13: 'jsle', 51 | } 52 | 53 | MODES = { 54 | 0: 'imm', 55 | 1: 'abs', 56 | 2: 'ind', 57 | 3: 'mem', 58 | 6: 'xadd', 59 | } 60 | 61 | SIZES = { 62 | 0: 'w', 63 | 1: 'h', 64 | 2: 'b', 65 | 3: 'dw', 66 | } 67 | 68 | BPF_CLASS_LD = 0 69 | BPF_CLASS_LDX = 1 70 | BPF_CLASS_ST = 2 71 | BPF_CLASS_STX = 3 72 | BPF_CLASS_ALU = 4 73 | BPF_CLASS_JMP = 5 74 | BPF_CLASS_ALU64 = 7 75 | 76 | BPF_ALU_NEG = 8 77 | BPF_ALU_END = 13 78 | 79 | def R(reg): 80 | return "r" + str(reg) 81 | 82 | def I(imm): 83 | return "%#x" % imm 84 | 85 | def M(base, off): 86 | if off != 0: 87 | return "[%s%s]" % (base, O(off)) 88 | else: 89 | return "[%s]" % base 90 | 91 | def O(off): 92 | if off <= 32767: 93 | return "+" + str(off) 94 | else: 95 | return "-" + str(65536-off) 96 | 97 | def disassemble_one(data, offset): 98 | code, regs, off, imm = Inst.unpack_from(data, offset) 99 | dst_reg = regs & 0xf 100 | src_reg = (regs >> 4) & 0xf 101 | cls = code & 7 102 | 103 | class_name = CLASSES.get(cls) 104 | 105 | if cls == BPF_CLASS_ALU or cls == BPF_CLASS_ALU64: 106 | source = (code >> 3) & 1 107 | opcode = (code >> 4) & 0xf 108 | opcode_name = ALU_OPCODES.get(opcode) 109 | if cls == BPF_CLASS_ALU: 110 | opcode_name += "32" 111 | 112 | if opcode == BPF_ALU_END: 113 | opcode_name = source == 1 and "be" or "le" 114 | return "%s%d %s" % (opcode_name, imm, R(dst_reg)) 115 | elif opcode == BPF_ALU_NEG: 116 | return "%s %s" % (opcode_name, R(dst_reg)) 117 | elif source == 0: 118 | return "%s %s, %s" % (opcode_name, R(dst_reg), I(imm)) 119 | else: 120 | return "%s %s, %s" % (opcode_name, R(dst_reg), R(src_reg)) 121 | elif cls == BPF_CLASS_JMP: 122 | source = (code >> 3) & 1 123 | opcode = (code >> 4) & 0xf 124 | opcode_name = JMP_OPCODES.get(opcode) 125 | 126 | if opcode_name == "exit": 127 | return opcode_name 128 | elif opcode_name == "call": 129 | return "%s %s" % (opcode_name, I(imm)) 130 | elif opcode_name == "ja": 131 | return "%s %s" % (opcode_name, O(off)) 132 | elif source == 0: 133 | return "%s %s, %s, %s" % (opcode_name, R(dst_reg), I(imm), O(off)) 134 | else: 135 | return "%s %s, %s, %s" % (opcode_name, R(dst_reg), R(src_reg), O(off)) 136 | elif cls == BPF_CLASS_LD or cls == BPF_CLASS_LDX or cls == BPF_CLASS_ST or cls == BPF_CLASS_STX: 137 | size = (code >> 3) & 3 138 | mode = (code >> 5) & 7 139 | mode_name = MODES.get(mode, str(mode)) 140 | # TODO use different syntax for non-MEM instructions 141 | size_name = SIZES.get(size, str(size)) 142 | if code == 0x18: # lddw 143 | _, _, _, imm2 = Inst.unpack_from(data, offset+8) 144 | imm = (imm2 << 32) | imm 145 | return "%s %s, %s" % (class_name + size_name, R(dst_reg), I(imm)) 146 | elif code == 0x00: 147 | # Second instruction of lddw 148 | return None 149 | elif cls == BPF_CLASS_LDX: 150 | return "%s %s, %s" % (class_name + size_name, R(dst_reg), M(R(src_reg), off)) 151 | elif cls == BPF_CLASS_ST: 152 | return "%s %s, %s" % (class_name + size_name, M(R(dst_reg), off), I(imm)) 153 | elif cls == BPF_CLASS_STX: 154 | return "%s %s, %s" % (class_name + size_name, M(R(dst_reg), off), R(src_reg)) 155 | else: 156 | return "unknown mem instruction %#x" % code 157 | else: 158 | return "unknown instruction %#x" % code 159 | 160 | def disassemble(data): 161 | output = io() 162 | offset = 0 163 | while offset < len(data): 164 | s = disassemble_one(data, offset) 165 | if s: 166 | output.write(s + "\n") 167 | offset += 8 168 | return output.getvalue() 169 | -------------------------------------------------------------------------------- /test/include/test_minimal_bpf_host_ufunc.h: -------------------------------------------------------------------------------- 1 | #ifndef BPF_MINIMAL_TEST_UFUNC_DEFS_H 2 | #define BPF_MINIMAL_TEST_UFUNC_DEFS_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include "ebpf-vm.h" 8 | 9 | #define MAX_UFUNC_FUNCS 128 10 | #define MAX_ARGS 5 11 | 12 | /* Useful for eliminating compiler warnings. */ 13 | #define UFUNC_FN(f) ((void *)((void (*)(void))f)) 14 | 15 | enum ufunc_types { 16 | UFUNC_TYPE_VOID, 17 | UFUNC_TYPE_INT, 18 | UFUNC_TYPE_UINT, 19 | UFUNC_TYPE_LONG, 20 | UFUNC_TYPE_ULONG, 21 | UFUNC_TYPE_FLOAT, 22 | UFUNC_TYPE_DOUBLE, 23 | UFUNC_TYPE_POINTER, 24 | UFUNC_TYPE_STRUCT, 25 | UFUNC_TYPE_STRING, 26 | UFUNC_TYPE_BOOL, 27 | UFUNC_TYPE_INT8, 28 | UFUNC_TYPE_UINT8, 29 | UFUNC_TYPE_INT16, 30 | UFUNC_TYPE_UINT16, 31 | UFUNC_TYPE_INT32, 32 | UFUNC_TYPE_UINT32, 33 | UFUNC_TYPE_INT64, 34 | UFUNC_TYPE_UINT64, 35 | UFUNC_TYPE_INT128, 36 | UFUNC_TYPE_UINT128, 37 | UFUNC_TYPE_ENUM, 38 | UFUNC_TYPE_ARRAY, 39 | UFUNC_TYPE_UNION, 40 | UFUNC_TYPE_FUNCTION, 41 | }; 42 | 43 | static struct ebpf_ufunc_func_info *ebpf_resovle_ufunc_func(uint64_t func_id); 44 | 45 | typedef uint64_t (*ufunc_func)(uint64_t r1, uint64_t r2, uint64_t r3, 46 | uint64_t r4, uint64_t r5); 47 | 48 | struct ebpf_ufunc_func_info { 49 | ufunc_func func; 50 | enum ufunc_types ret_type; 51 | enum ufunc_types arg_types[MAX_ARGS]; 52 | int num_args; 53 | }; 54 | 55 | struct arg_list { 56 | uint64_t args[6]; 57 | }; 58 | 59 | union arg_val { 60 | uint64_t uint64; 61 | int64_t int64; 62 | double double_val; 63 | void *ptr; 64 | }; 65 | 66 | static inline union arg_val to_arg_val(enum ufunc_types type, uint64_t val) 67 | { 68 | union arg_val arg = { .uint64 = 0 }; 69 | switch (type) { 70 | case UFUNC_TYPE_INT: 71 | case UFUNC_TYPE_UINT: 72 | arg.uint64 = val; 73 | break; 74 | case UFUNC_TYPE_DOUBLE: 75 | #pragma GCC diagnostic push 76 | #pragma GCC diagnostic ignored "-Wstrict-aliasing" 77 | arg.double_val = *(double *)(uintptr_t)&val; 78 | #pragma GCC diagnostic pop 79 | break; 80 | case UFUNC_TYPE_POINTER: 81 | arg.ptr = (void *)(uintptr_t)val; 82 | break; 83 | default: 84 | // Handle other types 85 | break; 86 | } 87 | return arg; 88 | } 89 | 90 | static inline uint64_t from_arg_val(enum ufunc_types type, union arg_val val) 91 | { 92 | switch (type) { 93 | case UFUNC_TYPE_INT: 94 | case UFUNC_TYPE_UINT: 95 | return val.uint64; 96 | case UFUNC_TYPE_DOUBLE: 97 | #pragma GCC diagnostic push 98 | #pragma GCC diagnostic ignored "-Wstrict-aliasing" 99 | return *(uint64_t *)(uintptr_t)&val.double_val; 100 | #pragma GCC diagnostic pop 101 | case UFUNC_TYPE_POINTER: 102 | return (uint64_t)(uintptr_t)val.ptr; 103 | default: 104 | // Handle other types 105 | break; 106 | } 107 | return 0; 108 | } 109 | 110 | static uint64_t __ebpf_call_ufunc_dispatcher(uint64_t id, uint64_t arg_list) 111 | { 112 | assert(id < MAX_UFUNC_FUNCS); 113 | struct ebpf_ufunc_func_info *func_info = ebpf_resovle_ufunc_func(id); 114 | assert(func_info->func != NULL); 115 | // Prepare arguments 116 | struct arg_list *raw_args = (struct arg_list *)(uintptr_t)arg_list; 117 | union arg_val args[5]; 118 | #pragma GCC diagnostic push 119 | #pragma GCC diagnostic ignored "-Wnull-dereference" 120 | for (int i = 0; i < func_info->num_args; i++) { 121 | args[i] = 122 | to_arg_val(func_info->arg_types[i], raw_args->args[i]); 123 | } 124 | #pragma GCC diagnostic pop 125 | // Call the function 126 | union arg_val ret = { .uint64 = 0 }; 127 | switch (func_info->num_args) { 128 | case 0: 129 | ret.uint64 = func_info->func(0, 0, 0, 0, 0); 130 | break; 131 | case 1: 132 | ret.uint64 = func_info->func(args[0].uint64, 0, 0, 0, 0); 133 | break; 134 | case 2: 135 | ret.uint64 = func_info->func(args[0].uint64, args[1].uint64, 0, 136 | 0, 0); 137 | break; 138 | case 3: 139 | ret.uint64 = func_info->func(args[0].uint64, args[1].uint64, 140 | args[2].uint64, 0, 0); 141 | break; 142 | case 4: 143 | ret.uint64 = func_info->func(args[0].uint64, args[1].uint64, 144 | args[2].uint64, args[3].uint64, 0); 145 | break; 146 | case 5: 147 | ret.uint64 = func_info->func(args[0].uint64, args[1].uint64, 148 | args[2].uint64, args[3].uint64, 149 | args[4].uint64); 150 | break; 151 | default: 152 | // Handle other cases 153 | break; 154 | } 155 | 156 | // Convert the return value to the correct type 157 | return from_arg_val(func_info->ret_type, ret); 158 | } 159 | 160 | static uint64_t print_func(char *str) 161 | { 162 | printf("helper-1: %s\n", str); 163 | return strlen(str); 164 | } 165 | 166 | static int add_func(int a, int b) 167 | { 168 | return a + b; 169 | } 170 | 171 | /* temperially used for test. 172 | Should be implemented via resolvering the function via function name from BTF 173 | symbols 174 | */ 175 | struct ebpf_ufunc_func_info func_list[] = { 176 | { NULL, UFUNC_TYPE_INT, { UFUNC_TYPE_POINTER }, 1 }, 177 | { UFUNC_FN(add_func), 178 | UFUNC_TYPE_INT, 179 | { UFUNC_TYPE_INT, UFUNC_TYPE_INT }, 180 | 2 }, 181 | { UFUNC_FN(print_func), UFUNC_TYPE_ULONG, { UFUNC_TYPE_POINTER }, 1 }, 182 | }; 183 | 184 | static struct ebpf_ufunc_func_info *ebpf_resovle_ufunc_func(uint64_t func_id) 185 | { 186 | const uint64_t N_FUNC = 187 | sizeof(func_list) / sizeof(struct ebpf_ufunc_func_info); 188 | if (func_id < N_FUNC) { 189 | return &func_list[func_id]; 190 | } 191 | assert(0); 192 | return NULL; 193 | } 194 | 195 | static void register_ufunc_handler(struct ebpf_vm *vm) 196 | { 197 | ebpf_register(vm, 1, "__ebpf_call_ufunc_dispatcher", 198 | __ebpf_call_ufunc_dispatcher); 199 | } 200 | 201 | // static struct bpftime_prog *bpftime_create_context(void) 202 | // { 203 | // struct bpftime_prog *prog = 204 | // (struct bpftime_prog *)malloc(sizeof(struct bpftime_prog)); 205 | // struct ebpf_vm *vm = ebpf_create(); 206 | // context->vm = vm; 207 | // ebpf_register(context->vm, 1, "__ebpf_call_ufunc_dispatcher", 208 | // __ebpf_call_ufunc_dispatcher); 209 | // return context; 210 | // } 211 | 212 | #endif 213 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.16) 2 | cmake_policy(SET CMP0079 NEW) 3 | project( 4 | "llvm-bpf-jit" 5 | LANGUAGES C CXX 6 | VERSION 0.1.0 7 | ) 8 | 9 | if(NOT CMAKE_BUILD_TYPE) 10 | set(CMAKE_BUILD_TYPE "Debug") 11 | endif() 12 | 13 | if(NOT DEFINED SPDLOG_ACTIVE_LEVEL) 14 | if(CMAKE_BUILD_TYPE STREQUAL "Debug") 15 | add_compile_definitions(SPDLOG_ACTIVE_LEVEL=SPDLOG_LEVEL_TRACE) 16 | else() 17 | add_compile_definitions(SPDLOG_ACTIVE_LEVEL=SPDLOG_LEVEL_INFO) 18 | endif() 19 | endif() 20 | 21 | function(bpftime_setup_target target) 22 | set_property(TARGET ${target} PROPERTY CXX_STANDARD 20) 23 | target_include_directories(${target} 24 | PUBLIC src "include") 25 | set_target_properties(${target} PROPERTIES 26 | RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}") 27 | endfunction() 28 | 29 | function(bpftime_add_executable target) 30 | add_executable(${target} ${ARGN}) 31 | bpftime_setup_target(${target}) 32 | endfunction() 33 | 34 | function(bpftime_add_library target) 35 | add_library(${target} ${ARGN}) 36 | bpftime_setup_target(${target}) 37 | endfunction() 38 | 39 | bpftime_add_library(llvmbpf_vm 40 | src/llvm_jit_context.cpp 41 | src/compiler.cpp 42 | src/compiler_utils.cpp 43 | src/vm.cpp 44 | ) 45 | 46 | set_target_properties(llvmbpf_vm PROPERTIES ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/../") 47 | 48 | find_package(LLVM REQUIRED CONFIG) 49 | 50 | message(STATUS "Checking LLVM_VERSION_MAJOR macro value...") 51 | message(STATUS "LLVM_VERSION_MAJOR is: ${LLVM_VERSION_MAJOR}") 52 | 53 | if(${LLVM_PACKAGE_VERSION} VERSION_LESS 15) 54 | message(FATAL_ERROR "LLVM version must be >=15") 55 | endif() 56 | 57 | option(ENABLE_LLVM_SHARED "Link shared library of LLVM" NO) 58 | option(BUILD_LLVM_AOT_CLI "Build AOT cli, which rely on libbpf" NO) 59 | option(BPFTIME_ENABLE_LLVM_PRELOAD "Enable openEuler-specific LLVM preload workaround" OFF) 60 | 61 | if(ENABLE_LLVM_SHARED) 62 | list(PREPEND LLVM_LIBS LLVM) 63 | else() 64 | set(LLVM_COMPONENTS 65 | ${LLVM_TARGETS_TO_BUILD} 66 | Core 67 | Support 68 | Target 69 | Analysis 70 | ipo 71 | TransformUtils 72 | BitReader 73 | AsmParser 74 | Linker 75 | MC 76 | MCParser 77 | MCDisassembler 78 | Object 79 | ExecutionEngine 80 | RuntimeDyld 81 | OrcJIT 82 | MCJIT 83 | # X86 84 | X86CodeGen 85 | X86Desc 86 | X86Info 87 | X86AsmParser 88 | # NVPTX 89 | NVPTXCodeGen 90 | NVPTXDesc 91 | NVPTXInfo 92 | # AArch64 93 | AArch64CodeGen 94 | AArch64Desc 95 | AArch64Info 96 | AArch64AsmParser 97 | ) 98 | 99 | # Add SPIR-V components only if SPIR-V is enabled and LLVM version supports it 100 | if(LLVMBPF_ENABLE_SPIRV AND ${LLVM_VERSION_MAJOR} GREATER_EQUAL 18) 101 | list(APPEND LLVM_COMPONENTS 102 | SPIRVCodeGen 103 | SPIRVDesc 104 | SPIRVInfo 105 | ) 106 | message(STATUS "Adding SPIR-V LLVM components (LLVM ${LLVM_VERSION_MAJOR})") 107 | endif() 108 | 109 | llvm_map_components_to_libnames(LLVM_LIBS ${LLVM_COMPONENTS}) 110 | endif() 111 | include(FetchContent) 112 | 113 | if(NOT DEFINED SPDLOG_INCLUDE) 114 | message(STATUS " Adding spdlog seperately..") 115 | 116 | # spdlog 117 | # Fetch spdlog 118 | FetchContent_Declare( 119 | spdlog 120 | GIT_REPOSITORY https://github.com/gabime/spdlog.git 121 | GIT_TAG v1.15.2 # Specify the version you want to use 122 | ) 123 | 124 | # Make the spdlog target available 125 | FetchContent_MakeAvailable(spdlog) 126 | endif() 127 | 128 | # if BPFTIME_LLVM_JIT is set, then it's built in the bpftime project. 129 | # If not, it's built as a standalone library. 130 | if(${BPFTIME_LLVM_JIT}) 131 | add_subdirectory(cli) 132 | else() 133 | if(${BUILD_LLVM_AOT_CLI}) 134 | add_subdirectory(cli) 135 | endif() 136 | 137 | 138 | if(${BPFTIME_ENABLE_UNIT_TESTING}) 139 | if(NOT TARGET Catch2) 140 | message(STATUS "Adding Catch2 by FetchContent at llvmbpf") 141 | FetchContent_Declare( 142 | Catch2 143 | GIT_REPOSITORY https://github.com/catchorg/Catch2.git 144 | GIT_TAG v3.4.0 # Specify the version you want to use 145 | ) 146 | 147 | # Make the Catch2 target available 148 | FetchContent_MakeAvailable(Catch2) 149 | endif() 150 | endif() 151 | endif() 152 | 153 | message(STATUS "LLVM_LIBS=${LLVM_LIBS}") 154 | 155 | target_link_libraries(llvmbpf_vm PRIVATE ${LLVM_LIBS} PRIVATE spdlog::spdlog) 156 | target_include_directories(llvmbpf_vm 157 | PRIVATE ${LLVM_INCLUDE_DIRS} ${SPDLOG_INCLUDE} ${Boost_INCLUDE} ../include include # LLVM jit also used these headers 158 | ) 159 | add_dependencies(llvmbpf_vm spdlog::spdlog) 160 | if(BPFTIME_ENABLE_LLVM_PRELOAD) 161 | target_compile_definitions(llvmbpf_vm PRIVATE BPFTIME_ENABLE_LLVM_PRELOAD=1) 162 | endif() 163 | 164 | # SPIR-V support requires LLVM 18+ (native backend in LLVM 20+) 165 | if(LLVMBPF_ENABLE_SPIRV) 166 | if(${LLVM_VERSION_MAJOR} LESS 18) 167 | message(FATAL_ERROR "SPIR-V support requires LLVM 18 or higher. Current version: ${LLVM_VERSION_MAJOR}.${LLVM_VERSION_MINOR}") 168 | endif() 169 | if(${LLVM_VERSION_MAJOR} GREATER_EQUAL 20) 170 | message(STATUS "SPIR-V: Using native LLVM backend (LLVM ${LLVM_VERSION_MAJOR})") 171 | elseif(${LLVM_VERSION_MAJOR} GREATER_EQUAL 18) 172 | message(STATUS "SPIR-V: LLVM ${LLVM_VERSION_MAJOR} detected. Native backend available in LLVM 20+") 173 | message(STATUS "SPIR-V: Consider upgrading to LLVM 20 for native support") 174 | endif() 175 | target_compile_definitions(llvmbpf_vm PRIVATE LLVMBPF_HAS_SPIRV=1) 176 | endif() 177 | 178 | if(BPFTIME_ENABLE_CODE_COVERAGE) 179 | target_compile_options(llvmbpf_vm PUBLIC -O0 -g -fprofile-arcs -ftest-coverage) 180 | target_link_options(llvmbpf_vm PUBLIC -fprofile-arcs -ftest-coverage) 181 | message(DEBUG "Code coverage is enabled and provided with GCC.") 182 | endif() 183 | 184 | if(BPFTIME_ENABLE_UNIT_TESTING) 185 | message(STATUS "Build unit tests for the project. Tests should always be found in the test folder\n") 186 | add_subdirectory(test) 187 | endif() 188 | 189 | add_subdirectory(example) 190 | 191 | 192 | option(LLVMBPF_ENABLE_PTX "Whether build the ptx demo program, which requires CUDA" OFF) 193 | option(LLVMBPF_ENABLE_SPIRV "Whether build the SPIR-V demo program, which requires OpenCL" OFF) 194 | 195 | if(LLVMBPF_ENABLE_PTX) 196 | add_subdirectory(example/ptx) 197 | endif() 198 | 199 | if(LLVMBPF_ENABLE_SPIRV) 200 | add_subdirectory(example/spirv) 201 | endif() 202 | -------------------------------------------------------------------------------- /CLAUDE.md: -------------------------------------------------------------------------------- 1 | # CLAUDE.md 2 | 3 | This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. 4 | 5 | ## Project Overview 6 | 7 | llvmbpf is a high-performance userspace eBPF VM with LLVM JIT/AOT compiler. It compiles eBPF bytecode to native code via LLVM IR, supports multi-architecture execution (x86, ARM, NVPTX/CUDA), and provides AOT compilation of eBPF programs into standalone native ELF objects. 8 | 9 | This is a standalone VM library extracted from the bpftime project, focused solely on compilation and execution without maps, helpers, verifiers, or loaders. 10 | 11 | ## Build Commands 12 | 13 | ### Standard Build (Release) 14 | ```sh 15 | sudo apt install llvm-15-dev libzstd-dev 16 | cmake -B build -DCMAKE_BUILD_TYPE=Release 17 | cmake --build build --target all -j 18 | ``` 19 | 20 | ### Build with AOT CLI Tool 21 | ```sh 22 | sudo apt-get install libelf1 libelf-dev 23 | cmake -B build -DBUILD_LLVM_AOT_CLI=1 24 | cmake --build build --target all -j 25 | ``` 26 | 27 | ### Build with PTX/CUDA Support 28 | ```sh 29 | # Set LLVMBPF_CUDA_PATH to your CUDA installation (e.g., /usr/local/cuda-12.6) 30 | cmake -B build -DCMAKE_BUILD_TYPE=Release -DLLVMBPF_ENABLE_PTX=1 -DLLVMBPF_CUDA_PATH=/usr/local/cuda-12.6 31 | cmake --build build --target all -j 32 | ``` 33 | 34 | ### Build with SPIR-V/OpenCL Support 35 | ```sh 36 | # Requires LLVM 16+ with SPIR-V backend 37 | sudo apt install llvm-16-dev opencl-headers ocl-icd-opencl-dev 38 | cmake -B build -DCMAKE_BUILD_TYPE=Release -DLLVMBPF_ENABLE_SPIRV=1 39 | cmake --build build --target spirv_opencl_test -j 40 | ``` 41 | 42 | ### Build for Development/Testing 43 | ```sh 44 | # With unit tests and code coverage 45 | LLVM_DIR=/usr/lib/llvm-15/cmake cmake -B build -DCMAKE_BUILD_TYPE=Debug -DBPFTIME_ENABLE_UNIT_TESTING=1 -DBPFTIME_ENABLE_CODE_COVERAGE=1 -G Ninja 46 | cmake --build build --target all -j 47 | ``` 48 | 49 | ## Testing 50 | 51 | ### Run Unit Tests 52 | ```sh 53 | # Build with testing enabled first (see above) 54 | ./build/test/unit-test/llvm_jit_tests 55 | ``` 56 | 57 | ### Run BPF Conformance Tests 58 | ```sh 59 | # Create Python virtual environment 60 | python3.8 -m venv ./test 61 | source test/bin/activate 62 | pip install -r test/requirements.txt 63 | 64 | # Run pytest 65 | pytest -v -s test/test_framework 66 | # Or exclude known failures: 67 | pytest -k "test_jit.py and not err-infinite" 68 | ``` 69 | 70 | ### Run Example Programs 71 | ```sh 72 | ./build/vm-llvm-example 73 | ./build/maps-example 74 | ./build/example/ptx/ptx_test # If built with PTX support 75 | ./build/example/spirv/spirv_opencl_test # If built with SPIR-V support 76 | ``` 77 | 78 | ## Architecture 79 | 80 | ### Core Components 81 | 82 | **VM Layer (`include/llvmbpf.hpp`, `src/vm.cpp`)** 83 | - `llvmbpf_vm`: Main VM class exposing the public API 84 | - `precompiled_ebpf_function`: Function pointer type for JITed code (signature: `uint64_t (*)(void *mem, size_t mem_len)`) 85 | - Key methods: `load_code()`, `compile()`, `exec()`, `do_aot_compile()`, `load_aot_object()` 86 | 87 | **JIT/AOT Compiler (`src/llvm_jit_context.cpp`, `src/llvm_jit_context.hpp`)** 88 | - `llvm_bpf_jit_context`: Internal LLVM compilation context 89 | - Uses LLVM OrcJIT for runtime compilation 90 | - Generates LLVM IR from eBPF bytecode, then compiles to native code or PTX 91 | 92 | **eBPF → LLVM Translation (`src/compiler.cpp`, `src/compiler_utils.cpp`)** 93 | - `generateModule()`: Converts eBPF instructions to LLVM IR 94 | - Handles eBPF instruction set including LDDW (64-bit immediate loads) 95 | - Implements stack (512 bytes), registers (r0-r10), and external function calls 96 | 97 | **Instruction Set (`src/ebpf_inst.h`)** 98 | - Defines eBPF instruction structure and opcodes 99 | - Standard eBPF ISA with support for maps via LDDW helpers 100 | 101 | ### LDDW Helpers and Maps 102 | 103 | The VM supports map access through LDDW (load double-word) helpers that resolve map references: 104 | - `__lddw_helper_map_by_fd`: Resolve map by file descriptor 105 | - `__lddw_helper_map_by_idx`: Resolve map by index 106 | - `__lddw_helper_map_val`: Get map value pointer 107 | - `__lddw_helper_var_addr`: Get variable address 108 | - `__lddw_helper_code_addr`: Get code address 109 | 110 | Set these via `vm.set_lddw_helpers()`. Maps can be accessed via helper functions (e.g., `bpf_map_lookup_elem`) or as global variables. 111 | 112 | ### External Functions 113 | 114 | Register eBPF helpers via `register_external_function(index, name, fn_ptr)`. The compiler will generate calls to these external functions when the eBPF program invokes helpers. 115 | 116 | ### Compilation Modes 117 | 118 | **JIT Mode**: `compile()` returns a function pointer that can be called directly 119 | **AOT Mode**: `do_aot_compile()` generates native ELF object files that can be: 120 | - Linked with C code to create standalone binaries 121 | - Loaded back into the VM via `load_aot_object()` 122 | **PTX Mode**: `generate_ptx()` emits CUDA PTX assembly for NVIDIA GPU execution 123 | **SPIR-V Mode**: `generate_spirv()` emits SPIR-V binary for cross-vendor GPU execution (OpenCL, Vulkan) 124 | 125 | ## CLI Tool Usage 126 | 127 | The `bpftime-vm` CLI tool (in `cli/`) provides AOT compilation: 128 | 129 | ```sh 130 | # Generate LLVM IR 131 | ./build/cli/bpftime-vm build program.bpf.o -emit-llvm > program.ll 132 | 133 | # AOT compile to native ELF 134 | ./build/cli/bpftime-vm build program.bpf.o 135 | # Outputs: program_name.o for each program in the ELF 136 | 137 | # Run AOT-compiled program 138 | ./build/cli/bpftime-vm run program_name.o input.bin 139 | ``` 140 | 141 | Note: The standalone CLI does not support helpers/maps. For full functionality, use bpftime's tools. 142 | 143 | ## Key Constraints 144 | 145 | - **LLVM Version**: 146 | - Minimum: LLVM >= 15 for JIT/AOT/PTX 147 | - SPIR-V: LLVM >= 16 (requires native SPIR-V backend) 148 | - **eBPF Stack Size**: 512 bytes (`EBPF_STACK_SIZE`) 149 | - **Max External Functions**: 8192 (`MAX_EXT_FUNCS`) 150 | - **No Built-in Maps/Helpers**: The library provides hooks but no implementations 151 | 152 | ## Integration with bpftime 153 | 154 | llvmbpf is designed as a component of the larger bpftime project. For loading eBPF from ELF files with proper relocations, or for full userspace eBPF runtime with maps/helpers/verifiers, use bpftime. The bpftime project can dump map definitions and relocated bytecode via `bpftimetool export`, which can then be used to build standalone binaries. 155 | 156 | ## Examples Directory Structure 157 | 158 | - `example/basic.cpp`: Basic VM usage 159 | - `example/maps.cpp`: Map access via helpers and LDDW 160 | - `example/standalone/`: Standalone binary compilation example 161 | - `example/inline/`: Inlining optimization example (merge helper functions into LLVM IR) 162 | - `example/load-llvm-ir/`: Load original LLVM IR instead of eBPF bytecode 163 | - `example/ptx/`: CUDA PTX generation examples (NVIDIA GPUs) 164 | - `example/spirv/`: SPIR-V generation examples (OpenCL, cross-vendor GPUs) 165 | -------------------------------------------------------------------------------- /cli/main.cpp: -------------------------------------------------------------------------------- 1 | #include "spdlog/spdlog.h" 2 | #include "spdlog/cfg/env.h" 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include "llvmbpf.hpp" 14 | 15 | extern "C" { 16 | struct bpf_object; 17 | struct bpf_program; 18 | struct bpf_insn; 19 | void bpf_object__close(bpf_object *obj); 20 | bpf_program *bpf_object__next_program(const bpf_object *obj, bpf_program *prog); 21 | const char *bpf_program__name(const bpf_program *prog); 22 | bpf_object *bpf_object__open(const char *path); 23 | const bpf_insn *bpf_program__insns(const bpf_program *prog); 24 | size_t bpf_program__insn_cnt(const bpf_program *prog); 25 | } 26 | 27 | using namespace bpftime; 28 | 29 | static void print_usage(const std::string &program_name) 30 | { 31 | std::cerr 32 | << "Usage: " << program_name << " [options]\n" 33 | << "Commands:\n" 34 | << " build [-o ] [-emit-llvm]\n" 35 | << " Build native ELF(s) from eBPF ELF. Each program in the eBPF ELF will be built into a single native ELF.\n" 36 | << " If -emit-llvm is specified, the LLVM IR will be printed to stdout.\n" 37 | << " run [MEMORY]\n" 38 | << " Run a native eBPF program.\n"; 39 | } 40 | 41 | static std::optional 42 | parse_optional_argument(int argc, const char **argv, int &i, 43 | const std::string &option) 44 | { 45 | if (std::string(argv[i]) == option && i + 1 < argc) { 46 | return argv[++i]; 47 | } 48 | return std::nullopt; 49 | } 50 | 51 | static bool has_argument(int argc, const char **argv, const std::string &option) 52 | { 53 | for (int i = 0; i < argc; ++i) { 54 | if (std::string(argv[i]) == option) { 55 | return true; 56 | } 57 | } 58 | return false; 59 | } 60 | 61 | uint64_t bpftime_trace_printk(uint64_t fmt, uint64_t fmt_size, ...) 62 | { 63 | const char *fmt_str = (const char *)fmt; 64 | va_list args; 65 | #pragma GCC diagnostic push 66 | #pragma GCC diagnostic ignored "-Wformat-nonliteral" 67 | #pragma GCC diagnostic ignored "-Wvarargs" 68 | va_start(args, fmt_str); 69 | long ret = vprintf(fmt_str, args); 70 | #pragma GCC diagnostic pop 71 | va_end(args); 72 | return 0; 73 | } 74 | 75 | static int build_ebpf_program(const std::string &ebpf_elf, 76 | const std::filesystem::path &output, 77 | bool emit_llvm) 78 | { 79 | bpf_object *obj = bpf_object__open(ebpf_elf.c_str()); 80 | if (!obj) { 81 | SPDLOG_CRITICAL("Unable to open BPF ELF: {}", errno); 82 | return 1; 83 | } 84 | std::unique_ptr elf( 85 | obj, bpf_object__close); 86 | bpf_program *prog; 87 | for ((prog) = bpf_object__next_program((elf.get()), __null); 88 | (prog) != __null; 89 | (prog) = bpf_object__next_program((elf.get()), (prog))) { 90 | const char *name = bpf_program__name(prog); 91 | if (!emit_llvm) 92 | SPDLOG_INFO("Processing program {}", name); 93 | llvmbpf_vm vm; 94 | 95 | if (vm.load_code((const void *)bpf_program__insns(prog), 96 | (uint32_t)bpf_program__insn_cnt(prog) * 8) < 97 | 0) { 98 | SPDLOG_ERROR( 99 | "Unable to load instructions of program {}: {}", 100 | name, vm.get_error_message()); 101 | return 1; 102 | } 103 | // add 1000 pesudo helpers so it can be used with helpers 104 | for (int i = 0; i < 1000; i++) { 105 | vm.register_external_function( 106 | i, "helper_" + std::to_string(i), nullptr); 107 | } 108 | auto result = vm.do_aot_compile(emit_llvm); 109 | if (!result) { 110 | SPDLOG_ERROR("Failed to compile program {}: {}", name, 111 | vm.get_error_message()); 112 | return 1; 113 | } 114 | auto out_path = output / (std::string(name) + ".o"); 115 | std::ofstream ofs(out_path, std::ios::binary); 116 | ofs.write((const char *)result->data(), result->size()); 117 | if (!emit_llvm) 118 | SPDLOG_INFO("Program {} written to {}", name, 119 | out_path.c_str()); 120 | } 121 | return 0; 122 | } 123 | 124 | using bpf_func = uint64_t (*)(const void *, uint64_t); 125 | 126 | static int run_ebpf_program(const std::filesystem::path &elf, 127 | std::optional memory) 128 | { 129 | std::ifstream file(elf, std::ios::binary | std::ios::ate); 130 | if (!file.is_open()) { 131 | SPDLOG_CRITICAL("Unable to open ELF file: {}", elf.string()); 132 | return 1; 133 | } 134 | 135 | auto size = file.tellg(); 136 | std::vector file_buffer(size); 137 | 138 | file.seekg(0, std::ios::beg); 139 | if (!file.read((char *)file_buffer.data(), size)) { 140 | SPDLOG_CRITICAL("Failed to read ELF file: {}", elf.string()); 141 | return 1; 142 | } 143 | 144 | file.close(); 145 | 146 | llvmbpf_vm vm; 147 | vm.register_external_function(6, "bpf_trace_printk", 148 | (void *)bpftime_trace_printk); 149 | auto func = vm.load_aot_object(file_buffer); 150 | if (!func) { 151 | SPDLOG_CRITICAL("Failed to load AOT object from ELF file: {}", 152 | vm.get_error_message()); 153 | return 1; 154 | } 155 | 156 | uint64_t return_val; 157 | if (memory) { 158 | std::ifstream mem_file(*memory, 159 | std::ios::binary | std::ios::ate); 160 | if (!mem_file.is_open()) { 161 | SPDLOG_CRITICAL("Unable to open memory file: {}", 162 | *memory); 163 | return 1; 164 | } 165 | 166 | auto mem_size = mem_file.tellg(); 167 | std::vector mem_buffer(mem_size); 168 | 169 | mem_file.seekg(0, std::ios::beg); 170 | if (!mem_file.read((char *)mem_buffer.data(), mem_size)) { 171 | SPDLOG_CRITICAL("Failed to read memory file: {}", 172 | *memory); 173 | return 1; 174 | } 175 | 176 | mem_file.close(); 177 | 178 | int res = vm.exec(mem_buffer.data(), mem_buffer.size(), 179 | return_val); 180 | if (res < 0) { 181 | SPDLOG_CRITICAL("Execution failed: {}", 182 | vm.get_error_message()); 183 | return 1; 184 | } 185 | } else { 186 | int res = vm.exec(nullptr, 0, return_val); 187 | if (res < 0) { 188 | SPDLOG_CRITICAL("Execution failed: {}", 189 | vm.get_error_message()); 190 | return 1; 191 | } 192 | } 193 | 194 | SPDLOG_INFO("Program executed successfully. Return value: {}", 195 | return_val); 196 | return 0; 197 | } 198 | 199 | int main(int argc, const char **argv) 200 | { 201 | spdlog::cfg::load_env_levels(); 202 | // Check for at least one argument (the command) 203 | if (argc < 2) { 204 | print_usage(argv[0]); 205 | return 1; 206 | } 207 | 208 | std::string command = argv[1]; 209 | 210 | if (command == "build") { 211 | if (argc < 3) { 212 | print_usage(argv[0]); 213 | return 1; 214 | } 215 | 216 | std::string ebpf_elf = argv[2]; 217 | std::string output = "."; 218 | 219 | // Parse optional output argument 220 | for (int i = 3; i < argc; ++i) { 221 | auto opt_output = 222 | parse_optional_argument(argc, argv, i, "-o"); 223 | if (opt_output) { 224 | output = *opt_output; 225 | } 226 | } 227 | 228 | bool emit_llvm = has_argument(argc, argv, "-emit-llvm"); 229 | 230 | return build_ebpf_program(ebpf_elf, output, emit_llvm); 231 | } else if (command == "run") { 232 | if (argc < 3) { 233 | print_usage(argv[0]); 234 | return 1; 235 | } 236 | 237 | std::filesystem::path elf_path = argv[2]; 238 | std::optional memory_file; 239 | 240 | if (argc > 3) { 241 | memory_file = argv[3]; 242 | } 243 | 244 | return run_ebpf_program(elf_path, memory_file); 245 | } else { 246 | std::cerr << "Unknown command: " << command << "\n"; 247 | print_usage(argv[0]); 248 | return 1; 249 | } 250 | } 251 | -------------------------------------------------------------------------------- /test/src/test.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Big Switch Networks, Inc 3 | * Copyright 2017 Google Inc. 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | #define _GNU_SOURCE 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include "ebpf-vm.h" 29 | 30 | // void 31 | // ebpf_set_register_offset(int x); 32 | static void* 33 | readfile(const char* path, size_t maxlen, size_t* len); 34 | static void 35 | register_functions(struct ebpf_vm* vm); 36 | 37 | static void 38 | usage(const char* name) 39 | { 40 | fprintf(stderr, "usage: %s [-h] [-j|--jit] [-m|--mem PATH] BINARY\n", name); 41 | fprintf(stderr, "\nExecutes the eBPF code in BINARY and prints the result to stdout.\n"); 42 | fprintf( 43 | stderr, "If --mem is given then the specified file will be read and a pointer\nto its data passed in r1.\n"); 44 | fprintf(stderr, "If --jit is given then the JIT compiler will be used.\n"); 45 | fprintf(stderr, "\nOther options:\n"); 46 | fprintf(stderr, " -r, --register-offset NUM: Change the mapping from eBPF to x86 registers\n"); 47 | fprintf(stderr, " -U, --unload: unload the code and reload it (for testing only)\n"); 48 | fprintf( 49 | stderr, " -R, --reload: reload the code, without unloading it first (for testing only, this should fail)\n"); 50 | } 51 | 52 | int 53 | main(int argc, char** argv) 54 | { 55 | struct option longopts[] = { 56 | { 57 | .name = "help", 58 | .val = 'h', 59 | }, 60 | {.name = "mem", .val = 'm', .has_arg = 1}, 61 | {.name = "jit", .val = 'j'}, 62 | {.name = "register-offset", .val = 'r', .has_arg = 1}, 63 | {.name = "unload", .val = 'U'}, /* for unit test only */ 64 | {.name = "reload", .val = 'R'}, /* for unit test only */ 65 | {0}}; 66 | 67 | const char* mem_filename = NULL; 68 | bool jit = false; 69 | bool unload = false; 70 | bool reload = false; 71 | 72 | uint64_t secret = (uint64_t)rand() << 32 | (uint64_t)rand(); 73 | 74 | int opt; 75 | while ((opt = getopt_long(argc, argv, "hm:jr:UR", longopts, NULL)) != -1) { 76 | switch (opt) { 77 | case 'm': 78 | mem_filename = optarg; 79 | break; 80 | case 'j': 81 | jit = true; 82 | break; 83 | case 'r': 84 | #if defined(__x86_64__) || defined(_M_X64) 85 | // ebpf_set_register_offset(atoi(optarg)); 86 | #endif 87 | break; 88 | case 'h': 89 | usage(argv[0]); 90 | return 0; 91 | case 'U': 92 | unload = true; 93 | break; 94 | case 'R': 95 | reload = true; 96 | break; 97 | default: 98 | usage(argv[0]); 99 | return 0; 100 | } 101 | } 102 | 103 | if (unload && reload) { 104 | fprintf(stderr, "-U and -R can not be used together\n"); 105 | return 1; 106 | } 107 | 108 | if (argc != optind + 1) { 109 | usage(argv[0]); 110 | return 0; 111 | } 112 | 113 | const char* code_filename = argv[optind]; 114 | size_t code_len; 115 | void* code = readfile(code_filename, 1024 * 1024, &code_len); 116 | if (code == NULL) { 117 | return 1; 118 | } 119 | 120 | size_t mem_len = 0; 121 | void* mem = NULL; 122 | if (mem_filename != NULL) { 123 | mem = readfile(mem_filename, 1024 * 1024, &mem_len); 124 | if (mem == NULL) { 125 | return 1; 126 | } 127 | } 128 | 129 | struct ebpf_vm* vm = ebpf_create("llvm"); 130 | if (!vm) { 131 | fprintf(stderr, "Failed to create VM\n"); 132 | return 1; 133 | } 134 | 135 | if (ebpf_set_pointer_secret(vm, secret) != 0) { 136 | fprintf(stderr, "Failed to set pointer secret\n"); 137 | return 1; 138 | } 139 | 140 | register_functions(vm); 141 | 142 | /* 143 | * The ELF magic corresponds to an RSH instruction with an offset, 144 | * which is invalid. 145 | */ 146 | #if defined(UBPF_HAS_ELF_H) 147 | bool elf = code_len >= SELFMAG && !memcmp(code, ELFMAG, SELFMAG); 148 | #endif 149 | 150 | char* errmsg; 151 | int rv; 152 | load: 153 | #if defined(UBPF_HAS_ELF_H) 154 | if (elf) { 155 | rv = ebpf_load_elf(vm, code, code_len, &errmsg); 156 | } else { 157 | #endif 158 | rv = ebpf_load(vm, code, code_len, &errmsg); 159 | #if defined(UBPF_HAS_ELF_H) 160 | } 161 | #endif 162 | if (unload) { 163 | ebpf_unload_code(vm); 164 | unload = false; 165 | goto load; 166 | } 167 | if (reload) { 168 | reload = false; 169 | goto load; 170 | } 171 | 172 | free(code); 173 | 174 | if (rv < 0) { 175 | fprintf(stderr, "Failed to load code: %s\n", errmsg); 176 | free(errmsg); 177 | ebpf_destroy(vm); 178 | return 1; 179 | } 180 | 181 | uint64_t ret; 182 | 183 | if (jit) { 184 | ebpf_jit_fn fn = ebpf_compile(vm, &errmsg); 185 | if (fn == NULL) { 186 | fprintf(stderr, "Failed to compile: %s\n", errmsg); 187 | free(errmsg); 188 | free(mem); 189 | return 1; 190 | } 191 | ret = fn(mem, mem_len); 192 | } else { 193 | if (ebpf_exec(vm, mem, mem_len, &ret) < 0) 194 | ret = UINT64_MAX; 195 | } 196 | 197 | printf("0x%" PRIx64 "\n", ret); 198 | 199 | ebpf_destroy(vm); 200 | free(mem); 201 | 202 | return 0; 203 | } 204 | 205 | static void* 206 | readfile(const char* path, size_t maxlen, size_t* len) 207 | { 208 | FILE* file; 209 | if (!strcmp(path, "-")) { 210 | file = fdopen(STDIN_FILENO, "r"); 211 | } else { 212 | file = fopen(path, "r"); 213 | } 214 | 215 | if (file == NULL) { 216 | fprintf(stderr, "Failed to open %s: %s\n", path, strerror(errno)); 217 | return NULL; 218 | } 219 | 220 | char* data = calloc(maxlen, 1); 221 | size_t offset = 0; 222 | size_t rv; 223 | while ((rv = fread(data + offset, 1, maxlen - offset, file)) > 0) { 224 | offset += rv; 225 | } 226 | 227 | if (ferror(file)) { 228 | fprintf(stderr, "Failed to read %s: %s\n", path, strerror(errno)); 229 | fclose(file); 230 | free(data); 231 | return NULL; 232 | } 233 | 234 | if (!feof(file)) { 235 | fprintf(stderr, "Failed to read %s because it is too large (max %u bytes)\n", path, (unsigned)maxlen); 236 | fclose(file); 237 | free(data); 238 | return NULL; 239 | } 240 | 241 | fclose(file); 242 | if (len) { 243 | *len = offset; 244 | } 245 | return (void*)data; 246 | } 247 | 248 | uint64_t 249 | memfrob_ext(uint64_t s, uint64_t n) 250 | { 251 | size_t p1 = s; 252 | for (uint64_t i = 0; i < n; i++) { 253 | ((char*)p1)[i] ^= 42; 254 | } 255 | return s; 256 | } 257 | 258 | static uint64_t 259 | gather_bytes(uint64_t a, uint64_t b, uint64_t c, uint64_t d, uint64_t e) 260 | { 261 | return (((uint64_t)a) << (uint64_t)32) | (((uint32_t)b) << (uint64_t)24) | (((uint32_t)c) << (uint64_t)16) | (((uint16_t)d) << (uint64_t)8) | (uint64_t)e; 262 | } 263 | 264 | static void 265 | trash_registers(void) 266 | { 267 | /* Overwrite all caller-save registers */ 268 | #if __x86_64__ 269 | asm("mov $0xf0, %rax;" 270 | "mov $0xf1, %rcx;" 271 | "mov $0xf2, %rdx;" 272 | "mov $0xf3, %rsi;" 273 | "mov $0xf4, %rdi;" 274 | "mov $0xf5, %r8;" 275 | "mov $0xf6, %r9;" 276 | "mov $0xf7, %r10;" 277 | "mov $0xf8, %r11;"); 278 | #elif __aarch64__ 279 | asm("mov w0, #0xf0;" 280 | "mov w1, #0xf1;" 281 | "mov w2, #0xf2;" 282 | "mov w3, #0xf3;" 283 | "mov w4, #0xf4;" 284 | "mov w5, #0xf5;" 285 | "mov w6, #0xf6;" 286 | "mov w7, #0xf7;" 287 | "mov w8, #0xf8;" 288 | "mov w9, #0xf9;" 289 | "mov w10, #0xfa;" 290 | "mov w11, #0xfb;" 291 | "mov w12, #0xfc;" 292 | "mov w13, #0xfd;" 293 | "mov w14, #0xfe;" 294 | "mov w15, #0xff;" :: 295 | : "w0", "w1", "w2", "w3", "w4", "w5", "w6", "w7", "w8", "w9", "w10", "w11", "w12", "w13", "w14", "w15"); 296 | #elif __arm__ 297 | // implementation for arm32 architecture 298 | asm("mov r0, #0xf0;" 299 | "mov r1, #0xf1;" 300 | "mov r2, #0xf2;" 301 | "mov r3, #0xf3;" 302 | "mov r4, #0xf4;" 303 | "mov r5, #0xf5;" 304 | "mov r6, #0xf6;" 305 | "mov r7, #0xf7;" 306 | "mov r8, #0xf8;" 307 | "mov r9, #0xf9;" 308 | ); 309 | #else 310 | fprintf(stderr, "trash_registers not implemented for this architecture.\n"); 311 | exit(1); 312 | #endif 313 | } 314 | 315 | static uint32_t 316 | sqrti(uint32_t x) 317 | { 318 | return sqrt(x); 319 | } 320 | 321 | static uint64_t 322 | unwind(uint64_t i) 323 | { 324 | return i; 325 | } 326 | 327 | static uint64_t 328 | strcmp_ext(uint64_t a, uint64_t b) { 329 | size_t p1 = a; 330 | size_t p2 = b; 331 | return strcmp((const char *)p1, (const char *)p2); 332 | } 333 | 334 | static void 335 | register_functions(struct ebpf_vm* vm) 336 | { 337 | ebpf_register(vm, 0, "gather_bytes", gather_bytes); 338 | ebpf_register(vm, 1, "memfrob", memfrob_ext); 339 | ebpf_register(vm, 2, "trash_registers", trash_registers); 340 | ebpf_register(vm, 3, "sqrti", sqrti); 341 | ebpf_register(vm, 4, "strcmp_ext", strcmp_ext); 342 | ebpf_register(vm, 5, "unwind", unwind); 343 | ebpf_set_unwind_function_index(vm, 5); 344 | } 345 | -------------------------------------------------------------------------------- /example/ptx/README.md: -------------------------------------------------------------------------------- 1 | # ptx program 2 | 3 | This is a simple demo program which shows how to call llvmbpf to generate PTX, and uses CUDA driver API to execute the compiled PTX. 4 | 5 | Set `LLVMBPF_CUDA_PATH` to the directory where CUDA installs, for example, `/usr/local/cuda-12.6` 6 | 7 | This example demonstrates how to use llvmbpf to generate NVIDIA PTX code from eBPF programs and execute it on CUDA-capable GPUs. It showcases the complete workflow: 8 | 9 | 1. Initializing LLVM components 10 | 2. Defining an eBPF program 11 | 3. Compiling the eBPF program to PTX using llvmbpf 12 | 4. Compiling the PTX to CUDA binary using NVIDIA's PTX compiler 13 | 5. Loading and executing the binary using CUDA Driver API 14 | 6. Handling host-device communication for helper functions 15 | 16 | ## Program Overview 17 | 18 | ### eBPF to PTX Compilation Flow 19 | 20 | The program defines a simple eBPF program that interacts with a BPF map, converts it to PTX, and then runs it on the GPU. This demonstrates how BPF programs can be executed on NVIDIA GPUs with proper support for helper functions. 21 | 22 | ### Host-Device Communication 23 | 24 | The program implements a communication mechanism between the host (CPU) and device (GPU) to handle BPF helper functions. When the BPF program calls a helper function like `map_lookup_elem`, the GPU code signals the host, which processes the request and returns the result. 25 | 26 | ### Execution Model 27 | 28 | 1. The eBPF program is compiled to PTX 29 | 2. The PTX is wrapped with trampoline code to handle helper function calls 30 | 3. NVIDIA's PTX compiler converts the PTX to a CUDA binary 31 | 4. The binary is loaded and executed on the GPU 32 | 5. A host thread handles helper function requests from the GPU 33 | 34 | ## Key Components 35 | 36 | 1. **eBPF Program**: Defined as an array of `ebpf_inst` structures 37 | 2. **LLVM JIT Context**: Used to compile eBPF to PTX 38 | 3. **PTX Compiler Interface**: Uses NVIDIA's PTX compiler to generate executable code 39 | 4. **Shared Memory Structure**: Enables communication between host and device 40 | 5. **Helper Function Handlers**: Process requests from the GPU on the host 41 | 42 | ## Usage 43 | 44 | Build and run the example: 45 | 46 | ```sh 47 | # set the CUDA path, for example, /usr/local/cuda-12.6 48 | cmake -B build -DCMAKE_BUILD_TYPE=Release -DLLVMBPF_ENABLE_PTX=1 -DLLVMBPF_CUDA_PATH=/usr/local/cuda-12.6 49 | cmake --build build --target all -j 50 | ``` 51 | 52 | Run the PTX example: 53 | 54 | ```sh 55 | build/example/ptx/ptx_test 56 | ``` 57 | 58 | ## Code Explanation 59 | 60 | Here's a detailed explanation of how the `ptx_test.cpp` code works: 61 | 62 | ### 1. Initialization and Includes 63 | 64 | The code starts by including necessary headers for LLVM, CUDA, and eBPF functionality. The missing header files that need to be fixed: 65 | 66 | ```cpp:llvmbpf/example/ptx/ptx_test.cpp 67 | // LLVM headers are included for JIT compilation 68 | #include 69 | #include 70 | 71 | // Project-specific headers need to be properly included 72 | #include // This needs to be fixed 73 | #include // This needs to be fixed 74 | 75 | // CUDA headers that need to be correctly included 76 | #include // CUDA Driver API 77 | #include // NVIDIA PTX Compiler API 78 | #include // CUDA Runtime API 79 | ``` 80 | 81 | ### 2. eBPF Program Definition 82 | 83 | The code defines a test eBPF program as an array of `ebpf_inst` structures. This program: 84 | 1. Takes a memory pointer in `r1` 85 | 2. Saves it to `r6` 86 | 3. Prepares arguments for a map lookup 87 | 4. Calls the `map_lookup_elem` helper function 88 | 5. Stores the result to the provided memory 89 | 6. Returns 0 90 | 91 | ```cpp 92 | static const struct ebpf_inst test_prog[] = { 93 | // r6 = r1 (save input pointer) 94 | { EBPF_OP_MOV64_REG, 6, 1, 0, 0 }, 95 | 96 | // Prepare key for map lookup (4 integers: 111,0,0,0) 97 | { EBPF_OP_MOV64_IMM, 1, 0, 0, 111 }, 98 | { EBPF_OP_STXW, 10, 1, -16, 0 }, 99 | // ... more instructions to prepare arguments ... 100 | 101 | // Call map_lookup_elem (helper function 1) 102 | { EBPF_OP_CALL, 0, 0, 0, 1 }, 103 | 104 | // Store result to output memory 105 | { EBPF_OP_LDXW, 1, 0, 0, 0 }, 106 | { EBPF_OP_STXW, 6, 1, 0, 0 }, 107 | 108 | // Return 0 109 | { EBPF_OP_MOV64_IMM, 0, 0, 0, 0 }, 110 | { EBPF_OP_EXIT, 0, 0, 0, 0 } 111 | }; 112 | ``` 113 | 114 | ### 3. PTX Compilation Function 115 | 116 | The `compile()` function uses NVIDIA's PTX compiler API to convert PTX code to CUDA binary: 117 | 118 | ```cpp 119 | static std::vector compile(const std::string &ptx) 120 | { 121 | nvPTXCompilerHandle compiler = NULL; 122 | // Initialize PTX compiler 123 | NVPTXCOMPILER_SAFE_CALL(nvPTXCompilerCreate(&compiler, 124 | (size_t)ptx.size(), 125 | ptx.c_str())); 126 | 127 | // Set compilation options - hardcoded to sm_60 (Maxwell/Pascal architecture) 128 | // NOTE: This does NOT auto-detect GPU architecture. To target a different 129 | // GPU, modify this value (e.g., sm_75 for Turing, sm_86 for Ampere) 130 | const char *compile_options[] = { "--gpu-name=sm_60", "--verbose" }; 131 | 132 | // Compile PTX to binary 133 | NVPTXCOMPILER_SAFE_CALL(nvPTXCompilerCompile(compiler, 2, compile_options)); 134 | 135 | // Retrieve compiled binary 136 | size_t elfSize; 137 | NVPTXCOMPILER_SAFE_CALL( 138 | nvPTXCompilerGetCompiledProgramSize(compiler, &elfSize)); 139 | std::vector elf_binary(elfSize, 0); 140 | NVPTXCOMPILER_SAFE_CALL(nvPTXCompilerGetCompiledProgram( 141 | compiler, (void *)elf_binary.data())); 142 | 143 | // Clean up 144 | NVPTXCOMPILER_SAFE_CALL(nvPTXCompilerDestroy(&compiler)); 145 | return elf_binary; 146 | } 147 | ``` 148 | 149 | ### 4. Host-Device Communication 150 | 151 | The code implements a communication mechanism between host and device using a shared memory structure: 152 | 153 | ```cpp 154 | struct CommSharedMem { 155 | int flag1; // Device -> Host signal 156 | int flag2; // Host -> Device signal 157 | int occupy_flag; // Lock mechanism 158 | int request_id; // Identifies the request type 159 | long map_id; // Map identifier 160 | HelperCallRequest req; // Request data 161 | HelperCallResponse resp; // Response data 162 | uint64_t time_sum[8]; // Timing information 163 | }; 164 | ``` 165 | 166 | ### 5. CUDA Kernel Execution 167 | 168 | The `elfLoadAndKernelLaunch()` function: 169 | 1. Initializes CUDA 170 | 2. Loads the compiled binary 171 | 3. Sets up shared memory for host-device communication 172 | 4. Launches the kernel 173 | 5. Starts a host thread to handle helper function calls 174 | 6. Waits for kernel completion 175 | 176 | ```cpp 177 | static int elfLoadAndKernelLaunch(void *elf, size_t elfSize) 178 | { 179 | // Initialize CUDA 180 | CUDA_SAFE_CALL(cuInit(0)); 181 | CUDA_SAFE_CALL(cuDeviceGet(&cuDevice, 0)); 182 | CUDA_SAFE_CALL(cuCtxCreate(&context, 0, cuDevice)); 183 | 184 | // Load compiled binary 185 | CUDA_SAFE_CALL(cuModuleLoadDataEx(&module, elf, 0, 0, 0)); 186 | 187 | // Set up communication channel 188 | auto comm = std::make_unique(); 189 | memset(comm.get(), 0, sizeof(CommSharedMem)); 190 | 191 | // Register memory with CUDA for fast access 192 | CUDA_SAFE_CALL(cuMemHostRegister(comm.get(), 193 | sizeof(CommSharedMem), 194 | CU_MEMHOSTREGISTER_DEVICEMAP)); 195 | 196 | // Set up map info and shared memory for the kernel 197 | // ... 198 | 199 | // Get kernel function and launch it 200 | CUDA_SAFE_CALL(cuModuleGetFunction(&kernel, module, "bpf_main")); 201 | CUDA_SAFE_CALL(cuLaunchKernel(kernel, 1, 1, 1, // grid dim 202 | 1, 1, 1, // block dim 203 | 0, nullptr, // shared mem and stream 204 | args, 0)); // arguments 205 | 206 | // Start host thread to handle helper calls 207 | std::thread hostThread([&]() { 208 | while (!should_exit.load()) { 209 | if (comm->flag1 == 1) { 210 | // Process helper function request 211 | // ... 212 | comm->flag2 = 1; // Signal completion 213 | } 214 | std::this_thread::sleep_for(std::chrono::milliseconds(10)); 215 | } 216 | }); 217 | 218 | // Wait for kernel completion 219 | CUDA_SAFE_CALL(cuCtxSynchronize()); 220 | hostThread.join(); 221 | 222 | // Clean up 223 | // ... 224 | return 0; 225 | } 226 | ``` 227 | 228 | ### 6. Main Function 229 | 230 | The `main()` function ties everything together: 231 | 1. Sets up signal handler 232 | 2. Initializes LLVM components 233 | 3. Creates a llvmbpf VM 234 | 4. Registers helper functions 235 | 5. Loads the eBPF program 236 | 6. Generates PTX code 237 | 7. Compiles PTX to CUDA binary 238 | 8. Executes the binary on the GPU 239 | 240 | ```cpp 241 | int main() 242 | { 243 | signal(SIGINT, signal_handler); 244 | 245 | // Initialize LLVM components 246 | llvm::InitializeAllTargetInfos(); 247 | llvm::InitializeAllTargets(); 248 | llvm::InitializeAllTargetMCs(); 249 | llvm::InitializeAllAsmPrinters(); 250 | llvm::InitializeAllAsmParsers(); 251 | 252 | // Verify NVPTX target is available 253 | // ... 254 | 255 | // Set up llvmbpf VM 256 | llvmbpf_vm vm; 257 | vm.register_external_function(1, "map_lookup", (void *)test_func); 258 | vm.register_external_function(2, "map_update", (void *)test_func); 259 | vm.register_external_function(3, "map_delete", (void *)test_func); 260 | 261 | // Load eBPF program 262 | vm.load_code((void *)test_prog, sizeof(test_prog)); 263 | 264 | // Generate PTX 265 | llvm_bpf_jit_context ctx(vm); 266 | auto result = *ctx.generate_ptx(false, "bpf_main", "sm_60"); 267 | 268 | // Wrap PTX with trampoline code for helper functions 269 | result = wrap_ptx_with_trampoline(patch_helper_names_and_header( 270 | patch_main_from_func_to_entry(result))); 271 | 272 | // Compile PTX to CUDA binary 273 | auto bin = compile(result); 274 | 275 | // Execute on GPU 276 | elfLoadAndKernelLaunch(bin.data(), bin.size()); 277 | 278 | return 0; 279 | } 280 | ``` 281 | 282 | This code demonstrates how to run eBPF programs on NVIDIA GPUs by compiling them to PTX, with support for calling back to the host for helper functions. 283 | --------------------------------------------------------------------------------