llvmbpf

Userspace eBPF VM with llvm JIT/AOT compiler

https://github.com/eunomia-bpf/llvmbpf

Science Score: 44.0%

This score indicates how likely this project is to be science-related based on various indicators:

  • CITATION.cff file
    Found CITATION.cff file
  • codemeta.json file
    Found codemeta.json file
  • .zenodo.json file
    Found .zenodo.json file
  • DOI references
  • Academic publication links
  • Committers with academic emails
  • Institutional organization owner
  • JOSS paper metadata
  • Scientific vocabulary similarity
    Low similarity (12.3%) to scientific vocabulary

Keywords

aot ebpf jit llvm runtime virtual-machine
Last synced: 6 months ago · JSON representation ·

Repository

Userspace eBPF VM with llvm JIT/AOT compiler

Basic Info
Statistics
  • Stars: 66
  • Watchers: 5
  • Forks: 8
  • Open Issues: 4
  • Releases: 0
Topics
aot ebpf jit llvm runtime virtual-machine
Created over 1 year ago · Last pushed 9 months ago
Metadata Files
Readme Funding License Citation

README.md

Userspace eBPF VM with LLVM JIT/AOT Compiler

Build and Test VM codecov

A high-performance, multi-architecture JIT/AOT compiler and virtual machine (VM) based on LLVM.

This component is part of the bpftime project but focuses solely on the core VM. It offers the following capabilities:

  • Operates as a standalone eBPF VM library or compiler tool.
  • Compiles eBPF bytecode into LLVM IR files.
  • Compiles eBPF ELF files into AOTed native code ELF object files, which can be linked like C-compiled objects or loaded into llvmbpf.
  • Loads and executes AOT-compiled ELF object files within the eBPF runtime.
  • Supports eBPF helpers and maps lddw functions.
  • Supports PTX generation for CUDA on GPU.

This library is optimized for performance, flexibility, and minimal dependencies. It does not include maps implement, helpers, verifiers, or loaders for eBPF applications, making it suitable as a lightweight, high-performance library.

For a comprehensive userspace eBPF runtime that includes support for maps, helpers, and seamless execution of Uprobe, syscall trace, XDP, and other eBPF programs—similar to kernel functionality but in userspace—please refer to the bpftime project.

build project

sh sudo apt install llvm-15-dev libzstd-dev cmake -B build -DCMAKE_BUILD_TYPE=Release cmake --build build --target all -j

Usage

Use llvmbpf as a library

See example of how to use the library as a vm:

```cpp void runebpfprog(const void *code, sizet codelen) { uint64t res = 0; llvmbpfvm vm;

res = vm.load_code(code, code_len);
if (res) {
    return;
}
vm.register_external_function(2, "print", (void *)ffi_print_func);
auto func = vm.compile();
if (!func) {
    return;
}
int err = vm.exec(&bpf_mem, sizeof(bpf_mem), res);
if (err != 0) {
    return;
}
printf("res = %" PRIu64 "\n", res);

} ```

Use llvmbpf as a AOT compiler

Build with cli:

sh sudo apt-get install libelf1 libelf-dev cmake -B build -DBUILD_LLVM_AOT_CLI=1

You can use the cli to generate the LLVM IR from eBPF bytecode:

```console

./build/cli/bpftime-vm build .github/assets/sum.bpf.o -emit-llvm > test.bpf.ll

opt -O3 -S test.bpf.ll -opaque-pointers -o test.opt.ll

cat test.opt.ll

; ModuleID = 'test.bpf.ll' source_filename = "bpf-jit"

; Function Attrs: nofree norecurse nosync nounwind memory(read, inaccessiblemem: none) define i64 @bpfmain(ptr %0, i64 %1) localunnamedaddr #0 { setupBlock: %2 = ptrtoint ptr %0 to i64 %3 = load i32, ptr %0, align 4 %4 = icmp slt i32 %3, 1 br i1 %4, label %bbinst30, label %bbinst_15

bbinst15: ; preds = %setupBlock, %bbinst15 %storemerge32 = phi i32 [ %11, %bbinst15 ], [ 1, %setupBlock ] %stackBegin29.sroa.2.031 = phi i32 [ %10, %bbinst15 ], [ 0, %setupBlock ] %5 = sext i32 %storemerge32 to i64 %6 = shl nsw i64 %5, 2 %7 = add i64 %6, %2 %8 = inttoptr i64 %7 to ptr %9 = load i32, ptr %8, align 4 %10 = add i32 %9, %stackBegin29.sroa.2.031 %11 = add i32 %storemerge32, 1 %12 = icmp sgt i32 %11, %3 br i1 %12, label %bbinst30, label %bbinst15

bbinst30: ; preds = %bbinst15, %setupBlock %stackBegin29.sroa.2.0.lcssa = phi i32 [ 0, %setupBlock ], [ %10, %bbinst15 ] %13 = zext i32 %stackBegin29.sroa.2.0.lcssa to i64 ret i64 %13 }

attributes #0 = { nofree norecurse nosync nounwind memory(read, inaccessiblemem: none) } ```

