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.
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.
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 21 (exactly this version, as we need Project Panama with is a preview feature), we'll switch to Java 22 as soon as it is released
- libbcc (see bcc installation instructions, be sure to install the libbpfcc-dev package)
- root privileges (for eBPF programs)
- Maven 3.6.3 (or newer to build the project)
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
limactl shell hello-ebpf
# You'll need to be root for most of the examples
sudo -s
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 21:
java --enable-preview -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>hello-ebpf</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)
Planned posts:
- Hello eBPF: Recording data in perf buffers and program arrays (3)
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 |
... more to come from the books' repository.
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:
- Implement the API so that we can recreate all bcc examples from the book
- Make it properly available as a library on Maven Central
- Support the newer libbpf library
- Allow writing eBPF programs in Java
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
or you can run the tests in a container using testutil/bin/java
:
./mvnw test -Djvm=testutil/bin/java
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.