There are user land libraries for eBPF that allow you to write eBPF applications in C++, Rust, Go, Python and even Lua. But there are none for Java, which is a pity. So... I decided to write bindings using Project Panama and bcc, the first and widely used userland library for eBPF, which is typically used with its Python API. Work is on the way to work on the libbpf support in the bpf and bpf-processor modules.
Based on the overview from ebpf.io, duke image from OpenJDK.
Let's discover eBPF together. Join me on the journey to write all examples from the Learning eBPF book (get it also from Bookshop.org, Amazon, or O'Reilly), by Liz Rice in Java, implementing a Java userland library for eBPF along the way, with a blog series to document the journey.
This project is still in its early stages, and a read-along of the book is recommended:
We're currently at page 23 of the book in the blog series and page 36 with this repo.
It is evolving fast, you can already implement all examples and exercises from chapter 2.
A sample project using the library can be found in the sample-bcc-project repository.
Provide a library (and documentation) for Java developers to explore eBPF and write their own eBPF programs, and the examples from the book without having to Python.
The initial goal is to be as close to bcc Python API as possible so that the examples from the book can be ported to Java easily.
You can find the Java versions of the examples in the bcc/src/main/me/bechberger/samples and the API in the bcc/src/main/me/bechberger/bcc directory.
These might change in the future, but for now, you need the following:
Either a Linux machine with the following:
- Linux 64-bit (or a VM)
- Java 22 or later
- libbcc (see bcc installation instructions, be sure to install the libbpfcc-dev package)
- e.g.
apt install bpfcc-tools libbpfcc-dev linux-tools-common linux-tools-$(uname -r)
on Ubuntu
- e.g.
- root privileges (for eBPF programs)
On Mac OS, you can use the Lima VM (or use the
hello-ebpf.yaml
file as a guide to install the prerequisites):
limactl start hello-ebpf.yaml --mount-writable
limactl shell hello-ebpf sudo bin/install.sh
limactl shell hello-ebpf
# You'll need to be root for most of the examples
sudo -s PATH=$PATH
To build the project, make sure you have all prerequisites installed, then just run:
./build.sh
Be sure to run the following in a shell with root privileges that uses JDK 22:
java -cp bcc/target/bcc.jar --enable-native-access=ALL-UNNAMED me.bechberger.ebpf.samples.EXAMPLE_NAME
# or in the project directory
./run.sh EXAMPLE_NAME
# list all examples
./run.sh
The following runs the hello world sample from the vcc repository. It currently prints something like:
> ./run.sh bcc.HelloWorld
<...>-30325 [042] ...21 10571.161861: bpf_trace_printk: Hello, World!
zsh-30325 [004] ...21 10571.164091: bpf_trace_printk: Hello, World!
zsh-30325 [115] ...21 10571.166249: bpf_trace_printk: Hello, World!
zsh-39907 [127] ...21 10571.167210: bpf_trace_printk: Hello, World!
zsh-30325 [115] ...21 10572.231333: bpf_trace_printk: Hello, World!
zsh-30325 [060] ...21 10572.233574: bpf_trace_printk: Hello, World!
zsh-30325 [099] ...21 10572.235698: bpf_trace_printk: Hello, World!
zsh-39911 [100] ...21 10572.236664: bpf_trace_printk: Hello, World!
MediaSu~isor #3-19365 [064] ...21 10573.417254: bpf_trace_printk: Hello, World!
MediaSu~isor #3-22497 [000] ...21 10573.417254: bpf_trace_printk: Hello, World!
MediaPD~oder #1-39914 [083] ...21 10573.418197: bpf_trace_printk: Hello, World!
MediaSu~isor #3-39913 [116] ...21 10573.418249: bpf_trace_printk: Hello, World!
The related code is (chapter2/HelloWorld.java):
public class HelloWorld {
public static void main(String[] args) {
try (BPF b = BPF.builder("""
int hello(void *ctx) {
bpf_trace_printk("Hello, World!");
return 0;
}
""").build()) {
var syscall = b.get_syscall_fnname("execve");
b.attach_kprobe(syscall, "hello");
b.trace_print();
}
}
}
Which is equivalent to the Python code and prints "Hello, World!" for each execve
syscall:
from bcc import BPF
program = r"""
int hello(void *ctx) {
bpf_trace_printk("Hello World!");
return 0;
}
"""
b = BPF(text=program)
syscall = b.get_syscall_fnname("execve")
b.attach_kprobe(event=syscall, fn_name="hello")
b.trace_print()
You can use the debug.sh
to run an example with a debugger port open at port 5005.
The library is available as a Maven package:
<dependency>
<groupId>me.bechberger</groupId>
<artifactId>bcc</artifactId>
<version>0.1.0-SNAPSHOT</version>
</dependency>
You might have to add the https://s01.oss.sonatype.org/content/repositories/releases/ repo:
<repositories>
<repository>
<id>snapshots</id>
<url>https://s01.oss.sonatype.org/content/repositories/snapshots/</url>
<releases>
<enabled>false</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
</repositories>
Posts covering the development of this project:
- Dec 01, 2023: Finding all used Classes, Methods, and Functions of a Python Module
- Dec 11, 2023: From C to Java Code using Panama
- Jan 01, 2024: Hello eBPF: Developing eBPF Apps in Java (1)
- Jan 12, 2024: Hello eBPF: Recording data in basic eBPF maps (2)
- Jan 29, 2024: Hello eBPF: Recording data in perf event buffers (3)
- Feb 12, 2024: Hello eBPF: Tail calls and your first eBPF application (4)
- Feb 26, 2024: Hello eBPF: First steps with libbpf (5)
- Mar 12, 2024: Hello eBPF: Ring buffers in libbpf (6)
- Mar 22, 2024: Hello eBPF: Auto Layouting Structs (7)
We implement the Java API alongside implementing the examples from the book, so we track the progress of the implementation by the examples we have implemented. We also use examples from different sources like the bcc repository and state this in the first column.
Chapter /Source |
Example | Java class | Status | Description |
---|---|---|---|---|
bcc | bcc/hello_world.py | HelloWorld | works | Basic hello world |
2 | chapter2/hello.py | chapter2.HelloWorld | works | print "Hello World!" for each execve syscall |
2 | chapter2/hello-map.py | chapter2.HelloMap | works | Count and print execve calls per user |
own | - | own.HelloStructMap | works | Count and print execve calls per user and store the result as a struct in a map |
2 | chapter2/hello-buffer.py | chapter2.HelloBuffer | works | Record information in perf buffer |
2 | chapter2/hello-tail.py | chapter2.HelloTail | works | Print a message when a syscall is called, and also when a timer is created or deleted. |
2 | - | chapter2.ex | works | Implementation of some of the exercises for chapter 2 |
own | own/disassembler-test.py | own.DisassemblerTest | works | Disassemble byte-code for the HelloMap example |
The examples from the book and other sources like Ansil H's blog posts
are implemented in the bpf/src/main/me/bechberger/ebpf/samples directory.
You can run them using the ./run_bpf.sh
script. All examples have accompanying tests in the
bpf/src/test directory.
Source | Java Class | Description |
---|---|---|
Ansil H | HelloWorld | A simple hello world example |
Ansil H | RingSample | Record openat calls in a ring buffer |
TypeProcessingSample | RingSample using the @Type annotation | |
HashMapSample | Record openat calls in a hash map | |
TypeProcessingSample | RingSample using more code generation |
All classes and methods have the name as in the Python API, introducing things like builders only
for more complex cases (like the constructor of BPF
).
The comments for all of these entities are copied from the Python API and extended where necessary.
A look ahead into the future so you know what to expect:
- Support the newer libbpf library
- Started in the bpf and bpf-processor modules
- Allow writing eBPF programs in Java
- Drop libbcc and the BCC tools
These plans might change, but I'll try to keep this up to date. I'm open to suggestions, contributions, and ideas.
Tests are run using JUnit 5 and ./mvnw test
.
You can either run
./mvnw test -Dmaven.test.skip=false
or you can run the tests in a container using testutil/bin/java
:
./mvnw test -Djvm=testutil/bin/java -Dmaven.test.skip=false
This requires virtme (apt install virtme
), python 3, and docker to be installed.
You can run custom commands in the container using testutil/run-in-container.sh
.
Read more in the testutil/README.md.
I'm unable to get it running in the CI, so I'm currently running the tests locally.
Contributions are welcome; just open an issue or a pull request. Discussions take place in the discussions section of the GitHub repository.
I'm happy to include more example programs, API documentation, or helper methods, as well as links to repositories and projects that use this library.
Apache 2.0, Copyright 2023 SAP SE or an SAP affiliate company, Johannes Bechberger and contributors
This is a side project. The amount of time I can invest might vary over time.