AOT Compile a eBPF program:

```console

./build/cli/bpftime-vm build .github/assets/sum.bpf.o

[2024-08-10 14:54:06.453] [info] [main.cpp:56] Processing program test [2024-08-10 14:54:06.479] [info] [main.cpp:69] Program test written to ./test.o ```

Load and run a AOTed eBPF program:

```console

echo "AwAAAAEAAAACAAAAAwAAAA==" | base64 -d > test.bin

./build/cli/bpftime-vm run test.o test.bin

[2024-08-10 14:57:16.986] [info] [llvmjitcontext.cpp:392] LLVM-JIT: Loading aot object [2024-08-10 14:57:16.991] [info] [main.cpp:136] Program executed successfully. Return value: 6 ```

See Build into standalone binary for deployment for more details.

load eBPF bytecode from ELF file

You can use llvmbpf together with libbpf to load the eBPF bytecode directly from bpf.o ELF file. For example:

```c bpfobject *obj = bpfobjectopen(ebpfelf.cstr()); if (!obj) { return 1; } std::uniqueptr<bpfobject, decltype(&bpf_objectclose)> elf( obj, bpfobject_close);

bpfprogram *prog; for ((prog) = bpfobjectnextprogram((elf.get()), _null); (prog) != _null; (prog) = bpfobjectnextprogram((elf.get()), (prog))) { const char *name = bpfprogram_name(prog); llvmbpfvm vm;

vm.load_code((const void *)bpf_program__insns(prog),
     (uint32_t)bpf_program__insn_cnt(prog) * 8);

... } ```

For complete code example, please refer to cli.

However, the bpf.o ELF file has no map and data relocation support. We would recommend using the bpftime to load and relocation the eBPF bytecode from ELF file. This include:

  • Write a loader like normal kernel eBPF loader to load the eBPF bytecode, you can find a example here.
  • The loader will use the libbpf, which support:
    • Relocation for map. The map id will be allocated by the loader and bpftime, you can use the map id to access map through the helpers.
    • The data can be accessed through the lddw helper function.
  • After the loader load the eBPF bytecode and complete the relocation, you can use the bpftimetool to dump the map information and eBPF bytecode.

Maps and data relocation support

bpftime already has maps and data relocation support. The easiest way to use it is just use bpftime and write the loader and eBPF program like kernel eBPF. The llvmbpf libray provide a approach to interact with the maps.

See example/maps.cpp of how to use the library as a vm and works with maps:

The eBPF can work with maps in two ways:

  • Using helper functions to access the maps, like bpf_map_lookup_elem, bpf_map_update_elem, etc.
  • Using maps as global variables in the eBPF program, and access the maps directly.

For a eBPF program like https://github.com/eunomia-bpf/bpftime/blob/master/example/xdp-counter/:

```c // use map type define struct { _uint(type, BPFMAPTYPEARRAY); _type(key, _u32); _type(value, _u32); _uint(maxentries, CTRLARRAYSIZE); } ctl_array SEC(".maps");

// use global variable define _u64 cntrsarray[CNTRSARRAYSIZE];

SEC("xdp") int xdppass(struct xdpmd* ctx) { void* dataend = (void*)(long)ctx->dataend; void* data = (void*)(long)ctx->data; _u32 ctlflagpos = 0; _u32 cntr_pos = 0;

// access maps with helpers _u32* flag = bpfmaplookupelem(&ctlarray, &ctlflagpos); if (!flag || (*flag != 0)) { return XDPPASS; };

// access maps with global variables cntrsarray[cntrpos]++;

if (data + sizeof(struct ethhdr) > dataend) return XDPDROP; swapsrcdstmac(data); return XDPTX; } ```

We can define the map and access them like:

```cpp uint32t ctlarray[2] = { 0, 0 }; uint64t cntrsarray[2] = { 0, 0 };

void bpfmaplookupelem(uint64t mapfd, void *key) { std::cout << "bpfmaplookupelem " << mapfd << std::endl; if (mapfd == 5) { return &ctl_array[(uint32t *)key]; } else if (mapfd == 6) { return &cntrsarray[*(uint32t *)key]; } else { return nullptr; } return 0; }

uint64t mapbyfd(uint32t fd) { std::cout << "mapbyfd " << fd << std::endl; return fd; }

uint64t mapval(uint64t val) { std::cout << "mapval " << val << std::endl; if (val == 5) { return (uint64t)(void *)ctlarray; } else if (val == 6) { return (uint64t)(void *)cntrsarray; } else { return 0; } }

int main(int argc, char *argv[]) { auto code = xdpcounterbytecode; sizet codelen = sizeof(xdpcounterbytecode) - 1; uint64t res = 0; llvmbpfvm vm;

res = vm.loadcode(code, codelen); if (res) { std::cout << vm.geterrormessage() << std::endl; exit(1); } vm.registerexternalfunction(1, "bpfmaplookupelem", (void *)bpfmaplookupelem); // set the lddw helpers for accessing maps vm.setlddwhelpers(mapbyfd, nullptr, mapval, nullptr, nullptr); auto func = vm.compile(); if (!func) { std::cout << vm.geterrormessage() << std::endl; exit(1); } // Map value (counter) should be 0 std::cout << "cntrsarray[0] = " << cntrsarray[0] << std::endl; int err = vm.exec(&bpfmem, sizeof(bpfmem), res); std::cout << "\nreturn value = " << res << std::endl; // counter should be 1 std::cout << "cntrsarray[0] = " << cntrs_array[0] << std::endl; .... } ```

Reference:

Build into standalone binary for deployment

You can build the eBPF program into a standalone binary, which does not rely on any external libraries, and can be exec like nomal c code with helper and maps support.

This can help:

  • Easily deploy the eBPF program to any machine without the need to install any dependencies.
  • Avoid the overhead of loading the eBPF bytecode and maps at runtime.
  • Suitable for microcontroller or embedded systems, which does not have a OS.

Take https://github.com/eunomia-bpf/bpftime/blob/master/example/xdp-counter/ as an example:

In the bpftime project:

```sh

load the eBPF program with bpftime

LD_PRELOAD=build/runtime/syscall-server/libbpftime-syscall-server.so example/xdp-counter/xdp-counter example/xdp-counter/.output/xdp-counter.bpf.o veth1

dump the map and eBPF bytecode define

./build/tools/bpftimetool/bpftimetool export res.json

build the eBPF program into llvm IR

./build/tools/aot/bpftime-aot compile --emit_llvm 1>xdp-counter.ll ```

You can see example/xdp-counter.json for an example json file dump by bpftime.

The result xdp-counter.ll can be found in example/standalone/xdp-counter.ll.

Then you can write a C code and compile it with the llvm IR:

```c

include

include

include

int bpfmain(void* ctx, uint64t size);

uint32t ctlarray[2] = { 0, 0 }; uint64t cntrsarray[2] = { 0, 0 };

void bpfhelperext0001(uint64t mapfd, void *key) { printf("bpfmaplookupelem %lu\n", mapfd); if (mapfd == 5) { return &ctlarray[(uint32t *)key]; } else if (mapfd == 6) { return &cntrsarray[*(uint32t *)key]; } else { return NULL; } return 0; }

void* _lddwhelpermapval(uint64t val) { printf("mapval %lu\n", val); if (val == 5) { return (void *)ctlarray; } else if (val == 6) { return (void *)cntrsarray; } else { return NULL; } }

uint8t bpfmem[] = { 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88 };

int main() { printf("The value of cntrsarray[0] is %" PRIu64 "\n", cntrsarray[0]); printf("calling ebpf program...\n"); bpfmain(bpfmem, sizeof(bpfmem)); printf("The value of cntrsarray[0] is %" PRIu64 "\n", cntrsarray[0]); printf("calling ebpf program...\n"); bpfmain(bpfmem, sizeof(bpfmem)); printf("The value of cntrsarray[0] is %" PRIu64 "\n", cntrsarray[0]); return 0; } ```

Compile the C code with the llvm IR:

sh clang -g main.c xdp-counter.ll -o standalone

And you can run the standalone eBPF program directly.

PTX generation for CUDA on GPU

llvmbpf can generate PTX code for CUDA and run eBPF program on GPU. See example/ptx for an example.

```sh

set the CUDA path, for example, /usr/local/cuda-12.6

cmake -B build -DCMAKEBUILDTYPE=Release -DLLVMBPFENABLEPTX=1 -DLLVMBPFCUDAPATH=/usr/local/cuda-12.6 cmake --build build --target all -j ```

Run the PTX example:

sh build/example/ptx/ptx_test

optimizaion

Based on the AOT compiler, we can apply some optimization strategies:

inline the maps and helper function

Inline the maps and helper function into the eBPF program, so that the eBPF program can be optimized with const propagation, dead code elimination, etc by the LLVM optimizer. llvmbpf can also eliminate the cost of function calls.

Prepare a C code:

```c

uint32t ctlarray[2] = { 0, 0 }; uint64t cntrsarray[2] = { 0, 0 };

void bpfhelperext0001(uint64t mapfd, void *key) { if (mapfd == 5) { return &ctlarray[(uint32t *)key]; } else if (mapfd == 6) { return &cntrsarray[*(uint32t *)key]; } else { return NULL; } return 0; }

void* _lddwhelpermapval(uint64t val) { if (val == 5) { return (void *)ctlarray; } else if (val == 6) { return (void *)cntrs_array; } else { return NULL; } } ```

Merge the modules with llvm-link and inline them:

sh clang -S -O3 -emit-llvm libmap.c -o libmap.ll llvm-link -S -o xdp-counter-inline.ll xdp-counter.ll libmap.ll opt --always-inline -S xdp-counter-inline.ll -o xdp-counter-inline.ll clang -O3 -g -c xdp-counter-inline.ll -o inline.o

Run the code with cli:

c ./build/cli/bpftime-vm run example/inline/inline.o test.bin

Or you can compile as standalone binary and link with the C code:

console $ clang -O3 example/inline/inline.o example/inline/main.c -o inline $ /workspaces/llvmbpf/inline calling ebpf program... return value = 1

Use original LLVM IR from C code

eBPF is a instruction set define for verification, but may not be the best for performance.

llvmbpf also support using the original LLVM IR from C code. See example/load-llvm-ir for an example. You can:

  • Compile the C code to eBPF for verify
  • Compile the C code to LLVM IR and native code for execution in the VM.

The C code:

```c int bpfhelperext0006(const char *fmt, ... );

int bpfmain(void* ctx, int size) { _bpfhelperext0006("hello world: %d\n", size); return 0; } ```

You can compile it with clang -g -c bpf_module.c -o bpf_module.o, and Run the code with cli:

c ./build/cli/bpftime-vm run example/load-llvm-ir/bpf_module.o test.bin

Test

Unit test

Compile:

sh sudo apt install llvm-15-dev libzstd-dev cmake -B build -DCMAKE_BUILD_TYPE=Debug -DBPFTIME_ENABLE_UNIT_TESTING=1 -DBPFTIME_ENABLE_CODE_COVERAGE=1 cmake --build build --target all -j

The unit tests can be found at build/test/unit-test/llvm_jit_tests.

Test with bpf-conformance

See the CI in .github/workflows/bpf_conformance.yml for how to run the bpf-conformance tests.

The test result can be found in https://eunomia-bpf.github.io/llvmbpf/bpf_conformance_results.txt

License

MIT

Owner

  • Name: eunomia-bpf
  • Login: eunomia-bpf
  • Kind: organization
  • Email: team@eunomia.dev
  • Location: China

Simplify and enhance eBPF programming with Webassembly, and GPT!

Citation (CITATION.cff)

# This CITATION.cff file was generated with cffinit.
# Visit https://bit.ly/cffinit to generate yours today!

cff-version: 1.2.0
title: bpftime
message: >-
  If you use this software, please cite it using the
  metadata from this file.
type: software
authors:
  - given-names: Yusheng
    family-names: Zheng
    email: yunwei356@gmail.com
    affiliation: UC Santa Cruz
  - given-names: Tong
    family-names: Yu
    affiliation: eunomia-bpf Community
  - given-names: Yiwei
    family-names: Yang
    affiliation: UC Santa Cruz
  - given-names: Yanpeng
    family-names: Hu
    affiliation: ShanghaiTech University
  - given-names: Xiaozheng
    family-names: Lai
    affiliation: South China University of Technology
  - given-names: Dan
    family-names: Williams
    affiliation: Virginia Tech
  - given-names: Andi
    family-names: Quinn
    affiliation: UC Santa Cruz
identifiers:
  - type: url
    value: 'https://www.usenix.org/conference/osdi25/presentation/zheng-yusheng'
    description: >-
      Extending Applications Safely and Efficiently
repository-code: 'https://github.com/eunomia-bpf/bpftime'
url: 'https://eunomia.dev/bpftime/'
abstract: >-
  This paper introduces the Extension Interface Model (EIM) and
  bpftime, a framework for safer and more efficient application
  extension. EIM treats extension features as resources, allowing
  managers to specify exact resource needs. bpftime uses eBPF-style
  verification, hardware isolation, and dynamic binary rewriting
  to achieve efficiency and compatibility with the existing eBPF
  ecosystem. The system demonstrates the approach across 6 use
  cases involving security, performance monitoring, and configuration
  exploration. By operating in userspace, bpftime achieves significant
  performance improvements while maintaining safety guarantees and
  compatibility with existing eBPF toolchains.
keywords:
  - userspace
  - plugin
  - eBPF
license: MIT

GitHub Events

Total
  • Issues event: 1
  • Watch event: 20
  • Issue comment event: 11
  • Push event: 16
  • Pull request review comment event: 6
  • Pull request review event: 8
  • Pull request event: 19
  • Fork event: 5
  • Create event: 3
Last Year
  • Issues event: 1
  • Watch event: 20
  • Issue comment event: 11
  • Push event: 16
  • Pull request review comment event: 6
  • Pull request review event: 8
  • Pull request event: 19
  • Fork event: 5
  • Create event: 3

Committers

Last synced: 10 months ago

All Time
  • Total Commits: 25
  • Total Committers: 4
  • Avg Commits per committer: 6.25
  • Development Distribution Score (DDS): 0.28
Past Year
  • Commits: 25
  • Committers: 4
  • Avg Commits per committer: 6.25
  • Development Distribution Score (DDS): 0.28
Top Committers
Name Email Commits
云微 y****6@g****m 18
Officeyutong y****x@g****m 5
Sy03 1****0@q****m 1
Kristófer Fannar Björnsson 6****r 1
Committer Domains (Top 20 + Academic)
qq.com: 1

Issues and Pull Requests

Last synced: 6 months ago

All Time
  • Total issues: 5
  • Total pull requests: 30
  • Average time to close issues: about 20 hours
  • Average time to close pull requests: 1 day
  • Total issue authors: 3
  • Total pull request authors: 5
  • Average comments per issue: 1.2
  • Average comments per pull request: 0.8
  • Merged pull requests: 22
  • Bot issues: 0
  • Bot pull requests: 0
Past Year
  • Issues: 2
  • Pull requests: 14
  • Average time to close issues: 1 day
  • Average time to close pull requests: 3 days
  • Issue authors: 2
  • Pull request authors: 5
  • Average comments per issue: 1.0
  • Average comments per pull request: 0.71
  • Merged pull requests: 9
  • Bot issues: 0
  • Bot pull requests: 0
Top Authors
Issue Authors
  • yunwei37 (3)
  • victoryang00 (1)
Pull Request Authors
  • yunwei37 (28)
  • Officeyutong (12)
  • Sy0307 (4)
  • kristoferfannar (1)
  • h313 (1)
Top Labels
Issue Labels
bug (3) enhancement (1) help wanted (1)
Pull Request Labels
size/L (16) size/M (8) size/XS (6) size/S (6) size/XXL (3) size/XL (2)

Dependencies

.github/workflows/test-aot-cli.yml actions
  • actions/checkout v2 composite
.github/workflows/test-vm.yml actions
.github/workflows/unit-test.yml actions
  • actions/checkout v2 composite
test/requirements.txt pypi
  • colorama * test
  • nose * test
  • pyelftools * test
  • pytest * test
.github/workflows/bpf_conformance.yml actions
  • actions/cache v2 composite
  • actions/checkout v2 composite
  • actions/deploy-pages v4 composite
  • actions/setup-python v4 composite
  • actions/upload-pages-artifact v3 composite
.github/workflows/codeql.yml actions
  • actions/checkout v4 composite
  • github/codeql-action/analyze v3 composite
  • github/codeql-action/init v3 composite