From afa7c17a4b96d4a1085a24c9476158db59459a03 Mon Sep 17 00:00:00 2001 From: Johannes Bechberger Date: Mon, 1 Apr 2024 21:05:53 +0200 Subject: [PATCH 01/11] WIP --- annotations/pom.xml | 4 +- .../bechberger/ebpf/annotations/bpf/Type.java | 11 +- bcc/pom.xml | 11 +- .../main/java/me/bechberger/ebpf/bcc/BPF.java | 32 +-- .../java/me/bechberger/ebpf/bcc/BPFTable.java | 23 +- .../ebpf/samples/chapter2/HelloBuffer.java | 4 +- .../ebpf/samples/own/HelloStructMap.java | 4 +- .../ebpf/bcc/structs/HelloBufferTest.java | 4 +- bpf-processor/pom.xml | 20 +- .../main/java/me/bechberger/cast/CAST.java | 64 ++++- .../ebpf/bpf/processor/Processor.java | 23 +- .../ebpf/bpf/processor/TypeProcessor.java | 256 +++++++++++------- .../me/bechberger/ebpf/type}/BPFType.java | 228 ++++++++++++++-- bpf/pom.xml | 9 +- .../me/bechberger/ebpf/bpf/BPFProgram.java | 3 +- .../bechberger/ebpf/bpf/map/BPFBaseMap.java | 6 +- .../bechberger/ebpf/bpf/map/BPFHashMap.java | 4 +- .../ebpf/bpf/map/BPFRingBuffer.java | 5 +- .../ebpf/samples/HashMapSample.java | 7 +- .../bechberger/ebpf/samples/RingSample.java | 3 +- .../ebpf/samples/TypeProcessingSample.java | 3 - .../me/bechberger/ebpf/bpf/HashMapTest.java | 2 +- .../bechberger/ebpf/bpf/RingBufferTest.java | 5 +- .../bechberger/ebpf/bpf/TypeLayoutTest.java | 8 +- .../ebpf/bpf/TypeProcessingTest.java | 8 +- pom.xml | 8 +- rawbcc/README.md | 2 +- rawbcc/bin/jextract_bindings.py | 22 +- rawbcc/pom.xml | 14 +- rawbpf/CHANGELOG | 6 + rawbpf/README.md | 2 +- rawbpf/pom.xml | 14 +- shared/pom.xml | 13 +- .../bechberger/ebpf/shared/Disassembler.java | 6 +- 34 files changed, 573 insertions(+), 261 deletions(-) rename {shared/src/main/java/me/bechberger/ebpf/shared => bpf-processor/src/main/java/me/bechberger/ebpf/type}/BPFType.java (71%) diff --git a/annotations/pom.xml b/annotations/pom.xml index 0b8896f..91716c7 100644 --- a/annotations/pom.xml +++ b/annotations/pom.xml @@ -13,8 +13,8 @@ ebpf-annotations - 21 - 21 + 22 + 22 UTF-8 diff --git a/annotations/src/main/java/me/bechberger/ebpf/annotations/bpf/Type.java b/annotations/src/main/java/me/bechberger/ebpf/annotations/bpf/Type.java index 320b036..14d0730 100644 --- a/annotations/src/main/java/me/bechberger/ebpf/annotations/bpf/Type.java +++ b/annotations/src/main/java/me/bechberger/ebpf/annotations/bpf/Type.java @@ -23,7 +23,6 @@ *
  • integer types (int, long, ...), optionally annotated with {@link Unsigned} if unsigned
  • *
  • String types, annotated with {@link Size} to specify the size
  • *
  • Other {@link Type} annotated types
  • - *
  • {@link Type.Member} annotated member, to specify the BPFType directly
  • * */ @Target(ElementType.TYPE) @@ -31,12 +30,4 @@ /** Name of the generated BPFStructType, uses the type as default */ String name() default ""; - - @Target({ElementType.TYPE, ElementType.TYPE_USE}) - @Retention(RetentionPolicy.CLASS) - public @interface Member { - - /** Java statement directly copied into the result at the place of the BPFType */ - String bpfType(); - } -} +} \ No newline at end of file diff --git a/bcc/pom.xml b/bcc/pom.xml index 6d29b86..97f2e13 100644 --- a/bcc/pom.xml +++ b/bcc/pom.xml @@ -38,8 +38,8 @@ UTF-8 - 21 - 21 + 22 + 22 @@ -49,9 +49,6 @@ org.apache.maven.plugins maven-compiler-plugin 3.8.1 - - --enable-preview - maven-assembly-plugin @@ -85,7 +82,7 @@ me.bechberger rawbcc - 0.1.3 + 0.1.4 me.bechberger @@ -111,4 +108,4 @@ hello-ebpf 0.1.0-SNAPSHOT - + \ No newline at end of file diff --git a/bcc/src/main/java/me/bechberger/ebpf/bcc/BPF.java b/bcc/src/main/java/me/bechberger/ebpf/bcc/BPF.java index 0cd4452..ec082cb 100644 --- a/bcc/src/main/java/me/bechberger/ebpf/bcc/BPF.java +++ b/bcc/src/main/java/me/bechberger/ebpf/bcc/BPF.java @@ -147,11 +147,11 @@ cflags_array, len(cflags_array), */ var maybeModule = bpf_module_create_c_from_string(arena, textNative, debug, MemorySegment.NULL, 0, allowRLimit ? 1 : 0, MemorySegment.NULL); if (maybeModule.err() != 0 && maybeModule.err() != 2) { - throw new BPFCallException(STR."Failed to compile BPF module: \{Util.errnoString(maybeModule.err())}"); + throw new BPFCallException("Failed to compile BPF module: " + Util.errnoString(maybeModule.err())); } module = maybeModule.result(); - if (module == null) throw new RuntimeException(STR."Failed to compile BPF module \{fileName}"); + if (module == null) throw new RuntimeException("Failed to compile BPF module " + fileName); trace_autoload(); } @@ -205,7 +205,7 @@ public Arena arena() { * @param name name of the function */ public boolean doesFunctionExistInText(String name) { - return Pattern.compile(STR." \{name}\\(.*\\).*\\{").matcher(text).find(); + return Pattern.compile(" " + name + "\\(.*\\).*\\{").matcher(text).find(); } private static HandlerWithErrno BCC_FUNC_LOAD = new HandlerWithErrno<>("bcc_func_load", @@ -240,11 +240,11 @@ private static ResultAndErr bcc_func_load(Arena arena, MemorySegment mo public BPFFunction load_func(String func_name, int prog_type, MemorySegment device, int attach_type) { if (funcs.containsKey(func_name)) return funcs.get(func_name); if (!doesFunctionExistInText(func_name)) - throw new RuntimeException(STR."Trying to use undefined function \{func_name}"); + throw new RuntimeException("Trying to use undefined function " + func_name); try (var arena = Arena.ofConfined()) { MemorySegment funcNameNative = arena.allocateUtf8String(func_name); if (Lib.bpf_function_start(module, funcNameNative) == null) - throw new RuntimeException(STR."Unknown program \{func_name}"); + throw new RuntimeException("Unknown program " + func_name); int log_level = 0; if ((debug & LogLevel.DEBUG_BPF_REGISTER_STATE) != 0) { log_level = 2; @@ -261,7 +261,7 @@ public BPFFunction load_func(String func_name, int prog_type, MemorySegment devi disableCleanup = true; if (res.err() == PanamaUtil.ERRNO_PERM_ERROR) throw new BPFCallException("Need to run with root priviledges to load BPF functions, bcc_load_func failed", res.err()); - throw new BPFCallException(STR."Failed to load BPF function \{func_name}", res.err()); + throw new BPFCallException("Failed to load BPF function " + func_name, res.err()); } var fn = new BPFFunction(this, func_name, res.result()); funcs.put(func_name, fn); @@ -289,7 +289,7 @@ public BPFFunction load_raw_tracepoint_func(String func_name) { MemorySegment dump_func(Arena arena, String func_name) { var funcNameNative = arena.allocateUtf8String(func_name); if (Lib.bpf_function_start(module, funcNameNative) == null) - throw new RuntimeException(STR."Unknown program \{func_name}"); + throw new RuntimeException("Unknown program " + func_name); var start = Lib.bpf_function_start(module, funcNameNative); var size = Lib.bpf_function_size(module, funcNameNative); return start.asSlice(0, size); @@ -423,7 +423,7 @@ public BPF attach_raw_tracepoint(@Nullable String tracepoint, @Nullable String f try (var arena = Arena.ofConfined()) { if (tracepoint == null || fn_name == null) return this; if (raw_tracepoint_fds.containsKey(tracepoint)) - throw new RuntimeException(STR."Raw tracepoint \{tracepoint} has been attached"); + throw new RuntimeException("Raw tracepoint " +tracepoint + " has been attached"); var fn = load_func(fn_name, Lib.BPF_PROG_TYPE_RAW_TRACEPOINT()); int fd = Lib.bpf_attach_raw_tracepoint(fn.fd, arena.allocateUtf8String(tracepoint)); if (fd < 0) throw new RuntimeException("Failed to attach BPF to raw tracepoint"); @@ -443,7 +443,7 @@ public BPF attach_raw_tracepoint(@Nullable String tracepoint, @Nullable String f public void detach_raw_tracepoint(@Nullable String tracepoint) { if (tracepoint == null) return; if (!raw_tracepoint_fds.containsKey(tracepoint)) - throw new RuntimeException(STR."Raw tracepoint \{tracepoint} is not attached"); + throw new RuntimeException("Raw tracepoint " + tracepoint + " is not attached"); Lib.close(raw_tracepoint_fds.get(tracepoint)); raw_tracepoint_fds.remove(tracepoint); } @@ -478,7 +478,7 @@ public BPF attach_kprobe(@Nullable String event, int event_off, @Nullable String } if (failed == matches.size()) { var probesStr = String.join("/", probes); - throw new FailedToAttachException(STR."Failed to attach BPF program \{fn_name} to kprobe \{probesStr}" + + throw new FailedToAttachException("Failed to attach BPF program " + fn_name + " to kprobe " + probesStr + "it's not traceable (either non-existing, inlined, or marked as \"notrace\")"); } return this; @@ -486,7 +486,7 @@ public BPF attach_kprobe(@Nullable String event, int event_off, @Nullable String _check_probe_quota(1); var fn = load_func(fn_name, Lib.BPF_PROG_TYPE_KPROBE()); assert event != null; - var ev_name = STR."p_\{event.replace("+", "_").replace(".", "_")}"; + var ev_name = "p_" + event.replace("+", "_").replace(".", "_"); int fd; try (var arena = Arena.ofConfined()) { var evNameNative = arena.allocateUtf8String(ev_name); @@ -494,7 +494,7 @@ public BPF attach_kprobe(@Nullable String event, int event_off, @Nullable String fd = Lib.bpf_attach_kprobe(fn.fd, 0, evNameNative, eventNative, event_off, 0); } if (fd < 0) { - throw new FailedToAttachException(STR."Failed to attach BPF program \{fn_name} to kprobe \{event}," + + throw new FailedToAttachException("Failed to attach BPF program " + fn_name + " to kprobe " + event + "," + "it's not traceable (either non-existing, inlined, or marked as \"notrace\")"); } _add_kprobe_fd(ev_name, fn_name, fd); @@ -516,7 +516,7 @@ private void detach_kprobe_event(String ev_name) { } private void detach_kprobe_event_by_fn(String ev_name, String fn_name) { - if (!kprobe_fds.containsKey(ev_name)) throw new RuntimeException(STR."Kprobe \{ev_name} is not attached"); + if (!kprobe_fds.containsKey(ev_name)) throw new RuntimeException("Kprobe " + ev_name + " is not attached"); var res = Lib.bpf_close_perf_event_fd(kprobe_fds.get(ev_name).get(fn_name)); if (res < 0) throw new RuntimeException("Failed to close kprobe FD"); _del_kprobe_fd(ev_name, fn_name); @@ -537,7 +537,7 @@ private void detach_kprobe_event_by_fn(String ev_name, String fn_name) { var nameNative = arena.allocateUtf8String(name); var mapId = Lib.bpf_table_id(module, nameNative); var mapFd = Lib.bpf_table_fd(module, nameNative); - if (mapFd < 0) throw new RuntimeException(STR."Failed to load BPF Table \{name}"); + if (mapFd < 0) throw new RuntimeException("Failed to load BPF Table " + name); // TODO: error checking return provider.createTable(this, mapId, mapFd, name); } @@ -787,7 +787,7 @@ public String get_syscall_fnname(String fnName) { */ private String get_syscall_prefix() { for (var prefix : syscallPrefixes) { - if (ksymname(STR."\{prefix}bpf") != -1) { + if (ksymname(prefix + "bpf") != -1) { return prefix; } } @@ -915,4 +915,4 @@ public boolean hasPerfBuffer(BPFTable.PerfEventArray.PerfEventArrayId id) { public MemorySegment getPerfBuffer(BPFTable.PerfEventArray.PerfEventArrayId id) { return perfBuffers.get(id); } -} +} \ No newline at end of file diff --git a/bcc/src/main/java/me/bechberger/ebpf/bcc/BPFTable.java b/bcc/src/main/java/me/bechberger/ebpf/bcc/BPFTable.java index 55e01dc..ef2d1b2 100644 --- a/bcc/src/main/java/me/bechberger/ebpf/bcc/BPFTable.java +++ b/bcc/src/main/java/me/bechberger/ebpf/bcc/BPFTable.java @@ -16,11 +16,8 @@ package me.bechberger.ebpf.bcc; import me.bechberger.ebpf.annotations.Unsigned; -import me.bechberger.ebpf.bcc.raw.Lib; -import me.bechberger.ebpf.bcc.raw.bcc_perf_buffer_opts; -import me.bechberger.ebpf.bcc.raw.perf_reader_lost_cb; -import me.bechberger.ebpf.bcc.raw.perf_reader_raw_cb; -import me.bechberger.ebpf.shared.BPFType; +import me.bechberger.ebpf.bcc.raw.*; +import me.bechberger.ebpf.type.BPFType; import me.bechberger.ebpf.shared.PanamaUtil; import me.bechberger.ebpf.shared.Util; import org.jetbrains.annotations.NotNull; @@ -921,8 +918,8 @@ public Integer set(int index, String path) { */ public static class PerfEventArray extends ArrayBase implements Closeable { - public record FuncAndLostCallbacks(MemorySegment func, MemorySegment lost, perf_reader_raw_cb rawFunc, - @Nullable perf_reader_lost_cb rawLost) { + public record FuncAndLostCallbacks(MemorySegment func, MemorySegment lost, perf_reader_raw_cb.Function rawFunc, + @Nullable perf_reader_lost_cb.Function rawLost) { } @FunctionalInterface @@ -1033,7 +1030,7 @@ public void open_perf_buffer(EventCallback callback, int pageCnt, @Nullable L private void open_perf_buffer(int cpu, EventCallback callback, int pageCnt, @Nullable LostCallback lostCallback, int wakeupEvents) { - perf_reader_raw_cb rawFn = (ctx, data, size) -> { + perf_reader_raw_cb.Function rawFn = (ctx, data, size) -> { try { callback.call(this, cpu, data, size); } catch (IOException e) { @@ -1045,7 +1042,7 @@ private void open_perf_buffer(int cpu, EventCallback callback, int pageCnt, @ } }; - perf_reader_lost_cb rawLostFn = (ctx, lost) -> { + perf_reader_lost_cb.Function rawLostFn = (ctx, lost) -> { try { assert lostCallback != null; lostCallback.call(this, lost); @@ -1061,9 +1058,9 @@ private void open_perf_buffer(int cpu, EventCallback callback, int pageCnt, @ var fn = perf_reader_raw_cb.allocate(rawFn, bpf.arena()); var lostFn = lostCallback != null ? perf_reader_lost_cb.allocate(rawLostFn, bpf.arena()) : MemorySegment.NULL; var opts = bcc_perf_buffer_opts.allocate(arena); - bcc_perf_buffer_opts.pid$set(opts, -1); - bcc_perf_buffer_opts.cpu$set(opts, cpu); - bcc_perf_buffer_opts.wakeup_events$set(opts, wakeupEvents); + bcc_perf_buffer_opts.pid(opts, -1); + bcc_perf_buffer_opts.cpu(opts, cpu); + bcc_perf_buffer_opts.wakeup_events(opts, wakeupEvents); var reader = Lib.bpf_open_perf_buffer_opts(fn, lostFn, MemorySegment.NULL, pageCnt, opts); if (reader == null) { throw new IllegalStateException("Could not open perf buffer"); @@ -1173,4 +1170,4 @@ public LpmTrie(BPF bpf, long mapId, int mapFd, BPFType keyType, BPFType le super(bpf, MapTypeId.LPM_TRIE, mapId, mapFd, keyType, leafType, name); } } -} +} \ No newline at end of file diff --git a/bcc/src/main/java/me/bechberger/ebpf/samples/chapter2/HelloBuffer.java b/bcc/src/main/java/me/bechberger/ebpf/samples/chapter2/HelloBuffer.java index 2b79628..e95154d 100644 --- a/bcc/src/main/java/me/bechberger/ebpf/samples/chapter2/HelloBuffer.java +++ b/bcc/src/main/java/me/bechberger/ebpf/samples/chapter2/HelloBuffer.java @@ -6,7 +6,7 @@ import me.bechberger.ebpf.annotations.Size; import me.bechberger.ebpf.bcc.BPF; import me.bechberger.ebpf.bcc.BPFTable; -import me.bechberger.ebpf.shared.BPFType; +import me.bechberger.ebpf.type.BPFType; import java.util.List; @@ -108,4 +108,4 @@ int hello(void *ctx) { } } } -} +} \ No newline at end of file diff --git a/bcc/src/main/java/me/bechberger/ebpf/samples/own/HelloStructMap.java b/bcc/src/main/java/me/bechberger/ebpf/samples/own/HelloStructMap.java index 40b4fc0..7ae81fe 100644 --- a/bcc/src/main/java/me/bechberger/ebpf/samples/own/HelloStructMap.java +++ b/bcc/src/main/java/me/bechberger/ebpf/samples/own/HelloStructMap.java @@ -6,7 +6,7 @@ import me.bechberger.ebpf.annotations.Unsigned; import me.bechberger.ebpf.bcc.BPF; import me.bechberger.ebpf.bcc.BPFTable; -import me.bechberger.ebpf.shared.BPFType; +import me.bechberger.ebpf.type.BPFType; import java.util.List; @@ -66,4 +66,4 @@ int hello(void *ctx) { } } } -} +} \ No newline at end of file diff --git a/bcc/src/test/java/me/bechberger/ebpf/bcc/structs/HelloBufferTest.java b/bcc/src/test/java/me/bechberger/ebpf/bcc/structs/HelloBufferTest.java index 1b05f78..ebb9ff1 100644 --- a/bcc/src/test/java/me/bechberger/ebpf/bcc/structs/HelloBufferTest.java +++ b/bcc/src/test/java/me/bechberger/ebpf/bcc/structs/HelloBufferTest.java @@ -6,7 +6,7 @@ import me.bechberger.ebpf.annotations.Size; import me.bechberger.ebpf.bcc.BPF; import me.bechberger.ebpf.bcc.BPFTable; -import me.bechberger.ebpf.shared.BPFType; +import me.bechberger.ebpf.type.BPFType; import me.bechberger.ebpf.bcc.Utils; import org.junit.jupiter.api.Test; @@ -84,4 +84,4 @@ int hello(void *ctx) { } } } -} +} \ No newline at end of file diff --git a/bpf-processor/pom.xml b/bpf-processor/pom.xml index 71485e3..74026ff 100644 --- a/bpf-processor/pom.xml +++ b/bpf-processor/pom.xml @@ -13,8 +13,8 @@ Annotation processor of hello-ebpf - 21 - 21 + 22 + 22 UTF-8 @@ -24,10 +24,6 @@ maven-compiler-plugin 3.8.1 - - --add-exports - jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED - @@ -39,5 +35,17 @@ javapoet 1.13.0 + + org.junit.jupiter + junit-jupiter-params + 5.10.2 + test + + + me.bechberger + ebpf-annotations + 0.1.0-SNAPSHOT + compile + \ No newline at end of file diff --git a/bpf-processor/src/main/java/me/bechberger/cast/CAST.java b/bpf-processor/src/main/java/me/bechberger/cast/CAST.java index e95844e..e4db179 100644 --- a/bpf-processor/src/main/java/me/bechberger/cast/CAST.java +++ b/bpf-processor/src/main/java/me/bechberger/cast/CAST.java @@ -23,12 +23,18 @@ public interface CAST { List children(); - sealed interface Expression extends CAST permits Declarator, InitDeclarator, Initializer, OperatorExpression, - PrimaryExpression { + Statement toStatement(); + + sealed interface Expression extends CAST permits Declarator, InitDeclarator, Initializer, OperatorExpression, PrimaryExpression { @Override List children(); + @Override + default Statement toStatement() { + return Statement.expression(this); + } + static PrimaryExpression.Constant constant(Object value) { return new PrimaryExpression.Constant(value); } @@ -48,6 +54,10 @@ static PrimaryExpression.ParenthesizedExpression parenthesizedExpression(Express static PrimaryExpression.EnumerationConstant enumerationConstant(String name) { return new PrimaryExpression.EnumerationConstant(name); } + + static PrimaryExpression.VerbatimExpression verbatim(String code) { + return new PrimaryExpression.VerbatimExpression(code); + } } /** @@ -94,6 +104,11 @@ static CAnnotation annotation(String annotation, String value) { static CAnnotation sec(String value) { return new CAnnotation("SEC", value); } + + @Override + public Statement toStatement() { + throw new UnsupportedOperationException("CAnnotation cannot be converted to a statement"); + } } /** @@ -143,6 +158,18 @@ public List children() { return List.of(expression); } } + + record VerbatimExpression(String code) implements PrimaryExpression { + @Override + public List children() { + return List.of(); + } + + @Override + public String toString() { + return code; + } + } } /** @@ -567,7 +594,7 @@ public String toString() { record IdentifierDeclarator(PrimaryExpression.Variable name) implements Declarator { @Override public List children() { - return List.of(); + return List.of(name); } @Override @@ -576,6 +603,18 @@ public String toString() { } } + record StructIdentifierDeclarator(PrimaryExpression.Variable name) implements Declarator { + @Override + public List children() { + return List.of(name); + } + + @Override + public String toString() { + return "struct " + name.toString(); + } + } + static Declarator pointer(Declarator declarator) { return new PointerDeclarator(declarator); } @@ -608,6 +647,10 @@ static StructMember structMember(Declarator declarator, PrimaryExpression.Variab PrimaryExpression ebpfSize) { return new StructMember(declarator, name, ebpfSize); } + + static Declarator structIdentifier(PrimaryExpression.Variable name) { + return new StructIdentifierDeclarator(name); + } } @@ -628,6 +671,11 @@ default String toPrettyString() { return toPrettyString("", " "); } + @Override + default Statement toStatement() { + return this; + } + record ExpressionStatement(Expression expression) implements Statement { @Override @@ -908,16 +956,16 @@ public String toString() { } } - record Typedef(Declarator declarator) implements Statement { + record Typedef(Declarator declarator, PrimaryExpression.Variable name) implements Statement { @Override public List children() { - return List.of(declarator); + return List.of(declarator, name); } @Override public String toPrettyString(String indent, String increment) { - return indent + "typedef " + declarator; + return indent + "typedef " + declarator + " " + name + ";"; } @Override @@ -1042,8 +1090,8 @@ static Statement include(String file) { return new Include(file); } - static Statement typedef(Declarator declarator) { - return new Typedef(declarator); + static Statement typedef(Declarator declarator, PrimaryExpression.Variable name) { + return new Typedef(declarator, name); } static Statement caseStatement(Expression expression, Statement body) { diff --git a/bpf-processor/src/main/java/me/bechberger/ebpf/bpf/processor/Processor.java b/bpf-processor/src/main/java/me/bechberger/ebpf/bpf/processor/Processor.java index 995fd1f..43e8606 100644 --- a/bpf-processor/src/main/java/me/bechberger/ebpf/bpf/processor/Processor.java +++ b/bpf-processor/src/main/java/me/bechberger/ebpf/bpf/processor/Processor.java @@ -69,7 +69,9 @@ public void processBPFProgram(TypeElement typeElement) { + "BPF but is not abstract", typeElement); return; } - byte[] bytes = compileProgram(typeElement); + var typeProcessorResult = new TypeProcessor(processingEnv).processBPFTypeRecords(typeElement); + var codeToInsert = typeProcessorResult.toCCode(); + byte[] bytes = compileProgram(typeElement, codeToInsert); // create class that implement the class of typeElement and override the getByteCode method to return the // compiled eBPF program, but store this ebpf program as a base64 string if (bytes == null) { @@ -83,7 +85,7 @@ public void processBPFProgram(TypeElement typeElement) { String name = typeElement.getSimpleName().toString() + "Impl"; TypeSpec typeSpec = createType(typeElement.getSimpleName() + "Impl", typeElement.asType(), bytes, - new TypeProcessor(processingEnv).processBPFTypeRecords(typeElement)); + typeProcessorResult.fields()); try { var file = processingEnv.getFiler().createSourceFile(pkg + "." + name, typeElement); // delete file if it exists @@ -134,7 +136,7 @@ private TypeSpec createType(String name, TypeMirror baseType, byte[] byteCode, L return spec.build(); } - private byte[] compileProgram(TypeElement typeElement) { + private byte[] compileProgram(TypeElement typeElement, String codeToInsert) { Optional elem = typeElement.getEnclosedElements().stream().filter(e -> e.getKind().isField() && e.getSimpleName().toString().equals("EBPF_PROGRAM")).findFirst(); // check that the class has a static field EBPF_PROGRAM of type String or Path @@ -175,9 +177,22 @@ private byte[] compileProgram(TypeElement typeElement) { } } this.processingEnv.getMessager().printNote("EBPF Program: " + ebpfProgram, typeElement); + ebpfProgram = placeDefinitionsIntoEBPFProgram(ebpfProgram, codeToInsert); return compile(ebpfProgram, element); } + private String placeDefinitionsIntoEBPFProgram(String ebpfProgram, String codeToInsert) { + // insert the code to insert into the ebpf program + // if the insertion fails, print an error + // if the insertion succeeds, return the new ebpf program + int index = ebpfProgram.lastIndexOf("#include"); + if (index == -1) { + this.processingEnv.getMessager().printError("Could not find #include in eBPF program", null); + return null; + } + return ebpfProgram.substring(0, index) + codeToInsert + ebpfProgram.substring(index); + } + private static String findNewestClangVersion() { for (int i = 24; i > 12; i--) { try { @@ -353,4 +368,4 @@ private Path obtainPathToVMLinuxHeader() { } return getEBPFFolder() == null ? null : getEBPFFolder().resolve(name); } -} +} \ No newline at end of file diff --git a/bpf-processor/src/main/java/me/bechberger/ebpf/bpf/processor/TypeProcessor.java b/bpf-processor/src/main/java/me/bechberger/ebpf/bpf/processor/TypeProcessor.java index 0dc2fa5..9c19233 100644 --- a/bpf-processor/src/main/java/me/bechberger/ebpf/bpf/processor/TypeProcessor.java +++ b/bpf-processor/src/main/java/me/bechberger/ebpf/bpf/processor/TypeProcessor.java @@ -1,9 +1,8 @@ package me.bechberger.ebpf.bpf.processor; -import com.squareup.javapoet.ClassName; import com.squareup.javapoet.FieldSpec; -import com.squareup.javapoet.ParameterizedTypeName; -import com.squareup.javapoet.TypeName; +import me.bechberger.cast.CAST; +import me.bechberger.ebpf.type.BPFType; import javax.annotation.processing.ProcessingEnvironment; import javax.lang.model.AnnotatedConstruct; @@ -11,35 +10,61 @@ import javax.lang.model.type.PrimitiveType; import javax.lang.model.type.TypeMirror; import java.util.*; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Function; import java.util.stream.Collectors; /** * Handles {@code @Type} annotated records */ -class TypeProcessor { - - private final String SIZE_ANNOTATION = "me.bechberger.ebpf.annotations.Size"; - private final String UNSIGNED_ANNOTATION = "me.bechberger.ebpf.annotations.Unsigned"; - private final String TYPE_ANNOTATION = "me.bechberger.ebpf.annotations.bpf.Type"; - private final String TYPE_MEMBER_ANNOTATION = "me.bechberger.ebpf.annotations.bpf.Type.Member"; - - private final String BPF_PACKAGE = "me.bechberger.ebpf.shared"; - private final String BPF_TYPE = "me.bechberger.ebpf.shared.BPFType"; - private final String BPF_INT_TYPE = BPF_TYPE + ".BPFIntType"; +public class TypeProcessor { + public final static String SIZE_ANNOTATION = "me.bechberger.ebpf.annotations.Size"; + public final static String UNSIGNED_ANNOTATION = "me.bechberger.ebpf.annotations.Unsigned"; + public final static String TYPE_ANNOTATION = "me.bechberger.ebpf.annotations.bpf.Type"; + public final static String BPF_PACKAGE = "me.bechberger.ebpf.shared"; + public final static String BPF_TYPE = "me.bechberger.ebpf.shared.BPFType"; /** * Helper class to keep track of defined types */ - private record DefinedTypes(Map typeToFieldName) { + private class DefinedTypes { + + private final Map typeToFieldName; + private final Map nameToSpecFieldName; + + private final Map nameToTypeElement; + + DefinedTypes(Map typeToFieldName) { + this.typeToFieldName = typeToFieldName; + this.nameToSpecFieldName = new HashMap<>(); + this.nameToTypeElement = new HashMap<>(); + this.typeToFieldName.forEach((k, v) -> { + var name = getTypeRecordBpfName(k); + this.nameToSpecFieldName.put(name, v); + this.nameToTypeElement.put(name, k); + }); + } public boolean isTypeDefined(TypeElement typeElement) { return this.typeToFieldName.containsKey(typeElement); } + public boolean isNameDefined(String name) { + return this.nameToSpecFieldName.containsKey(name); + } + public Optional getFieldName(TypeElement typeElement) { return Optional.ofNullable(this.typeToFieldName.get(typeElement)); } + public Optional getSpecFieldName(String name) { + return Optional.ofNullable(this.nameToSpecFieldName.get(name)); + } + + public Optional getTypeElement(String name) { + return Optional.ofNullable(this.nameToTypeElement.get(name)); + } + @Override public String toString() { return this.typeToFieldName.toString(); @@ -82,20 +107,80 @@ record Pair(Optional a, Element e) { }).filter(Objects::nonNull).toList(); } + record TypeProcessorResult(List fields, List definingStatements) { + + String toCCode() { + return definingStatements.stream().map(CAST.Statement::toPrettyString).collect(Collectors.joining("\n")); + } + } + /** * Process the records annotated with {@code @Type} in the given class * * @param outerTypeElement the class to process that contains the records * @return a list of field specs that define the related {@code BPFStructType} instances */ - List processBPFTypeRecords(TypeElement outerTypeElement) { + TypeProcessorResult processBPFTypeRecords(TypeElement outerTypeElement) { List innerTypeElements = getInnerBPFTypeElements(outerTypeElement); definedTypes = getDefinedTypes(innerTypeElements); - var list = innerTypeElements.stream().map(this::processBPFTypeRecord).toList(); - if (list.stream().anyMatch(Optional::isEmpty)) { - return List.of(); + + Map> alreadyDefinedTypes = new HashMap<>(); + // detect recursion + Set currentlyDefining = new HashSet<>(); + + List processedTypes = new ArrayList<>(); + + AtomicReference>> obtainType = new AtomicReference<>(); + obtainType.set(name -> { + if (alreadyDefinedTypes.containsKey(name)) { + return alreadyDefinedTypes.get(name); + } + if (currentlyDefining.contains(name)) { + this.processingEnv.getMessager().printError("Recursion detected for type " + name, outerTypeElement); + throw new IllegalStateException("Recursion detected for type " + name); + } + currentlyDefining.add(name); + var typeElement = definedTypes.getTypeElement(name).get(); + var type = processBPFTypeRecord(typeElement, obtainType.get()); + if (type.isEmpty()) { + return null; + } + alreadyDefinedTypes.put(name, type.get()); + currentlyDefining.remove(name); + processedTypes.add(typeElement); + return type.get(); + }); + + Function, String> typeToSpecField = t -> { + if (t instanceof BPFType.BPFStructType structType) { + return definedTypes.getSpecFieldName(structType.bpfName()).get(); + } + return null; + }; + + while (processedTypes.size() < innerTypeElements.size()) { + var unprocessed = innerTypeElements.stream().filter(e -> !processedTypes.contains(e)).toList(); + var type = processBPFTypeRecord(unprocessed.get(0), obtainType.get()); + if (type.isEmpty()) { + return new TypeProcessorResult(List.of(), List.of()); + } + alreadyDefinedTypes.put(type.get().bpfName(), type.get()); + processedTypes.add(unprocessed.get(0)); + } + + List fields = new ArrayList<>(); + List definingStatements = new ArrayList<>(); + + for (var processedType : processedTypes) { + var name = getTypeRecordBpfName(processedType); + var type = alreadyDefinedTypes.get(getTypeRecordBpfName(processedType)); + var spec = type.toFieldSpecGenerator().get().apply(definedTypes.getSpecFieldName(name).get(), + typeToSpecField); + fields.add(spec); + var def = type.toCDeclarationStatement(); + def.ifPresent(definingStatements::add); } - return list.stream().map(Optional::get).toList(); + return new TypeProcessorResult(fields, definingStatements); } /** @@ -112,28 +197,10 @@ private String getTypeRecordBpfName(TypeElement typeElement) { return name.get().getValue().getValue().toString(); } - /** - * Mirrors a generic BPFType instance - */ - record BPFTypeMirror(String expression) { - } - - /** - * Mirrors the BPFStructMember constructor - */ - record UBPFStructMemberMirror(String name, BPFTypeMirror type, String javaType) { - } - - /** - * Mirrors the BPFStructType constructor - */ - record StructTypeMirror(String bpfName, List members, TypeMirror javaClass) { - } - - private Optional processBPFTypeRecord(TypeElement typeElement) { + @SuppressWarnings({"unchecked", "rawtypes"}) + private Optional> processBPFTypeRecord(TypeElement typeElement, Function> nameToCustomType) { String className = typeElement.getSimpleName().toString(); String name = getTypeRecordBpfName(typeElement); - String fieldName = typeToFieldName(typeElement); var constructors = typeElement.getEnclosedElements().stream().filter(e -> e.getKind() == ElementKind.CONSTRUCTOR).toList(); if (constructors.size() != 1) { @@ -142,40 +209,29 @@ private Optional processBPFTypeRecord(TypeElement typeElement) { return Optional.empty(); } var constructor = (ExecutableElement) constructors.getFirst(); - var members = processBPFTypeRecordMembers(constructor.getParameters()); + Optional>> members = + processBPFTypeRecordMembers(constructor.getParameters(), nameToCustomType); if (members.isEmpty()) { return Optional.empty(); } - // now create the BPFStructType instance FieldSpec - // that we get something like - /* - BPFType.BPFStructType fieldName = BPFStructType.autoLayout("name", List.of( - new BPFType.UBPFStructMember<>("e_pid", BPFType.BPFIntType.UINT32, Event::pid), - ), new BPFType.AnnotatedClass(className.class, List.of()), fields -> new Event((int)fields.get(0), - (String)fields.get(1), (String)fields.get(2))); - */ - ClassName bpfStructType = ClassName.get(BPF_PACKAGE, "BPFType.BPFStructType"); - TypeName fieldType = ParameterizedTypeName.get(bpfStructType, ClassName.get("", className)); - String memberExpression = - members.get().stream().map(m -> "new " + BPF_TYPE + ".UBPFStructMember<>(" + "\"" + m.name() + "\", " + m.type().expression() + ", " + className + "::" + m.name() + ")").collect(Collectors.joining(", ")); - ClassName bpfType = ClassName.get(BPF_PACKAGE, "BPFType"); - String creatorExpr = - members.get().stream().map(m -> "(" + m.javaType + ")fields.get(" + members.get().indexOf(m) + ")").collect(Collectors.joining(", ")); - return Optional.of(FieldSpec.builder(fieldType, fieldName).addModifiers(Modifier.FINAL, Modifier.STATIC, - Modifier.PUBLIC).initializer("$T.autoLayout($S, java.util.List.of($L), new $T.AnnotatedClass($T" + - ".class, java.util.List" + ".of()" + "), " + "fields -> new $T($L))", bpfStructType, name, - memberExpression, bpfType, ClassName.get("", className), ClassName.get("", className), creatorExpr).build()); + + BPFType.AnnotatedClass annotatedClass = new BPFType.AnnotatedClass(className, List.of()); + + return Optional.of(BPFType.BPFStructType.autoLayout(name, + (List>)(List)members.get(), + annotatedClass, null)); } - private Optional> processBPFTypeRecordMembers(List recordMembers) { - var list = recordMembers.stream().map(this::processBPFTypeRecordMember).toList(); + @SuppressWarnings({"unchecked", "rawtypes"}) + private Optional>> processBPFTypeRecordMembers(List recordMembers, Function> nameToCustomType) { + var list = recordMembers.stream().map(m -> processBPFTypeRecordMember(m, nameToCustomType)).toList(); if (list.stream().anyMatch(Optional::isEmpty)) { return Optional.empty(); } - return Optional.of(list.stream().map(Optional::get).toList()); + return Optional.of((List>)(List)list.stream().map(Optional::get).toList()); } - record AnnotationValues(boolean unsigned, Optional size, Optional bpfType) { + record AnnotationValues(boolean unsigned, Optional size) { } private AnnotationValues getAnnotationValuesForRecordMember(VariableElement element) { @@ -189,22 +245,18 @@ private AnnotationValues getAnnotationValuesForRecordMember(VariableElement elem size = Optional.of((Integer) value.get().getValue().getValue()); } } - var typeAnnotation = getAnnotationMirror(element.asType(), TYPE_MEMBER_ANNOTATION); - if (typeAnnotation.isPresent()) { - var value = typeAnnotation.get().getElementValues().entrySet().stream().findFirst(); - if (value.isPresent()) { - bpfType = Optional.of((String) value.get().getValue().getValue()); - } - } - return new AnnotationValues(unsigned, size, bpfType); + return new AnnotationValues(unsigned, size); } - private Optional processBPFTypeRecordMember(VariableElement element) { + private Optional> processBPFTypeRecordMember(VariableElement element, + Function> nameToCustomType) { AnnotationValues annotations = getAnnotationValuesForRecordMember(element); TypeMirror type = element.asType(); var bpfType = processBPFTypeRecordMemberType(element, annotations, type); - return bpfType.map(bpfTypeMirror -> new UBPFStructMemberMirror(element.getSimpleName().toString(), - bpfTypeMirror, lastPart(type.toString()))); + return bpfType.map(t -> { + return new BPFType.UBPFStructMember<>(element.getSimpleName().toString(), + t.toBPFType(nameToCustomType), null, null); + }); } private static final Set integerTypes = Set.of("int", "long", "short", "byte", "char"); @@ -226,11 +278,8 @@ private boolean isStringType(TypeMirror type) { private Optional processBPFTypeRecordMemberType(Element element, AnnotationValues annotations, TypeMirror type) { - if (annotations.bpfType().isPresent()) { - return processCustomType(annotations.bpfType().get(), type); - } if (isIntegerType(type)) { - return processIntegerType(element, annotations, type); + return processIntegerType(element, annotations, type).map(tp -> (t -> tp)); } if (isStringType(type)) { return processStringType(element, annotations, type); @@ -242,17 +291,25 @@ private Optional processBPFTypeRecordMemberType(Element element, return Optional.empty(); } - private Optional processCustomType(String bpfType, TypeMirror type) { - return Optional.of(new BPFTypeMirror(bpfType)); + @FunctionalInterface + interface BPFTypeMirror { + + BPFType toBPFType(Function> nameToCustomType); } - private Optional getBaseIntegerType(Element element, TypeMirror type) { + private Optional> getIntegerType(Element element, TypeMirror type, boolean unsigned) { return switch (lastPart(type.toString())) { - case "int" -> Optional.of("INT32"); - case "long" -> Optional.of("INT64"); - case "short" -> Optional.of("INT16"); - case "byte" -> Optional.of("INT8"); - case "char" -> Optional.of("UINT16"); + case "int" -> Optional.of(unsigned ? BPFType.BPFIntType.UINT32 : BPFType.BPFIntType.INT32); + case "long" -> Optional.of(unsigned ? BPFType.BPFIntType.UINT64 : BPFType.BPFIntType.INT64); + case "short" -> Optional.of(unsigned ? BPFType.BPFIntType.UINT16 : BPFType.BPFIntType.INT16); + case "byte" -> Optional.of(unsigned ? BPFType.BPFIntType.UINT8 : BPFType.BPFIntType.INT8); + case "char" -> { + if (unsigned) { + this.processingEnv.getMessager().printError("Unsigned char not supported", element); + yield Optional.empty(); + } + yield Optional.of(BPFType.BPFIntType.CHAR); + } default -> { this.processingEnv.getMessager().printError("Unsupported integer type " + type, element); yield Optional.empty(); @@ -260,13 +317,30 @@ private Optional getBaseIntegerType(Element element, TypeMirror type) { }; } - private Optional processIntegerType(Element element, AnnotationValues annotations, TypeMirror type) { + private Optional> processIntegerType(Element element, AnnotationValues annotations, TypeMirror type) { if (annotations.size().isPresent()) { // annotation not supported for integer types and log this.processingEnv.getMessager().printError("Size annotation not supported for integer types", element); return Optional.empty(); } - return getBaseIntegerType(element, type).map(s -> new BPFTypeMirror(BPF_INT_TYPE + "." + (annotations.unsigned ? "U" : "") + s)); + boolean unsigned = annotations.unsigned; + return switch (lastPart(type.toString())) { + case "int" -> Optional.of(unsigned ? BPFType.BPFIntType.UINT32 : BPFType.BPFIntType.INT32); + case "long" -> Optional.of(unsigned ? BPFType.BPFIntType.UINT64 : BPFType.BPFIntType.INT64); + case "short" -> Optional.of(unsigned ? BPFType.BPFIntType.UINT16 : BPFType.BPFIntType.INT16); + case "byte" -> Optional.of(unsigned ? BPFType.BPFIntType.UINT8 : BPFType.BPFIntType.INT8); + case "char" -> { + if (unsigned) { + this.processingEnv.getMessager().printError("Unsigned char not supported", element); + yield Optional.empty(); + } + yield Optional.of(BPFType.BPFIntType.CHAR); + } + default -> { + this.processingEnv.getMessager().printError("Unsupported integer type " + type, element); + yield Optional.empty(); + } + }; } private Optional processStringType(Element element, AnnotationValues annotations, TypeMirror type) { @@ -278,7 +352,7 @@ private Optional processStringType(Element element, AnnotationVal this.processingEnv.getMessager().printError("Unsigned annotation not supported for string types", element); return Optional.empty(); } - return Optional.of(new BPFTypeMirror("new " + BPF_TYPE + ".StringType(" + annotations.size().get() + ")")); + return Optional.of(t -> new BPFType.StringType(annotations.size().get())); } private Optional processDefinedType(Element element, AnnotationValues annotations, TypeMirror type) { @@ -297,7 +371,7 @@ private Optional processDefinedType(Element element, AnnotationVa element); return Optional.empty(); } - return Optional.of(new BPFTypeMirror(fieldName.get())); + return Optional.of(t -> t.apply(fieldName.get())); } private DefinedTypes getDefinedTypes(List innerTypeElements) { @@ -319,4 +393,4 @@ private String typeToFieldName(TypeElement typeElement) { private static String toSnakeCase(String name) { return name.replaceAll("([a-z0-9])([A-Z])", "$1_$2"); } -} +} \ No newline at end of file diff --git a/shared/src/main/java/me/bechberger/ebpf/shared/BPFType.java b/bpf-processor/src/main/java/me/bechberger/ebpf/type/BPFType.java similarity index 71% rename from shared/src/main/java/me/bechberger/ebpf/shared/BPFType.java rename to bpf-processor/src/main/java/me/bechberger/ebpf/type/BPFType.java index 6e65ce8..f64b3a9 100644 --- a/shared/src/main/java/me/bechberger/ebpf/shared/BPFType.java +++ b/bpf-processor/src/main/java/me/bechberger/ebpf/type/BPFType.java @@ -1,18 +1,30 @@ -package me.bechberger.ebpf.shared; +package me.bechberger.ebpf.type; +import com.squareup.javapoet.ClassName; +import com.squareup.javapoet.FieldSpec; +import com.squareup.javapoet.ParameterizedTypeName; +import com.squareup.javapoet.TypeName; +import me.bechberger.cast.CAST; import me.bechberger.ebpf.annotations.AnnotationInstances; import org.jetbrains.annotations.Nullable; +import javax.lang.model.element.Modifier; import java.lang.annotation.Annotation; import java.lang.foreign.Arena; import java.lang.foreign.MemoryLayout; import java.lang.foreign.MemorySegment; import java.lang.foreign.ValueLayout; import java.util.*; +import java.util.function.BiFunction; import java.util.function.Function; +import java.util.stream.Collectors; import java.util.stream.IntStream; -import static me.bechberger.ebpf.shared.BPFType.BPFIntType.CHAR; +import static me.bechberger.cast.CAST.Declarator.identifier; +import static me.bechberger.cast.CAST.Expression.variable; +import static me.bechberger.ebpf.bpf.processor.TypeProcessor.BPF_PACKAGE; +import static me.bechberger.ebpf.bpf.processor.TypeProcessor.BPF_TYPE; +import static me.bechberger.ebpf.type.BPFType.BPFIntType.CHAR; /** @@ -24,7 +36,11 @@ public sealed interface BPFType { /** * Java class with annotations */ - record AnnotatedClass(Class klass, List annotations) { + record AnnotatedClass(String klass, List annotations) { + + public AnnotatedClass(Class klass, List annotations) { + this(klass.getName(), annotations); + } } /** @@ -66,11 +82,15 @@ default long size() { */ long alignment(); + private static long padSize(long size, long alignment) { + return (size + alignment - 1) & -alignment; + } + /** * Padded size of the type in bytes, use for all array index computations */ default long sizePadded() { - return PanamaUtil.padSize(layout().byteSize(), alignment()); + return padSize(layout().byteSize(), alignment()); } /** @@ -78,6 +98,32 @@ default long sizePadded() { */ AnnotatedClass javaClass(); + default Optional toCDeclaration() { + return Optional.empty(); // for structs already defined in C + } + + default Optional toCDeclarationStatement() { + return Optional.empty(); + } + + default CAST.Declarator toCUse() { + return identifier(bpfName()); + } + + default Optional, String>, FieldSpec>> toFieldSpecGenerator() { + return Optional.empty(); + } + + default String toJavaUse() { + return javaClass().klass; + } + + default String toJavaUseInGenerics() { + return toJavaUse(); + } + + String toJavaFieldSpecUse(Function, String> typeToSpecFieldName); + /** * Make sure to guarantee type-safety */ @@ -133,12 +179,37 @@ public long alignment() { return size(); } + @Override + public String toJavaUse() { + return switch (javaClass.klass) { + case "java.lang.Integer" -> "int"; + case "java.lang.Long" -> "long"; + case "java.lang.Short" -> "short"; + case "java.lang.Byte" -> "byte"; + case "java.lang.Boolean" -> "boolean"; + default -> javaClass().klass; + }; + } + + @Override + public String toJavaUseInGenerics() { + return javaClass.klass; + } + + @Override + public String toJavaFieldSpecUse(Function, String> typeToSpecFieldName) { + return getClass().getSimpleName() + "." + Objects.requireNonNull(typeToSpecName.get(this)); + } + private static final Map> registeredTypes = new HashMap<>(); + private static final Map, String> typeToSpecName = new IdentityHashMap<>(); /** * Create a new BPFIntType and register it */ - private static BPFIntType createType(String bpfName, Class klass, V layout, + private static BPFIntType createType(String bpfName, String specFieldName, + Class klass, + V layout, MemoryParser parser, MemorySetter setter, boolean signed) { var type = new BPFIntType<>(bpfName, layout, parser, setter, new AnnotatedClass(klass, signed ? @@ -147,13 +218,14 @@ private static BPFIntType createType(String bpfNam throw new IllegalArgumentException("Type " + type.javaClass() + " already registered as " + registeredTypes.get(type.javaClass()).bpfName()); } registeredTypes.put(type.javaClass(), type); + typeToSpecName.put(type, specFieldName); return type; } /** * bool/u8 mapped to {@code boolean} */ - public static final BPFIntType BOOL = createType("bool", Boolean.class, ValueLayout.JAVA_BYTE, + public static final BPFIntType BOOL = createType("bool", "BOOL", Boolean.class, ValueLayout.JAVA_BYTE, segment -> { return segment.get(ValueLayout.JAVA_BYTE, 0) == 1; }, (segment, obj) -> { @@ -163,7 +235,7 @@ private static BPFIntType createType(String bpfNam /** * char mapped to {@code byte} (essentially an unsigned byte) */ - public static final BPFIntType CHAR = createType("char", Byte.class, ValueLayout.JAVA_BYTE, + public static final BPFIntType CHAR = createType("char", "CHAR", Byte.class, ValueLayout.JAVA_BYTE, segment -> { return segment.get(ValueLayout.JAVA_BYTE, 0); }, (segment, obj) -> { @@ -175,7 +247,8 @@ private static BPFIntType createType(String bpfNam /** * i8 mapped to {@code byte} */ - public static final BPFIntType INT8 = createType("s8", Byte.class, ValueLayout.JAVA_BYTE, segment -> { + public static final BPFIntType INT8 = createType("s8", "INT8", Byte.class, ValueLayout.JAVA_BYTE, + segment -> { return segment.get(ValueLayout.JAVA_BYTE, 0); }, (segment, obj) -> { segment.set(ValueLayout.JAVA_BYTE, 0, obj); @@ -184,7 +257,7 @@ private static BPFIntType createType(String bpfNam /** * s16 mapped to {@code short} */ - public static final BPFIntType INT16 = createType("s16", Short.class, ValueLayout.JAVA_SHORT, + public static final BPFIntType INT16 = createType("s16", "INT16", Short.class, ValueLayout.JAVA_SHORT, segment -> { return segment.get(ValueLayout.JAVA_SHORT, 0); }, (segment, obj) -> { @@ -194,7 +267,7 @@ private static BPFIntType createType(String bpfNam /** * u16 mapped to {@code @Unsigned short} */ - public static final BPFIntType UINT16 = createType("u16", Short.class, ValueLayout.JAVA_SHORT, + public static final BPFIntType UINT16 = createType("u16", "UINT16", Short.class, ValueLayout.JAVA_SHORT, segment -> { return segment.get(ValueLayout.JAVA_SHORT, 0); }, (segment, obj) -> { @@ -204,7 +277,7 @@ private static BPFIntType createType(String bpfNam /** * s32 mapped to {@code int} */ - public static final BPFIntType INT32 = createType("s32", Integer.class, ValueLayout.JAVA_INT, + public static final BPFIntType INT32 = createType("s32", "INT32", Integer.class, ValueLayout.JAVA_INT, segment -> { return segment.get(ValueLayout.JAVA_INT, 0); }, (segment, obj) -> { @@ -214,7 +287,8 @@ private static BPFIntType createType(String bpfNam /** * u32 mapped to {@code @Unsigned int} */ - public static final BPFIntType UINT32 = createType("u32", Integer.class, ValueLayout.JAVA_INT, + public static final BPFIntType UINT32 = createType("u32", "UINT32", Integer.class, + ValueLayout.JAVA_INT, segment -> { return segment.get(ValueLayout.JAVA_INT, 0); }, (segment, obj) -> { @@ -225,7 +299,8 @@ private static BPFIntType createType(String bpfNam /** * s64 mapped to {@code long} */ - public static final BPFIntType INT64 = createType("s64", Long.class, ValueLayout.JAVA_LONG, segment -> { + public static final BPFIntType INT64 = createType("s64", "INT64", Long.class, ValueLayout.JAVA_LONG, + segment -> { return segment.get(ValueLayout.JAVA_LONG, 0); }, (segment, obj) -> { segment.set(ValueLayout.JAVA_LONG, 0, obj); @@ -234,7 +309,8 @@ private static BPFIntType createType(String bpfNam /** * u64 mapped to {@code @Unsigned long} */ - public static final BPFIntType UINT64 = createType("u64", Long.class, ValueLayout.JAVA_LONG, segment -> { + public static final BPFIntType UINT64 = createType("u64", "UINT64", Long.class, ValueLayout.JAVA_LONG, + segment -> { return segment.get(ValueLayout.JAVA_LONG, 0); }, (segment, obj) -> { segment.set(ValueLayout.JAVA_LONG, 0, obj); @@ -245,7 +321,6 @@ private static BPFIntType createType(String bpfNam * void* */ public static final BPFType POINTER = new BPFTypedef<>("void*", BPFIntType.UINT64); - } /** @@ -256,7 +331,17 @@ private static BPFIntType createType(String bpfNam * @param offset offset from the start of the struct in bytes * @param getter function that takes the struct and returns the member */ - record BPFStructMember(String name, BPFType type, int offset, Function getter) { + record BPFStructMember(String name, BPFType type, int offset, Function getter, + @Nullable String ebpfSize) { + + public BPFStructMember(String name, BPFType type, int offset, Function getter) { + this(name, type, offset, getter, null); + } + + CAST.Declarator.StructMember toCStructMember() { + return CAST.Declarator.structMember(type.toCUse(), CAST.Expression.variable(name), + ebpfSize == null ? null : CAST.Expression.verbatim(ebpfSize)); + } } /** @@ -266,9 +351,13 @@ record BPFStructMember(String name, BPFType type, int offset, Function< * @param type type of the member * @param getter function that takes the struct and returns the member */ - record UBPFStructMember(String name, BPFType type, Function getter) { + record UBPFStructMember(String name, BPFType type, Function getter, @Nullable String ebpfSize) { + + public UBPFStructMember(String name, BPFType type, Function getter) { + this(name, type, getter, null); + } public BPFStructMember position(int offset) { - return new BPFStructMember<>(name, type, offset, getter); + return new BPFStructMember<>(name, type, offset, getter, ebpfSize); } } @@ -332,7 +421,7 @@ private MemoryLayout createLayout(List> members) { List> result = new ArrayList<>(); long offset = 0; for (var member : members) { - offset = PanamaUtil.padSize(offset, member.type.alignment()); + offset = padSize(offset, member.type.alignment()); result.add(member.position((int) offset)); offset += member.type.size(); } @@ -422,6 +511,50 @@ public String toString() { return "BPFStructType[" + "bpfName=" + bpfName + ", " + "members=" + members + ", " + "javaClass=" + javaClass + ", " + "constructor=" + constructor + ']'; } + @Override + public Optional toCDeclaration() { + return Optional.of(CAST.Declarator.struct(CAST.Expression.variable(bpfName), + members.stream().map(BPFStructMember::toCStructMember).toList())); + } + + @Override + public Optional toCDeclarationStatement() { + return toCDeclaration().map(d -> CAST.Statement.declarationStatement(d, null)); + } + + @Override + public CAST.Declarator toCUse() { + return CAST.Declarator.structIdentifier(CAST.Expression.variable(bpfName)); + } + + @Override + public Optional, String>, FieldSpec>> toFieldSpecGenerator() { + return Optional.of((fieldName, typeToSpecName)-> { + String className = this.javaClass.klass; + ClassName bpfStructType = ClassName.get(BPF_PACKAGE, "BPFType.BPFStructType"); + TypeName fieldType = ParameterizedTypeName.get(bpfStructType, ClassName.get("", className)); + String memberExpression = + members.stream().map(m -> "new " + BPF_TYPE + ".UBPFStructMember<>(" + "\"" + m.name() + "\"," + + " " + typeToSpecName.apply(m.type()) + ", " + className + "::" + m.name() + + ")").collect(Collectors.joining(", ")); + ClassName bpfType = ClassName.get(BPF_PACKAGE, "BPFType"); + String creatorExpr = IntStream.range(0, members.size()).mapToObj(i -> "(" + members.get(i).type.toJavaUse() + ")" + "fields.get(" + i + ")").collect(Collectors.joining(", ")); + return FieldSpec.builder(fieldType, fieldName).addModifiers(Modifier.FINAL, Modifier.STATIC, Modifier.PUBLIC) + .initializer("$T.autoLayout($S, java.util.List.of($L), new $T.AnnotatedClass($T" + ".class, " + + "java.util.List" + ".of()" + "), " + "fields -> new $T($L))", bpfStructType, bpfName, + memberExpression, bpfType, ClassName.get("", className), ClassName.get("", className), creatorExpr).build(); + }); + } + + @Override + public String toJavaUse() { + return javaClass.klass; + } + + @Override + public String toJavaFieldSpecUse(Function, String> typeToSpecFieldName) { + return typeToSpecFieldName.apply(this); + } } /** @@ -477,6 +610,26 @@ public long getOffsetAtIndex(int index) { public static BPFArrayType of(BPFType memberType, int length) { return new BPFArrayType<>(memberType.bpfName() + "[" + length + "]", memberType, length); } + + @Override + public Optional toCDeclaration() { + return Optional.empty(); + } + + @Override + public CAST.Declarator toCUse() { + return CAST.Declarator.array(memberType.toCUse(), CAST.Expression.constant(length)); + } + + @Override + public String toJavaUse() { + return "java.util.List<" + memberType.toJavaUseInGenerics() + ">"; + } + + @Override + public String toJavaFieldSpecUse(Function, String> typeToSpecFieldName) { + return "new " + BPF_TYPE + ".BPFArrayType<>(\""+ bpfName + "\", " + typeToSpecFieldName.apply(memberType) + ", " + length + ")"; + } } /** @@ -528,6 +681,21 @@ public long alignment() { public AnnotatedClass javaClass() { return new AnnotatedClass(String.class, List.of(AnnotationInstances.size(length))); } + + @Override + public Optional toCDeclaration() { + return Optional.empty(); + } + + @Override + public CAST.Declarator toCUse() { + return CAST.Declarator.array(CHAR.toCUse(), CAST.Expression.constant(length)); + } + + @Override + public String toJavaFieldSpecUse(Function, String> typeToSpecFieldName) { + return "new " + BPF_TYPE + ".StringType(" + length + ")"; + } } /** @@ -559,6 +727,21 @@ public long alignment() { public AnnotatedClass javaClass() { return wrapped.javaClass(); } + + @Override + public Optional toCDeclaration() { + return Optional.of(CAST.Statement.typedef(wrapped.toCUse(), variable(bpfName))); + } + + @Override + public Optional toCDeclarationStatement() { + return Optional.of(CAST.Statement.typedef(wrapped.toCUse(), variable(bpfName))); + } + + @Override + public String toJavaFieldSpecUse(Function, String> typeToSpecFieldName) { + return "new " + BPF_TYPE + ".BPFTypedef<>(" + "\"" + bpfName + "\", " + typeToSpecFieldName.apply(wrapped) + ")"; + } } @@ -624,6 +807,11 @@ public MemorySetter> setter() { public AnnotatedClass javaClass() { return new AnnotatedClass(BPFUnion.class, List.of()); } + + @Override + public String toJavaFieldSpecUse(Function, String> typeToSpecFieldName) { + throw new UnsupportedOperationException(); + } } interface BPFUnion { @@ -688,4 +876,4 @@ public boolean equals(Object obj) { that.possibleMembers); } } -} +} \ No newline at end of file diff --git a/bpf/pom.xml b/bpf/pom.xml index e0d0606..73aebec 100644 --- a/bpf/pom.xml +++ b/bpf/pom.xml @@ -37,8 +37,8 @@ UTF-8 - 21 - 21 + 22 + 22 @@ -53,7 +53,6 @@ me.bechberger.ebpf.bpf.processor.Processor - --enable-preview --add-exports jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED @@ -113,7 +112,7 @@ me.bechberger rawbpf - 0.1.0 + 0.1.1 me.bechberger @@ -133,4 +132,4 @@ hello-ebpf 0.1.0-SNAPSHOT - + \ No newline at end of file diff --git a/bpf/src/main/java/me/bechberger/ebpf/bpf/BPFProgram.java b/bpf/src/main/java/me/bechberger/ebpf/bpf/BPFProgram.java index da6b0f2..a93e39c 100644 --- a/bpf/src/main/java/me/bechberger/ebpf/bpf/BPFProgram.java +++ b/bpf/src/main/java/me/bechberger/ebpf/bpf/BPFProgram.java @@ -3,7 +3,7 @@ import me.bechberger.ebpf.bpf.map.*; import me.bechberger.ebpf.bpf.raw.Lib; import me.bechberger.ebpf.bpf.raw.LibraryLoader; -import me.bechberger.ebpf.shared.BPFType; +import me.bechberger.ebpf.type.BPFType; import me.bechberger.ebpf.shared.PanamaUtil; import me.bechberger.ebpf.shared.PanamaUtil.HandlerWithErrno; import me.bechberger.ebpf.shared.TraceLog; @@ -16,7 +16,6 @@ import java.lang.foreign.MemorySegment; import java.nio.file.Files; import java.nio.file.Path; -import java.util.Arrays; import java.util.function.Function; /** diff --git a/bpf/src/main/java/me/bechberger/ebpf/bpf/map/BPFBaseMap.java b/bpf/src/main/java/me/bechberger/ebpf/bpf/map/BPFBaseMap.java index b14f020..42633d5 100644 --- a/bpf/src/main/java/me/bechberger/ebpf/bpf/map/BPFBaseMap.java +++ b/bpf/src/main/java/me/bechberger/ebpf/bpf/map/BPFBaseMap.java @@ -2,7 +2,7 @@ import me.bechberger.ebpf.bpf.BPFError; import me.bechberger.ebpf.bpf.raw.Lib; -import me.bechberger.ebpf.shared.BPFType; +import me.bechberger.ebpf.type.BPFType; import me.bechberger.ebpf.shared.PanamaUtil; import org.jetbrains.annotations.Nullable; @@ -121,7 +121,7 @@ public Iterator keyIterator() { record MemAndKey(MemorySegment mem, K key) {} - Arena arena = Arena.ofConfined(); + final Arena arena = Arena.ofConfined(); @Nullable MemAndKey next = obtainNext(null); MemorySegment nextKeyMem; @@ -237,4 +237,4 @@ public int slowSize() { return size; } } -} +} \ No newline at end of file diff --git a/bpf/src/main/java/me/bechberger/ebpf/bpf/map/BPFHashMap.java b/bpf/src/main/java/me/bechberger/ebpf/bpf/map/BPFHashMap.java index 8f4e3a6..5566f76 100644 --- a/bpf/src/main/java/me/bechberger/ebpf/bpf/map/BPFHashMap.java +++ b/bpf/src/main/java/me/bechberger/ebpf/bpf/map/BPFHashMap.java @@ -1,6 +1,6 @@ package me.bechberger.ebpf.bpf.map; -import me.bechberger.ebpf.shared.BPFType; +import me.bechberger.ebpf.type.BPFType; /** * A base map based on BPF hash map @@ -27,4 +27,4 @@ public BPFHashMap(FileDescriptor fd, BPFType keyType, BPFType valueType) { public boolean usesLRU() { return typeId == MapTypeId.LRU_HASH; } -} +} \ No newline at end of file diff --git a/bpf/src/main/java/me/bechberger/ebpf/bpf/map/BPFRingBuffer.java b/bpf/src/main/java/me/bechberger/ebpf/bpf/map/BPFRingBuffer.java index 40ae610..8a62b7d 100644 --- a/bpf/src/main/java/me/bechberger/ebpf/bpf/map/BPFRingBuffer.java +++ b/bpf/src/main/java/me/bechberger/ebpf/bpf/map/BPFRingBuffer.java @@ -3,9 +3,8 @@ import me.bechberger.ebpf.bpf.BPFError; import me.bechberger.ebpf.bpf.raw.Lib; import me.bechberger.ebpf.bpf.raw.ring_buffer_sample_fn; -import me.bechberger.ebpf.shared.BPFType; +import me.bechberger.ebpf.type.BPFType; -import java.io.IOException; import java.lang.foreign.Arena; import java.lang.foreign.FunctionDescriptor; import java.lang.foreign.MemorySegment; @@ -201,4 +200,4 @@ public void close() { ringArena.close(); super.close(); } -} +} \ No newline at end of file diff --git a/bpf/src/main/java/me/bechberger/ebpf/samples/HashMapSample.java b/bpf/src/main/java/me/bechberger/ebpf/samples/HashMapSample.java index 1f35cec..5e8b569 100644 --- a/bpf/src/main/java/me/bechberger/ebpf/samples/HashMapSample.java +++ b/bpf/src/main/java/me/bechberger/ebpf/samples/HashMapSample.java @@ -1,13 +1,8 @@ package me.bechberger.ebpf.samples; -import me.bechberger.ebpf.annotations.Size; -import me.bechberger.ebpf.annotations.Unsigned; import me.bechberger.ebpf.annotations.bpf.BPF; -import me.bechberger.ebpf.annotations.bpf.Type; import me.bechberger.ebpf.bpf.BPFProgram; -import me.bechberger.ebpf.shared.BPFType; - -import java.util.Map; +import me.bechberger.ebpf.type.BPFType; /** * Print the number of openat syscalls per process, using a hash map to count them diff --git a/bpf/src/main/java/me/bechberger/ebpf/samples/RingSample.java b/bpf/src/main/java/me/bechberger/ebpf/samples/RingSample.java index fdce891..224302f 100644 --- a/bpf/src/main/java/me/bechberger/ebpf/samples/RingSample.java +++ b/bpf/src/main/java/me/bechberger/ebpf/samples/RingSample.java @@ -3,9 +3,8 @@ import me.bechberger.ebpf.annotations.Size; import me.bechberger.ebpf.annotations.Unsigned; import me.bechberger.ebpf.annotations.bpf.BPF; -import me.bechberger.ebpf.annotations.bpf.Type; import me.bechberger.ebpf.bpf.BPFProgram; -import me.bechberger.ebpf.shared.BPFType; +import me.bechberger.ebpf.type.BPFType; import java.util.List; diff --git a/bpf/src/main/java/me/bechberger/ebpf/samples/TypeProcessingSample.java b/bpf/src/main/java/me/bechberger/ebpf/samples/TypeProcessingSample.java index dbc450c..2bd010f 100644 --- a/bpf/src/main/java/me/bechberger/ebpf/samples/TypeProcessingSample.java +++ b/bpf/src/main/java/me/bechberger/ebpf/samples/TypeProcessingSample.java @@ -5,9 +5,6 @@ import me.bechberger.ebpf.annotations.bpf.BPF; import me.bechberger.ebpf.annotations.bpf.Type; import me.bechberger.ebpf.bpf.BPFProgram; -import me.bechberger.ebpf.shared.BPFType; - -import java.util.List; /** * Adaption of {@link RingSample} that shows how to use the {@link Type} annotation and related annotation processing diff --git a/bpf/src/test/java/me/bechberger/ebpf/bpf/HashMapTest.java b/bpf/src/test/java/me/bechberger/ebpf/bpf/HashMapTest.java index d56814e..52e7581 100644 --- a/bpf/src/test/java/me/bechberger/ebpf/bpf/HashMapTest.java +++ b/bpf/src/test/java/me/bechberger/ebpf/bpf/HashMapTest.java @@ -3,7 +3,7 @@ import me.bechberger.ebpf.annotations.bpf.BPF; import me.bechberger.ebpf.bpf.map.BPFBaseMap; import me.bechberger.ebpf.bpf.map.BPFHashMap; -import me.bechberger.ebpf.shared.BPFType; +import me.bechberger.ebpf.type.BPFType; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; diff --git a/bpf/src/test/java/me/bechberger/ebpf/bpf/RingBufferTest.java b/bpf/src/test/java/me/bechberger/ebpf/bpf/RingBufferTest.java index f8a9797..8a58aca 100644 --- a/bpf/src/test/java/me/bechberger/ebpf/bpf/RingBufferTest.java +++ b/bpf/src/test/java/me/bechberger/ebpf/bpf/RingBufferTest.java @@ -6,11 +6,10 @@ import me.bechberger.ebpf.bpf.map.BPFMap; import me.bechberger.ebpf.bpf.map.BPFRingBuffer; import me.bechberger.ebpf.samples.RingSample; -import me.bechberger.ebpf.shared.BPFType; +import me.bechberger.ebpf.type.BPFType; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Timeout; -import java.nio.file.Files; import java.nio.file.Path; import java.util.List; import java.util.concurrent.atomic.AtomicReference; @@ -205,4 +204,4 @@ public void testFailingParse() throws InterruptedException { assertTrue(System.currentTimeMillis() - start < 1000); } } -} +} \ No newline at end of file diff --git a/bpf/src/test/java/me/bechberger/ebpf/bpf/TypeLayoutTest.java b/bpf/src/test/java/me/bechberger/ebpf/bpf/TypeLayoutTest.java index 2e4e6c7..e55def5 100644 --- a/bpf/src/test/java/me/bechberger/ebpf/bpf/TypeLayoutTest.java +++ b/bpf/src/test/java/me/bechberger/ebpf/bpf/TypeLayoutTest.java @@ -1,7 +1,7 @@ package me.bechberger.ebpf.bpf; import me.bechberger.ebpf.annotations.Unsigned; -import me.bechberger.ebpf.shared.BPFType; +import me.bechberger.ebpf.type.BPFType; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; @@ -11,8 +11,8 @@ import java.util.List; import java.util.stream.Stream; -import static me.bechberger.ebpf.shared.BPFType.BPFStructType; -import static me.bechberger.ebpf.shared.BPFType.UBPFStructMember; +import static me.bechberger.ebpf.type.BPFType.BPFStructType; +import static me.bechberger.ebpf.type.BPFType.UBPFStructMember; import static org.junit.jupiter.api.Assertions.assertEquals; /** @@ -122,4 +122,4 @@ record PaddingEntry(long l, byte c) { assertEquals(8, type.alignment()); } -} +} \ No newline at end of file diff --git a/bpf/src/test/java/me/bechberger/ebpf/bpf/TypeProcessingTest.java b/bpf/src/test/java/me/bechberger/ebpf/bpf/TypeProcessingTest.java index 99e5275..488901b 100644 --- a/bpf/src/test/java/me/bechberger/ebpf/bpf/TypeProcessingTest.java +++ b/bpf/src/test/java/me/bechberger/ebpf/bpf/TypeProcessingTest.java @@ -4,13 +4,13 @@ import me.bechberger.ebpf.annotations.Unsigned; import me.bechberger.ebpf.annotations.bpf.BPF; import me.bechberger.ebpf.annotations.bpf.Type; -import me.bechberger.ebpf.shared.BPFType; +import me.bechberger.ebpf.type.BPFType; import org.junit.jupiter.api.Test; import java.util.List; -import static me.bechberger.ebpf.shared.BPFType.BPFIntType.CHAR; -import static me.bechberger.ebpf.shared.BPFType.BPFIntType.UINT32; +import static me.bechberger.ebpf.type.BPFType.BPFIntType.CHAR; +import static me.bechberger.ebpf.type.BPFType.BPFIntType.UINT32; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertInstanceOf; @@ -123,4 +123,4 @@ public void testRecordWithDirectMemberType() { assertEquals(new SimpleRecordTestProgram.RecordWithDirectMemberType(42, (byte)43), type.constructor().apply(List.of(42, (byte)43))); } -} +} \ No newline at end of file diff --git a/pom.xml b/pom.xml index 4d02718..46ad436 100644 --- a/pom.xml +++ b/pom.xml @@ -43,7 +43,7 @@ maven-surefire-plugin 3.2.5 - --enable-preview --enable-native-access=ALL-UNNAMED + --enable-native-access=ALL-UNNAMED 5 1 @@ -102,8 +102,8 @@ UTF-8 - 21 - 21 + 22 + 22 true @@ -126,4 +126,4 @@ - + \ No newline at end of file diff --git a/rawbcc/README.md b/rawbcc/README.md index d2e413e..7a8008c 100644 --- a/rawbcc/README.md +++ b/rawbcc/README.md @@ -12,7 +12,7 @@ These bindings are regularly updated and published on Maven Central: me.bechberger rawbcc - 0.1.3 + 0.1.4 ``` diff --git a/rawbcc/bin/jextract_bindings.py b/rawbcc/bin/jextract_bindings.py index 8263ef3..74c2364 100644 --- a/rawbcc/bin/jextract_bindings.py +++ b/rawbcc/bin/jextract_bindings.py @@ -34,17 +34,17 @@ import tarfile BIN_FOLDER = Path(__file__).parent.parent / "bin" -JEXTRACT_PATH = BIN_FOLDER / "jextract-21" +JEXTRACT_PATH = BIN_FOLDER / "jextract-22" JEXTRACT_TOOL_PATH = JEXTRACT_PATH / "bin" / "jextract" -JEXTRACT_VERSION = 2 +JEXTRACT_VERSION = "3-13" def download_jextract(): # download jextract shutil.rmtree(JEXTRACT_PATH, ignore_errors=True) print("Downloading jextract") - url = (f"https://download.java.net/java/early_access/jextract/" - f"1/openjdk-21-jextract+1-{JEXTRACT_VERSION}_linux-x64_bin.tar.gz") + url = (f"https://download.java.net/java/early_access/jextract/22/3/" + f"/openjdk-22-jextract+{JEXTRACT_VERSION}_linux-x64_bin.tar.gz") os.makedirs(BIN_FOLDER, exist_ok=True) urllib.request.urlretrieve(url, BIN_FOLDER / "jextract.tar.gz") # extract jextract @@ -105,10 +105,10 @@ def assert_java21(): try: output = subprocess.check_output("java -version", shell=True, stderr=subprocess.STDOUT).decode() - assert "version \"21" in output, \ - "Please run this script with JDK 21" + assert "version \"22" in output, \ + "Please run this script with JDK 22" except FileNotFoundError: - print("Please install JDK 21 and run this script with JDK 21") + print("Please install JDK 22 and run this script with JDK 22") sys.exit(1) @@ -128,9 +128,9 @@ def run_jextract(header: Path, mod_header_folder: Path, shutil.rmtree(del_path, ignore_errors=True) os.makedirs(dest_path, exist_ok=True) subprocess.check_call( - f"{JEXTRACT_TOOL_PATH} {modified_header} " - f"--source --output {dest_path} {'-t ' + package if package else ''} " - f"--header-class-name {name}", + f"{JEXTRACT_TOOL_PATH} " + f"--output {dest_path} {'-t ' + package if package else ''} " + f"--header-class-name {name} {modified_header}", shell=True) @@ -143,4 +143,4 @@ def run_jextract(header: Path, mod_header_folder: Path, sys.argv[3]) else: print("Usage: python3 jextract_bindings.py
    ") - sys.exit(1) + sys.exit(1) \ No newline at end of file diff --git a/rawbcc/pom.xml b/rawbcc/pom.xml index e2ea24c..5760267 100644 --- a/rawbcc/pom.xml +++ b/rawbcc/pom.xml @@ -7,7 +7,7 @@ rawbcc me.bechberger Raw bindings for libbcc - 0.1.3 + 0.1.4 https://github.com/parttimenerd/hello-ebpf @@ -33,9 +33,9 @@ UTF-8 - 21 - 21 - 21 + 22 + 22 + 22 @@ -103,8 +103,7 @@ maven-compiler-plugin 3.8.0 - 21 - --enable-preview + 22 @@ -137,8 +136,7 @@ - 21 - --enable-preview + 22 diff --git a/rawbpf/CHANGELOG b/rawbpf/CHANGELOG index 825c32f..3979086 100644 --- a/rawbpf/CHANGELOG +++ b/rawbpf/CHANGELOG @@ -1 +1,7 @@ # Changelog + +## 0.1.1 + +### Changed + +- Moved to JDK 22 from JDK 21 \ No newline at end of file diff --git a/rawbpf/README.md b/rawbpf/README.md index 8197bcf..067ea82 100644 --- a/rawbpf/README.md +++ b/rawbpf/README.md @@ -12,7 +12,7 @@ These bindings are regularly updated and published on Maven Central: me.bechberger rawbpf - 0.1.0 + 0.1.1 ``` diff --git a/rawbpf/pom.xml b/rawbpf/pom.xml index 2612cd7..a290109 100644 --- a/rawbpf/pom.xml +++ b/rawbpf/pom.xml @@ -7,7 +7,7 @@ rawbpf me.bechberger Raw bindings for libbpf - 0.1.0 + 0.1.1 https://github.com/parttimenerd/hello-ebpf @@ -33,9 +33,9 @@ UTF-8 - 21 - 21 - 21 + 22 + 22 + 22 @@ -103,8 +103,7 @@ maven-compiler-plugin 3.8.0 - 21 - --enable-preview + 22 @@ -137,8 +136,7 @@ - 21 - --enable-preview + 22 diff --git a/shared/pom.xml b/shared/pom.xml index 27c7910..763d549 100644 --- a/shared/pom.xml +++ b/shared/pom.xml @@ -14,8 +14,8 @@ Code used both in bcc and ebpf - 21 - 21 + 22 + 22 UTF-8 @@ -25,9 +25,6 @@ maven-compiler-plugin 3.8.1 - - --enable-preview - @@ -39,6 +36,12 @@ ebpf-annotations ${project.version} + + me.bechberger + bpf-processor + 0.1.0-SNAPSHOT + compile + \ No newline at end of file diff --git a/shared/src/main/java/me/bechberger/ebpf/shared/Disassembler.java b/shared/src/main/java/me/bechberger/ebpf/shared/Disassembler.java index 2728cab..66cd77a 100644 --- a/shared/src/main/java/me/bechberger/ebpf/shared/Disassembler.java +++ b/shared/src/main/java/me/bechberger/ebpf/shared/Disassembler.java @@ -16,13 +16,15 @@ package me.bechberger.ebpf.shared; +import me.bechberger.ebpf.type.BPFType; + import java.lang.foreign.MemorySegment; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; -import static me.bechberger.ebpf.shared.BPFType.BPFIntType.UINT64; +import static me.bechberger.ebpf.type.BPFType.BPFIntType.UINT64; /** * Dissassembles eBPF bytecode @@ -320,4 +322,4 @@ public static String disassemble_prog(String func_name, MemorySegment bpfstr) { } return String.join(System.lineSeparator(), instr_list); } -} +} \ No newline at end of file From 5b92f5abc08f6ff94602cecf55166691b012c65a Mon Sep 17 00:00:00 2001 From: Johannes Bechberger Date: Tue, 2 Apr 2024 22:09:10 +0300 Subject: [PATCH 02/11] Update to JDK 22 --- .github/workflows/ci.yml | 6 ++--- .github/workflows/early-access.yml | 2 +- .github/workflows/release.yml | 2 +- .gitignore | 3 ++- README.md | 7 +++--- .../main/java/me/bechberger/ebpf/bcc/BPF.java | 24 +++++++++---------- .../java/me/bechberger/ebpf/bcc/BPFTable.java | 4 ++-- .../me/bechberger/ebpf/bcc/SymbolCache.java | 12 +++++----- .../java/me/bechberger/ebpf/bcc/Util.java | 2 +- .../java/me/bechberger/ebpf/bcc/Utils.java | 6 ++--- bin/install.sh | 8 +++++++ .../ebpf/bpf/processor/Processor.java | 2 +- .../java/me/bechberger/ebpf/type/BPFType.java | 4 ++-- .../me/bechberger/ebpf/bpf/BPFProgram.java | 6 ++--- .../java/me/bechberger/ebpf/bpf/Util.java | 2 +- .../me/bechberger/ebpf/bpf/map/BPFMap.java | 6 ++--- .../ebpf/bpf/map/BPFRingBuffer.java | 2 +- hello-ebpf.yaml | 12 +++------- rawbcc/README.md | 2 +- rawbpf/README.md | 2 +- run.sh | 2 +- run_bpf.sh | 2 +- .../me/bechberger/ebpf/shared/PanamaUtil.java | 6 ++--- 23 files changed, 63 insertions(+), 61 deletions(-) create mode 100755 bin/install.sh diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 615e21a..1eff5ea 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -17,10 +17,10 @@ jobs: steps: - uses: actions/checkout@v4 - - name: Setup Java 21 + - name: Setup Java 22 uses: actions/setup-java@v4 with: - java-version: '21' + java-version: '22' distribution: 'adopt' - name: Cache Maven @@ -58,7 +58,7 @@ jobs: - name: Set up Java uses: actions/setup-java@v4 with: - java-version: '21' + java-version: '22' distribution: 'adopt' - run: sudo pip3 install https://github.com/amluto/virtme/archive/beb85146cd91de37ae455eccb6ab67c393e6e290.zip diff --git a/.github/workflows/early-access.yml b/.github/workflows/early-access.yml index e4c2411..42f8479 100644 --- a/.github/workflows/early-access.yml +++ b/.github/workflows/early-access.yml @@ -5,7 +5,7 @@ on: branches: [ main ] env: - JAVA_VERSION: '21' + JAVA_VERSION: '22' JAVA_DISTRO: 'adopt' jobs: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 2e36f47..9258a0e 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -8,7 +8,7 @@ on: required: true env: - JAVA_VERSION: '21' + JAVA_VERSION: '22' JAVA_DISTRO: 'adopt' jobs: diff --git a/.gitignore b/.gitignore index c0be2f7..6aa7104 100644 --- a/.gitignore +++ b/.gitignore @@ -403,4 +403,5 @@ hello-ebpf.iml *.zst *.c kprobe* -vmlinux* \ No newline at end of file +vmlinux* +**/jextract-* \ No newline at end of file diff --git a/README.md b/README.md index d82972f..a570427 100644 --- a/README.md +++ b/README.md @@ -56,8 +56,7 @@ 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](https://openjdk.org/projects/panama/) with is a preview - feature), we'll switch to Java 22 as soon as it is released +- Java 22 or later - libbcc (see [bcc installation instructions](https://github.com/iovisor/bcc/blob/master/INSTALL.md), 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 - root privileges (for eBPF programs) @@ -81,10 +80,10 @@ To build the project, make sure you have all prerequisites installed, then just Running the examples -------------------- -Be sure to run the following in a shell with root privileges that uses JDK 21: +Be sure to run the following in a shell with root privileges that uses JDK 22: ```shell -java --enable-preview -cp bcc/target/bcc.jar --enable-native-access=ALL-UNNAMED me.bechberger.ebpf.samples.EXAMPLE_NAME +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 diff --git a/bcc/src/main/java/me/bechberger/ebpf/bcc/BPF.java b/bcc/src/main/java/me/bechberger/ebpf/bcc/BPF.java index ec082cb..97bc683 100644 --- a/bcc/src/main/java/me/bechberger/ebpf/bcc/BPF.java +++ b/bcc/src/main/java/me/bechberger/ebpf/bcc/BPF.java @@ -136,7 +136,7 @@ public BPF build() { */ private BPF(String text, String fileName, @Nullable Path hdrFile, boolean allowRLimit, int debug) { this.text = text; - MemorySegment textNative = arena.allocateUtf8String(text); + MemorySegment textNative = arena.allocateFrom(text); this.debug = debug; /* @@ -242,7 +242,7 @@ public BPFFunction load_func(String func_name, int prog_type, MemorySegment devi if (!doesFunctionExistInText(func_name)) throw new RuntimeException("Trying to use undefined function " + func_name); try (var arena = Arena.ofConfined()) { - MemorySegment funcNameNative = arena.allocateUtf8String(func_name); + MemorySegment funcNameNative = arena.allocateFrom(func_name); if (Lib.bpf_function_start(module, funcNameNative) == null) throw new RuntimeException("Unknown program " + func_name); int log_level = 0; @@ -287,7 +287,7 @@ public BPFFunction load_raw_tracepoint_func(String func_name) { } MemorySegment dump_func(Arena arena, String func_name) { - var funcNameNative = arena.allocateUtf8String(func_name); + var funcNameNative = arena.allocateFrom(func_name); if (Lib.bpf_function_start(module, funcNameNative) == null) throw new RuntimeException("Unknown program " + func_name); var start = Lib.bpf_function_start(module, funcNameNative); @@ -400,8 +400,8 @@ public static boolean support_raw_tracepoint_in_module() { public static boolean kernel_struct_has_field(String structName, String fieldName) { try (var arena = Arena.ofConfined()) { - var structNameNative = arena.allocateUtf8String(structName); - var fieldNameNative = arena.allocateUtf8String(fieldName); + var structNameNative = arena.allocateFrom(structName); + var fieldNameNative = arena.allocateFrom(fieldName); return Lib.kernel_struct_has_field(structNameNative, fieldNameNative) == 1; } } @@ -425,7 +425,7 @@ public BPF attach_raw_tracepoint(@Nullable String tracepoint, @Nullable String f if (raw_tracepoint_fds.containsKey(tracepoint)) throw new RuntimeException("Raw tracepoint " +tracepoint + " has been attached"); var fn = load_func(fn_name, Lib.BPF_PROG_TYPE_RAW_TRACEPOINT()); - int fd = Lib.bpf_attach_raw_tracepoint(fn.fd, arena.allocateUtf8String(tracepoint)); + int fd = Lib.bpf_attach_raw_tracepoint(fn.fd, arena.allocateFrom(tracepoint)); if (fd < 0) throw new RuntimeException("Failed to attach BPF to raw tracepoint"); raw_tracepoint_fds.put(tracepoint, fd); return this; @@ -489,8 +489,8 @@ public BPF attach_kprobe(@Nullable String event, int event_off, @Nullable String var ev_name = "p_" + event.replace("+", "_").replace(".", "_"); int fd; try (var arena = Arena.ofConfined()) { - var evNameNative = arena.allocateUtf8String(ev_name); - var eventNative = arena.allocateUtf8String(event); + var evNameNative = arena.allocateFrom(ev_name); + var eventNative = arena.allocateFrom(event); fd = Lib.bpf_attach_kprobe(fn.fd, 0, evNameNative, eventNative, event_off, 0); } if (fd < 0) { @@ -522,7 +522,7 @@ private void detach_kprobe_event_by_fn(String ev_name, String fn_name) { _del_kprobe_fd(ev_name, fn_name); if (kprobe_fds.get(ev_name).isEmpty()) { try (var arena = Arena.ofConfined()) { - var evNameNative = arena.allocateUtf8String(ev_name); + var evNameNative = arena.allocateFrom(ev_name); res = Lib.bpf_detach_kprobe(evNameNative); } if (res < 0) throw new RuntimeException("Failed to detach BPF from kprobe"); @@ -534,7 +534,7 @@ private void detach_kprobe_event_by_fn(String ev_name, String fn_name) { */ public > T get_table(String name, BPFTable.TableProvider provider) { try (var arena = Arena.ofConfined()) { - var nameNative = arena.allocateUtf8String(name); + var nameNative = arena.allocateFrom(name); var mapId = Lib.bpf_table_id(module, nameNative); var mapFd = Lib.bpf_table_fd(module, nameNative); if (mapFd < 0) throw new RuntimeException("Failed to load BPF Table " + name); @@ -865,7 +865,7 @@ public void perf_buffer_poll() { */ public void perf_buffer_poll(int timeout) { try (var arena = Arena.ofConfined()) { - var readers = arena.allocateArray(PanamaUtil.POINTER, perfBuffers.size()); + var readers = arena.allocate(PanamaUtil.POINTER, perfBuffers.size()); int i = 0; for (var v : perfBuffers.values()) { readers.setAtIndex(POINTER, i++, v); @@ -881,7 +881,7 @@ public void perf_buffer_poll(int timeout) { */ public void perf_buffer_consume() { try (var arena = Arena.ofConfined()) { - var readers = arena.allocateArray(PanamaUtil.POINTER, perfBuffers.size()); + var readers = arena.allocate(PanamaUtil.POINTER, perfBuffers.size()); int i = 0; for (var v : perfBuffers.values()) { readers.setAtIndex(POINTER, i++, v); diff --git a/bcc/src/main/java/me/bechberger/ebpf/bcc/BPFTable.java b/bcc/src/main/java/me/bechberger/ebpf/bcc/BPFTable.java index ef2d1b2..ea9dca4 100644 --- a/bcc/src/main/java/me/bechberger/ebpf/bcc/BPFTable.java +++ b/bcc/src/main/java/me/bechberger/ebpf/bcc/BPFTable.java @@ -281,7 +281,7 @@ private AllocatedKeyAndLeafs alloc_key_values(Arena arena, boolean allocateKeys, if (count < 1 || count > maxEntries) { throw new AssertionError("Invalid count"); } - return new AllocatedKeyAndLeafs(count, allocateKeys ? arena.allocateArray(keyType.layout(), count) : null, allocateValues ? arena.allocateArray(leafType.layout(), count) : null); + return new AllocatedKeyAndLeafs(count, allocateKeys ? arena.allocate(keyType.layout(), count) : null, allocateValues ? arena.allocate(leafType.layout(), count) : null); } /** @@ -902,7 +902,7 @@ public Integer set(int index, String path) { // use Lib.fopen try (Arena arena = Arena.ofConfined()) { var pathInC = allocateNullOrString(arena, path); - try (FileDesc desc = new FileDesc(Lib.open(pathInC, O_RDONLY))) { + try (FileDesc desc = new FileDesc(Lib.open.makeInvoker().apply(pathInC, (int)O_RDONLY))) { return set(index, desc.fd()); } } diff --git a/bcc/src/main/java/me/bechberger/ebpf/bcc/SymbolCache.java b/bcc/src/main/java/me/bechberger/ebpf/bcc/SymbolCache.java index 01a894b..7b7b84b 100644 --- a/bcc/src/main/java/me/bechberger/ebpf/bcc/SymbolCache.java +++ b/bcc/src/main/java/me/bechberger/ebpf/bcc/SymbolCache.java @@ -44,19 +44,19 @@ public ResolveResult resolve(long addr, boolean demangle) { res = Lib.bcc_symcache_resolve_no_demangle(cache, addr, sym); } if (res < 0) { - if (bcc_symbol.module$get(sym) != null && bcc_symbol.offset$get(sym) != 0) { - return new ResolveResult(null, bcc_symbol.offset$get(sym), PanamaUtil.toString(bcc_symbol.module$get(sym))); + if (bcc_symbol.module(sym) != null && bcc_symbol.offset(sym) != 0) { + return new ResolveResult(null, bcc_symbol.offset(sym), PanamaUtil.toString(bcc_symbol.module(sym))); } return new ResolveResult(null, addr, null); } String name_res; if (demangle) { - name_res = PanamaUtil.toString(bcc_symbol.demangle_name$get(sym)); + name_res = PanamaUtil.toString(bcc_symbol.demangle_name(sym)); Lib.bcc_symbol_free_demangle_name(sym); } else { - name_res = PanamaUtil.toString(bcc_symbol.name$get(sym)); + name_res = PanamaUtil.toString(bcc_symbol.name(sym)); } - return new ResolveResult(name_res, bcc_symbol.offset$get(sym), PanamaUtil.toString(bcc_symbol.module$get(sym))); + return new ResolveResult(name_res, bcc_symbol.offset(sym), PanamaUtil.toString(bcc_symbol.module(sym))); } } @@ -68,7 +68,7 @@ public long resolve_name(String module, String name) { try (Arena arena = Arena.ofConfined()) { var addr = arena.allocate(8); var moduleStr = PanamaUtil.allocateNullOrString(arena, module); - var nameStr = arena.allocateUtf8String(name); + var nameStr = arena.allocateFrom(name); int res = Lib.bcc_symcache_resolve_name(cache, moduleStr, nameStr, addr); if (res < 0) { return -1; diff --git a/bcc/src/main/java/me/bechberger/ebpf/bcc/Util.java b/bcc/src/main/java/me/bechberger/ebpf/bcc/Util.java index cbef499..37d5401 100644 --- a/bcc/src/main/java/me/bechberger/ebpf/bcc/Util.java +++ b/bcc/src/main/java/me/bechberger/ebpf/bcc/Util.java @@ -8,6 +8,6 @@ public class Util { * Format errno to string using {@link Lib#strerror} */ public static String errnoString(int error) { - return Lib.strerror(error).getUtf8String(0); + return Lib.strerror(error).getString(0); } } diff --git a/bcc/src/test/java/me/bechberger/ebpf/bcc/Utils.java b/bcc/src/test/java/me/bechberger/ebpf/bcc/Utils.java index dacd000..d662dce 100644 --- a/bcc/src/test/java/me/bechberger/ebpf/bcc/Utils.java +++ b/bcc/src/test/java/me/bechberger/ebpf/bcc/Utils.java @@ -28,9 +28,9 @@ public static void runCommand(String... command) { public static void triggerExecve(String path, String... args) { try (var arena = Arena.ofConfined()) { - var pathNative = arena.allocateUtf8String(path); - var argsNative = arena.allocateUtf8String(Stream.concat(Stream.of(path), Stream.of(args)).collect(Collectors.joining("\0"))); - var envNative = arena.allocateUtf8String("\0"); + var pathNative = arena.allocateFrom(path); + var argsNative = arena.allocateFrom(Stream.concat(Stream.of(path), Stream.of(args)).collect(Collectors.joining("\0"))); + var envNative = arena.allocateFrom("\0"); var ret = Lib.execve(pathNative, argsNative, envNative); } } diff --git a/bin/install.sh b/bin/install.sh new file mode 100755 index 0000000..796bff2 --- /dev/null +++ b/bin/install.sh @@ -0,0 +1,8 @@ +#!/bin/bash +sed "/#\$nrconf{restart} = 'i';/s/.*/\$nrconf{restart} = 'a';/" /etc/needrestart/needrestart.conf +apt-get update +apt-get install -y apt-transport-https ca-certificates curl clang llvm jq maven +apt-get install -y libelf-dev libpcap-dev libbfd-dev binutils-dev build-essential make +apt-get install -y linux-tools-common linux-tools-$(uname -r) +apt-get install -y bpfcc-tools libbpfcc-dev libbpf-dev +apt-get install -y python3-pip zsh tmux openjdk-22-jre-headless \ No newline at end of file diff --git a/bpf-processor/src/main/java/me/bechberger/ebpf/bpf/processor/Processor.java b/bpf-processor/src/main/java/me/bechberger/ebpf/bpf/processor/Processor.java index 43e8606..d22c899 100644 --- a/bpf-processor/src/main/java/me/bechberger/ebpf/bpf/processor/Processor.java +++ b/bpf-processor/src/main/java/me/bechberger/ebpf/bpf/processor/Processor.java @@ -31,7 +31,7 @@ * The processor compiles the eBPF program and takes care of {@code @Type} inner types. */ @SupportedAnnotationTypes({"me.bechberger.ebpf.annotations.bpf.BPF"}) -@SupportedSourceVersion(SourceVersion.RELEASE_21) +@SupportedSourceVersion(SourceVersion.RELEASE_22) public class Processor extends AbstractProcessor { public boolean process(Set annotations, RoundEnvironment env) { diff --git a/bpf-processor/src/main/java/me/bechberger/ebpf/type/BPFType.java b/bpf-processor/src/main/java/me/bechberger/ebpf/type/BPFType.java index f64b3a9..48bece7 100644 --- a/bpf-processor/src/main/java/me/bechberger/ebpf/type/BPFType.java +++ b/bpf-processor/src/main/java/me/bechberger/ebpf/type/BPFType.java @@ -652,7 +652,7 @@ public MemoryLayout layout() { @Override public MemoryParser parser() { - return segment -> segment.getUtf8String(0); + return segment -> segment.getString(0); } @Override @@ -660,7 +660,7 @@ public MemorySetter setter() { return (segment, obj) -> { byte[] bytes = obj.getBytes(); if (bytes.length + 1 < length) { - segment.setUtf8String(0, obj); + segment.setString(0, obj); } else { byte[] dest = new byte[length]; System.arraycopy(bytes, 0, dest, 0, length - 1); diff --git a/bpf/src/main/java/me/bechberger/ebpf/bpf/BPFProgram.java b/bpf/src/main/java/me/bechberger/ebpf/bpf/BPFProgram.java index a93e39c..49611a5 100644 --- a/bpf/src/main/java/me/bechberger/ebpf/bpf/BPFProgram.java +++ b/bpf/src/main/java/me/bechberger/ebpf/bpf/BPFProgram.java @@ -135,7 +135,7 @@ private static BPFType.BPFStructType getTypeForImplClass(Class outerIm private MemorySegment loadProgram() { Path objFile = getTmpObjectFile(); try (Arena arena = Arena.ofConfined()) { - MemorySegment fileName = arena.allocateUtf8String(objFile.toString()); + MemorySegment fileName = arena.allocateFrom(objFile.toString()); MemorySegment ebpf_object = Lib.bpf_object__open_file(fileName, MemorySegment.NULL); if (ebpf_object == MemorySegment.NULL) { throw new BPFLoadError("Failed to load eBPF object"); @@ -188,7 +188,7 @@ public BPFProgramNotFound(String name) { */ public ProgramHandle getProgramByName(String name) { try (Arena arena = Arena.ofConfined()) { - MemorySegment prog = Lib.bpf_object__find_program_by_name(this.ebpf_object, arena.allocateUtf8String(name)); + MemorySegment prog = Lib.bpf_object__find_program_by_name(this.ebpf_object, arena.allocateFrom(name)); if (prog == MemorySegment.NULL || prog.address() == 0) { throw new BPFProgramNotFound(name); } @@ -283,7 +283,7 @@ public BPFMapNotFoundError(String name) { */ private FileDescriptor getMapDescriptorByName(String name) { try (Arena arena = Arena.ofConfined()) { - MemorySegment map = Lib.bpf_object__find_map_by_name(this.ebpf_object, arena.allocateUtf8String(name)); + MemorySegment map = Lib.bpf_object__find_map_by_name(this.ebpf_object, arena.allocateFrom(name)); if (map == MemorySegment.NULL || map.address() == 0) { throw new BPFMapNotFoundError(name); } diff --git a/bpf/src/main/java/me/bechberger/ebpf/bpf/Util.java b/bpf/src/main/java/me/bechberger/ebpf/bpf/Util.java index 0370a0f..b013ac7 100644 --- a/bpf/src/main/java/me/bechberger/ebpf/bpf/Util.java +++ b/bpf/src/main/java/me/bechberger/ebpf/bpf/Util.java @@ -13,7 +13,7 @@ public class Util { * Format errno to string using {@link Lib#strerror} */ public static String errnoString(int error) { - return Lib.strerror(error).getUtf8String(0); + return Lib.strerror(error).getString(0); } /** diff --git a/bpf/src/main/java/me/bechberger/ebpf/bpf/map/BPFMap.java b/bpf/src/main/java/me/bechberger/ebpf/bpf/map/BPFMap.java index ef42c97..c0520d5 100644 --- a/bpf/src/main/java/me/bechberger/ebpf/bpf/map/BPFMap.java +++ b/bpf/src/main/java/me/bechberger/ebpf/bpf/map/BPFMap.java @@ -69,9 +69,9 @@ private static MemorySegment obtainRawInfo(Arena arena, FileDescriptor fd) { public static MapInfo getInfo(FileDescriptor fd) { try (var arena = Arena.ofConfined()) { var info = obtainRawInfo(arena, fd); - return new MapInfo(fd, MapTypeId.fromId(bpf_map_info.type$get(info)), bpf_map_info.key_size$get(info), - bpf_map_info.value_size$get(info), bpf_map_info.max_entries$get(info), - bpf_map_info.map_flags$get(info)); + return new MapInfo(fd, MapTypeId.fromId(bpf_map_info.type(info)), bpf_map_info.key_size(info), + bpf_map_info.value_size(info), bpf_map_info.max_entries(info), + bpf_map_info.map_flags(info)); } } diff --git a/bpf/src/main/java/me/bechberger/ebpf/bpf/map/BPFRingBuffer.java b/bpf/src/main/java/me/bechberger/ebpf/bpf/map/BPFRingBuffer.java index 8a62b7d..d5eabde 100644 --- a/bpf/src/main/java/me/bechberger/ebpf/bpf/map/BPFRingBuffer.java +++ b/bpf/src/main/java/me/bechberger/ebpf/bpf/map/BPFRingBuffer.java @@ -105,7 +105,7 @@ private static ResultAndErr ring_buffer__new(Arena arena, int fd, } private MemorySegment initRingBuffer(FileDescriptor fd, BPFType eventType, EventCallback callback) { - ring_buffer_sample_fn sample = (ctx, data, len) -> { + ring_buffer_sample_fn.Function sample = (ctx, data, len) -> { E event; try { event = eventType.parseMemory(data); diff --git a/hello-ebpf.yaml b/hello-ebpf.yaml index c7dfa02..68f34c3 100644 --- a/hello-ebpf.yaml +++ b/hello-ebpf.yaml @@ -1,9 +1,9 @@ # This example requires Lima v0.8.0 or later # based on https://github.com/lizrice/learning-ebpf/blob/main/learning-ebpf.yaml images: - - location: "https://cloud-images.ubuntu.com/releases/22.04/release/ubuntu-22.04-server-cloudimg-amd64.img" + - location: "https://cloud-images.ubuntu.com/releases/23.10/release/ubuntu-23.10-server-cloudimg-amd64.img" arch: "x86_64" - - location: "https://cloud-images.ubuntu.com/releases/22.04/release/ubuntu-22.04-server-cloudimg-arm64.img" + - location: "https://cloud-images.ubuntu.com/releases/23.10/release/ubuntu-23.10-server-cloudimg-arm64.img" arch: "aarch64" cpus: 4 @@ -16,10 +16,4 @@ mounts: writable: true provision: - mode: system - script: | - apt-get update - apt-get install -y apt-transport-https ca-certificates curl clang llvm jq - apt-get install -y libelf-dev libpcap-dev libbfd-dev binutils-dev build-essential make - apt-get install -y linux-tools-common linux-tools-$(uname -r) - apt-get install -y bpfcc-tools libbpfcc-dev libbpf-dev - apt-get install -y python3-pip zsh tmux openjdk-21-jre-headless maven \ No newline at end of file + script: ./bin/install.sh \ No newline at end of file diff --git a/rawbcc/README.md b/rawbcc/README.md index 7a8008c..c67319c 100644 --- a/rawbcc/README.md +++ b/rawbcc/README.md @@ -30,7 +30,7 @@ To load the libbcc, there is a helper class `LibraryLoader`: Requirements ------------ -- Java 21 with `--enable-preview` +- Java 22 - Linux 64-bit - libbcc (see [bcc installation instructions](https://github.com/iovisor/bcc/blob/master/INSTALL.md), be sure to install the libbpfcc-dev package) - I know that the packages there are outdated for some distributions, but installing the packages from diff --git a/rawbpf/README.md b/rawbpf/README.md index 067ea82..bf57631 100644 --- a/rawbpf/README.md +++ b/rawbpf/README.md @@ -30,7 +30,7 @@ To load the libbpf, there is a helper class `LibraryLoader`: Requirements ------------ -- Java 21 with `--enable-preview` +- Java 22 - Linux 64-bit - libbpf (install `libbpf-dev` on Ubuntu or Debian) - e.g. `apt install libbpf-dev linux-tools-common linux-tools-$(uname -r)` on Ubuntu or Debian diff --git a/run.sh b/run.sh index c1ff04e..0236824 100755 --- a/run.sh +++ b/run.sh @@ -21,4 +21,4 @@ CLASS=$1 # Run the program shift -java --enable-preview -cp target/bcc.jar --enable-native-access=ALL-UNNAMED $JAVA_OPTS me.bechberger.ebpf.samples.$CLASS $@ \ No newline at end of file +java -cp target/bcc.jar --enable-native-access=ALL-UNNAMED $JAVA_OPTS me.bechberger.ebpf.samples.$CLASS $@ \ No newline at end of file diff --git a/run_bpf.sh b/run_bpf.sh index 4513204..cf43a3a 100755 --- a/run_bpf.sh +++ b/run_bpf.sh @@ -22,4 +22,4 @@ CLASS=$1 # Run the program shift -java --enable-preview -cp target/bpf.jar --enable-native-access=ALL-UNNAMED $JAVA_OPTS me.bechberger.ebpf.samples.$CLASS $@ \ No newline at end of file +java -cp target/bpf.jar --enable-native-access=ALL-UNNAMED $JAVA_OPTS me.bechberger.ebpf.samples.$CLASS $@ \ No newline at end of file diff --git a/shared/src/main/java/me/bechberger/ebpf/shared/PanamaUtil.java b/shared/src/main/java/me/bechberger/ebpf/shared/PanamaUtil.java index 47d5528..dbd6140 100644 --- a/shared/src/main/java/me/bechberger/ebpf/shared/PanamaUtil.java +++ b/shared/src/main/java/me/bechberger/ebpf/shared/PanamaUtil.java @@ -39,7 +39,7 @@ public static String toString(MemorySegment segment) { if (segment == MemorySegment.NULL) { return null; } - return segment.getUtf8String(0); + return segment.getString(0); } /** @@ -66,7 +66,7 @@ public static boolean hasBCCBatchFunctions() { /** * Pointer type */ - public static final AddressLayout POINTER = ValueLayout.ADDRESS.withTargetLayout(MemoryLayout.sequenceLayout(JAVA_BYTE)); + public static final AddressLayout POINTER = ValueLayout.ADDRESS.withTargetLayout(MemoryLayout.sequenceLayout(0, JAVA_BYTE)); /** * No such file or directory errno value @@ -96,7 +96,7 @@ public static MemorySegment allocateNullOrString(Arena arena, @Nullable String s if (string == null) { return MemorySegment.NULL; } - return arena.allocateUtf8String(string); + return arena.allocateFrom(string); } /** Result and errno */ From 7ba9947b0f24eb8b74e2615c5c79aeb2217460fc Mon Sep 17 00:00:00 2001 From: Johannes Bechberger Date: Tue, 2 Apr 2024 22:14:35 +0300 Subject: [PATCH 03/11] Auto-generate C code for structs --- .../bechberger/ebpf/annotations/bpf/Type.java | 3 + .../main/java/me/bechberger/cast/CAST.java | 341 +++++++----------- .../ebpf/bpf/processor/Processor.java | 66 +++- .../ebpf/bpf/processor/TypeProcessor.java | 43 +-- .../test/java/me/bechberger/cast/ASTTest.java | 14 +- .../ebpf/samples/TypeProcessingSample.java | 2 +- .../ebpf/samples/TypeProcessingSample2.java | 94 +++++ 7 files changed, 299 insertions(+), 264 deletions(-) create mode 100644 bpf/src/main/java/me/bechberger/ebpf/samples/TypeProcessingSample2.java diff --git a/annotations/src/main/java/me/bechberger/ebpf/annotations/bpf/Type.java b/annotations/src/main/java/me/bechberger/ebpf/annotations/bpf/Type.java index 14d0730..597112a 100644 --- a/annotations/src/main/java/me/bechberger/ebpf/annotations/bpf/Type.java +++ b/annotations/src/main/java/me/bechberger/ebpf/annotations/bpf/Type.java @@ -30,4 +30,7 @@ /** Name of the generated BPFStructType, uses the type as default */ String name() default ""; + + /** Don't generate C code and insert it into the ebpf program string */ + boolean noCCodeGeneration() default false; } \ No newline at end of file diff --git a/bpf-processor/src/main/java/me/bechberger/cast/CAST.java b/bpf-processor/src/main/java/me/bechberger/cast/CAST.java index e4db179..03c5185 100644 --- a/bpf-processor/src/main/java/me/bechberger/cast/CAST.java +++ b/bpf-processor/src/main/java/me/bechberger/cast/CAST.java @@ -1,5 +1,6 @@ package me.bechberger.cast; +import me.bechberger.cast.CAST.Declarator.ArrayDeclarator; import org.jetbrains.annotations.Nullable; import java.util.*; @@ -25,6 +26,18 @@ public interface CAST { Statement toStatement(); + /** Generate pretty printed code */ + default String toPrettyString() { + return toPrettyString("", " "); + } + + /** Generate pretty printed code */ + String toPrettyString(String indent, String increment); + + static String toStringLiteral(String value) { + return "\"" + value.replace("\\", "\\\\").replace("\"", "\\\"") + "\""; + } + sealed interface Expression extends CAST permits Declarator, InitDeclarator, Initializer, OperatorExpression, PrimaryExpression { @Override @@ -77,9 +90,6 @@ default List children() { return List.of(); } - @Override - String toString(); - /** * Annotation like @SEC("...") * @@ -93,8 +103,8 @@ public List children() { } @Override - public String toString() { - return annotation + "(" + Expression.constant(value) + ")"; + public String toPrettyString(String indent, String increment) { + return indent + annotation + "(" + Expression.constant(value).toPrettyString() + ")"; } static CAnnotation annotation(String annotation, String value) { @@ -116,16 +126,16 @@ public Statement toStatement() { */ record Variable(String name, CAnnotation... annotations) implements PrimaryExpression { @Override - public String toString() { - String annString = Arrays.stream(annotations).map(Object::toString).collect(Collectors.joining(" ")); - return name + (annString.isEmpty() ? "" : " " + annString); + public String toPrettyString(String indent, String increment) { + String annString = Arrays.stream(annotations).map(CAnnotation::toPrettyString).collect(Collectors.joining(" ")); + return indent + name + (annString.isEmpty() ? "" : " " + annString); } } record EnumerationConstant(String name) implements PrimaryExpression { @Override - public String toString() { - return name; + public String toPrettyString(String indent, String increment) { + return indent + name; } } @@ -134,12 +144,11 @@ public String toString() { */ record Constant(Object value) implements PrimaryExpression { @Override - public String toString() { - if (value instanceof String) { -// Escape the string - return "\"" + value.toString().replace("\\", "\\\\").replace("\"", "\\\"") + "\""; + public String toPrettyString(String indent, String increment) { + if (value instanceof String str) { + return indent + toStringLiteral(str); } else { - return value.toString(); + return indent + value.toString(); } } } @@ -149,8 +158,8 @@ public String toString() { */ record ParenthesizedExpression(Expression expression) implements PrimaryExpression { @Override - public String toString() { - return "(" + expression + ")"; + public String toPrettyString(String indent, String increment) { + return indent + "(" + expression.toPrettyString() + ")"; } @Override @@ -166,8 +175,8 @@ public List children() { } @Override - public String toString() { - return code; + public String toPrettyString(String indent, String increment) { + return indent + code; } } } @@ -273,7 +282,7 @@ public List children() { * Takes care of operator precedence and associativity */ @Override - public String toString() { + public String toPrettyString(String indent, String increment) { // idea: // if the operator is a binary operator, we need to check the precedence of the children // if the precedence of the child is lower, we need to wrap it in parentheses @@ -306,126 +315,126 @@ public String toString() { // if the operator is a unary operator, we need to check if the child is a binary operator if (operator().precedence == 3) { Expression operator1 = children().getFirst(); - String op1String = operator1.toString(); + String op1String = operator1.toPrettyString(); if (operator1 instanceof OperatorExpression operatorExpression) { if (operatorExpression.operator().precedence < operator().precedence) { - op1String = "(" + operatorExpression + ")"; + op1String = "(" + op1String + ")"; } } - return operator() + op1String; + return indent + operator() + op1String; } else { // if the operator is a postfix operator, we need to wrap the child in parentheses if (operator().precedence == 2) { Expression operator1 = children().getFirst(); - String op1String = operator1.toString(); - if (operator1 instanceof OperatorExpression operatorExpression) { - op1String = "(" + operatorExpression + ")"; + String op1String = operator1.toPrettyString(); + if (operator1 instanceof OperatorExpression) { + op1String = "(" + op1String + ")"; } - return op1String + operator(); + return indent + op1String + operator(); } else { // if the operator is an assignment operator, we need to wrap the left child in parentheses if (operator().precedence == 16) { Expression operator1 = children().get(0); Expression operator2 = children().get(1); - String op1String = operator1.toString(); - String op2String = operator2.toString(); - if (operator1 instanceof OperatorExpression operatorExpression) { - op1String = "(" + operatorExpression + ")"; + String op1String = operator1.toPrettyString(); + String op2String = operator2.toPrettyString(); + if (operator1 instanceof OperatorExpression) { + op1String = "(" + op1String + ")"; } if (operator2 instanceof OperatorExpression operatorExpression) { if (operatorExpression.operator().precedence < operator().precedence) { - op2String = "(" + operatorExpression + ")"; + op2String = "(" + op2String + ")"; } } - return op1String + " " + operator() + " " + op2String; + return indent + op1String + " " + operator() + " " + op2String; } else { // if the operator is a ternary operator, we need to wrap the children in parentheses if (operator() == Operator.CONDITIONAL) { Expression operator1 = children().get(0); Expression operator2 = children().get(1); Expression operator3 = children().get(2); - String op1String = operator1.toString(); - String op2String = operator2.toString(); - String op3String = operator3.toString(); - if (operator1 instanceof OperatorExpression operatorExpression) { - op1String = "(" + operatorExpression + ")"; + String op1String = operator1.toPrettyString(); + String op2String = operator2.toPrettyString(); + String op3String = operator3.toPrettyString(); + if (operator1 instanceof OperatorExpression) { + op1String = "(" + op1String + ")"; } - if (operator2 instanceof OperatorExpression operatorExpression) { - op2String = "(" + operatorExpression + ")"; + if (operator2 instanceof OperatorExpression) { + op2String = "(" + op2String + ")"; } - if (operator3 instanceof OperatorExpression operatorExpression) { - op3String = "(" + operatorExpression + ")"; + if (operator3 instanceof OperatorExpression) { + op3String = "(" + op3String + ")"; } - return op1String + " ? " + op2String + " : " + op3String; + return indent + op1String + " ? " + op2String + " : " + op3String; } else if (operator() == Operator.MEMBER_ACCESS) { Expression operator1 = children().get(0); Expression operator2 = children().get(1); - String op1String = operator1.toString(); - String op2String = operator2.toString(); - if (operator2 instanceof OperatorExpression operatorExpression) { - op2String = "(" + operatorExpression + ")"; + String op1String = operator1.toPrettyString(); + String op2String = operator2.toPrettyString(); + if (operator2 instanceof OperatorExpression) { + op2String = "(" + op2String + ")"; } - return op1String + "." + op2String; + return indent + op1String + "." + op2String; } else if (operator() == Operator.SUBSCRIPT) { Expression operator1 = children().get(0); Expression operator2 = children().get(1); - String op1String = operator1.toString(); - String op2String = operator2.toString(); - if (operator2 instanceof OperatorExpression operatorExpression) { - op2String = "(" + operatorExpression + ")"; + String op1String = operator1.toPrettyString(); + String op2String = operator2.toPrettyString(); + if (operator2 instanceof OperatorExpression) { + op2String = "(" + op2String + ")"; } - return op1String + "[" + op2String + "]"; + return indent + op1String + "[" + op2String + "]"; } else if (operator() == Operator.FUNCTION_CALL) { Expression func = children().getFirst(); - String funcString = func.toString(); - if (func instanceof OperatorExpression operatorExpression) { + String funcString = func.toPrettyString(); + if (func instanceof OperatorExpression) { funcString = "(" + funcString + ")"; } - return funcString + "(" + children().stream().skip(1).map(Object::toString).collect(Collectors.joining(", ")) + ")"; + return indent + funcString + "(" + children().stream().skip(1).map(CAST::toPrettyString).collect(Collectors.joining(", ")) + ")"; } else if (operator() == Operator.SIZEOF) { Expression operator1 = children().getFirst(); - String op1String = operator1.toString(); - if (operator1 instanceof OperatorExpression operatorExpression) { - op1String = "(" + operatorExpression + ")"; + String op1String = operator1.toPrettyString(); + if (operator1 instanceof OperatorExpression) { + op1String = "(" + op1String + ")"; } - return "sizeof(" + op1String + ")"; + return indent + "sizeof(" + op1String + ")"; } else if (operator() == Operator.CAST) { - return "(" + children().get(0) + ")" + children().get(1); + return indent + "(" + children().get(0).toPrettyString() + ")" + children().get(1); } else if (operator().isUnitary()) { Expression operator1 = children().getFirst(); - String op1String = operator1.toString(); - if (operator1 instanceof OperatorExpression operatorExpression) { - op1String = "(" + operatorExpression + ")"; + String op1String = operator1.toPrettyString(); + if (operator1 instanceof OperatorExpression) { + op1String = "(" + op1String + ")"; } if (operator().associativity == Operator.Associativity.RIGHT) { - return operator() + op1String; + return indent + operator() + op1String; } else { - return op1String + operator(); + return indent + op1String + operator(); } } else { Expression operator1 = children().get(0); Expression operator2 = children().get(1); - String op1String = operator1.toString(); - String op2String = operator2.toString(); + String op1String = operator1.toPrettyString(); + String op2String = operator2.toPrettyString(); if (operator1 instanceof OperatorExpression operatorExpression) { if (operatorExpression.operator().precedence < operator().precedence) { - op1String = "(" + operatorExpression + ")"; + op1String = "(" + op1String + ")"; } else if (operatorExpression.operator().precedence == operator().precedence) { if (operatorExpression.operator().associativity == Operator.Associativity.LEFT) { - op1String = "(" + operatorExpression + ")"; + op1String = "(" + op1String + ")"; } } } if (operator2 instanceof OperatorExpression operatorExpression) { if (operatorExpression.operator().precedence < operator().precedence) { - op2String = "(" + operatorExpression + ")"; + op2String = "(" + op2String + ")"; } else if (operatorExpression.operator().precedence == operator().precedence) { if (operatorExpression.operator().associativity == Operator.Associativity.RIGHT) { - op2String = "(" + operatorExpression + ")"; + op2String = "(" + op2String + ")"; } } } - return op1String + " " + operator() + " " + op2String; + return indent + op1String + " " + operator() + " " + op2String; } } @@ -484,8 +493,8 @@ public List children() { } @Override - public String toString() { - return (name == null ? "" : name + " = ") + expression; + public String toPrettyString(String indent, String increment) { + return indent + (name == null ? "" : name.toPrettyString() + " = ") + expression.toPrettyString(); } } @@ -498,22 +507,14 @@ public List children() { } @Override - public String toString() { - return "{" + declarators.stream().map(InitDeclarator::toString).collect(Collectors.joining(", ")) + "}"; + public String toPrettyString(String indent, String increment) { + return indent + "{" + declarators.stream().map(InitDeclarator::toPrettyString).collect(Collectors.joining(", ")) + "}"; } } } sealed interface Declarator extends Expression { - default String toPrettyString(String indent, String increment) { - return indent + this; - } - - default String toPrettyString() { - return toPrettyString("", " "); - } - record PointerDeclarator(Declarator declarator) implements Declarator { @Override public List children() { @@ -521,8 +522,8 @@ public List children() { } @Override - public String toString() { - return "*" + declarator; + public String toPrettyString(String indent, String increment) { + return indent + "*" + declarator.toPrettyString(); } } @@ -533,8 +534,13 @@ public List children() { } @Override - public String toString() { - return declarator + (size == null ? "[]" : "[" + size + "]"); + public String toPrettyString(String indent, String increment) { + return indent + declarator.toPrettyString() + (size == null ? "[]" : "[" + size.toPrettyString() + "]"); + } + + public String toPrettyVariableDefinition(Expression name, String indent) { + return indent + declarator.toPrettyString() + " " + name.toPrettyString() + + (size == null ? "[]" : "[" + size.toPrettyString() + "]"); } } @@ -549,17 +555,15 @@ public List children() { return List.of(declarator, name); } - @Override - public String toString() { - return declarator + " " + name; - } - @Override public String toPrettyString(String indent, String increment) { if (ebpfSize == null) { - return declarator.toPrettyString(indent, increment) + " " + name + ";"; + if (declarator instanceof ArrayDeclarator arr) { + return arr.toPrettyVariableDefinition(name, indent) + ";"; + } + return declarator.toPrettyString(indent, increment) + " " + name.toPrettyString() + ";"; } - return indent + declarator + " (" + name + ", " + ebpfSize + ");"; + return indent + declarator.toPrettyString() + " (" + name.toPrettyString() + ", " + ebpfSize.toPrettyString() + ");"; } } @@ -575,7 +579,7 @@ public List children() { @Override public String toPrettyString(String indent, String increment) { - return indent + "struct " + (name == null ? "" : name + " ") + "{\n" + members.stream().map(m -> m.toPrettyString(indent + increment, increment)).collect(Collectors.joining("\n")) + "\n" + indent + "}"; + return indent + "struct " + (name == null ? "" : name.toPrettyString() + " ") + "{\n" + members.stream().map(m -> m.toPrettyString(indent + increment, increment)).collect(Collectors.joining("\n")) + "\n" + indent + "}"; } } @@ -586,8 +590,8 @@ public List children() { } @Override - public String toString() { - return declarator + "(" + parameters.stream().map(Object::toString).collect(Collectors.joining(", ")) + ")"; + public String toPrettyString(String indent, String increment) { + return declarator.toPrettyString(indent, increment) + "(" + parameters.stream().map(CAST::toPrettyString).collect(Collectors.joining(", ")) + ")"; } } @@ -598,8 +602,8 @@ public List children() { } @Override - public String toString() { - return name.toString(); + public String toPrettyString(String indent, String increment) { + return name.toPrettyString(indent, increment); } } @@ -610,8 +614,8 @@ public List children() { } @Override - public String toString() { - return "struct " + name.toString(); + public String toPrettyString(String indent, String increment) { + return indent + "struct " + name.toPrettyString(); } } @@ -656,21 +660,6 @@ static Declarator structIdentifier(PrimaryExpression.Variable name) { interface Statement extends CAST { - /** - * Pretty string representation of the AST - * - * @param indent indent of the current line, ignored by expressions - * @param increment increment of the indent - * @return pretty string representation of the AST - */ - default String toPrettyString(String indent, String increment) { - return indent + this.children().getFirst() + ";"; - } - - default String toPrettyString() { - return toPrettyString("", " "); - } - @Override default Statement toStatement() { return this; @@ -684,8 +673,8 @@ public List children() { } @Override - public String toString() { - return toPrettyString("", ""); + public String toPrettyString(String indent, String increment) { + return expression.toPrettyString(indent, increment) + ";"; } } @@ -698,7 +687,10 @@ public List children() { @Override public String toPrettyString(String indent, String increment) { - return type.toPrettyString(indent, increment) + " " + name + ";"; + if (type instanceof ArrayDeclarator arr) { + return arr.toPrettyVariableDefinition(name, indent) + ";"; + } + return type.toPrettyString(indent, increment) + " " + name.toPrettyString() + ";"; } } @@ -714,11 +706,6 @@ public List children() { public String toPrettyString(String indent, String increment) { return indent + "{\n" + statements.stream().map(s -> s.toPrettyString(indent + increment, increment)).collect(Collectors.joining("\n")) + "\n" + indent + "}"; } - - @Override - public String toString() { - return toPrettyString("", " "); - } } record IfStatement(Expression condition, Statement thenStatement, @@ -732,15 +719,10 @@ public List children() { @Override public String toPrettyString(String indent, String increment) { - return indent + "if (" + condition + ")\n" + thenStatement.toPrettyString(indent + increment, + return indent + "if (" + condition.toPrettyString() + ")\n" + thenStatement.toPrettyString(indent + increment, increment) + (elseStatement == null ? "" : " else\n" + elseStatement.toPrettyString(indent + increment, increment)); } - - @Override - public String toString() { - return toPrettyString("", " "); - } } record WhileStatement(Expression condition, Statement body) implements Statement { @@ -752,12 +734,7 @@ public List children() { @Override public String toPrettyString(String indent, String increment) { - return indent + "while (" + condition + ")\n" + body.toPrettyString(indent + increment, increment); - } - - @Override - public String toString() { - return toPrettyString("", " "); + return indent + "while (" + condition.toPrettyString() + ")\n" + body.toPrettyString(indent + increment, increment); } } @@ -782,15 +759,10 @@ public List children() { @Override public String toPrettyString(String indent, String increment) { - return indent + "for (" + (init == null ? "" : init) + "; " + (condition == null ? "" : condition) + + return indent + "for (" + (init == null ? "" : init.toPrettyString()) + "; " + (condition == null ? "" : condition.toPrettyString()) + "; " + (increment == null ? "" : increment) + ")\n" + body.toPrettyString(indent + increment, increment); } - - @Override - public String toString() { - return toPrettyString("", " "); - } } record ReturnStatement(@Nullable Expression expression) implements Statement { @@ -802,12 +774,7 @@ public List children() { @Override public String toPrettyString(String indent, String increment) { - return indent + "return" + (expression == null ? "" : " " + expression) + ";"; - } - - @Override - public String toString() { - return toPrettyString("", " "); + return indent + "return" + (expression == null ? "" : " " + expression.toPrettyString()) + ";"; } } @@ -822,11 +789,6 @@ public List children() { public String toPrettyString(String indent, String increment) { return indent + "break;"; } - - @Override - public String toString() { - return toPrettyString("", " "); - } } record ContinueStatement() implements Statement { @@ -840,11 +802,6 @@ public List children() { public String toPrettyString(String indent, String increment) { return indent + "continue;"; } - - @Override - public String toString() { - return toPrettyString("", " "); - } } record EmptyStatement() implements Statement { @@ -858,11 +815,6 @@ public List children() { public String toPrettyString(String indent, String increment) { return indent + ";"; } - - @Override - public String toString() { - return toPrettyString("", " "); - } } record DeclarationStatement(Declarator declarator, @Nullable Initializer initializer) implements Statement { @@ -874,12 +826,7 @@ public List children() { @Override public String toPrettyString(String indent, String increment) { - return indent + declarator + (initializer == null ? "" : " = " + initializer) + ";"; - } - - @Override - public String toString() { - return toPrettyString("", " "); + return indent + declarator.toPrettyString() + (initializer == null ? "" : " = " + initializer.toPrettyString()) + ";"; } } @@ -892,12 +839,7 @@ public List children() { @Override public String toPrettyString(String indent, String increment) { - return indent + declarator + ";"; - } - - @Override - public String toString() { - return toPrettyString("", " "); + return declarator.toPrettyString(indent, increment) + ";"; } } @@ -911,12 +853,7 @@ public List children() { @Override public String toPrettyString(String indent, String increment) { - return indent + declarator + "\n" + body.toPrettyString(indent + increment, increment); - } - - @Override - public String toString() { - return toPrettyString("", " "); + return declarator.toPrettyString(indent, increment) + "\n" + body.toPrettyString(indent + increment, increment); } } @@ -929,12 +866,7 @@ public List children() { @Override public String toPrettyString(String indent, String increment) { - return indent + "#define " + name + " " + value; - } - - @Override - public String toString() { - return toPrettyString("", " "); + return indent + "#define " + name + " " + value.toPrettyString(); } } @@ -949,11 +881,6 @@ public List children() { public String toPrettyString(String indent, String increment) { return indent + "#include " + file; } - - @Override - public String toString() { - return toPrettyString("", " "); - } } record Typedef(Declarator declarator, PrimaryExpression.Variable name) implements Statement { @@ -965,12 +892,7 @@ public List children() { @Override public String toPrettyString(String indent, String increment) { - return indent + "typedef " + declarator + " " + name + ";"; - } - - @Override - public String toString() { - return toPrettyString("", " "); + return indent + "typedef " + declarator.toPrettyString() + " " + name.toPrettyString() + ";"; } } @@ -983,12 +905,7 @@ public List children() { @Override public String toPrettyString(String indent, String increment) { - return indent + "case " + expression + ":\n" + body.toPrettyString(indent + increment, increment); - } - - @Override - public String toString() { - return toPrettyString("", " "); + return indent + "case " + expression.toPrettyString() + ":\n" + body.toPrettyString(indent + increment, increment); } } @@ -1003,11 +920,6 @@ public List children() { public String toPrettyString(String indent, String increment) { return indent + "default:\n" + body.toPrettyString(indent + increment, increment); } - - @Override - public String toString() { - return toPrettyString("", " "); - } } record SwitchStatement(Expression expression, Statement body) implements Statement { @@ -1019,12 +931,7 @@ public List children() { @Override public String toPrettyString(String indent, String increment) { - return indent + "switch (" + expression + ")\n" + body.toPrettyString(indent + increment, increment); - } - - @Override - public String toString() { - return toPrettyString("", " "); + return indent + "switch (" + expression.toPrettyString() + ")\n" + body.toPrettyString(indent + increment, increment); } } diff --git a/bpf-processor/src/main/java/me/bechberger/ebpf/bpf/processor/Processor.java b/bpf-processor/src/main/java/me/bechberger/ebpf/bpf/processor/Processor.java index d22c899..fcdcf60 100644 --- a/bpf-processor/src/main/java/me/bechberger/ebpf/bpf/processor/Processor.java +++ b/bpf-processor/src/main/java/me/bechberger/ebpf/bpf/processor/Processor.java @@ -23,6 +23,7 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.util.*; +import java.util.stream.IntStream; import java.util.zip.GZIPOutputStream; /** @@ -71,9 +72,11 @@ public void processBPFProgram(TypeElement typeElement) { } var typeProcessorResult = new TypeProcessor(processingEnv).processBPFTypeRecords(typeElement); var codeToInsert = typeProcessorResult.toCCode(); - byte[] bytes = compileProgram(typeElement, codeToInsert); - // create class that implement the class of typeElement and override the getByteCode method to return the - // compiled eBPF program, but store this ebpf program as a base64 string + var combinedCode = combineEBPFProgram(typeElement, codeToInsert); + if (combinedCode == null) { + return; + } + byte[] bytes = compile(combinedCode.ebpfProgram(), combinedCode.codeField()); if (bytes == null) { return; } @@ -85,7 +88,7 @@ public void processBPFProgram(TypeElement typeElement) { String name = typeElement.getSimpleName().toString() + "Impl"; TypeSpec typeSpec = createType(typeElement.getSimpleName() + "Impl", typeElement.asType(), bytes, - typeProcessorResult.fields()); + typeProcessorResult.fields(), combinedCode.ebpfProgram()); try { var file = processingEnv.getFiler().createSourceFile(pkg + "." + name, typeElement); // delete file if it exists @@ -129,14 +132,24 @@ private static String gzipBase64Encode(byte[] byteCode) { * inner records * @return the generated class */ - private TypeSpec createType(String name, TypeMirror baseType, byte[] byteCode, List bpfTypeFields) { + private TypeSpec createType(String name, TypeMirror baseType, byte[] byteCode, List bpfTypeFields, + String fullCCode) { var spec = - TypeSpec.classBuilder(name).superclass(baseType).addModifiers(Modifier.PUBLIC, Modifier.FINAL).addField(FieldSpec.builder(String.class, "BYTE_CODE", Modifier.PRIVATE, Modifier.STATIC, Modifier.FINAL).addJavadoc("Base64 encoded and gzipped eBPF byte-code").initializer("$S", gzipBase64Encode(byteCode)).build()).addMethod(MethodSpec.methodBuilder("getByteCode").addAnnotation(Override.class).addModifiers(Modifier.PUBLIC).returns(byte[].class).addStatement("return me.bechberger.ebpf.bpf.Util.decodeGzippedBase64(BYTE_CODE)").build()); + TypeSpec.classBuilder(name).superclass(baseType).addModifiers(Modifier.PUBLIC, Modifier.FINAL) + .addField(FieldSpec.builder(String.class, "BYTE_CODE", Modifier.PRIVATE, Modifier.STATIC, Modifier.FINAL) + .addJavadoc("Base64 encoded and gzipped eBPF byte-code of the program\n{@snippet : \n" + fullCCode + "\n}") + .initializer("$S", gzipBase64Encode(byteCode)).build()) + .addMethod(MethodSpec.methodBuilder("getByteCode") + .addAnnotation(Override.class).addModifiers(Modifier.PUBLIC).returns(byte[].class) + .addStatement("return me.bechberger.ebpf.bpf.Util.decodeGzippedBase64(BYTE_CODE)").build()); bpfTypeFields.forEach(spec::addField); return spec.build(); } - - private byte[] compileProgram(TypeElement typeElement, String codeToInsert) { + + record CombinedCode(String ebpfProgram, VariableElement codeField) { + } + + private @Nullable CombinedCode combineEBPFProgram(TypeElement typeElement, String codeToInsert) { Optional elem = typeElement.getEnclosedElements().stream().filter(e -> e.getKind().isField() && e.getSimpleName().toString().equals("EBPF_PROGRAM")).findFirst(); // check that the class has a static field EBPF_PROGRAM of type String or Path @@ -177,28 +190,29 @@ private byte[] compileProgram(TypeElement typeElement, String codeToInsert) { } } this.processingEnv.getMessager().printNote("EBPF Program: " + ebpfProgram, typeElement); - ebpfProgram = placeDefinitionsIntoEBPFProgram(ebpfProgram, codeToInsert); - return compile(ebpfProgram, element); + return new CombinedCode(placeDefinitionsIntoEBPFProgram(ebpfProgram, codeToInsert), element); } private String placeDefinitionsIntoEBPFProgram(String ebpfProgram, String codeToInsert) { // insert the code to insert into the ebpf program // if the insertion fails, print an error // if the insertion succeeds, return the new ebpf program - int index = ebpfProgram.lastIndexOf("#include"); - if (index == -1) { - this.processingEnv.getMessager().printError("Could not find #include in eBPF program", null); - return null; + var lines = ebpfProgram.lines().toList(); + var lastInclude = IntStream.range(0, lines.size()).filter(i -> lines.get(i).startsWith("#include")).max().orElse(-1); + if (lastInclude == -1) { + return codeToInsert + "\n" + ebpfProgram; } - return ebpfProgram.substring(0, index) + codeToInsert + ebpfProgram.substring(index); + return String.join("\n",lines.subList(0, lastInclude + 1)) + "\n\n" + + codeToInsert + "\n" + String.join("\n", lines.subList(lastInclude + 1, lines.size())); } private static String findNewestClangVersion() { - for (int i = 24; i > 12; i--) { + for (int i = 12; i > 11; i--) { try { - var process = new ProcessBuilder("clang-" + i, "--version").start(); + var name = i == 12 ? "clang" : "clang-" + i; + var process = new ProcessBuilder(name, "--version").start(); if (process.waitFor() == 0) { - return "clang-" + i; + return name; } } catch (IOException | InterruptedException e) { // ignore @@ -210,6 +224,12 @@ private static String findNewestClangVersion() { private static String newestClang = findNewestClangVersion(); private byte[] compile(String ebpfProgram, VariableElement element) { + if (dontCompile()) { + System.out.println("EBPF program to compile:"); + System.out.println("-".repeat(10)); + System.out.println(ebpfProgram); + return new byte[]{0}; + } // obtain the path to the vmlinux.h header file var vmlinuxHeader = getPathToVMLinuxHeader(); if (vmlinuxHeader == null) { @@ -356,7 +376,11 @@ private Path obtainPathToVMLinuxHeader() { public @Nullable Path getEBPFFolder() { String p = processingEnv.getOptions().getOrDefault("ebpf.folder", null); - return p == null ? null : Path.of(p); + if (p == null) { + String val = System.getenv("EBPF_FOLDER"); + return val == null ? null : Path.of(val); + } + return Path.of(p); } private @Nullable Path getPath(String name) { @@ -368,4 +392,8 @@ private Path obtainPathToVMLinuxHeader() { } return getEBPFFolder() == null ? null : getEBPFFolder().resolve(name); } + + private boolean dontCompile() { + return "true".equals(System.getenv("EBPF_DONT_COMPILE")); + } } \ No newline at end of file diff --git a/bpf-processor/src/main/java/me/bechberger/ebpf/bpf/processor/TypeProcessor.java b/bpf-processor/src/main/java/me/bechberger/ebpf/bpf/processor/TypeProcessor.java index 9c19233..dcfd342 100644 --- a/bpf-processor/src/main/java/me/bechberger/ebpf/bpf/processor/TypeProcessor.java +++ b/bpf-processor/src/main/java/me/bechberger/ebpf/bpf/processor/TypeProcessor.java @@ -3,6 +3,7 @@ import com.squareup.javapoet.FieldSpec; import me.bechberger.cast.CAST; import me.bechberger.ebpf.type.BPFType; +import org.jetbrains.annotations.Nullable; import javax.annotation.processing.ProcessingEnvironment; import javax.lang.model.AnnotatedConstruct; @@ -22,8 +23,8 @@ public class TypeProcessor { public final static String SIZE_ANNOTATION = "me.bechberger.ebpf.annotations.Size"; public final static String UNSIGNED_ANNOTATION = "me.bechberger.ebpf.annotations.Unsigned"; public final static String TYPE_ANNOTATION = "me.bechberger.ebpf.annotations.bpf.Type"; - public final static String BPF_PACKAGE = "me.bechberger.ebpf.shared"; - public final static String BPF_TYPE = "me.bechberger.ebpf.shared.BPFType"; + public final static String BPF_PACKAGE = "me.bechberger.ebpf.type"; + public final static String BPF_TYPE = "me.bechberger.ebpf.type.BPFType"; /** * Helper class to keep track of defined types */ @@ -87,6 +88,15 @@ private Optional getAnnotationMirror(AnnotatedConstr return element.getAnnotationMirrors().stream().filter(a -> a.getAnnotationType().asElement().toString().equals(annotationName)).findFirst(); } + private Map getAnnotationValues(AnnotationMirror annotation) { + return annotation.getElementValues().entrySet().stream().collect(Collectors.toMap(e -> e.getKey().toString(), Map.Entry::getValue)); + } + + @SuppressWarnings("unchecked") + private T getAnnotationValue(AnnotationMirror annotation, String name, T defaultValue) { + return annotation.getElementValues().entrySet().stream().filter(e -> e.getKey().getSimpleName().toString().equals(name)).map(e -> (T)e.getValue().getValue()).findFirst().orElse(defaultValue); + } + private boolean hasAnnotation(AnnotatedConstruct element, String annotationName) { return getAnnotationMirror(element, annotationName).isPresent(); } @@ -114,6 +124,10 @@ String toCCode() { } } + boolean shouldGenerateCCode(TypeElement innerElement) { + return !getAnnotationMirror(innerElement, TYPE_ANNOTATION).map(a -> getAnnotationValue(a, "noCCodeGeneration", false)).orElse(false); + } + /** * Process the records annotated with {@code @Type} in the given class * @@ -177,8 +191,9 @@ TypeProcessorResult processBPFTypeRecords(TypeElement outerTypeElement) { var spec = type.toFieldSpecGenerator().get().apply(definedTypes.getSpecFieldName(name).get(), typeToSpecField); fields.add(spec); - var def = type.toCDeclarationStatement(); - def.ifPresent(definingStatements::add); + if (shouldGenerateCCode(processedType)) { + type.toCDeclarationStatement().ifPresent(definingStatements::add); + } } return new TypeProcessorResult(fields, definingStatements); } @@ -297,26 +312,6 @@ interface BPFTypeMirror { BPFType toBPFType(Function> nameToCustomType); } - private Optional> getIntegerType(Element element, TypeMirror type, boolean unsigned) { - return switch (lastPart(type.toString())) { - case "int" -> Optional.of(unsigned ? BPFType.BPFIntType.UINT32 : BPFType.BPFIntType.INT32); - case "long" -> Optional.of(unsigned ? BPFType.BPFIntType.UINT64 : BPFType.BPFIntType.INT64); - case "short" -> Optional.of(unsigned ? BPFType.BPFIntType.UINT16 : BPFType.BPFIntType.INT16); - case "byte" -> Optional.of(unsigned ? BPFType.BPFIntType.UINT8 : BPFType.BPFIntType.INT8); - case "char" -> { - if (unsigned) { - this.processingEnv.getMessager().printError("Unsigned char not supported", element); - yield Optional.empty(); - } - yield Optional.of(BPFType.BPFIntType.CHAR); - } - default -> { - this.processingEnv.getMessager().printError("Unsupported integer type " + type, element); - yield Optional.empty(); - } - }; - } - private Optional> processIntegerType(Element element, AnnotationValues annotations, TypeMirror type) { if (annotations.size().isPresent()) { // annotation not supported for integer types and log diff --git a/bpf-processor/src/test/java/me/bechberger/cast/ASTTest.java b/bpf-processor/src/test/java/me/bechberger/cast/ASTTest.java index 84628b2..fef33c8 100644 --- a/bpf-processor/src/test/java/me/bechberger/cast/ASTTest.java +++ b/bpf-processor/src/test/java/me/bechberger/cast/ASTTest.java @@ -7,8 +7,7 @@ import java.util.List; import java.util.stream.Stream; -import static me.bechberger.cast.CAST.Declarator.struct; -import static me.bechberger.cast.CAST.Declarator.structMember; +import static me.bechberger.cast.CAST.Declarator.*; import static me.bechberger.cast.CAST.Expression.constant; import static me.bechberger.cast.CAST.Expression.variable; import static me.bechberger.cast.CAST.OperatorExpression.binary; @@ -39,7 +38,16 @@ static Stream declAstAndExpectedCode() { List.of(structMember(CAST.Declarator.identifier("int"), variable("b"), constant(1)))), variable( "myVar", sec("a"))) - , "struct myStruct {\n int (b, 1);\n} myVar SEC(\"a\");")); + , "struct myStruct {\n int (b, 1);\n} myVar SEC(\"a\");"), + Arguments.of( + struct(variable("x"), + List.of(structMember(array(identifier("a"), + constant(10)), + variable("x")))).toStatement(), + """ + struct x { + a x[10]; + };""")); } @ParameterizedTest diff --git a/bpf/src/main/java/me/bechberger/ebpf/samples/TypeProcessingSample.java b/bpf/src/main/java/me/bechberger/ebpf/samples/TypeProcessingSample.java index 2bd010f..13ae33d 100644 --- a/bpf/src/main/java/me/bechberger/ebpf/samples/TypeProcessingSample.java +++ b/bpf/src/main/java/me/bechberger/ebpf/samples/TypeProcessingSample.java @@ -83,7 +83,7 @@ int kprobe__do_sys_openat2 (struct pt_regs *ctx) private static final int FILE_NAME_LEN = 256; private static final int TASK_COMM_LEN = 16; - @Type + @Type(noCCodeGeneration = true) record Event(@Unsigned int pid, @Size(FILE_NAME_LEN) String filename, @Size(TASK_COMM_LEN) String comm) {} diff --git a/bpf/src/main/java/me/bechberger/ebpf/samples/TypeProcessingSample2.java b/bpf/src/main/java/me/bechberger/ebpf/samples/TypeProcessingSample2.java new file mode 100644 index 0000000..93a172d --- /dev/null +++ b/bpf/src/main/java/me/bechberger/ebpf/samples/TypeProcessingSample2.java @@ -0,0 +1,94 @@ +package me.bechberger.ebpf.samples; + +import me.bechberger.ebpf.annotations.Size; +import me.bechberger.ebpf.annotations.Unsigned; +import me.bechberger.ebpf.annotations.bpf.BPF; +import me.bechberger.ebpf.annotations.bpf.Type; +import me.bechberger.ebpf.bpf.BPFProgram; + +/** + * Adaption of {@link TypeProcessingSample} that shows how code is autogenerated + */ +@BPF +public abstract class TypeProcessingSample2 extends BPFProgram { + + static final String EBPF_PROGRAM = """ + #include "vmlinux.h" + #include + #include + #include + + #define FILE_NAME_LEN 256 + #define TASK_COMM_LEN 16 + + // eBPF map reference + struct + { + __uint (type, BPF_MAP_TYPE_RINGBUF); + __uint (max_entries, 256 * 4096); + } rb SEC (".maps"); + + // The ebpf auto-attach logic needs the SEC + SEC ("kprobe/do_sys_openat2") + int kprobe__do_sys_openat2 (struct pt_regs *ctx) + { + char filename[256]; + char comm[TASK_COMM_LEN] = { }; + struct event *evt; + const char fmt_str[] = "do_sys_openat2 called by:%s file:%s pid:%d"; + + // Reserve the ring-buffer + evt = bpf_ringbuf_reserve (&rb, sizeof (struct event), 0); + if (!evt) + { + return 0; + } + // Get the PID of the process. + evt->e_pid = bpf_get_current_pid_tgid (); // Get current process PID + + // Read the filename from the second argument + // The x86 arch/ABI have first argument in di and second in si registers (man syscall) + bpf_probe_read (evt->e_filename, sizeof (filename), (char *) ctx->si); + + // Read the current process name + bpf_get_current_comm (evt->e_comm, sizeof (comm)); + + // Compare process name with our "sample_write" name + // -- parttimenerd: we don't need this in our example + //if (memcmp (evt->e_comm, TARGET_NAME, 12) == 0) + // { + // Print a message with filename, process name, and PID + bpf_trace_printk (fmt_str, sizeof (fmt_str), evt->e_comm, + evt->e_filename, evt->e_pid); + // Also send the same message to the ring-buffer + bpf_ringbuf_submit (evt, 0); + // return 0; + // } + // If the program name is not matching with TARGET_NAME, then discard the data + //bpf_ringbuf_discard (evt, 0); + return 0; + } + + char _license[] SEC ("license") = "GPL"; + """; + + private static final int FILE_NAME_LEN = 256; + private static final int TASK_COMM_LEN = 16; + + @Type(name = "event") + record Event(@Unsigned int pid, @Size(FILE_NAME_LEN) String filename, @Size(TASK_COMM_LEN) String comm) {} + + + public static void main(String[] args) { + try (TypeProcessingSample2 program = BPFProgram.load(TypeProcessingSample2.class)) { + program.autoAttachProgram(program.getProgramByName("kprobe__do_sys_openat2")); + var eventType = program.getTypeForClass(Event.class); + var ringBuffer = program.getRingBufferByName("rb", eventType, (buffer, event) -> { + System.out.printf("do_sys_openat2 called by:%s file:%s pid:%d\n", event.comm(), event.filename(), event.pid()); + }); + while (true) { + ringBuffer.consumeAndThrow(); + } + } + } +} \ No newline at end of file From 517a13d836a0580d1ba7548cf9ce53bbb5099002 Mon Sep 17 00:00:00 2001 From: Johannes Bechberger Date: Tue, 2 Apr 2024 22:50:01 +0300 Subject: [PATCH 04/11] Emit C code for constants --- .../main/java/me/bechberger/cast/CAST.java | 2 +- .../ebpf/bpf/processor/Processor.java | 28 ++++++++----- .../ebpf/bpf/processor/TypeProcessor.java | 39 +++++++++++++++++-- .../ebpf/samples/TypeProcessingSample2.java | 16 +++----- 4 files changed, 61 insertions(+), 24 deletions(-) diff --git a/bpf-processor/src/main/java/me/bechberger/cast/CAST.java b/bpf-processor/src/main/java/me/bechberger/cast/CAST.java index 03c5185..1e93eaa 100644 --- a/bpf-processor/src/main/java/me/bechberger/cast/CAST.java +++ b/bpf-processor/src/main/java/me/bechberger/cast/CAST.java @@ -989,7 +989,7 @@ static Statement functionDeclarationStatement(Declarator.FunctionDeclarator decl return new FunctionDeclarationStatement(declarator, body); } - static Statement define(String name, PrimaryExpression.Constant value) { + static Define define(String name, PrimaryExpression.Constant value) { return new Define(name, value); } diff --git a/bpf-processor/src/main/java/me/bechberger/ebpf/bpf/processor/Processor.java b/bpf-processor/src/main/java/me/bechberger/ebpf/bpf/processor/Processor.java index fcdcf60..86bd781 100644 --- a/bpf-processor/src/main/java/me/bechberger/ebpf/bpf/processor/Processor.java +++ b/bpf-processor/src/main/java/me/bechberger/ebpf/bpf/processor/Processor.java @@ -4,6 +4,8 @@ import com.squareup.javapoet.JavaFile; import com.squareup.javapoet.MethodSpec; import com.squareup.javapoet.TypeSpec; +import me.bechberger.cast.CAST.Statement.Define; +import me.bechberger.ebpf.annotations.Size; import org.jetbrains.annotations.Nullable; import javax.annotation.processing.AbstractProcessor; @@ -23,9 +25,13 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.util.*; +import java.util.stream.Collectors; import java.util.stream.IntStream; import java.util.zip.GZIPOutputStream; +import static me.bechberger.cast.CAST.Expression.constant; +import static me.bechberger.cast.CAST.Statement.define; + /** * Annotation processor that processes classes annotated with {@code @BPF}. *

    @@ -71,8 +77,8 @@ public void processBPFProgram(TypeElement typeElement) { return; } var typeProcessorResult = new TypeProcessor(processingEnv).processBPFTypeRecords(typeElement); - var codeToInsert = typeProcessorResult.toCCode(); - var combinedCode = combineEBPFProgram(typeElement, codeToInsert); + var codeToInsert = typeProcessorResult.toCCodeWithoutDefines(); + var combinedCode = combineEBPFProgram(typeElement, typeProcessorResult.defines(), codeToInsert); if (combinedCode == null) { return; } @@ -145,11 +151,11 @@ private TypeSpec createType(String name, TypeMirror baseType, byte[] byteCode, L bpfTypeFields.forEach(spec::addField); return spec.build(); } - + record CombinedCode(String ebpfProgram, VariableElement codeField) { } - - private @Nullable CombinedCode combineEBPFProgram(TypeElement typeElement, String codeToInsert) { + + private @Nullable CombinedCode combineEBPFProgram(TypeElement typeElement, List defines, String codeToInsert) { Optional elem = typeElement.getEnclosedElements().stream().filter(e -> e.getKind().isField() && e.getSimpleName().toString().equals("EBPF_PROGRAM")).findFirst(); // check that the class has a static field EBPF_PROGRAM of type String or Path @@ -190,10 +196,10 @@ record CombinedCode(String ebpfProgram, VariableElement codeField) { } } this.processingEnv.getMessager().printNote("EBPF Program: " + ebpfProgram, typeElement); - return new CombinedCode(placeDefinitionsIntoEBPFProgram(ebpfProgram, codeToInsert), element); + return new CombinedCode(placeDefinitionsIntoEBPFProgram(ebpfProgram, defines, codeToInsert), element); } - private String placeDefinitionsIntoEBPFProgram(String ebpfProgram, String codeToInsert) { + private String placeDefinitionsIntoEBPFProgram(String ebpfProgram, List defines, String codeToInsert) { // insert the code to insert into the ebpf program // if the insertion fails, print an error // if the insertion succeeds, return the new ebpf program @@ -202,7 +208,11 @@ private String placeDefinitionsIntoEBPFProgram(String ebpfProgram, String codeTo if (lastInclude == -1) { return codeToInsert + "\n" + ebpfProgram; } - return String.join("\n",lines.subList(0, lastInclude + 1)) + "\n\n" + + var filteredDefines = defines.stream().filter(d -> { + var tester = "#define " + d.name() + " "; + return lines.stream().noneMatch(l -> l.startsWith(tester)); + }).toList(); + return String.join("\n", lines.subList(0, lastInclude + 1)) + "\n\n" + filteredDefines.stream().map(Define::toPrettyString).collect(Collectors.joining("\n")) + "\n\n" + codeToInsert + "\n" + String.join("\n", lines.subList(lastInclude + 1, lines.size())); } @@ -392,7 +402,7 @@ private Path obtainPathToVMLinuxHeader() { } return getEBPFFolder() == null ? null : getEBPFFolder().resolve(name); } - + private boolean dontCompile() { return "true".equals(System.getenv("EBPF_DONT_COMPILE")); } diff --git a/bpf-processor/src/main/java/me/bechberger/ebpf/bpf/processor/TypeProcessor.java b/bpf-processor/src/main/java/me/bechberger/ebpf/bpf/processor/TypeProcessor.java index dcfd342..c71abf7 100644 --- a/bpf-processor/src/main/java/me/bechberger/ebpf/bpf/processor/TypeProcessor.java +++ b/bpf-processor/src/main/java/me/bechberger/ebpf/bpf/processor/TypeProcessor.java @@ -2,6 +2,7 @@ import com.squareup.javapoet.FieldSpec; import me.bechberger.cast.CAST; +import me.bechberger.cast.CAST.Statement.Define; import me.bechberger.ebpf.type.BPFType; import org.jetbrains.annotations.Nullable; @@ -15,6 +16,9 @@ import java.util.function.Function; import java.util.stream.Collectors; +import static me.bechberger.cast.CAST.Expression.constant; +import static me.bechberger.cast.CAST.Statement.define; + /** * Handles {@code @Type} annotated records */ @@ -117,9 +121,9 @@ record Pair(Optional a, Element e) { }).filter(Objects::nonNull).toList(); } - record TypeProcessorResult(List fields, List definingStatements) { + record TypeProcessorResult(List fields, List defines, List definingStatements) { - String toCCode() { + String toCCodeWithoutDefines() { return definingStatements.stream().map(CAST.Statement::toPrettyString).collect(Collectors.joining("\n")); } } @@ -176,7 +180,7 @@ TypeProcessorResult processBPFTypeRecords(TypeElement outerTypeElement) { var unprocessed = innerTypeElements.stream().filter(e -> !processedTypes.contains(e)).toList(); var type = processBPFTypeRecord(unprocessed.get(0), obtainType.get()); if (type.isEmpty()) { - return new TypeProcessorResult(List.of(), List.of()); + return new TypeProcessorResult(List.of(), List.of(), List.of()); } alreadyDefinedTypes.put(type.get().bpfName(), type.get()); processedTypes.add(unprocessed.get(0)); @@ -195,7 +199,34 @@ TypeProcessorResult processBPFTypeRecords(TypeElement outerTypeElement) { type.toCDeclarationStatement().ifPresent(definingStatements::add); } } - return new TypeProcessorResult(fields, definingStatements); + return new TypeProcessorResult(fields, createDefineStatements(outerTypeElement), definingStatements); + } + + + private @Nullable CAST.Statement.Define processField(VariableElement field) { + // check that the field is static, final and of type boolean, ..., int, long, float, double or String + // create a #define statement for the field + // return the #define statement + if (!field.getModifiers().contains(Modifier.STATIC) || !field.getModifiers().contains(Modifier.FINAL) || field.getSimpleName().toString().equals("EBPF_PROGRAM")) { + return null; + } + TypeMirror type = field.asType(); + return switch (type.toString()) { + case "boolean" -> + define(field.getSimpleName().toString(), constant(field.getConstantValue().equals(true) ? "1" : "0")); + case "byte", "short", "int", "long", "float", "double" -> + new Define(field.getSimpleName().toString(), constant(field.getConstantValue())); + case "java.lang.String" -> + new Define(field.getSimpleName().toString(), constant(field.getConstantValue().toString())); + default -> null; + }; + } + + private List createDefineStatements(TypeElement typeElement) { + // idea: find all static final fields with type boolean, ..., int, long, float, double or String of the typeElement + // create a #define statement for each of them (name is the field name) + // return the list of #define statements + return typeElement.getEnclosedElements().stream().filter(e -> e.getKind() == ElementKind.FIELD).map(e -> (VariableElement) e).map(this::processField).filter(Objects::nonNull).toList(); } /** diff --git a/bpf/src/main/java/me/bechberger/ebpf/samples/TypeProcessingSample2.java b/bpf/src/main/java/me/bechberger/ebpf/samples/TypeProcessingSample2.java index 93a172d..ad08e8a 100644 --- a/bpf/src/main/java/me/bechberger/ebpf/samples/TypeProcessingSample2.java +++ b/bpf/src/main/java/me/bechberger/ebpf/samples/TypeProcessingSample2.java @@ -12,14 +12,17 @@ @BPF public abstract class TypeProcessingSample2 extends BPFProgram { + private static final int FILE_NAME_LEN = 256; + private static final int TASK_COMM_LEN = 16; + + @Type(name = "event") + record Event(@Unsigned int pid, @Size(FILE_NAME_LEN) String filename, @Size(TASK_COMM_LEN) String comm) {} + static final String EBPF_PROGRAM = """ #include "vmlinux.h" #include #include #include - - #define FILE_NAME_LEN 256 - #define TASK_COMM_LEN 16 // eBPF map reference struct @@ -72,13 +75,6 @@ int kprobe__do_sys_openat2 (struct pt_regs *ctx) char _license[] SEC ("license") = "GPL"; """; - private static final int FILE_NAME_LEN = 256; - private static final int TASK_COMM_LEN = 16; - - @Type(name = "event") - record Event(@Unsigned int pid, @Size(FILE_NAME_LEN) String filename, @Size(TASK_COMM_LEN) String comm) {} - - public static void main(String[] args) { try (TypeProcessingSample2 program = BPFProgram.load(TypeProcessingSample2.class)) { program.autoAttachProgram(program.getProgramByName("kprobe__do_sys_openat2")); From 76c0c920a9116b80fa0645158991c9bdaad97765 Mon Sep 17 00:00:00 2001 From: Johannes Bechberger Date: Tue, 2 Apr 2024 23:38:35 +0300 Subject: [PATCH 05/11] Generate license variable --- .../bechberger/ebpf/annotations/bpf/BPF.java | 5 +++ .../main/java/me/bechberger/cast/CAST.java | 23 ++++++++---- .../ebpf/bpf/processor/Processor.java | 37 +++++++++++++++---- .../ebpf/bpf/processor/TypeProcessor.java | 34 +++++++++++++---- .../ebpf/samples/TypeProcessingSample2.java | 4 +- 5 files changed, 77 insertions(+), 26 deletions(-) diff --git a/annotations/src/main/java/me/bechberger/ebpf/annotations/bpf/BPF.java b/annotations/src/main/java/me/bechberger/ebpf/annotations/bpf/BPF.java index 0f20ee8..4986e1e 100644 --- a/annotations/src/main/java/me/bechberger/ebpf/annotations/bpf/BPF.java +++ b/annotations/src/main/java/me/bechberger/ebpf/annotations/bpf/BPF.java @@ -21,4 +21,9 @@ */ @Target(ElementType.TYPE) public @interface BPF { + /** + * License of the eBPF program, the EBPF_PROGRAM contains a SEC("license") variable + * only iff this license is not present. Typically, the license is GPL. + */ + String license() default ""; } diff --git a/bpf-processor/src/main/java/me/bechberger/cast/CAST.java b/bpf-processor/src/main/java/me/bechberger/cast/CAST.java index 1e93eaa..cea5b5d 100644 --- a/bpf-processor/src/main/java/me/bechberger/cast/CAST.java +++ b/bpf-processor/src/main/java/me/bechberger/cast/CAST.java @@ -111,7 +111,7 @@ static CAnnotation annotation(String annotation, String value) { return new CAnnotation(annotation, value); } - static CAnnotation sec(String value) { + public static CAnnotation sec(String value) { return new CAnnotation("SEC", value); } @@ -127,9 +127,13 @@ public Statement toStatement() { record Variable(String name, CAnnotation... annotations) implements PrimaryExpression { @Override public String toPrettyString(String indent, String increment) { - String annString = Arrays.stream(annotations).map(CAnnotation::toPrettyString).collect(Collectors.joining(" ")); + var annString = annotationsString(); return indent + name + (annString.isEmpty() ? "" : " " + annString); } + + public String annotationsString() { + return Arrays.stream(annotations).map(CAnnotation::toPrettyString).collect(Collectors.joining(" ")); + } } record EnumerationConstant(String name) implements PrimaryExpression { @@ -678,19 +682,20 @@ public String toPrettyString(String indent, String increment) { } } - record VariableDefinition(Declarator type, PrimaryExpression.Variable name) implements Statement { + record VariableDefinition(Declarator type, PrimaryExpression.Variable name, @Nullable Expression value) implements Statement { @Override public List children() { - return List.of(type, name); + return List.of(type, name, value); } @Override public String toPrettyString(String indent, String increment) { + String app = value == null ? "" : " = " + value.toPrettyString(); if (type instanceof ArrayDeclarator arr) { - return arr.toPrettyVariableDefinition(name, indent) + ";"; + return arr.toPrettyVariableDefinition(Expression.variable(name.name), indent) + (name.annotations.length == 0 ? "" : " " + name.annotationsString()) + app + ";"; } - return type.toPrettyString(indent, increment) + " " + name.toPrettyString() + ";"; + return type.toPrettyString(indent, increment) + " " + name.toPrettyString() + app + ";"; } } @@ -1014,7 +1019,11 @@ static Statement switchStatement(Expression expression, Statement body) { } static Statement variableDefinition(Declarator type, PrimaryExpression.Variable name) { - return new VariableDefinition(type, name); + return new VariableDefinition(type, name, null); + } + + static Statement variableDefinition(Declarator type, PrimaryExpression.Variable name, Expression value) { + return new VariableDefinition(type, name, value); } } } \ No newline at end of file diff --git a/bpf-processor/src/main/java/me/bechberger/ebpf/bpf/processor/Processor.java b/bpf-processor/src/main/java/me/bechberger/ebpf/bpf/processor/Processor.java index 86bd781..72586a8 100644 --- a/bpf-processor/src/main/java/me/bechberger/ebpf/bpf/processor/Processor.java +++ b/bpf-processor/src/main/java/me/bechberger/ebpf/bpf/processor/Processor.java @@ -4,8 +4,10 @@ import com.squareup.javapoet.JavaFile; import com.squareup.javapoet.MethodSpec; import com.squareup.javapoet.TypeSpec; +import me.bechberger.cast.CAST; import me.bechberger.cast.CAST.Statement.Define; import me.bechberger.ebpf.annotations.Size; +import me.bechberger.ebpf.bpf.processor.TypeProcessor.TypeProcessorResult; import org.jetbrains.annotations.Nullable; import javax.annotation.processing.AbstractProcessor; @@ -18,6 +20,7 @@ import javax.lang.model.element.TypeElement; import javax.lang.model.element.VariableElement; import javax.lang.model.type.TypeMirror; +import javax.sound.sampled.Line; import javax.tools.Diagnostic; import java.io.ByteArrayOutputStream; import java.io.IOException; @@ -27,6 +30,7 @@ import java.util.*; import java.util.stream.Collectors; import java.util.stream.IntStream; +import java.util.stream.Stream; import java.util.zip.GZIPOutputStream; import static me.bechberger.cast.CAST.Expression.constant; @@ -41,11 +45,13 @@ @SupportedSourceVersion(SourceVersion.RELEASE_22) public class Processor extends AbstractProcessor { + private static final String BPF = "me.bechberger.ebpf.annotations.bpf.BPF"; + public boolean process(Set annotations, RoundEnvironment env) { this.processingEnv.getMessager().printNote("Processing BPF annotations"); annotations.forEach(annotation -> { Set elements = env.getElementsAnnotatedWith(annotation); - if (annotation.getQualifiedName().toString().equals("me.bechberger.ebpf.annotations.bpf.BPF")) { + if (annotation.getQualifiedName().toString().equals(BPF)) { elements.stream().filter(TypeElement.class::isInstance).map(TypeElement.class::cast).forEach(this::processBPFProgram); } }); @@ -77,8 +83,7 @@ public void processBPFProgram(TypeElement typeElement) { return; } var typeProcessorResult = new TypeProcessor(processingEnv).processBPFTypeRecords(typeElement); - var codeToInsert = typeProcessorResult.toCCodeWithoutDefines(); - var combinedCode = combineEBPFProgram(typeElement, typeProcessorResult.defines(), codeToInsert); + var combinedCode = combineEBPFProgram(typeElement, typeProcessorResult); if (combinedCode == null) { return; } @@ -155,7 +160,8 @@ private TypeSpec createType(String name, TypeMirror baseType, byte[] byteCode, L record CombinedCode(String ebpfProgram, VariableElement codeField) { } - private @Nullable CombinedCode combineEBPFProgram(TypeElement typeElement, List defines, String codeToInsert) { + private @Nullable CombinedCode combineEBPFProgram(TypeElement typeElement, TypeProcessorResult tpResult) { + var codeToInsert = tpResult.toCCodeWithoutDefines(); Optional elem = typeElement.getEnclosedElements().stream().filter(e -> e.getKind().isField() && e.getSimpleName().toString().equals("EBPF_PROGRAM")).findFirst(); // check that the class has a static field EBPF_PROGRAM of type String or Path @@ -196,10 +202,10 @@ record CombinedCode(String ebpfProgram, VariableElement codeField) { } } this.processingEnv.getMessager().printNote("EBPF Program: " + ebpfProgram, typeElement); - return new CombinedCode(placeDefinitionsIntoEBPFProgram(ebpfProgram, defines, codeToInsert), element); + return new CombinedCode(placeDefinitionsIntoEBPFProgram(typeElement, ebpfProgram, tpResult.defines(), codeToInsert, tpResult.licenseDefinition()), element); } - private String placeDefinitionsIntoEBPFProgram(String ebpfProgram, List defines, String codeToInsert) { + private String placeDefinitionsIntoEBPFProgram(TypeElement outer, String ebpfProgram, List defines, String codeToInsert, @Nullable CAST.Statement licenseDefinition) { // insert the code to insert into the ebpf program // if the insertion fails, print an error // if the insertion succeeds, return the new ebpf program @@ -212,8 +218,23 @@ private String placeDefinitionsIntoEBPFProgram(String ebpfProgram, List var tester = "#define " + d.name() + " "; return lines.stream().noneMatch(l -> l.startsWith(tester)); }).toList(); - return String.join("\n", lines.subList(0, lastInclude + 1)) + "\n\n" + filteredDefines.stream().map(Define::toPrettyString).collect(Collectors.joining("\n")) + "\n\n" + - codeToInsert + "\n" + String.join("\n", lines.subList(lastInclude + 1, lines.size())); + + var license = lines.stream().filter(l -> l.matches(".*SEC *\\(\"license\"\\).*")).findFirst().orElse(null); + if (licenseDefinition == null) { + if (license == null) { + throw new RuntimeException("No license defined"); + } + } else { + if (license != null) { + throw new RuntimeException("License defined twice"); + } + } + String includes = lines.subList(0, lastInclude + 1).stream().map(l -> l.startsWith("#") ? l.trim() : l).collect(Collectors.joining("\n")); + String definitions = filteredDefines.stream().map(Define::toPrettyString).collect(Collectors.joining("\n")); + String afterIncludes = String.join("\n", lines.subList(lastInclude + 1, lines.size())); + String end = licenseDefinition == null ? "" : "\n\n" + licenseDefinition.toStatement().toPrettyString(); + // TODO: record line number remapping + return Stream.of(includes, definitions, codeToInsert, afterIncludes, end).filter(s -> !s.isBlank()).map(String::trim).collect(Collectors.joining("\n\n")); } private static String findNewestClangVersion() { diff --git a/bpf-processor/src/main/java/me/bechberger/ebpf/bpf/processor/TypeProcessor.java b/bpf-processor/src/main/java/me/bechberger/ebpf/bpf/processor/TypeProcessor.java index c71abf7..5509435 100644 --- a/bpf-processor/src/main/java/me/bechberger/ebpf/bpf/processor/TypeProcessor.java +++ b/bpf-processor/src/main/java/me/bechberger/ebpf/bpf/processor/TypeProcessor.java @@ -2,6 +2,9 @@ import com.squareup.javapoet.FieldSpec; import me.bechberger.cast.CAST; +import me.bechberger.cast.CAST.Declarator; +import me.bechberger.cast.CAST.PrimaryExpression.CAnnotation; +import me.bechberger.cast.CAST.Statement; import me.bechberger.cast.CAST.Statement.Define; import me.bechberger.ebpf.type.BPFType; import org.jetbrains.annotations.Nullable; @@ -17,7 +20,8 @@ import java.util.stream.Collectors; import static me.bechberger.cast.CAST.Expression.constant; -import static me.bechberger.cast.CAST.Statement.define; +import static me.bechberger.cast.CAST.Expression.variable; +import static me.bechberger.cast.CAST.Statement.*; /** * Handles {@code @Type} annotated records @@ -87,21 +91,21 @@ public String toString() { /** * Get a specific annotation which is present on the element (if not present returns {@code Optional.empty()}) */ - private Optional getAnnotationMirror(AnnotatedConstruct element, + static Optional getAnnotationMirror(AnnotatedConstruct element, String annotationName) { return element.getAnnotationMirrors().stream().filter(a -> a.getAnnotationType().asElement().toString().equals(annotationName)).findFirst(); } - private Map getAnnotationValues(AnnotationMirror annotation) { + static Map getAnnotationValues(AnnotationMirror annotation) { return annotation.getElementValues().entrySet().stream().collect(Collectors.toMap(e -> e.getKey().toString(), Map.Entry::getValue)); } @SuppressWarnings("unchecked") - private T getAnnotationValue(AnnotationMirror annotation, String name, T defaultValue) { + static T getAnnotationValue(AnnotationMirror annotation, String name, T defaultValue) { return annotation.getElementValues().entrySet().stream().filter(e -> e.getKey().getSimpleName().toString().equals(name)).map(e -> (T)e.getValue().getValue()).findFirst().orElse(defaultValue); } - private boolean hasAnnotation(AnnotatedConstruct element, String annotationName) { + static boolean hasAnnotation(AnnotatedConstruct element, String annotationName) { return getAnnotationMirror(element, annotationName).isPresent(); } @@ -121,7 +125,8 @@ record Pair(Optional a, Element e) { }).filter(Objects::nonNull).toList(); } - record TypeProcessorResult(List fields, List defines, List definingStatements) { + record TypeProcessorResult(List fields, List defines, List definingStatements, + @Nullable Statement licenseDefinition) { String toCCodeWithoutDefines() { return definingStatements.stream().map(CAST.Statement::toPrettyString).collect(Collectors.joining("\n")); @@ -180,7 +185,7 @@ TypeProcessorResult processBPFTypeRecords(TypeElement outerTypeElement) { var unprocessed = innerTypeElements.stream().filter(e -> !processedTypes.contains(e)).toList(); var type = processBPFTypeRecord(unprocessed.get(0), obtainType.get()); if (type.isEmpty()) { - return new TypeProcessorResult(List.of(), List.of(), List.of()); + return new TypeProcessorResult(List.of(), List.of(), List.of(), null); } alreadyDefinedTypes.put(type.get().bpfName(), type.get()); processedTypes.add(unprocessed.get(0)); @@ -199,9 +204,22 @@ TypeProcessorResult processBPFTypeRecords(TypeElement outerTypeElement) { type.toCDeclarationStatement().ifPresent(definingStatements::add); } } - return new TypeProcessorResult(fields, createDefineStatements(outerTypeElement), definingStatements); + return new TypeProcessorResult(fields, createDefineStatements(outerTypeElement), definingStatements, + getLicenseDefinitionStatement(outerTypeElement)); } + private @Nullable Statement getLicenseDefinitionStatement(TypeElement outerTypeElement) { + var annotation = getAnnotationMirror(outerTypeElement, "me.bechberger.ebpf.annotations.bpf.BPF"); + if (annotation.isEmpty()) { + return null; + } + var license = getAnnotationValue(annotation.get(), "license", ""); + if (license.isEmpty()) { + return null; + } + // char _license[] SEC ("license") = "GPL"; + return variableDefinition(Declarator.array(Declarator.identifier("char"), null), variable("_license", CAnnotation.sec("license"))); + } private @Nullable CAST.Statement.Define processField(VariableElement field) { // check that the field is static, final and of type boolean, ..., int, long, float, double or String diff --git a/bpf/src/main/java/me/bechberger/ebpf/samples/TypeProcessingSample2.java b/bpf/src/main/java/me/bechberger/ebpf/samples/TypeProcessingSample2.java index ad08e8a..7ca9442 100644 --- a/bpf/src/main/java/me/bechberger/ebpf/samples/TypeProcessingSample2.java +++ b/bpf/src/main/java/me/bechberger/ebpf/samples/TypeProcessingSample2.java @@ -9,7 +9,7 @@ /** * Adaption of {@link TypeProcessingSample} that shows how code is autogenerated */ -@BPF +@BPF(license = "GPL") public abstract class TypeProcessingSample2 extends BPFProgram { private static final int FILE_NAME_LEN = 256; @@ -71,8 +71,6 @@ int kprobe__do_sys_openat2 (struct pt_regs *ctx) //bpf_ringbuf_discard (evt, 0); return 0; } - - char _license[] SEC ("license") = "GPL"; """; public static void main(String[] args) { From f22ff4eb0b2223aa1e3eeaf86c15e0da5d21f29e Mon Sep 17 00:00:00 2001 From: Johannes Bechberger Date: Wed, 3 Apr 2024 15:13:48 +0300 Subject: [PATCH 06/11] Generate map definitions and constructors --- .../ebpf/annotations/bpf/BPFMapClass.java | 51 +++++ .../annotations/bpf/BPFMapDefinition.java | 30 +++ bin/install.sh | 2 +- .../main/java/me/bechberger/cast/CAST.java | 16 ++ .../ebpf/bpf/processor/Processor.java | 135 ++++++++----- .../ebpf/bpf/processor/TypeProcessor.java | 180 ++++++++++++++++-- .../java/me/bechberger/ebpf/type/BPFType.java | 17 +- .../me/bechberger/ebpf/bpf/BPFProgram.java | 2 +- .../bechberger/ebpf/bpf/map/BPFHashMap.java | 13 ++ .../ebpf/bpf/map/BPFRingBuffer.java | 36 ++++ .../ebpf/samples/TypeProcessingSample2.java | 43 ++--- 11 files changed, 424 insertions(+), 101 deletions(-) create mode 100644 annotations/src/main/java/me/bechberger/ebpf/annotations/bpf/BPFMapClass.java create mode 100644 annotations/src/main/java/me/bechberger/ebpf/annotations/bpf/BPFMapDefinition.java diff --git a/annotations/src/main/java/me/bechberger/ebpf/annotations/bpf/BPFMapClass.java b/annotations/src/main/java/me/bechberger/ebpf/annotations/bpf/BPFMapClass.java new file mode 100644 index 0000000..53ad3c5 --- /dev/null +++ b/annotations/src/main/java/me/bechberger/ebpf/annotations/bpf/BPFMapClass.java @@ -0,0 +1,51 @@ +package me.bechberger.ebpf.annotations.bpf; + +import java.io.FileDescriptor; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Specify that a class is a BPF map class + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.CLASS) +public @interface BPFMapClass { + /** + * Template for the generated C code + *

    + * Available placeholders: + *

      + *
    • $field: The field name
    • + *
    • $maxEntries: max entries as specified in the {@link BPFMapDefinition} annotation
    • + *
    • $class: name of the class
    • + *
    • $c1, ...: C type names for every generic type parameter
    • + *
    • $b1, ...: BPFTypes
    • + *
    • $j1, ...: Java class names
    • + *
    + *

    + * Example: + * {@snippet : + * struct { + * __uint (type, BPF_MAP_TYPE_RINGBUF); + * __uint (max_entries, $maxEntries); + * } $field SEC(".maps"); + * } + */ + String cTemplate(); + + /** + * Code template for generating the Java generation code, see {@link BPFMapClass#cTemplate()} for available placeholders, + * here are the additions: + *

      + *
    • $fd: code that creates a FileDescriptor instance
    • + *
    + *

    + * Example: + * {@snippet : + * new $class<>($fd, $b1) + * } + */ + String javaTemplate(); +} diff --git a/annotations/src/main/java/me/bechberger/ebpf/annotations/bpf/BPFMapDefinition.java b/annotations/src/main/java/me/bechberger/ebpf/annotations/bpf/BPFMapDefinition.java new file mode 100644 index 0000000..cb162d9 --- /dev/null +++ b/annotations/src/main/java/me/bechberger/ebpf/annotations/bpf/BPFMapDefinition.java @@ -0,0 +1,30 @@ +package me.bechberger.ebpf.annotations.bpf; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Declare maps for the eBPF program by annotating non-final, non-private instance fields of a type + * annotated with {@link BPFMapClass} with this annotation. The generic type parameters have to adhere + * to the same constraints as the members of {@link Type} annotated records. + *

    + * Example: + * {@snippet : + * @BPFMapDefinition(maxEntries = 1024) + * private BPFHashMap map; + * } + * this defines a hash map with 1024 entries. + */ +@Target(ElementType.TYPE_USE) +@Retention(RetentionPolicy.CLASS) +public @interface BPFMapDefinition { + /** + * Maximum number of entries in the map. + *

    + * For ring buffers, this is the number of bytes in the buffer which should be a multiple of the kernel page size + * (usually 4096), has to be larger than zero + */ + int maxEntries(); +} diff --git a/bin/install.sh b/bin/install.sh index 796bff2..13a503e 100755 --- a/bin/install.sh +++ b/bin/install.sh @@ -1,5 +1,5 @@ #!/bin/bash -sed "/#\$nrconf{restart} = 'i';/s/.*/\$nrconf{restart} = 'a';/" /etc/needrestart/needrestart.conf +sed "/#\$nrconf{restart} = 'i';/s/.*/\$nrconf{restart} = 'a';/" /etc/needrestart/needrestart.conf > /etc/needrestart/needrestart.conf apt-get update apt-get install -y apt-transport-https ca-certificates curl clang llvm jq maven apt-get install -y libelf-dev libpcap-dev libbfd-dev binutils-dev build-essential make diff --git a/bpf-processor/src/main/java/me/bechberger/cast/CAST.java b/bpf-processor/src/main/java/me/bechberger/cast/CAST.java index cea5b5d..100cbf6 100644 --- a/bpf-processor/src/main/java/me/bechberger/cast/CAST.java +++ b/bpf-processor/src/main/java/me/bechberger/cast/CAST.java @@ -940,6 +940,18 @@ public String toPrettyString(String indent, String increment) { } } + record VerbatimStatement(String code) implements Statement { + @Override + public List children() { + return List.of(); + } + + @Override + public String toPrettyString(String indent, String increment) { + return code.lines().map(l -> indent + l).collect(Collectors.joining("\n")); + } + } + static Statement expression(Expression expression) { return new ExpressionStatement(expression); } @@ -1025,5 +1037,9 @@ static Statement variableDefinition(Declarator type, PrimaryExpression.Variable static Statement variableDefinition(Declarator type, PrimaryExpression.Variable name, Expression value) { return new VariableDefinition(type, name, value); } + + static Statement verbatim(String code) { + return new VerbatimStatement(code); + } } } \ No newline at end of file diff --git a/bpf-processor/src/main/java/me/bechberger/ebpf/bpf/processor/Processor.java b/bpf-processor/src/main/java/me/bechberger/ebpf/bpf/processor/Processor.java index 72586a8..9238945 100644 --- a/bpf-processor/src/main/java/me/bechberger/ebpf/bpf/processor/Processor.java +++ b/bpf-processor/src/main/java/me/bechberger/ebpf/bpf/processor/Processor.java @@ -6,7 +6,6 @@ import com.squareup.javapoet.TypeSpec; import me.bechberger.cast.CAST; import me.bechberger.cast.CAST.Statement.Define; -import me.bechberger.ebpf.annotations.Size; import me.bechberger.ebpf.bpf.processor.TypeProcessor.TypeProcessorResult; import org.jetbrains.annotations.Nullable; @@ -20,7 +19,6 @@ import javax.lang.model.element.TypeElement; import javax.lang.model.element.VariableElement; import javax.lang.model.type.TypeMirror; -import javax.sound.sampled.Line; import javax.tools.Diagnostic; import java.io.ByteArrayOutputStream; import java.io.IOException; @@ -28,14 +26,10 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.util.*; -import java.util.stream.Collectors; +import java.util.function.Consumer; import java.util.stream.IntStream; -import java.util.stream.Stream; import java.util.zip.GZIPOutputStream; -import static me.bechberger.cast.CAST.Expression.constant; -import static me.bechberger.cast.CAST.Statement.define; - /** * Annotation processor that processes classes annotated with {@code @BPF}. *

    @@ -87,7 +81,7 @@ public void processBPFProgram(TypeElement typeElement) { if (combinedCode == null) { return; } - byte[] bytes = compile(combinedCode.ebpfProgram(), combinedCode.codeField()); + byte[] bytes = compile(combinedCode); if (bytes == null) { return; } @@ -99,7 +93,7 @@ public void processBPFProgram(TypeElement typeElement) { String name = typeElement.getSimpleName().toString() + "Impl"; TypeSpec typeSpec = createType(typeElement.getSimpleName() + "Impl", typeElement.asType(), bytes, - typeProcessorResult.fields(), combinedCode.ebpfProgram()); + typeProcessorResult.fields(), combinedCode); try { var file = processingEnv.getFiler().createSourceFile(pkg + "." + name, typeElement); // delete file if it exists @@ -144,24 +138,34 @@ private static String gzipBase64Encode(byte[] byteCode) { * @return the generated class */ private TypeSpec createType(String name, TypeMirror baseType, byte[] byteCode, List bpfTypeFields, - String fullCCode) { + CombinedCode code) { var spec = TypeSpec.classBuilder(name).superclass(baseType).addModifiers(Modifier.PUBLIC, Modifier.FINAL) .addField(FieldSpec.builder(String.class, "BYTE_CODE", Modifier.PRIVATE, Modifier.STATIC, Modifier.FINAL) - .addJavadoc("Base64 encoded and gzipped eBPF byte-code of the program\n{@snippet : \n" + fullCCode + "\n}") - .initializer("$S", gzipBase64Encode(byteCode)).build()) - .addMethod(MethodSpec.methodBuilder("getByteCode") - .addAnnotation(Override.class).addModifiers(Modifier.PUBLIC).returns(byte[].class) - .addStatement("return me.bechberger.ebpf.bpf.Util.decodeGzippedBase64(BYTE_CODE)").build()); + .addJavadoc("Base64 encoded and gzipped eBPF byte-code of the program\n{@snippet : \n" + code.ebpfProgram + "\n}") + .initializer("$S", gzipBase64Encode(byteCode)).build()); + // insert the spec fields bpfTypeFields.forEach(spec::addField); + spec.addMethod(MethodSpec.methodBuilder("getByteCode") + .addAnnotation(Override.class).addModifiers(Modifier.PUBLIC).returns(byte[].class) + .addStatement("return me.bechberger.ebpf.bpf.Util.decodeGzippedBase64(BYTE_CODE)").build()); + // implement the constructor and set the map fields + var constructor = MethodSpec.constructorBuilder().addModifiers(Modifier.PUBLIC); + code.tp.mapDefinitions().forEach(m -> constructor.addStatement("$L", m.javaFieldInitializer())); + spec.addMethod(constructor.build()); return spec.build(); } - record CombinedCode(String ebpfProgram, VariableElement codeField) { + /** + * The combined ebpf program code + * @param ebpfProgram base ebpf program for the EBPF_PROGRAM variable + * @param codeField field that contains the EBPF_PROGRAM + * @param codeLineMapping line number in generated -> original line number + */ + record CombinedCode(String ebpfProgram, VariableElement codeField, Map codeLineMapping, TypeProcessorResult tp) { } private @Nullable CombinedCode combineEBPFProgram(TypeElement typeElement, TypeProcessorResult tpResult) { - var codeToInsert = tpResult.toCCodeWithoutDefines(); Optional elem = typeElement.getEnclosedElements().stream().filter(e -> e.getKind().isField() && e.getSimpleName().toString().equals("EBPF_PROGRAM")).findFirst(); // check that the class has a static field EBPF_PROGRAM of type String or Path @@ -202,39 +206,71 @@ record CombinedCode(String ebpfProgram, VariableElement codeField) { } } this.processingEnv.getMessager().printNote("EBPF Program: " + ebpfProgram, typeElement); - return new CombinedCode(placeDefinitionsIntoEBPFProgram(typeElement, ebpfProgram, tpResult.defines(), codeToInsert, tpResult.licenseDefinition()), element); + return combineEBPFProgram(typeElement, element, ebpfProgram, tpResult); } - private String placeDefinitionsIntoEBPFProgram(TypeElement outer, String ebpfProgram, List defines, String codeToInsert, @Nullable CAST.Statement licenseDefinition) { - // insert the code to insert into the ebpf program - // if the insertion fails, print an error - // if the insertion succeeds, return the new ebpf program + /** Combines the code */ + private @Nullable CombinedCode combineEBPFProgram(TypeElement outer, VariableElement field, String ebpfProgram, TypeProcessorResult tpResult) { + Map codeLineMapping = new HashMap<>(); // line number in generated -> original line number + var lines = ebpfProgram.lines().toList(); var lastInclude = IntStream.range(0, lines.size()).filter(i -> lines.get(i).startsWith("#include")).max().orElse(-1); if (lastInclude == -1) { - return codeToInsert + "\n" + ebpfProgram; + this.processingEnv.getMessager().printError("No includes found in EBPF program", field); + return null; } - var filteredDefines = defines.stream().filter(d -> { + var resultLines = new ArrayList<>(lines.subList(0, lastInclude + 1)); + IntStream.range(0, lastInclude + 1).forEach(i -> codeLineMapping.put(i, i)); + + resultLines.add(""); + + Consumer addLine = l -> resultLines.addAll(l.lines().toList()); + + var filteredDefines = tpResult.defines().stream().filter(d -> { var tester = "#define " + d.name() + " "; return lines.stream().noneMatch(l -> l.startsWith(tester)); }).toList(); + filteredDefines.stream().map(Define::toPrettyString).forEach(addLine); + + resultLines.add(""); + var license = lines.stream().filter(l -> l.matches(".*SEC *\\(\"license\"\\).*")).findFirst().orElse(null); - if (licenseDefinition == null) { + if (tpResult.licenseDefinition() == null) { if (license == null) { - throw new RuntimeException("No license defined"); + this.processingEnv.getMessager().printError("No license defined", field); + return null; } } else { if (license != null) { - throw new RuntimeException("License defined twice"); + this.processingEnv.getMessager().printError("License defined in EBPF program and via annotation", field); + return null; } } - String includes = lines.subList(0, lastInclude + 1).stream().map(l -> l.startsWith("#") ? l.trim() : l).collect(Collectors.joining("\n")); - String definitions = filteredDefines.stream().map(Define::toPrettyString).collect(Collectors.joining("\n")); - String afterIncludes = String.join("\n", lines.subList(lastInclude + 1, lines.size())); - String end = licenseDefinition == null ? "" : "\n\n" + licenseDefinition.toStatement().toPrettyString(); - // TODO: record line number remapping - return Stream.of(includes, definitions, codeToInsert, afterIncludes, end).filter(s -> !s.isBlank()).map(String::trim).collect(Collectors.joining("\n\n")); + + // we already inserted the includes and the defines + // now we insert the struct definitions + tpResult.definingStatements().stream().map(CAST::toPrettyString).forEach(l -> { + addLine.accept(l); + resultLines.add(""); + }); + + // and the defined maps + tpResult.mapDefinitions().stream().map(m -> m.structDefinition().toPrettyString()).forEach(l -> { + addLine.accept(l); + resultLines.add(""); + }); + + // now + var afterIncludes = lines.subList(lastInclude + 1, lines.size()); + for (int i = 0; i < afterIncludes.size(); i++) { + codeLineMapping.put(resultLines.size() + i, lastInclude + 1 + i); + } + resultLines.addAll(afterIncludes); + if (license == null) { + addLine.accept(tpResult.licenseDefinition().toStatement().toPrettyString()); + } + return new CombinedCode(String.join("\n", resultLines), field, codeLineMapping, tpResult); } private static String findNewestClangVersion() { @@ -252,13 +288,13 @@ private static String findNewestClangVersion() { throw new RuntimeException("Could not find clang"); } - private static String newestClang = findNewestClangVersion(); + private static final String newestClang = findNewestClangVersion(); - private byte[] compile(String ebpfProgram, VariableElement element) { + private byte[] compile(CombinedCode code) { if (dontCompile()) { System.out.println("EBPF program to compile:"); System.out.println("-".repeat(10)); - System.out.println(ebpfProgram); + System.out.println(code.ebpfProgram); return new byte[]{0}; } // obtain the path to the vmlinux.h header file @@ -274,7 +310,7 @@ private byte[] compile(String ebpfProgram, VariableElement element) { tempFile.toFile().deleteOnExit(); var process = new ProcessBuilder(newestClang, "-O2", "-g", "-target", "bpf", "-c", "-o", tempFile.toString(), "-I", vmlinuxHeader.getParent().toString(), "-x", "c", "-", "--sysroot=/").redirectInput(ProcessBuilder.Redirect.PIPE).redirectError(ProcessBuilder.Redirect.PIPE).start(); - process.getOutputStream().write(ebpfProgram.getBytes()); + process.getOutputStream().write(code.ebpfProgram.getBytes()); process.getOutputStream().close(); ByteArrayOutputStream error = new ByteArrayOutputStream(); process.getErrorStream().transferTo(error); @@ -282,9 +318,9 @@ private byte[] compile(String ebpfProgram, VariableElement element) { System.err.println("Could not compile eBPF program"); String errorString = error.toString(); - this.processingEnv.getMessager().printError("Could not compile eBPF program", element); - this.processingEnv.getMessager().printError(errorString, element); - printErrorMessages(ebpfProgram, errorString, element); + this.processingEnv.getMessager().printError("Could not compile eBPF program", code.codeField); + this.processingEnv.getMessager().printError(errorString, code.codeField); + printErrorMessages(code, errorString); throw new RuntimeException("Could not compile eBPF program"); } return Files.readAllBytes(tempFile); @@ -293,9 +329,9 @@ private byte[] compile(String ebpfProgram, VariableElement element) { } } - private void printErrorMessages(String ebpfProgram, String errorString, VariableElement element) { - Path file = Paths.get(this.processingEnv.getElementUtils().getFileObjectOf(element).getName()); - Map lineMap = getLineMap(ebpfProgram, element); + private void printErrorMessages(CombinedCode code, String errorString) { + Path file = Paths.get(this.processingEnv.getElementUtils().getFileObjectOf(code.codeField).getName()); + Map lineMap = getLineMap(code); for (String line : errorString.split("\n")) { // example ":5:58: error: expected ';' after expression" if (line.startsWith(":")) { @@ -306,7 +342,7 @@ private void printErrorMessages(String ebpfProgram, String errorString, Variable Line l = lineMap.get(lineNumber); if (l != null) { // format [ERROR] filename:[line,column] message - System.err.println(file.getFileName() + ":[" + l.line + "," + (l.start + column) + "] " + message); + System.err.println(Paths.get(".").relativize(file) + ":[" + l.line + "," + (l.start + column) + "] " + message); } else { System.err.println(line); } @@ -334,20 +370,23 @@ private List suggestionsForMessage(String message) { private record Line(int line, int start) { } - private Map getLineMap(String ebpfProgram, VariableElement element) { - Path file = Paths.get(this.processingEnv.getElementUtils().getFileObjectOf(element).getName()); - List linesInEBPFProgram = ebpfProgram.lines().toList(); + private Map getLineMap(CombinedCode code) { + Path file = Paths.get(this.processingEnv.getElementUtils().getFileObjectOf(code.codeField).getName()); + List linesInEBPFProgram = code.ebpfProgram.lines().toList(); List linesInFile; try { linesInFile = Files.readAllLines(file); } catch (IOException e) { - this.processingEnv.getMessager().printError("Could not read file " + file, element); + this.processingEnv.getMessager().printError("Could not read file " + file, code.codeField); return Map.of(); } // find line that (excluding whitespace) matches the start of the line in the ebpf program Map lineMap = new HashMap<>(); int line = 0; for (int i = 0; i < linesInEBPFProgram.size(); i++) { + if (!code.codeLineMapping.containsKey(i)) { + continue; + } String lineInEBPFProgram = linesInEBPFProgram.get(i); String lineInEBPFProgramTrimmed = lineInEBPFProgram.strip().replace("\\", ""); while (line < linesInFile.size()) { diff --git a/bpf-processor/src/main/java/me/bechberger/ebpf/bpf/processor/TypeProcessor.java b/bpf-processor/src/main/java/me/bechberger/ebpf/bpf/processor/TypeProcessor.java index 5509435..8f36bec 100644 --- a/bpf-processor/src/main/java/me/bechberger/ebpf/bpf/processor/TypeProcessor.java +++ b/bpf-processor/src/main/java/me/bechberger/ebpf/bpf/processor/TypeProcessor.java @@ -2,18 +2,15 @@ import com.squareup.javapoet.FieldSpec; import me.bechberger.cast.CAST; -import me.bechberger.cast.CAST.Declarator; import me.bechberger.cast.CAST.PrimaryExpression.CAnnotation; -import me.bechberger.cast.CAST.Statement; -import me.bechberger.cast.CAST.Statement.Define; import me.bechberger.ebpf.type.BPFType; +import me.bechberger.ebpf.type.BPFType.AnnotatedClass; import org.jetbrains.annotations.Nullable; import javax.annotation.processing.ProcessingEnvironment; import javax.lang.model.AnnotatedConstruct; import javax.lang.model.element.*; -import javax.lang.model.type.PrimitiveType; -import javax.lang.model.type.TypeMirror; +import javax.lang.model.type.*; import java.util.*; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Function; @@ -26,13 +23,16 @@ /** * Handles {@code @Type} annotated records */ -public class TypeProcessor { +class TypeProcessor { public final static String SIZE_ANNOTATION = "me.bechberger.ebpf.annotations.Size"; public final static String UNSIGNED_ANNOTATION = "me.bechberger.ebpf.annotations.Unsigned"; public final static String TYPE_ANNOTATION = "me.bechberger.ebpf.annotations.bpf.Type"; public final static String BPF_PACKAGE = "me.bechberger.ebpf.type"; public final static String BPF_TYPE = "me.bechberger.ebpf.type.BPFType"; + public final static String BPF_MAP_DEFINITION = "me.bechberger.ebpf.annotations.bpf.BPFMapDefinition"; + public final static String BPF_MAP_CLASS = "me.bechberger.ebpf.annotations.bpf.BPFMapClass"; + /** * Helper class to keep track of defined types */ @@ -40,16 +40,18 @@ private class DefinedTypes { private final Map typeToFieldName; private final Map nameToSpecFieldName; - + private final Map specFieldNameToName; private final Map nameToTypeElement; DefinedTypes(Map typeToFieldName) { this.typeToFieldName = typeToFieldName; this.nameToSpecFieldName = new HashMap<>(); + this.specFieldNameToName = new HashMap<>(); this.nameToTypeElement = new HashMap<>(); this.typeToFieldName.forEach((k, v) -> { var name = getTypeRecordBpfName(k); this.nameToSpecFieldName.put(name, v); + this.specFieldNameToName.put(v, name); this.nameToTypeElement.put(name, k); }); } @@ -78,6 +80,10 @@ public Optional getTypeElement(String name) { public String toString() { return this.typeToFieldName.toString(); } + + public String specFieldNameToName(String field) { + return this.specFieldNameToName.get(field); + } } private final ProcessingEnvironment processingEnv; @@ -126,7 +132,7 @@ record Pair(Optional a, Element e) { } record TypeProcessorResult(List fields, List defines, List definingStatements, - @Nullable Statement licenseDefinition) { + @Nullable Statement licenseDefinition, List mapDefinitions) { String toCCodeWithoutDefines() { return definingStatements.stream().map(CAST.Statement::toPrettyString).collect(Collectors.joining("\n")); @@ -153,6 +159,7 @@ TypeProcessorResult processBPFTypeRecords(TypeElement outerTypeElement) { List processedTypes = new ArrayList<>(); + // bpf name to type AtomicReference>> obtainType = new AtomicReference<>(); obtainType.set(name -> { if (alreadyDefinedTypes.containsKey(name)) { @@ -183,14 +190,18 @@ TypeProcessorResult processBPFTypeRecords(TypeElement outerTypeElement) { while (processedTypes.size() < innerTypeElements.size()) { var unprocessed = innerTypeElements.stream().filter(e -> !processedTypes.contains(e)).toList(); - var type = processBPFTypeRecord(unprocessed.get(0), obtainType.get()); + var type = processBPFTypeRecord(unprocessed.getFirst(), obtainType.get()); if (type.isEmpty()) { - return new TypeProcessorResult(List.of(), List.of(), List.of(), null); + return new TypeProcessorResult(List.of(), List.of(), List.of(), null, List.of()); } alreadyDefinedTypes.put(type.get().bpfName(), type.get()); - processedTypes.add(unprocessed.get(0)); + processedTypes.add(unprocessed.getFirst()); } + var mapDefinitions = processDefinedMaps(outerTypeElement, + field -> obtainType.get().apply(definedTypes.specFieldNameToName(field)), + type -> definedTypes.getSpecFieldName(type.bpfName()).get()); + List fields = new ArrayList<>(); List definingStatements = new ArrayList<>(); @@ -198,14 +209,14 @@ TypeProcessorResult processBPFTypeRecords(TypeElement outerTypeElement) { var name = getTypeRecordBpfName(processedType); var type = alreadyDefinedTypes.get(getTypeRecordBpfName(processedType)); var spec = type.toFieldSpecGenerator().get().apply(definedTypes.getSpecFieldName(name).get(), - typeToSpecField); + t -> t.toJavaFieldSpecUse(typeToSpecField)); fields.add(spec); if (shouldGenerateCCode(processedType)) { type.toCDeclarationStatement().ifPresent(definingStatements::add); } } return new TypeProcessorResult(fields, createDefineStatements(outerTypeElement), definingStatements, - getLicenseDefinitionStatement(outerTypeElement)); + getLicenseDefinitionStatement(outerTypeElement), mapDefinitions); } private @Nullable Statement getLicenseDefinitionStatement(TypeElement outerTypeElement) { @@ -299,10 +310,14 @@ record AnnotationValues(boolean unsigned, Optional size) { } private AnnotationValues getAnnotationValuesForRecordMember(VariableElement element) { - boolean unsigned = hasAnnotation(element.asType(), UNSIGNED_ANNOTATION); + return getAnnotationValuesForRecordMember(element.asType()); + } + + private AnnotationValues getAnnotationValuesForRecordMember(AnnotatedConstruct element) { + boolean unsigned = hasAnnotation(element, UNSIGNED_ANNOTATION); Optional size = Optional.empty(); Optional bpfType = Optional.empty(); - var sizeAnnotation = getAnnotationMirror(element.asType(), SIZE_ANNOTATION); + var sizeAnnotation = getAnnotationMirror(element, SIZE_ANNOTATION); if (sizeAnnotation.isPresent()) { var value = sizeAnnotation.get().getElementValues().entrySet().stream().findFirst(); if (value.isPresent()) { @@ -317,10 +332,8 @@ private AnnotationValues getAnnotationValuesForRecordMember(VariableElement elem AnnotationValues annotations = getAnnotationValuesForRecordMember(element); TypeMirror type = element.asType(); var bpfType = processBPFTypeRecordMemberType(element, annotations, type); - return bpfType.map(t -> { - return new BPFType.UBPFStructMember<>(element.getSimpleName().toString(), - t.toBPFType(nameToCustomType), null, null); - }); + return bpfType.map(t -> new BPFType.UBPFStructMember<>(element.getSimpleName().toString(), + t.toBPFType(nameToCustomType), null, null)); } private static final Set integerTypes = Set.of("int", "long", "short", "byte", "char"); @@ -329,8 +342,21 @@ private static String lastPart(String s) { return s.substring(s.lastIndexOf(" ") + 1); } + static final Map boxedToUnboxedIntegerType = Map.of( + "java.lang.Integer", "int", + "java.lang.Long", "long", + "java.lang.Short", "short", + "java.lang.Byte", "byte", + "java.lang.Character", "char", + "java.lang.Float", "float", + "java.lang.Double", "double", + "java.lang.Boolean", "boolean" + ); + private boolean isIntegerType(TypeMirror type) { - return type instanceof PrimitiveType p && integerTypes.contains(lastPart(p.toString())); + var typeName = lastPart(type.toString()); + return (type instanceof PrimitiveType p && integerTypes.contains(typeName)) || + boxedToUnboxedIntegerType.containsKey(typeName); } private boolean isStringType(TypeMirror type) { @@ -368,7 +394,10 @@ private Optional> processIntegerType(Element element, AnnotationValue return Optional.empty(); } boolean unsigned = annotations.unsigned; - return switch (lastPart(type.toString())) { + var typeName = lastPart(type.toString()); + var numberName = boxedToUnboxedIntegerType.getOrDefault(typeName, typeName); + return switch (numberName) { + case "boolean" -> Optional.of(BPFType.BPFIntType.BOOL); case "int" -> Optional.of(unsigned ? BPFType.BPFIntType.UINT32 : BPFType.BPFIntType.INT32); case "long" -> Optional.of(unsigned ? BPFType.BPFIntType.UINT64 : BPFType.BPFIntType.INT64); case "short" -> Optional.of(unsigned ? BPFType.BPFIntType.UINT16 : BPFType.BPFIntType.INT16); @@ -437,4 +466,113 @@ private String typeToFieldName(TypeElement typeElement) { private static String toSnakeCase(String name) { return name.replaceAll("([a-z0-9])([A-Z])", "$1_$2"); } + + /** + * Combines the C and the Java code to construct a map + * @param javaFieldInitializer code that initializes a map field in the constructor of the BPFProgram implementation + * @param structDefinition the C struct definition of the map + */ + record MapDefinition(String javaFieldName, String javaFieldInitializer, Statement structDefinition) { + } + + List processDefinedMaps(TypeElement outerElement, Function> fieldToType, + Function, String> typeToSpecFieldName) { + // take all @BPFMapDefinition annotated fields + return outerElement.getEnclosedElements().stream().filter(e -> e.getKind() == ElementKind.FIELD).map(e -> (VariableElement) e) + .filter(e -> getAnnotationMirror(e.asType(), BPF_MAP_DEFINITION).isPresent()) + .map(f -> processMapDefiningField(f, fieldToType, typeToSpecFieldName)).filter(Objects::nonNull).toList(); + } + + @SuppressWarnings({"unchecked", "rawtypes"}) + @Nullable MapDefinition processMapDefiningField(VariableElement field, + Function> fieldToType, + Function, String> typeToSpecFieldName) { + // create a FieldSpec for the field + // create a #define statement for the field + // return the FieldSpec and the #define statement + var annotation = getAnnotationMirror(field.asType(), BPF_MAP_DEFINITION); + assert annotation.isPresent(); + var maxEntries = getAnnotationValue(annotation.get(), "maxEntries", 0); + if (maxEntries == 0) { + this.processingEnv.getMessager().printError("maxEntries must be set and larger than 0", field); + } + + var type = field.asType(); + if (!(type instanceof DeclaredType declaredType)) { + this.processingEnv.getMessager().printError("Field must be a declared type", field); + return null; + } + // get generic type members + var typeParams = declaredType.getTypeArguments().stream() + .map(t -> processBPFTypeRecordMemberType(field, getAnnotationValuesForRecordMember(t), t) + .map(m -> m.toBPFType(fieldToType))).toList(); + if (typeParams.stream().anyMatch(Optional::isEmpty)) { + this.processingEnv.getMessager().printError("Type parameters must be valid", field); + return null; + } + List> typeParameters = (List>) (List) typeParams.stream().map(Optional::get).toList(); + + // now we just have to get the annotation of the fields map type + + var mapType = processingEnv.getTypeUtils().asElement(type); + + var mapClassAnnotation = getAnnotationMirror(processingEnv.getTypeUtils().asElement(type), BPF_MAP_CLASS); + if (mapClassAnnotation.isEmpty()) { + this.processingEnv.getMessager().printError("Only BPFMapClass annotated classes can be used for map definitions, " + + "please annotate " + mapType + " directly", field); + return null; + } + + var cTemplate = getAnnotationValue(mapClassAnnotation.get(), "cTemplate", ""); + if (cTemplate.isEmpty()) { + this.processingEnv.getMessager().printError("cTemplate must be set for class", mapType); + return null; + } + var javaTemplate = getAnnotationValue(mapClassAnnotation.get(), "javaTemplate", ""); + if (javaTemplate.isEmpty()) { + this.processingEnv.getMessager().printError("javaTemplate must be set for class", mapType); + return null; + } + var fieldName = field.getSimpleName().toString(); + var className = mapType.toString(); + + return new MapDefinition(field.getSimpleName().toString(), + processBPFClassJavaTemplate(field, javaTemplate, typeParameters, maxEntries, fieldName, className, typeToSpecFieldName), + processBPFClassCTemplate(field, cTemplate, typeParameters, maxEntries, fieldName, className, typeToSpecFieldName)); + } + + String processBPFClassJavaTemplate(VariableElement field, String javaTemplate, + List> typeParams, Integer maxEntries, + String fieldName, String className, + Function, String> typeToSpecFieldName) { + return "this." + field.getSimpleName() + " = " + processBPFClassTemplate(javaTemplate, typeParams, + maxEntries, fieldName, className, typeToSpecFieldName).strip(); + } + + + + Statement processBPFClassCTemplate(VariableElement field, String cTemplate, List> typeParameters, + Integer maxEntries, String fieldName, String className, + Function, String> typeToSpecFieldName) { + String raw = processBPFClassTemplate(cTemplate, typeParameters, + maxEntries, fieldName, className, typeToSpecFieldName); + return new VerbatimStatement(raw); + } + + String processBPFClassTemplate(String template, List> typeParams, int maxEntries, String fieldName, + String className, Function, String> typeToSpecFieldName) { + var classNames = typeParams.stream().map(BPFType::javaClass).map(AnnotatedClass::toString).toList(); + var cTypeNames = typeParams.stream().map(BPFType::bpfName).toList(); + var bFields = typeParams.stream().map(t -> t.toJavaFieldSpecUse(typeToSpecFieldName)).toList(); + String res = template; + for (int i = typeParams.size(); i > 0; i--) { + res = res.replace("$c" + i, cTypeNames.get(i - 1)) + .replace("$j" + i, classNames.get(i - 1)) + .replace("$b" + i, bFields.get(i - 1)); + } + return res.replace("$maxEntries", Integer.toString(maxEntries)) + .replace("$field", fieldName) + .replace("$class", className) + .replace("$fd", "getMapDescriptorByName(" + CAST.toStringLiteral(fieldName) + ")"); + } } \ No newline at end of file diff --git a/bpf-processor/src/main/java/me/bechberger/ebpf/type/BPFType.java b/bpf-processor/src/main/java/me/bechberger/ebpf/type/BPFType.java index 48bece7..7eb7700 100644 --- a/bpf-processor/src/main/java/me/bechberger/ebpf/type/BPFType.java +++ b/bpf-processor/src/main/java/me/bechberger/ebpf/type/BPFType.java @@ -22,8 +22,6 @@ import static me.bechberger.cast.CAST.Declarator.identifier; import static me.bechberger.cast.CAST.Expression.variable; -import static me.bechberger.ebpf.bpf.processor.TypeProcessor.BPF_PACKAGE; -import static me.bechberger.ebpf.bpf.processor.TypeProcessor.BPF_TYPE; import static me.bechberger.ebpf.type.BPFType.BPFIntType.CHAR; @@ -33,6 +31,9 @@ */ public sealed interface BPFType { + static String BPF_PACKAGE = "me.bechberger.ebpf.type"; + static String BPF_TYPE = BPF_PACKAGE + ".BPFType"; + /** * Java class with annotations */ @@ -41,6 +42,14 @@ record AnnotatedClass(String klass, List annotations) { public AnnotatedClass(Class klass, List annotations) { this(klass.getName(), annotations); } + + @Override + public String toString() { + if (annotations.isEmpty()) { + return klass; + } + return annotations.stream().map(Annotation::toString).collect(Collectors.joining(" ")) + " " + klass; + } } /** @@ -198,7 +207,7 @@ public String toJavaUseInGenerics() { @Override public String toJavaFieldSpecUse(Function, String> typeToSpecFieldName) { - return getClass().getSimpleName() + "." + Objects.requireNonNull(typeToSpecName.get(this)); + return getClass().getCanonicalName() + "." + Objects.requireNonNull(typeToSpecName.get(this)); } private static final Map> registeredTypes = new HashMap<>(); @@ -529,7 +538,7 @@ public CAST.Declarator toCUse() { @Override public Optional, String>, FieldSpec>> toFieldSpecGenerator() { - return Optional.of((fieldName, typeToSpecName)-> { + return Optional.of((fieldName, typeToSpecName) -> { String className = this.javaClass.klass; ClassName bpfStructType = ClassName.get(BPF_PACKAGE, "BPFType.BPFStructType"); TypeName fieldType = ParameterizedTypeName.get(bpfStructType, ClassName.get("", className)); diff --git a/bpf/src/main/java/me/bechberger/ebpf/bpf/BPFProgram.java b/bpf/src/main/java/me/bechberger/ebpf/bpf/BPFProgram.java index 49611a5..60ae4fa 100644 --- a/bpf/src/main/java/me/bechberger/ebpf/bpf/BPFProgram.java +++ b/bpf/src/main/java/me/bechberger/ebpf/bpf/BPFProgram.java @@ -281,7 +281,7 @@ public BPFMapNotFoundError(String name) { * @return the map descriptor * @throws BPFMapNotFoundError if the map cannot be found */ - private FileDescriptor getMapDescriptorByName(String name) { + public FileDescriptor getMapDescriptorByName(String name) { try (Arena arena = Arena.ofConfined()) { MemorySegment map = Lib.bpf_object__find_map_by_name(this.ebpf_object, arena.allocateFrom(name)); if (map == MemorySegment.NULL || map.address() == 0) { diff --git a/bpf/src/main/java/me/bechberger/ebpf/bpf/map/BPFHashMap.java b/bpf/src/main/java/me/bechberger/ebpf/bpf/map/BPFHashMap.java index 5566f76..fca2e30 100644 --- a/bpf/src/main/java/me/bechberger/ebpf/bpf/map/BPFHashMap.java +++ b/bpf/src/main/java/me/bechberger/ebpf/bpf/map/BPFHashMap.java @@ -1,5 +1,6 @@ package me.bechberger.ebpf.bpf.map; +import me.bechberger.ebpf.annotations.bpf.BPFMapClass; import me.bechberger.ebpf.type.BPFType; /** @@ -11,6 +12,18 @@ * @param key type * @param value type */ +@BPFMapClass( + cTemplate = """ + struct { + __uint (type, BPF_MAP_TYPE_HASH); + __uint (key_size, sizeof($c1)); + __uint (value_size, sizeof($c2)); + __uint (max_entries, $maxEntries); + } $field SEC(".maps"); + """, + javaTemplate = """ + new $class<>($fd, $b1, $b2) + """) public class BPFHashMap extends BPFBaseMap { /** diff --git a/bpf/src/main/java/me/bechberger/ebpf/bpf/map/BPFRingBuffer.java b/bpf/src/main/java/me/bechberger/ebpf/bpf/map/BPFRingBuffer.java index d5eabde..7ca35fb 100644 --- a/bpf/src/main/java/me/bechberger/ebpf/bpf/map/BPFRingBuffer.java +++ b/bpf/src/main/java/me/bechberger/ebpf/bpf/map/BPFRingBuffer.java @@ -1,5 +1,6 @@ package me.bechberger.ebpf.bpf.map; +import me.bechberger.ebpf.annotations.bpf.BPFMapClass; import me.bechberger.ebpf.bpf.BPFError; import me.bechberger.ebpf.bpf.raw.Lib; import me.bechberger.ebpf.bpf.raw.ring_buffer_sample_fn; @@ -21,6 +22,16 @@ * * @param type of the event */ +@BPFMapClass( + cTemplate = """ + struct { + __uint (type, BPF_MAP_TYPE_RINGBUF); + __uint (max_entries, $maxEntries); + } $field SEC(".maps"); + """, + javaTemplate = """ + new $class<>($fd, $b1) + """) public class BPFRingBuffer extends BPFMap { /** @@ -58,6 +69,8 @@ public interface EventCallback { */ private final MemorySegment rb; + private EventCallback callback; + /** * Error caught while calling the callback */ @@ -93,9 +106,32 @@ public BPFRingBuffer(FileDescriptor fd, BPFType eventType, EventCallback c super(MapTypeId.RINGBUF, fd); this.ringArena = Arena.ofConfined(); this.eventType = eventType; + this.callback = callback; this.rb = initRingBuffer(fd, eventType, callback); } + public BPFRingBuffer(FileDescriptor fd, BPFType eventType) { + super(MapTypeId.RINGBUF, fd); + this.ringArena = Arena.ofConfined(); + this.eventType = eventType; + this.rb = initRingBuffer(fd, eventType, (buffer, event) -> { + if (callback != null) { + callback.call(buffer, event); + } + }); + } + + /** + * Sets the callback if it is not already set, + * use in combination with {@link BPFRingBuffer#BPFRingBuffer(FileDescriptor, BPFType)} + */ + public void setCallback(EventCallback callback) { + if (this.callback != null) { + throw new IllegalStateException("Callback already set"); + } + this.callback = callback; + } + private static final HandlerWithErrno RING_BUFFER_NEW = new HandlerWithErrno<>("ring_buffer__new", FunctionDescriptor.of(POINTER, JAVA_INT, POINTER, POINTER, POINTER)); diff --git a/bpf/src/main/java/me/bechberger/ebpf/samples/TypeProcessingSample2.java b/bpf/src/main/java/me/bechberger/ebpf/samples/TypeProcessingSample2.java index 7ca9442..cd541de 100644 --- a/bpf/src/main/java/me/bechberger/ebpf/samples/TypeProcessingSample2.java +++ b/bpf/src/main/java/me/bechberger/ebpf/samples/TypeProcessingSample2.java @@ -3,9 +3,11 @@ import me.bechberger.ebpf.annotations.Size; import me.bechberger.ebpf.annotations.Unsigned; import me.bechberger.ebpf.annotations.bpf.BPF; +import me.bechberger.ebpf.annotations.bpf.BPFMapDefinition; import me.bechberger.ebpf.annotations.bpf.Type; import me.bechberger.ebpf.bpf.BPFProgram; - +import me.bechberger.ebpf.bpf.map.BPFHashMap; +import me.bechberger.ebpf.bpf.map.BPFRingBuffer; /** * Adaption of {@link TypeProcessingSample} that shows how code is autogenerated */ @@ -18,19 +20,16 @@ public abstract class TypeProcessingSample2 extends BPFProgram { @Type(name = "event") record Event(@Unsigned int pid, @Size(FILE_NAME_LEN) String filename, @Size(TASK_COMM_LEN) String comm) {} + @BPFMapDefinition(maxEntries = 256 * 4096) + BPFRingBuffer rb; + + static final String EBPF_PROGRAM = """ #include "vmlinux.h" #include #include #include - // eBPF map reference - struct - { - __uint (type, BPF_MAP_TYPE_RINGBUF); - __uint (max_entries, 256 * 4096); - } rb SEC (".maps"); - // The ebpf auto-attach logic needs the SEC SEC ("kprobe/do_sys_openat2") int kprobe__do_sys_openat2 (struct pt_regs *ctx) @@ -47,28 +46,20 @@ int kprobe__do_sys_openat2 (struct pt_regs *ctx) return 0; } // Get the PID of the process. - evt->e_pid = bpf_get_current_pid_tgid (); // Get current process PID + evt->pid = bpf_get_current_pid_tgid (); // Get current process PID // Read the filename from the second argument // The x86 arch/ABI have first argument in di and second in si registers (man syscall) - bpf_probe_read (evt->e_filename, sizeof (filename), (char *) ctx->si); + bpf_probe_read (evt->filename, sizeof (filename), (char *) ctx->si); // Read the current process name - bpf_get_current_comm (evt->e_comm, sizeof (comm)); + bpf_get_current_comm (evt->comm, sizeof (comm)); - // Compare process name with our "sample_write" name - // -- parttimenerd: we don't need this in our example - //if (memcmp (evt->e_comm, TARGET_NAME, 12) == 0) - // { - // Print a message with filename, process name, and PID - bpf_trace_printk (fmt_str, sizeof (fmt_str), evt->e_comm, - evt->e_filename, evt->e_pid); - // Also send the same message to the ring-buffer - bpf_ringbuf_submit (evt, 0); - // return 0; - // } - // If the program name is not matching with TARGET_NAME, then discard the data - //bpf_ringbuf_discard (evt, 0); + // Print a message with filename, process name, and PID + bpf_trace_printk (fmt_str, sizeof (fmt_str), evt->comm, + evt->filename, evt->pid); + // Also send the same message to the ring-buffer + bpf_ringbuf_submit (evt, 0); return 0; } """; @@ -77,11 +68,11 @@ public static void main(String[] args) { try (TypeProcessingSample2 program = BPFProgram.load(TypeProcessingSample2.class)) { program.autoAttachProgram(program.getProgramByName("kprobe__do_sys_openat2")); var eventType = program.getTypeForClass(Event.class); - var ringBuffer = program.getRingBufferByName("rb", eventType, (buffer, event) -> { + program.rb.setCallback((buffer, event) -> { System.out.printf("do_sys_openat2 called by:%s file:%s pid:%d\n", event.comm(), event.filename(), event.pid()); }); while (true) { - ringBuffer.consumeAndThrow(); + program.rb.consumeAndThrow(); } } } From 3e009f1b74982f5a1d14e7430cc45a67042b0e81 Mon Sep 17 00:00:00 2001 From: Johannes Bechberger Date: Thu, 4 Apr 2024 16:29:32 +0300 Subject: [PATCH 07/11] Fix on Mac OS M1 --- README.md | 3 ++- bcc/pom.xml | 4 ++++ bin/install.sh | 18 +++++++++++------- .../ebpf/bpf/processor/Processor.java | 14 +++++++++++++- .../me/bechberger/ebpf/samples/RingSample.java | 2 +- .../ebpf/samples/TypeProcessingSample.java | 2 +- .../ebpf/samples/TypeProcessingSample2.java | 2 +- .../me/bechberger/ebpf/bpf/RingBufferTest.java | 2 +- hello-ebpf.yaml | 8 +++++--- 9 files changed, 39 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index a570427..bd6fc02 100644 --- a/README.md +++ b/README.md @@ -63,7 +63,8 @@ Either a Linux machine with the following: On Mac OS, you can use the [Lima VM](https://lima-vm.io/) (or use the `hello-ebpf.yaml` file as a guide to install the prerequisites): ```sh -limactl start hello-ebpf.yaml +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 diff --git a/bcc/pom.xml b/bcc/pom.xml index 97f2e13..3d10a9d 100644 --- a/bcc/pom.xml +++ b/bcc/pom.xml @@ -49,6 +49,10 @@ org.apache.maven.plugins maven-compiler-plugin 3.8.1 + + 22 + 22 + maven-assembly-plugin diff --git a/bin/install.sh b/bin/install.sh index 13a503e..6de945d 100755 --- a/bin/install.sh +++ b/bin/install.sh @@ -1,8 +1,12 @@ #!/bin/bash -sed "/#\$nrconf{restart} = 'i';/s/.*/\$nrconf{restart} = 'a';/" /etc/needrestart/needrestart.conf > /etc/needrestart/needrestart.conf -apt-get update -apt-get install -y apt-transport-https ca-certificates curl clang llvm jq maven -apt-get install -y libelf-dev libpcap-dev libbfd-dev binutils-dev build-essential make -apt-get install -y linux-tools-common linux-tools-$(uname -r) -apt-get install -y bpfcc-tools libbpfcc-dev libbpf-dev -apt-get install -y python3-pip zsh tmux openjdk-22-jre-headless \ No newline at end of file +sudo bash -c "echo \"\\\$nrconf{restart} = 'a'\" >> /etc/needrestart/needrestart.conf" +sudo apt-get install -y apt-transport-https ca-certificates curl clang llvm +sudo apt-get install -y libelf-dev libpcap-dev libbfd-dev binutils-dev build-essential make +sudo apt-get install -y linux-tools-common linux-tools-$(uname -r) +sudo apt-get install -y bpfcc-tools libbpfcc-dev libbpf-dev +sudo apt-get install -y python3-pip zsh tmux +sudo apt-get install -y zip unzip git +curl -s "https://get.sdkman.io" | bash +source "$HOME/.sdkman/bin/sdkman-init.sh" +sdk install java 22-sapmchn +sdk install maven \ No newline at end of file diff --git a/bpf-processor/src/main/java/me/bechberger/ebpf/bpf/processor/Processor.java b/bpf-processor/src/main/java/me/bechberger/ebpf/bpf/processor/Processor.java index 9238945..e389506 100644 --- a/bpf-processor/src/main/java/me/bechberger/ebpf/bpf/processor/Processor.java +++ b/bpf-processor/src/main/java/me/bechberger/ebpf/bpf/processor/Processor.java @@ -19,6 +19,7 @@ import javax.lang.model.element.TypeElement; import javax.lang.model.element.VariableElement; import javax.lang.model.type.TypeMirror; +import javax.sound.sampled.Line; import javax.tools.Diagnostic; import java.io.ByteArrayOutputStream; import java.io.IOException; @@ -289,6 +290,17 @@ private static String findNewestClangVersion() { } private static final String newestClang = findNewestClangVersion(); + private static final Path includePath = findIncludePath(); + + /** Find the library include path */ + private static Path findIncludePath() { + // like /usr/include/aarch64-linux-gnu + var p = Path.of("/usr/include").resolve(System.getProperty("os.arch") + "-linux-gnu"); + if (!Files.exists(p)) { + throw new RuntimeException("Could not find include path " + p); + } + return p; + } private byte[] compile(CombinedCode code) { if (dontCompile()) { @@ -309,7 +321,7 @@ private byte[] compile(CombinedCode code) { var tempFile = Files.createTempFile("ebpf", ".o"); tempFile.toFile().deleteOnExit(); var process = new ProcessBuilder(newestClang, "-O2", "-g", "-target", "bpf", "-c", "-o", - tempFile.toString(), "-I", vmlinuxHeader.getParent().toString(), "-x", "c", "-", "--sysroot=/").redirectInput(ProcessBuilder.Redirect.PIPE).redirectError(ProcessBuilder.Redirect.PIPE).start(); + tempFile.toString(), "-I", vmlinuxHeader.getParent().toString(), "-x", "c", "-", "--sysroot=/", "-I" + includePath).redirectInput(ProcessBuilder.Redirect.PIPE).redirectError(ProcessBuilder.Redirect.PIPE).start(); process.getOutputStream().write(code.ebpfProgram.getBytes()); process.getOutputStream().close(); ByteArrayOutputStream error = new ByteArrayOutputStream(); diff --git a/bpf/src/main/java/me/bechberger/ebpf/samples/RingSample.java b/bpf/src/main/java/me/bechberger/ebpf/samples/RingSample.java index 224302f..2522212 100644 --- a/bpf/src/main/java/me/bechberger/ebpf/samples/RingSample.java +++ b/bpf/src/main/java/me/bechberger/ebpf/samples/RingSample.java @@ -60,7 +60,7 @@ int kprobe__do_sys_openat2 (struct pt_regs *ctx) // Read the filename from the second argument // The x86 arch/ABI have first argument in di and second in si registers (man syscall) - bpf_probe_read (evt->e_filename, sizeof (filename), (char *) ctx->si); + bpf_probe_read (evt->e_filename, sizeof (filename), (char *) ctx->regs[1]); // Read the current process name bpf_get_current_comm (evt->e_comm, sizeof (comm)); diff --git a/bpf/src/main/java/me/bechberger/ebpf/samples/TypeProcessingSample.java b/bpf/src/main/java/me/bechberger/ebpf/samples/TypeProcessingSample.java index 13ae33d..70cd9b9 100644 --- a/bpf/src/main/java/me/bechberger/ebpf/samples/TypeProcessingSample.java +++ b/bpf/src/main/java/me/bechberger/ebpf/samples/TypeProcessingSample.java @@ -56,7 +56,7 @@ int kprobe__do_sys_openat2 (struct pt_regs *ctx) // Read the filename from the second argument // The x86 arch/ABI have first argument in di and second in si registers (man syscall) - bpf_probe_read (evt->e_filename, sizeof (filename), (char *) ctx->si); + bpf_probe_read (evt->e_filename, sizeof (filename), (char *) ctx->regs[1]); // Read the current process name bpf_get_current_comm (evt->e_comm, sizeof (comm)); diff --git a/bpf/src/main/java/me/bechberger/ebpf/samples/TypeProcessingSample2.java b/bpf/src/main/java/me/bechberger/ebpf/samples/TypeProcessingSample2.java index cd541de..09a6657 100644 --- a/bpf/src/main/java/me/bechberger/ebpf/samples/TypeProcessingSample2.java +++ b/bpf/src/main/java/me/bechberger/ebpf/samples/TypeProcessingSample2.java @@ -50,7 +50,7 @@ int kprobe__do_sys_openat2 (struct pt_regs *ctx) // Read the filename from the second argument // The x86 arch/ABI have first argument in di and second in si registers (man syscall) - bpf_probe_read (evt->filename, sizeof (filename), (char *) ctx->si); + bpf_probe_read (evt->filename, sizeof (filename), (char *) ctx->regs[1]); // Read the current process name bpf_get_current_comm (evt->comm, sizeof (comm)); diff --git a/bpf/src/test/java/me/bechberger/ebpf/bpf/RingBufferTest.java b/bpf/src/test/java/me/bechberger/ebpf/bpf/RingBufferTest.java index 8a58aca..481475b 100644 --- a/bpf/src/test/java/me/bechberger/ebpf/bpf/RingBufferTest.java +++ b/bpf/src/test/java/me/bechberger/ebpf/bpf/RingBufferTest.java @@ -79,7 +79,7 @@ int kprobe__do_sys_openat2 (struct pt_regs *ctx) // Read the filename from the second argument // The x86 arch/ABI have first argument in di and second in si registers (man syscall)\s - bpf_probe_read (evt->e_filename, sizeof (filename), (char *) ctx->si); + bpf_probe_read (evt->e_filename, sizeof (filename), (char *) ctx->regs[1]); // Read the current process name bpf_get_current_comm (evt->e_comm, sizeof (comm)); diff --git a/hello-ebpf.yaml b/hello-ebpf.yaml index 68f34c3..524a55f 100644 --- a/hello-ebpf.yaml +++ b/hello-ebpf.yaml @@ -1,10 +1,12 @@ # This example requires Lima v0.8.0 or later # based on https://github.com/lizrice/learning-ebpf/blob/main/learning-ebpf.yaml images: - - location: "https://cloud-images.ubuntu.com/releases/23.10/release/ubuntu-23.10-server-cloudimg-amd64.img" + - location: "https://cloud-images.ubuntu.com/releases/23.10/release-20240307/ubuntu-23.10-server-cloudimg-amd64.img" arch: "x86_64" - - location: "https://cloud-images.ubuntu.com/releases/23.10/release/ubuntu-23.10-server-cloudimg-arm64.img" + digest: "sha256:415123eb3b3ba1841e39a25d0dd82da43f968c7625b9cdf6312235b9b8ec17e9" + - location: "https://cloud-images.ubuntu.com/releases/23.10/release-20240307/ubuntu-23.10-server-cloudimg-arm64.img" arch: "aarch64" + digest: "sha256:373e8866d33909b283b14c86c18f8a48844c8f9fe6aed3ca280288846fc4fb74" cpus: 4 memory: "10GiB" @@ -16,4 +18,4 @@ mounts: writable: true provision: - mode: system - script: ./bin/install.sh \ No newline at end of file + script: bin/install.sh \ No newline at end of file From 5a6f7b69a331af9bd7734e40c22c1973ab7399d8 Mon Sep 17 00:00:00 2001 From: Johannes Bechberger Date: Fri, 5 Apr 2024 17:14:20 +0300 Subject: [PATCH 08/11] Detach programs properly and more fixes --- README.md | 2 +- .../me/bechberger/ebpf/bcc/SymbolCache.java | 2 +- .../bechberger/ebpf/bcc/HelloWorldTest.java | 8 + .../ebpf/bcc/structs/HelloBufferTest.java | 2 + .../ebpf/bpf/processor/Processor.java | 46 +++-- .../ebpf/bpf/processor/TypeProcessor.java | 167 ++++++++++++------ .../me/bechberger/ebpf/bpf/BPFProgram.java | 76 +++++++- .../ebpf/bpf/map/BPFRingBuffer.java | 1 + .../bechberger/ebpf/bpf/HelloWorldTest.java | 22 ++- .../ebpf/bpf/MapGenerationTest.java | 106 +++++++++++ .../bechberger/ebpf/bpf/RingBufferTest.java | 24 ++- .../ebpf/bpf/TypeProcessingTest.java | 23 ++- .../me/bechberger/ebpf/shared/PanamaUtil.java | 10 +- .../me/bechberger/ebpf/shared/TraceLog.java | 10 +- .../ebpf/shared/util/LineReader.java | 21 ++- 15 files changed, 407 insertions(+), 113 deletions(-) create mode 100644 bpf/src/test/java/me/bechberger/ebpf/bpf/MapGenerationTest.java diff --git a/README.md b/README.md index bd6fc02..2fc169b 100644 --- a/README.md +++ b/README.md @@ -68,7 +68,7 @@ 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 +sudo -s PATH=$PATH ``` Build diff --git a/bcc/src/main/java/me/bechberger/ebpf/bcc/SymbolCache.java b/bcc/src/main/java/me/bechberger/ebpf/bcc/SymbolCache.java index 7b7b84b..e125c94 100644 --- a/bcc/src/main/java/me/bechberger/ebpf/bcc/SymbolCache.java +++ b/bcc/src/main/java/me/bechberger/ebpf/bcc/SymbolCache.java @@ -16,7 +16,7 @@ */ public class SymbolCache { - public static record ResolveResult(String symbol, long offset, String module) { + public record ResolveResult(String symbol, long offset, String module) { } private final MemorySegment cache; diff --git a/bcc/src/test/java/me/bechberger/ebpf/bcc/HelloWorldTest.java b/bcc/src/test/java/me/bechberger/ebpf/bcc/HelloWorldTest.java index 2b05e63..a0314ae 100644 --- a/bcc/src/test/java/me/bechberger/ebpf/bcc/HelloWorldTest.java +++ b/bcc/src/test/java/me/bechberger/ebpf/bcc/HelloWorldTest.java @@ -1,5 +1,6 @@ package me.bechberger.ebpf.bcc; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Timeout; @@ -10,6 +11,7 @@ */ public class HelloWorldTest { @Test + @Disabled public void testHelloWorld() throws Exception { try (BPF b = BPF.builder(""" int hello(void *ctx) { @@ -21,6 +23,12 @@ int hello(void *ctx) { b.attach_kprobe(syscall, "hello"); Utils.runCommand("uname", "-r"); var line = b.trace_readline(); + int tries = 0; + while (!line.contains("Hello, World!") && tries < 10) { + System.out.println(line); + line = b.trace_readline(); + tries++; + } assertTrue(line.contains("Hello, World!")); } } diff --git a/bcc/src/test/java/me/bechberger/ebpf/bcc/structs/HelloBufferTest.java b/bcc/src/test/java/me/bechberger/ebpf/bcc/structs/HelloBufferTest.java index ebb9ff1..6cdc411 100644 --- a/bcc/src/test/java/me/bechberger/ebpf/bcc/structs/HelloBufferTest.java +++ b/bcc/src/test/java/me/bechberger/ebpf/bcc/structs/HelloBufferTest.java @@ -8,6 +8,7 @@ import me.bechberger.ebpf.bcc.BPFTable; import me.bechberger.ebpf.type.BPFType; import me.bechberger.ebpf.bcc.Utils; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import java.util.List; @@ -34,6 +35,7 @@ public record Data(int pid, int uid, @Size(16) String command, @Size(12) String @Test + @Disabled public void testHelloBuffer() throws InterruptedException { try (var b = BPF.builder(""" BPF_PERF_OUTPUT(output); diff --git a/bpf-processor/src/main/java/me/bechberger/ebpf/bpf/processor/Processor.java b/bpf-processor/src/main/java/me/bechberger/ebpf/bpf/processor/Processor.java index e389506..ebad7f0 100644 --- a/bpf-processor/src/main/java/me/bechberger/ebpf/bpf/processor/Processor.java +++ b/bpf-processor/src/main/java/me/bechberger/ebpf/bpf/processor/Processor.java @@ -77,7 +77,13 @@ public void processBPFProgram(TypeElement typeElement) { + "BPF but is not abstract", typeElement); return; } - var typeProcessorResult = new TypeProcessor(processingEnv).processBPFTypeRecords(typeElement); + TypeProcessorResult typeProcessorResult; + try { + typeProcessorResult = new TypeProcessor(processingEnv).processBPFTypeRecords(typeElement); + } catch (Exception e) { + e.printStackTrace(); + throw new RuntimeException(e); + } var combinedCode = combineEBPFProgram(typeElement, typeProcessorResult); if (combinedCode == null) { return; @@ -216,14 +222,11 @@ record CombinedCode(String ebpfProgram, VariableElement codeField, Map lines.get(i).startsWith("#include")).max().orElse(-1); - if (lastInclude == -1) { - this.processingEnv.getMessager().printError("No includes found in EBPF program", field); - return null; - } var resultLines = new ArrayList<>(lines.subList(0, lastInclude + 1)); - IntStream.range(0, lastInclude + 1).forEach(i -> codeLineMapping.put(i, i)); - - resultLines.add(""); + if (lastInclude != -1) { + IntStream.range(0, lastInclude + 1).forEach(i -> codeLineMapping.put(i, i)); + resultLines.add(""); + } Consumer addLine = l -> resultLines.addAll(l.lines().toList()); @@ -239,8 +242,7 @@ record CombinedCode(String ebpfProgram, VariableElement codeField, Map l.matches(".*SEC *\\(\"license\"\\).*")).findFirst().orElse(null); if (tpResult.licenseDefinition() == null) { if (license == null) { - this.processingEnv.getMessager().printError("No license defined", field); - return null; + this.processingEnv.getMessager().printWarning("No license defined in EBPF program", field); } } else { if (license != null) { @@ -268,7 +270,7 @@ record CombinedCode(String ebpfProgram, VariableElement codeField, Map type) { + this(type.javaClass().klass()); + } + } + /** * Helper class to keep track of defined types */ private class DefinedTypes { - private final Map typeToFieldName; - private final Map nameToSpecFieldName; - private final Map specFieldNameToName; - private final Map nameToTypeElement; + private final Map nameToBPFName; + private final Map bpfNameToName; + private final Map typeToFieldName; + private final Map nameToSpecFieldName; + private final Map specFieldNameToName; + private final Map nameToTypeElement; - DefinedTypes(Map typeToFieldName) { + DefinedTypes(Map typeToFieldName) { + this.nameToBPFName = new HashMap<>(); + this.bpfNameToName = new HashMap<>(); this.typeToFieldName = typeToFieldName; this.nameToSpecFieldName = new HashMap<>(); this.specFieldNameToName = new HashMap<>(); @@ -53,6 +71,9 @@ private class DefinedTypes { this.nameToSpecFieldName.put(name, v); this.specFieldNameToName.put(v, name); this.nameToTypeElement.put(name, k); + var javaName = new JavaName(k); + this.nameToBPFName.put(javaName, name); + this.bpfNameToName.put(name, javaName); }); } @@ -60,19 +81,27 @@ public boolean isTypeDefined(TypeElement typeElement) { return this.typeToFieldName.containsKey(typeElement); } - public boolean isNameDefined(String name) { + public boolean isNameDefined(BPFName name) { return this.nameToSpecFieldName.containsKey(name); } - public Optional getFieldName(TypeElement typeElement) { + public boolean isNameDefined(SpecFieldName name) { + return this.specFieldNameToName.containsKey(name); + } + + public boolean isNameDefined(JavaName name) { + return this.nameToTypeElement.containsKey(name); + } + + public Optional getFieldName(TypeElement typeElement) { return Optional.ofNullable(this.typeToFieldName.get(typeElement)); } - public Optional getSpecFieldName(String name) { + public Optional getSpecFieldName(BPFName name) { return Optional.ofNullable(this.nameToSpecFieldName.get(name)); } - public Optional getTypeElement(String name) { + public Optional getTypeElement(BPFName name) { return Optional.ofNullable(this.nameToTypeElement.get(name)); } @@ -81,8 +110,36 @@ public String toString() { return this.typeToFieldName.toString(); } - public String specFieldNameToName(String field) { - return this.specFieldNameToName.get(field); + public BPFName specFieldNameToName(SpecFieldName field) { + if (this.specFieldNameToName.containsKey(field)) { + return this.specFieldNameToName.get(field); + } else { + throw new IllegalArgumentException("Field " + field + " not defined"); + } + } + + public SpecFieldName nameToSpecFieldName(BPFName name) { + if (this.nameToSpecFieldName.containsKey(name)) { + return this.nameToSpecFieldName.get(name); + } else { + throw new IllegalArgumentException("Name " + name + " not defined"); + } + } + + public JavaName bpfNameToName(BPFName name) { + if (this.bpfNameToName.containsKey(name)) { + return this.bpfNameToName.get(name); + } else { + throw new IllegalArgumentException("Name " + name + " not defined"); + } + } + + public BPFName nameToBPFName(JavaName name) { + if (this.nameToBPFName.containsKey(name)) { + return this.nameToBPFName.get(name); + } else { + throw new IllegalArgumentException("Name " + name + " not defined"); + } } } @@ -153,15 +210,16 @@ TypeProcessorResult processBPFTypeRecords(TypeElement outerTypeElement) { List innerTypeElements = getInnerBPFTypeElements(outerTypeElement); definedTypes = getDefinedTypes(innerTypeElements); - Map> alreadyDefinedTypes = new HashMap<>(); + Map> alreadyDefinedTypes = new HashMap<>(); // detect recursion - Set currentlyDefining = new HashSet<>(); + Set currentlyDefining = new HashSet<>(); List processedTypes = new ArrayList<>(); // bpf name to type - AtomicReference>> obtainType = new AtomicReference<>(); + AtomicReference>> obtainType = new AtomicReference<>(); obtainType.set(name -> { + Objects.requireNonNull(name); if (alreadyDefinedTypes.containsKey(name)) { return alreadyDefinedTypes.get(name); } @@ -170,9 +228,16 @@ TypeProcessorResult processBPFTypeRecords(TypeElement outerTypeElement) { throw new IllegalStateException("Recursion detected for type " + name); } currentlyDefining.add(name); - var typeElement = definedTypes.getTypeElement(name).get(); + var bpfName = definedTypes.nameToBPFName(name); + var typeElementOpt = definedTypes.getTypeElement(bpfName); + if (typeElementOpt.isEmpty()) { + this.processingEnv.getMessager().printError("Type " + name + " not defined", outerTypeElement); + return null; + } + var typeElement = typeElementOpt.get(); var type = processBPFTypeRecord(typeElement, obtainType.get()); if (type.isEmpty()) { + this.processingEnv.getMessager().printError("Type " + name + " could not be processed", outerTypeElement); return null; } alreadyDefinedTypes.put(name, type.get()); @@ -181,9 +246,9 @@ TypeProcessorResult processBPFTypeRecords(TypeElement outerTypeElement) { return type.get(); }); - Function, String> typeToSpecField = t -> { + Function, SpecFieldName> typeToSpecField = t -> { if (t instanceof BPFType.BPFStructType structType) { - return definedTypes.getSpecFieldName(structType.bpfName()).get(); + return definedTypes.getSpecFieldName(new BPFName(structType.bpfName())).get(); } return null; }; @@ -194,22 +259,23 @@ TypeProcessorResult processBPFTypeRecords(TypeElement outerTypeElement) { if (type.isEmpty()) { return new TypeProcessorResult(List.of(), List.of(), List.of(), null, List.of()); } - alreadyDefinedTypes.put(type.get().bpfName(), type.get()); + alreadyDefinedTypes.put(new JavaName(type.get()), type.get()); processedTypes.add(unprocessed.getFirst()); } var mapDefinitions = processDefinedMaps(outerTypeElement, - field -> obtainType.get().apply(definedTypes.specFieldNameToName(field)), - type -> definedTypes.getSpecFieldName(type.bpfName()).get()); + field -> obtainType.get().apply(definedTypes.bpfNameToName(definedTypes.specFieldNameToName(field))), + type -> definedTypes.getSpecFieldName(new BPFName(type.bpfName())).get()); List fields = new ArrayList<>(); List definingStatements = new ArrayList<>(); for (var processedType : processedTypes) { var name = getTypeRecordBpfName(processedType); - var type = alreadyDefinedTypes.get(getTypeRecordBpfName(processedType)); - var spec = type.toFieldSpecGenerator().get().apply(definedTypes.getSpecFieldName(name).get(), - t -> t.toJavaFieldSpecUse(typeToSpecField)); + var type = alreadyDefinedTypes.get(new JavaName(processedType)); + var fieldSpecName = definedTypes.getSpecFieldName(name).get().name; + var spec = type.toFieldSpecGenerator().get().apply(fieldSpecName, + t -> t.toJavaFieldSpecUse(t2 -> typeToSpecField.apply(t2).name())); fields.add(spec); if (shouldGenerateCCode(processedType)) { type.toCDeclarationStatement().ifPresent(definingStatements::add); @@ -229,7 +295,7 @@ TypeProcessorResult processBPFTypeRecords(TypeElement outerTypeElement) { return null; } // char _license[] SEC ("license") = "GPL"; - return variableDefinition(Declarator.array(Declarator.identifier("char"), null), variable("_license", CAnnotation.sec("license"))); + return variableDefinition(Declarator.array(Declarator.identifier("char"), null), variable("_license", CAnnotation.sec("license")), constant(license)); } private @Nullable CAST.Statement.Define processField(VariableElement field) { @@ -261,21 +327,19 @@ private List createDefineStatements(TypeElement typeElement) { /** * Obtains the name from the Type annotation, if not present uses the simple name of the type */ - private String getTypeRecordBpfName(TypeElement typeElement) { + private BPFName getTypeRecordBpfName(TypeElement typeElement) { var annotation = getAnnotationMirror(typeElement, "me.bechberger.ebpf.annotations.bpf.Type"); assert annotation.isPresent(); var name = annotation.get().getElementValues().entrySet().stream().filter(e -> e.getKey().getSimpleName().toString().equals("name")).findFirst(); - if (name.isEmpty()) { - return typeElement.getSimpleName().toString(); - } - return name.get().getValue().getValue().toString(); + return name.map(entry -> new BPFName(entry.getValue().getValue().toString())) + .orElseGet(() -> new BPFName(typeElement.getSimpleName().toString())); } @SuppressWarnings({"unchecked", "rawtypes"}) - private Optional> processBPFTypeRecord(TypeElement typeElement, Function> nameToCustomType) { - String className = typeElement.getSimpleName().toString(); - String name = getTypeRecordBpfName(typeElement); + private Optional> processBPFTypeRecord(TypeElement typeElement, Function> nameToCustomType) { + String className = typeElement.getQualifiedName().toString(); + var name = getTypeRecordBpfName(typeElement); var constructors = typeElement.getEnclosedElements().stream().filter(e -> e.getKind() == ElementKind.CONSTRUCTOR).toList(); if (constructors.size() != 1) { @@ -292,13 +356,13 @@ private Optional> processBPFTypeRecord(TypeElement type BPFType.AnnotatedClass annotatedClass = new BPFType.AnnotatedClass(className, List.of()); - return Optional.of(BPFType.BPFStructType.autoLayout(name, + return Optional.of(BPFType.BPFStructType.autoLayout(name.name(), (List>)(List)members.get(), annotatedClass, null)); } @SuppressWarnings({"unchecked", "rawtypes"}) - private Optional>> processBPFTypeRecordMembers(List recordMembers, Function> nameToCustomType) { + private Optional>> processBPFTypeRecordMembers(List recordMembers, Function> nameToCustomType) { var list = recordMembers.stream().map(m -> processBPFTypeRecordMember(m, nameToCustomType)).toList(); if (list.stream().anyMatch(Optional::isEmpty)) { return Optional.empty(); @@ -328,7 +392,7 @@ private AnnotationValues getAnnotationValuesForRecordMember(AnnotatedConstruct e } private Optional> processBPFTypeRecordMember(VariableElement element, - Function> nameToCustomType) { + Function> nameToCustomType) { AnnotationValues annotations = getAnnotationValuesForRecordMember(element); TypeMirror type = element.asType(); var bpfType = processBPFTypeRecordMemberType(element, annotations, type); @@ -384,7 +448,7 @@ private Optional processBPFTypeRecordMemberType(Element element, @FunctionalInterface interface BPFTypeMirror { - BPFType toBPFType(Function> nameToCustomType); + BPFType toBPFType(Function> nameToCustomType); } private Optional> processIntegerType(Element element, AnnotationValues annotations, TypeMirror type) { @@ -438,13 +502,14 @@ private Optional processDefinedType(Element element, AnnotationVa return Optional.empty(); } TypeElement typeElement = (TypeElement) processingEnv.getTypeUtils().asElement(type); - Optional fieldName = definedTypes.getFieldName(typeElement); + Optional fieldName = definedTypes.getFieldName(typeElement); if (fieldName.isEmpty()) { this.processingEnv.getMessager().printError("Type " + typeElement.getSimpleName() + " not defined", element); return Optional.empty(); } - return Optional.of(t -> t.apply(fieldName.get())); + var typeName = definedTypes.bpfNameToName(definedTypes.specFieldNameToName(fieldName.get())); + return Optional.of(t -> t.apply(typeName)); } private DefinedTypes getDefinedTypes(List innerTypeElements) { @@ -454,8 +519,8 @@ private DefinedTypes getDefinedTypes(List innerTypeElements) { /** * Field name is camel-case and upper-case version of simple type name */ - private String typeToFieldName(TypeElement typeElement) { - return toSnakeCase(typeElement.getSimpleName().toString()).toUpperCase(); + private SpecFieldName typeToFieldName(TypeElement typeElement) { + return new SpecFieldName(toSnakeCase(typeElement.getSimpleName().toString()).toUpperCase()); } /** @@ -475,8 +540,8 @@ private static String toSnakeCase(String name) { record MapDefinition(String javaFieldName, String javaFieldInitializer, Statement structDefinition) { } - List processDefinedMaps(TypeElement outerElement, Function> fieldToType, - Function, String> typeToSpecFieldName) { + List processDefinedMaps(TypeElement outerElement, Function> fieldToType, + Function, SpecFieldName> typeToSpecFieldName) { // take all @BPFMapDefinition annotated fields return outerElement.getEnclosedElements().stream().filter(e -> e.getKind() == ElementKind.FIELD).map(e -> (VariableElement) e) .filter(e -> getAnnotationMirror(e.asType(), BPF_MAP_DEFINITION).isPresent()) @@ -485,8 +550,8 @@ List processDefinedMaps(TypeElement outerElement, Function> fieldToType, - Function, String> typeToSpecFieldName) { + Function> fieldToType, + Function, SpecFieldName> typeToSpecFieldName) { // create a FieldSpec for the field // create a #define statement for the field // return the FieldSpec and the #define statement @@ -505,7 +570,7 @@ List processDefinedMaps(TypeElement outerElement, Function processBPFTypeRecordMemberType(field, getAnnotationValuesForRecordMember(t), t) - .map(m -> m.toBPFType(fieldToType))).toList(); + .map(m -> m.toBPFType(mt -> fieldToType.apply(definedTypes.nameToSpecFieldName(definedTypes.nameToBPFName(mt)))))).toList(); if (typeParams.stream().anyMatch(Optional::isEmpty)) { this.processingEnv.getMessager().printError("Type parameters must be valid", field); return null; @@ -544,26 +609,26 @@ List processDefinedMaps(TypeElement outerElement, Function> typeParams, Integer maxEntries, String fieldName, String className, - Function, String> typeToSpecFieldName) { - return "this." + field.getSimpleName() + " = " + processBPFClassTemplate(javaTemplate, typeParams, - maxEntries, fieldName, className, typeToSpecFieldName).strip(); + Function, SpecFieldName> typeToSpecFieldName) { + return "this." + field.getSimpleName() + " = recordMap(" + processBPFClassTemplate(javaTemplate, typeParams, + maxEntries, fieldName, className, typeToSpecFieldName).strip() + ")"; } Statement processBPFClassCTemplate(VariableElement field, String cTemplate, List> typeParameters, Integer maxEntries, String fieldName, String className, - Function, String> typeToSpecFieldName) { + Function, SpecFieldName> typeToSpecFieldName) { String raw = processBPFClassTemplate(cTemplate, typeParameters, maxEntries, fieldName, className, typeToSpecFieldName); return new VerbatimStatement(raw); } String processBPFClassTemplate(String template, List> typeParams, int maxEntries, String fieldName, - String className, Function, String> typeToSpecFieldName) { + String className, Function, SpecFieldName> typeToSpecFieldName) { var classNames = typeParams.stream().map(BPFType::javaClass).map(AnnotatedClass::toString).toList(); var cTypeNames = typeParams.stream().map(BPFType::bpfName).toList(); - var bFields = typeParams.stream().map(t -> t.toJavaFieldSpecUse(typeToSpecFieldName)).toList(); + var bFields = typeParams.stream().map(t -> t.toJavaFieldSpecUse(tm -> typeToSpecFieldName.apply(tm).name)).toList(); String res = template; for (int i = typeParams.size(); i > 0; i--) { res = res.replace("$c" + i, cTypeNames.get(i - 1)) diff --git a/bpf/src/main/java/me/bechberger/ebpf/bpf/BPFProgram.java b/bpf/src/main/java/me/bechberger/ebpf/bpf/BPFProgram.java index 60ae4fa..ea225d5 100644 --- a/bpf/src/main/java/me/bechberger/ebpf/bpf/BPFProgram.java +++ b/bpf/src/main/java/me/bechberger/ebpf/bpf/BPFProgram.java @@ -1,6 +1,8 @@ package me.bechberger.ebpf.bpf; +import me.bechberger.ebpf.annotations.bpf.BPF; import me.bechberger.ebpf.bpf.map.*; +import me.bechberger.ebpf.bpf.map.BPFRingBuffer.BPFRingBufferError; import me.bechberger.ebpf.bpf.raw.Lib; import me.bechberger.ebpf.bpf.raw.LibraryLoader; import me.bechberger.ebpf.type.BPFType; @@ -16,6 +18,9 @@ import java.lang.foreign.MemorySegment; import java.nio.file.Files; import java.nio.file.Path; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Set; import java.util.function.Function; /** @@ -100,11 +105,23 @@ public static S load(Class clazz) { */ private final MemorySegment ebpf_object; + /** + * Link to an attached program + */ + public record BPFLink(MemorySegment segment) {} + + private final Set attachedPrograms = new HashSet<>(); + + private final Set attachedMaps = new HashSet<>(); + + private volatile boolean closed = false; + /** * Load the eBPF program from the byte code */ public BPFProgram() { this.ebpf_object = loadProgram(); + Runtime.getRuntime().addShutdownHook(new Thread(this::close)); } public BPFType.BPFStructType getTypeForClass(Class innerType) { @@ -206,7 +223,7 @@ public BPFAttachError(String name, int errorCode) { } } - private static final HandlerWithErrno BCC_PROGRAM__ATTACH = + private static final HandlerWithErrno BPF_PROGRAM__ATTACH = new HandlerWithErrno<>("bpf_program__attach", FunctionDescriptor.of(PanamaUtil.POINTER, PanamaUtil.POINTER)); @@ -218,11 +235,26 @@ public BPFAttachError(String name, int errorCode) { * @param prog program to attach * @throws BPFAttachError when attaching fails */ - public void autoAttachProgram(ProgramHandle prog) { - var ret = BCC_PROGRAM__ATTACH.call(prog.prog()); + public BPFLink autoAttachProgram(ProgramHandle prog) { + var ret = BPF_PROGRAM__ATTACH.call(prog.prog()); if (ret.result() == MemorySegment.NULL) { throw new BPFAttachError(prog.name, ret.err()); } + var link = new BPFLink(ret.result()); + attachedPrograms.add(link); + return link; + } + + public void detachProgram(BPFLink link) { + if (!attachedPrograms.contains(link)) { + throw new IllegalArgumentException("Program not attached"); + } + if (link.segment.address() == 0) { + throw new IllegalArgumentException("Improper link"); + } + Lib.bpf_link__destroy(link.segment); + System.out.println("Detached program " + getClass().getCanonicalName()); + attachedPrograms.remove(link); } /** @@ -230,6 +262,16 @@ public void autoAttachProgram(ProgramHandle prog) { */ @Override public void close() { + if (closed) { + return; + } + closed = true; + for (var prog : new HashSet<>(attachedPrograms)) { + detachProgram(prog); + } + for (var map : new HashSet<>(attachedMaps)) { + map.close(); + } Lib.bpf_object__close(this.ebpf_object); } @@ -291,6 +333,11 @@ public FileDescriptor getMapDescriptorByName(String name) { } } + public T recordMap(T map) { + attachedMaps.add(map); + return map; + } + /** * Get a map by name * @@ -302,11 +349,13 @@ public FileDescriptor getMapDescriptorByName(String name) { * @throws BPFMap.BPFMapTypeMismatch if the type of the map does not match the expected type */ public M getMapByName(String name, Function mapCreator) { - return mapCreator.apply(getMapDescriptorByName(name)); + return recordMap(mapCreator.apply(getMapDescriptorByName(name))); } /** * Get a ring buffer by name + *

    + * Keep in mind to regularly call {@link BPFRingBuffer#consumeAndThrow()} to consume the events * * @param name the name of the ring buffer * @param eventType type of the event @@ -319,13 +368,28 @@ public M getMapByName(String name, Function BPFRingBuffer getRingBufferByName(String name, BPFType eventType, BPFRingBuffer.EventCallback callback) { - return getMapByName(name, fd -> new BPFRingBuffer<>(fd, eventType, callback)); + return recordMap(getMapByName(name, fd -> new BPFRingBuffer<>(fd, eventType, callback))); } public BPFHashMap getHashMapByName(String name, BPFType keyType, BPFType valueType) { var fd = getMapDescriptorByName(name); MapTypeId type = BPFMap.getInfo(fd).type(); - return new BPFHashMap<>(fd, type == MapTypeId.LRU_HASH, keyType, valueType); + return recordMap(new BPFHashMap<>(fd, type == MapTypeId.LRU_HASH, keyType, valueType)); + } + + /** + * Polls data from all ring buffers and consumes if available. + * + * @return the number of events consumed (max MAX_INT) + * @throws BPFRingBufferError if calling the consume method failed, + * or if any errors were caught in the call-back of any ring buffer + */ + public void consumeAndThrow() { + for (var map : attachedMaps) { + if (map instanceof BPFRingBuffer) { + ((BPFRingBuffer)map).consumeAndThrow(); + } + } } } \ No newline at end of file diff --git a/bpf/src/main/java/me/bechberger/ebpf/bpf/map/BPFRingBuffer.java b/bpf/src/main/java/me/bechberger/ebpf/bpf/map/BPFRingBuffer.java index 7ca35fb..edaecb3 100644 --- a/bpf/src/main/java/me/bechberger/ebpf/bpf/map/BPFRingBuffer.java +++ b/bpf/src/main/java/me/bechberger/ebpf/bpf/map/BPFRingBuffer.java @@ -232,6 +232,7 @@ public int consumeAndThrow() { @Override public void close() { + System.out.println("Closing ring buffer"); Lib.ring_buffer__free(rb); ringArena.close(); super.close(); diff --git a/bpf/src/test/java/me/bechberger/ebpf/bpf/HelloWorldTest.java b/bpf/src/test/java/me/bechberger/ebpf/bpf/HelloWorldTest.java index 90535cb..2c17a31 100644 --- a/bpf/src/test/java/me/bechberger/ebpf/bpf/HelloWorldTest.java +++ b/bpf/src/test/java/me/bechberger/ebpf/bpf/HelloWorldTest.java @@ -3,11 +3,10 @@ import me.bechberger.ebpf.annotations.bpf.BPF; import me.bechberger.ebpf.bpf.BPFProgram.BPFProgramNotFound; import me.bechberger.ebpf.samples.HelloWorld; +import me.bechberger.ebpf.shared.TraceLog; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Timeout; -import java.io.IOException; - import static org.junit.jupiter.api.Assertions.*; /** @@ -51,4 +50,23 @@ public void testFailingProgramByName() { assertThrows(BPFProgramNotFound.class, () -> program.getProgramByName("invalid-name")); } } + + // Test the program is properly closed after + // by running two programs after another (only the first prints), the second reads, or maybe one program is enough + + /** + * Test the program is properly closed after + */ + @Test + public void testProgramClose() { + try (var program = BPFProgram.load(Prog.class)) { + program.autoAttachProgram(program.getProgramByName("kprobe__do_sys_openat2")); + } + TestUtil.triggerOpenAt(); + // run for 20ms + long start = System.currentTimeMillis(); + while (System.currentTimeMillis() - start < 20) { + assertNull(TraceLog.getInstance().readLineIfPossible()); + } + } } diff --git a/bpf/src/test/java/me/bechberger/ebpf/bpf/MapGenerationTest.java b/bpf/src/test/java/me/bechberger/ebpf/bpf/MapGenerationTest.java new file mode 100644 index 0000000..0dcdeda --- /dev/null +++ b/bpf/src/test/java/me/bechberger/ebpf/bpf/MapGenerationTest.java @@ -0,0 +1,106 @@ +package me.bechberger.ebpf.bpf; + +import me.bechberger.ebpf.annotations.Size; +import me.bechberger.ebpf.annotations.Unsigned; +import me.bechberger.ebpf.annotations.bpf.BPF; +import me.bechberger.ebpf.annotations.bpf.BPFMapDefinition; +import me.bechberger.ebpf.annotations.bpf.Type; +import me.bechberger.ebpf.bpf.map.BPFRingBuffer; +import org.junit.jupiter.api.Test; + +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.atomic.AtomicReference; + +import static me.bechberger.ebpf.bpf.TestUtil.triggerOpenAt; +import static org.junit.jupiter.api.Assertions.fail; + +/** + * Based on {@link me.bechberger.ebpf.samples.TypeProcessingSample2} and {@link me.bechberger.ebpf.bpf.RingBufferTest} + */ +public class MapGenerationTest { + + @BPF + public static abstract class Prog extends BPFProgram { + + private static final int FILE_NAME_LEN = 256; + private static final int TASK_COMM_LEN = 16; + + @Type(name = "event") + public record Event(@Unsigned int pid, @Size(FILE_NAME_LEN) String filename, @Size(TASK_COMM_LEN) String comm) {} + + @BPFMapDefinition(maxEntries = 256 * 4096) + protected BPFRingBuffer rb; + + static final String EBPF_PROGRAM = """ + #include "vmlinux.h" + #include + #include + #include + + // non ring buffer map, used for the testWrongMapType test + struct { + __uint(type, BPF_MAP_TYPE_ARRAY); + __type(key, u32); + __type(value, long); + __uint(max_entries, 256); + } non_ring_buffer SEC(".maps"); + + // The ebpf auto-attach logic needs the SEC + SEC ("kprobe/do_sys_openat2") + int kprobe__do_sys_openat2 (struct pt_regs *ctx) + { + char filename[256]; + char comm[TASK_COMM_LEN] = { }; + struct event *evt; + const char fmt_str[] = "do_sys_openat2 called by:%s file:%s pid:%d"; + + // Reserve the ring-buffer + evt = bpf_ringbuf_reserve (&rb, sizeof (struct event), 0); + if (!evt) + { + return 0; + } + // Get the PID of the process. + evt->pid = bpf_get_current_pid_tgid (); // Get current process PID + + // Read the filename from the second argument + // The x86 arch/ABI have first argument in di and second in si registers (man syscall) + bpf_probe_read (evt->filename, sizeof (filename), (char *) ctx->regs[1]); + + // Read the current process name + bpf_get_current_comm (evt->comm, sizeof (comm)); + + // Also send the same message to the ring-buffer + bpf_ringbuf_submit (evt, 0); + return 0; + } + + char _license[] SEC ("license") = "GPL"; + """; + } + + @Test + public void testSuccessfulCase() throws Exception { + try (Prog program = BPFProgram.load(Prog.class)) { + program.autoAttachProgram(program.getProgramByName("kprobe__do_sys_openat2")); + AtomicReference eventRef = new AtomicReference<>(); + List paths = new ArrayList<>(); + program.rb.setCallback((buffer, event) -> { + paths.add(Path.of(event.filename)); + }); + Path openendPath = triggerOpenAt(); + long start = System.currentTimeMillis(); + while (System.currentTimeMillis() - start < 1000) { + Thread.sleep(10); + program.consumeAndThrow(); + if (paths.contains(openendPath)) { + return; + } + } + fail("No " + openendPath + " received, just " + paths); + } + } + +} diff --git a/bpf/src/test/java/me/bechberger/ebpf/bpf/RingBufferTest.java b/bpf/src/test/java/me/bechberger/ebpf/bpf/RingBufferTest.java index 481475b..1dc2449 100644 --- a/bpf/src/test/java/me/bechberger/ebpf/bpf/RingBufferTest.java +++ b/bpf/src/test/java/me/bechberger/ebpf/bpf/RingBufferTest.java @@ -5,12 +5,12 @@ import me.bechberger.ebpf.annotations.bpf.BPF; import me.bechberger.ebpf.bpf.map.BPFMap; import me.bechberger.ebpf.bpf.map.BPFRingBuffer; -import me.bechberger.ebpf.samples.RingSample; import me.bechberger.ebpf.type.BPFType; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Timeout; import java.nio.file.Path; +import java.util.ArrayList; import java.util.List; import java.util.concurrent.atomic.AtomicReference; @@ -78,7 +78,7 @@ int kprobe__do_sys_openat2 (struct pt_regs *ctx) evt->e_pid = bpf_get_current_pid_tgid (); // Get current process PID // Read the filename from the second argument - // The x86 arch/ABI have first argument in di and second in si registers (man syscall)\s + // The x86 arch/ABI have first argument in di and second in si registers (man syscall) bpf_probe_read (evt->e_filename, sizeof (filename), (char *) ctx->regs[1]); // Read the current process name @@ -128,33 +128,31 @@ public void testWrongMapType() { } @Test - @Timeout(5) - public void testSuccessfulCase() throws InterruptedException { - try (RingSample program = BPFProgram.load(RingSample.class)) { + public void testSuccessfulCase() throws Exception { + try (Prog program = BPFProgram.load(Prog.class)) { program.autoAttachProgram(program.getProgramByName("kprobe__do_sys_openat2")); AtomicReference eventRef = new AtomicReference<>(); + List paths = new ArrayList<>(); var ringBuffer = program.getRingBufferByName("rb", eventType, (buffer, event) -> { - eventRef.set(event); + paths.add(Path.of(event.filename)); }); Path openendPath = triggerOpenAt(); long start = System.currentTimeMillis(); while (System.currentTimeMillis() - start < 1000) { Thread.sleep(10); int ret = ringBuffer.consumeAndThrow(); - if (ret != 0) { - assertNotNull(eventRef.get()); - assertEquals(openendPath.toString(), eventRef.get().filename); - break; + if (paths.contains(openendPath)) { + return; } } - assertTrue(System.currentTimeMillis() - start < 1000); + fail("No " + openendPath + " received, just " + paths); } } @Test @Timeout(5) public void testFailingCallback() throws InterruptedException { - try (RingSample program = BPFProgram.load(RingSample.class)) { + try (Prog program = BPFProgram.load(Prog.class)) { program.autoAttachProgram(program.getProgramByName("kprobe__do_sys_openat2")); AtomicReference throwableRef = new AtomicReference<>(); var ringBuffer = program.getRingBufferByName("rb", eventType, (buffer, event) -> { @@ -186,7 +184,7 @@ public void testFailingCallback() throws InterruptedException { @Test @Timeout(5) public void testFailingParse() throws InterruptedException { - try (RingSample program = BPFProgram.load(RingSample.class)) { + try (Prog program = BPFProgram.load(Prog.class)) { program.autoAttachProgram(program.getProgramByName("kprobe__do_sys_openat2")); var ringBuffer = program.getRingBufferByName("rb", brokenEventType, (buffer, event) -> {}); long start = System.currentTimeMillis(); diff --git a/bpf/src/test/java/me/bechberger/ebpf/bpf/TypeProcessingTest.java b/bpf/src/test/java/me/bechberger/ebpf/bpf/TypeProcessingTest.java index 488901b..03b956a 100644 --- a/bpf/src/test/java/me/bechberger/ebpf/bpf/TypeProcessingTest.java +++ b/bpf/src/test/java/me/bechberger/ebpf/bpf/TypeProcessingTest.java @@ -9,7 +9,6 @@ import java.util.List; -import static me.bechberger.ebpf.type.BPFType.BPFIntType.CHAR; import static me.bechberger.ebpf.type.BPFType.BPFIntType.UINT32; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertInstanceOf; @@ -18,7 +17,7 @@ public class TypeProcessingTest { @BPF public static abstract class SimpleRecordTestProgram extends BPFProgram { - static final String EBPF_PROGRAM = ""; + static final String EBPF_PROGRAM = "#include \"vmlinux.h\""; @Type public record SimpleRecord(@Unsigned int value) { @@ -45,11 +44,6 @@ public record RecordWithMultipleMembers(byte value, @Size(10) String name, long @Type public record RecordWithOtherType(@Unsigned int value, SimpleRecord other) { } - - @Type - public record RecordWithDirectMemberType(@Unsigned int value, @Type.Member(bpfType = "me.bechberger.ebpf" + - ".shared.BPFType.BPFIntType.CHAR") byte other) { - } } @Test @@ -115,12 +109,15 @@ public void testRecordWithOtherType() { } @Test - public void testRecordWithDirectMemberType() { + public void testGeneratedCCode() { var type = BPFProgram.getTypeForClass(SimpleRecordTestProgram.class, - SimpleRecordTestProgram.RecordWithDirectMemberType.class); - assertEquals(CHAR, type.getMember("other").type()); - // check that constructor works - assertEquals(new SimpleRecordTestProgram.RecordWithDirectMemberType(42, (byte)43), - type.constructor().apply(List.of(42, (byte)43))); + SimpleRecordTestProgram.RecordWithOtherType.class); + assertEquals(""" + struct RecordWithOtherType { + u32 value; + struct SimpleRecord other; + }; + """.trim(), + type.toCDeclarationStatement().get().toPrettyString()); } } \ No newline at end of file diff --git a/shared/src/main/java/me/bechberger/ebpf/shared/PanamaUtil.java b/shared/src/main/java/me/bechberger/ebpf/shared/PanamaUtil.java index dbd6140..695de8e 100644 --- a/shared/src/main/java/me/bechberger/ebpf/shared/PanamaUtil.java +++ b/shared/src/main/java/me/bechberger/ebpf/shared/PanamaUtil.java @@ -136,8 +136,14 @@ public ResultAndErr call(Arena arena, Object... args) { Object[] argsWithState = new Object[args.length + 1]; argsWithState[0] = capturedState; System.arraycopy(args, 0, argsWithState, 1, args.length); - return new ResultAndErr<>((R) getHandle().invokeWithArguments(argsWithState), - (int) errnoHandle.get(capturedState)); + var result = (R) getHandle().invokeWithArguments(argsWithState); + int errno; + try { + errno = (int) errnoHandle.get(capturedState); + } catch (Throwable throwable) { + errno = capturedState.get(JAVA_INT, 0); + } + return new ResultAndErr<>(result, errno); } catch (Throwable throwable) { throw new RuntimeException(throwable); } diff --git a/shared/src/main/java/me/bechberger/ebpf/shared/TraceLog.java b/shared/src/main/java/me/bechberger/ebpf/shared/TraceLog.java index 8e42395..04a67d4 100644 --- a/shared/src/main/java/me/bechberger/ebpf/shared/TraceLog.java +++ b/shared/src/main/java/me/bechberger/ebpf/shared/TraceLog.java @@ -77,7 +77,7 @@ public static void close() { *

    * Currently, doesn't support non-blocking mode */ - public TraceFields readFields() { + public @Nullable TraceFields readFields() { while (true) { String tracedLine = traceFile.readLine(); if (tracedLine == null) return null; @@ -103,12 +103,16 @@ public TraceFields readFields() { } /** - * Read from the kernel debug trace pipe and return one line + * Read from the kernel debug trace pipe and return one line, might block */ - public String readLine() { + public @Nullable String readLine() { return traceFile.readLine(); } + public @Nullable String readLineIfPossible() { + return traceFile.readLineIfPossible(); + } + /** * Read from the kernel debug trace pipe and print on stdout. * @param format optional function to format the output diff --git a/shared/src/main/java/me/bechberger/ebpf/shared/util/LineReader.java b/shared/src/main/java/me/bechberger/ebpf/shared/util/LineReader.java index dfa22ce..9557b83 100644 --- a/shared/src/main/java/me/bechberger/ebpf/shared/util/LineReader.java +++ b/shared/src/main/java/me/bechberger/ebpf/shared/util/LineReader.java @@ -1,5 +1,7 @@ package me.bechberger.ebpf.shared.util; +import org.jetbrains.annotations.Nullable; + import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; @@ -40,13 +42,28 @@ public void close() { } /** - * Read a line from the file, or return null if the file is closed + * Read a line from the file, or return null if the file is closed, might block */ - public String readLine() { + public @Nullable String readLine() { try { return reader.readLine(); } catch (IOException e) { return null; } } + + /** + * Read a line from the file if there is content available, or return null + * @return the line or null if no line is available + */ + public @Nullable String readLineIfPossible() { + try { + if (reader.ready() && input.available() > 0) { + return readLine(); + } + return null; + } catch (IOException e) { + return null; + } + } } From dbd930337051b572af3b500368684b3c97834dbf Mon Sep 17 00:00:00 2001 From: Johannes Bechberger Date: Sat, 6 Apr 2024 21:42:53 +0200 Subject: [PATCH 09/11] Fix clang error message processing --- .../ebpf/bpf/processor/Processor.java | 78 +++++++++++++------ 1 file changed, 54 insertions(+), 24 deletions(-) diff --git a/bpf-processor/src/main/java/me/bechberger/ebpf/bpf/processor/Processor.java b/bpf-processor/src/main/java/me/bechberger/ebpf/bpf/processor/Processor.java index ebad7f0..cb36bbc 100644 --- a/bpf-processor/src/main/java/me/bechberger/ebpf/bpf/processor/Processor.java +++ b/bpf-processor/src/main/java/me/bechberger/ebpf/bpf/processor/Processor.java @@ -28,6 +28,7 @@ import java.nio.file.Paths; import java.util.*; import java.util.function.Consumer; +import java.util.stream.Collectors; import java.util.stream.IntStream; import java.util.zip.GZIPOutputStream; @@ -169,7 +170,8 @@ private TypeSpec createType(String name, TypeMirror baseType, byte[] byteCode, L * @param codeField field that contains the EBPF_PROGRAM * @param codeLineMapping line number in generated -> original line number */ - record CombinedCode(String ebpfProgram, VariableElement codeField, Map codeLineMapping, TypeProcessorResult tp) { + record CombinedCode(String ebpfProgram, VariableElement codeField, Map codeLineMapping, + TypeProcessorResult tp, Set generatedLines) { } private @Nullable CombinedCode combineEBPFProgram(TypeElement typeElement, TypeProcessorResult tpResult) { @@ -273,7 +275,8 @@ record CombinedCode(String ebpfProgram, VariableElement codeField, Map !codeLineMapping.containsKey(i)).boxed().collect(Collectors.toSet()); + return new CombinedCode(String.join("\n", resultLines), field, codeLineMapping, tpResult, generatedLines); } private static String findNewestClangVersion() { @@ -362,7 +365,7 @@ private void printErrorMessages(CombinedCode code, String errorString) { Line l = lineMap.get(lineNumber); if (l != null) { // format [ERROR] filename:[line,column] message - System.err.println(Paths.get(".").relativize(file) + ":[" + l.line + "," + (l.start + column) + "] " + message); + System.err.println(file + ":[" + l.line + "," + (l.start + column) + "] " + message); } else { System.err.println(line); } @@ -392,38 +395,65 @@ private record Line(int line, int start) { private Map getLineMap(CombinedCode code) { Path file = Paths.get(this.processingEnv.getElementUtils().getFileObjectOf(code.codeField).getName()); - List linesInEBPFProgram = code.ebpfProgram.lines().toList(); - List linesInFile; + List linesInGeneratedEBPFProgram = code.ebpfProgram.lines().toList(); + List strippedLinesInGeneratedEBPFProgram = linesInGeneratedEBPFProgram.stream().map(String::strip).toList(); + List linesInSourceFile; try { - linesInFile = Files.readAllLines(file); + linesInSourceFile = Files.readAllLines(file); } catch (IOException e) { this.processingEnv.getMessager().printError("Could not read file " + file, code.codeField); return Map.of(); } - // find line that (excluding whitespace) matches the start of the line in the ebpf program - Map lineMap = new HashMap<>(); - int line = 0; - for (int i = 0; i < linesInEBPFProgram.size(); i++) { - if (!code.codeLineMapping.containsKey(i)) { - continue; + List strippedLinesInSourceFile = linesInSourceFile.stream().map(String::strip).toList(); + Map generatedToSourceLine = new HashMap<>(); + + int genIndex = 0; + int sourceIndex = 0; + boolean start = true; + while (sourceIndex < strippedLinesInSourceFile.size() && genIndex < strippedLinesInGeneratedEBPFProgram.size()) { + // omit clearly generated lines + while (code.generatedLines.contains(genIndex)) { + genIndex++; + } + String strippedGenLine = strippedLinesInGeneratedEBPFProgram.get(genIndex); + int newSourceIndex = strippedLinesInSourceFile.subList(sourceIndex, strippedLinesInSourceFile.size()) + .indexOf(strippedGenLine) + sourceIndex; + + if (newSourceIndex == -1 + sourceIndex) { + return Map.of(); } - String lineInEBPFProgram = linesInEBPFProgram.get(i); - String lineInEBPFProgramTrimmed = lineInEBPFProgram.strip().replace("\\", ""); - while (line < linesInFile.size()) { - String lineInFile = linesInFile.get(line); - String lineInFileTrimmed = lineInFile.strip().replace("\\", ""); - if (lineInFileTrimmed.startsWith(lineInEBPFProgramTrimmed)) { - lineMap.put(i, new Line(line, lineInFile.indexOf(lineInEBPFProgram))); - line++; + + boolean newStart = false; + // check that there is no """ in between new and old source index + for (int i = sourceIndex; i <= newSourceIndex && !start; i++) { + if (strippedLinesInSourceFile.get(i).equals("\"\"\"")) { + genIndex = 0; + sourceIndex = i; + generatedToSourceLine.clear(); + start = true; + newStart = true; break; } - line++; } - if (lineMap.size() == linesInEBPFProgram.size()) { - return lineMap; + + if (newStart) { + continue; + } + sourceIndex = newSourceIndex; + + String sourceLine = linesInSourceFile.get(sourceIndex); + String genLine = linesInGeneratedEBPFProgram.get(genIndex); + int lineStart = sourceLine.indexOf(genLine); + if (lineStart == -1) { + return Map.of(); } + generatedToSourceLine.put(genIndex, new Line(sourceIndex, lineStart)); + start = false; + sourceIndex++; + genIndex++; } - return Map.of(); + + return generatedToSourceLine; } private @Nullable Optional obtainedPathToVMLinuxHeader = null; From ea8eecc83c058f1adcf8683cc044b9d017f66118 Mon Sep 17 00:00:00 2001 From: Johannes Bechberger Date: Sat, 6 Apr 2024 23:37:36 +0200 Subject: [PATCH 10/11] Improve naming of generated Impl classes Put them in the same package as the base class --- .../ebpf/bpf/processor/Processor.java | 48 +++++++++++++++---- .../me/bechberger/ebpf/bpf/BPFProgram.java | 7 ++- .../ebpf/bpf/MapGenerationTest.java | 4 +- .../ebpf/bpf/TypeProcessingTest.java | 12 ++--- 4 files changed, 49 insertions(+), 22 deletions(-) diff --git a/bpf-processor/src/main/java/me/bechberger/ebpf/bpf/processor/Processor.java b/bpf-processor/src/main/java/me/bechberger/ebpf/bpf/processor/Processor.java index cb36bbc..1876b24 100644 --- a/bpf-processor/src/main/java/me/bechberger/ebpf/bpf/processor/Processor.java +++ b/bpf-processor/src/main/java/me/bechberger/ebpf/bpf/processor/Processor.java @@ -14,10 +14,7 @@ import javax.annotation.processing.SupportedAnnotationTypes; import javax.annotation.processing.SupportedSourceVersion; import javax.lang.model.SourceVersion; -import javax.lang.model.element.Element; -import javax.lang.model.element.Modifier; -import javax.lang.model.element.TypeElement; -import javax.lang.model.element.VariableElement; +import javax.lang.model.element.*; import javax.lang.model.type.TypeMirror; import javax.sound.sampled.Line; import javax.tools.Diagnostic; @@ -96,19 +93,17 @@ public void processBPFProgram(TypeElement typeElement) { System.out.println("Compiled eBPF program " + bytes.length + " bytes"); this.processingEnv.getMessager().printMessage(Diagnostic.Kind.OTHER, "Compiled eBPF program", typeElement); - String pkg = typeElement.getQualifiedName().toString(); - pkg = pkg.substring(0, pkg.lastIndexOf('.')).toLowerCase(); - String name = typeElement.getSimpleName().toString() + "Impl"; + ImplName implName = typeToImplName(typeElement); - TypeSpec typeSpec = createType(typeElement.getSimpleName() + "Impl", typeElement.asType(), bytes, + TypeSpec typeSpec = createType(implName.className, typeElement.asType(), bytes, typeProcessorResult.fields(), combinedCode); try { - var file = processingEnv.getFiler().createSourceFile(pkg + "." + name, typeElement); + var file = processingEnv.getFiler().createSourceFile(implName.fullyQualifiedClassName, typeElement); // delete file if it exists if (Files.exists(Path.of(file.toUri()))) { Files.delete(Path.of(file.toUri())); } - JavaFile javaFile = JavaFile.builder(pkg, typeSpec).build(); + JavaFile javaFile = JavaFile.builder(implName.packageName, typeSpec).build(); try (var writer = file.openWriter()) { writer.write(javaFile.toString()); } @@ -118,6 +113,39 @@ public void processBPFProgram(TypeElement typeElement) { } } + public record ImplName(String className, String fullyQualifiedClassName, String packageName) {} + + /** Creates the name of the implementing class */ + private static ImplName classNameToImplName(String packageName, String className) { + if (packageName.isEmpty()) { + return new ImplName(className + "Impl", className + "Impl", packageName); + } + var simpleName = className.replace(".", "$") + "Impl"; + return new ImplName(simpleName, packageName + "." + simpleName, packageName); + } + + private static ImplName typeToImplName(TypeElement type) { + // problem type might be nested + List classNameParts = new ArrayList<>(); + var t = type; + classNameParts.add(t.getSimpleName().toString()); + while (t.getNestingKind() == NestingKind.MEMBER) { + if (t.getEnclosingElement() instanceof TypeElement typeElement) { + t = typeElement; + classNameParts.add(0, t.getSimpleName().toString()); + } + } + String qualifiedName = t.getQualifiedName().toString(); + return classNameToImplName(qualifiedName.substring(0, qualifiedName.length() - t.getSimpleName().length() - 1), + String.join(".", classNameParts)); + } + + public static ImplName classToImplName(Class klass) { + if (klass.getPackageName().isEmpty()) { + return classNameToImplName("", klass.getName()); + } + return classNameToImplName(klass.getPackageName(), klass.getName().substring(klass.getPackageName().length() + 1)); + } /** * GZIP the bytecode and then turns it into a Base64 String diff --git a/bpf/src/main/java/me/bechberger/ebpf/bpf/BPFProgram.java b/bpf/src/main/java/me/bechberger/ebpf/bpf/BPFProgram.java index ea225d5..050250d 100644 --- a/bpf/src/main/java/me/bechberger/ebpf/bpf/BPFProgram.java +++ b/bpf/src/main/java/me/bechberger/ebpf/bpf/BPFProgram.java @@ -3,6 +3,7 @@ import me.bechberger.ebpf.annotations.bpf.BPF; import me.bechberger.ebpf.bpf.map.*; import me.bechberger.ebpf.bpf.map.BPFRingBuffer.BPFRingBufferError; +import me.bechberger.ebpf.bpf.processor.Processor; import me.bechberger.ebpf.bpf.raw.Lib; import me.bechberger.ebpf.bpf.raw.LibraryLoader; import me.bechberger.ebpf.type.BPFType; @@ -71,10 +72,8 @@ public BPFLoadError(String message) { @SuppressWarnings("unchecked") private static Class getImplClass(Class clazz) { try { - String pkg = clazz.getCanonicalName(); - pkg = pkg.substring(0, pkg.lastIndexOf('.')).toLowerCase(); - String name = clazz.getSimpleName() + "Impl"; - return (Class)Class.forName(pkg + "." + name); + var implName = Processor.classToImplName(clazz); + return (Class)Class.forName(implName.fullyQualifiedClassName()); } catch (Exception e) { throw new RuntimeException(e); } diff --git a/bpf/src/test/java/me/bechberger/ebpf/bpf/MapGenerationTest.java b/bpf/src/test/java/me/bechberger/ebpf/bpf/MapGenerationTest.java index 0dcdeda..3edbb8a 100644 --- a/bpf/src/test/java/me/bechberger/ebpf/bpf/MapGenerationTest.java +++ b/bpf/src/test/java/me/bechberger/ebpf/bpf/MapGenerationTest.java @@ -28,10 +28,10 @@ public static abstract class Prog extends BPFProgram { private static final int TASK_COMM_LEN = 16; @Type(name = "event") - public record Event(@Unsigned int pid, @Size(FILE_NAME_LEN) String filename, @Size(TASK_COMM_LEN) String comm) {} + record Event(@Unsigned int pid, @Size(FILE_NAME_LEN) String filename, @Size(TASK_COMM_LEN) String comm) {} @BPFMapDefinition(maxEntries = 256 * 4096) - protected BPFRingBuffer rb; + BPFRingBuffer rb; static final String EBPF_PROGRAM = """ #include "vmlinux.h" diff --git a/bpf/src/test/java/me/bechberger/ebpf/bpf/TypeProcessingTest.java b/bpf/src/test/java/me/bechberger/ebpf/bpf/TypeProcessingTest.java index 03b956a..3c6d933 100644 --- a/bpf/src/test/java/me/bechberger/ebpf/bpf/TypeProcessingTest.java +++ b/bpf/src/test/java/me/bechberger/ebpf/bpf/TypeProcessingTest.java @@ -20,29 +20,29 @@ public static abstract class SimpleRecordTestProgram extends BPFProgram { static final String EBPF_PROGRAM = "#include \"vmlinux.h\""; @Type - public record SimpleRecord(@Unsigned int value) { + record SimpleRecord(@Unsigned int value) { } @Type(name = "Name") - public record SimpleNamedRecord(@Unsigned int value) { + record SimpleNamedRecord(@Unsigned int value) { } @Type - public record RecordWithString(@Size(10) String name) { + record RecordWithString(@Size(10) String name) { } static final int SIZE = 11; @Type - public record RecordWithSizeFromVariable(@Size(SIZE) String name) { + record RecordWithSizeFromVariable(@Size(SIZE) String name) { } @Type - public record RecordWithMultipleMembers(byte value, @Size(10) String name, long longValue) { + record RecordWithMultipleMembers(byte value, @Size(10) String name, long longValue) { } @Type - public record RecordWithOtherType(@Unsigned int value, SimpleRecord other) { + record RecordWithOtherType(@Unsigned int value, SimpleRecord other) { } } From 57c6da3d59f43901028dec6982aa4cb591a63004 Mon Sep 17 00:00:00 2001 From: Johannes Bechberger Date: Sun, 7 Apr 2024 10:57:52 +0200 Subject: [PATCH 11/11] Small refactoring --- README.md | 1 + rawbcc/bin/jextract_bindings.py | 12 ++++++------ 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 2fc169b..7d275a5 100644 --- a/README.md +++ b/README.md @@ -225,6 +225,7 @@ You can run them using the `./run_bpf.sh` script. All examples have accompanying | Ansil H | [RingSample](bpf/src/main/java/me/bechberger/ebpf/samples/RingSample.java) | Record openat calls in a ring buffer | | | [TypeProcessingSample](bpf/src/main/java/me/bechberger/ebpf/samples/TypeProcessingSample.java) | RingSample using the @Type annotation | | | [HashMapSample](bpf/src/main/java/me/bechberger/ebpf/samples/HashMapSample.java) | Record openat calls in a hash map | +| | [TypeProcessingSample](bpf/src/main/java/me/bechberger/ebpf/samples/TypeProcessingSample.java) | RingSample using more code generation | Classes and Methods ------- diff --git a/rawbcc/bin/jextract_bindings.py b/rawbcc/bin/jextract_bindings.py index 74c2364..6a7bd57 100644 --- a/rawbcc/bin/jextract_bindings.py +++ b/rawbcc/bin/jextract_bindings.py @@ -100,22 +100,22 @@ def create_modified_lib_header(header: Path, combined_header: Path, modified_hea f.write(line) -def assert_java21(): - """ assert that we are running JDK 21 by calling java -version """ +def assert_java22(): + """ assert that we are running JDK 22+ by calling java -version """ try: output = subprocess.check_output("java -version", shell=True, stderr=subprocess.STDOUT).decode() - assert "version \"22" in output, \ - "Please run this script with JDK 22" + assert any(f"version \"{v}" in output for v in range(22, 30)), \ + "Please run this script with JDK 22+" except FileNotFoundError: - print("Please install JDK 22 and run this script with JDK 22") + print("Please install JDK 22+ and run this script with JDK 22+") sys.exit(1) def run_jextract(header: Path, mod_header_folder: Path, dest_path: Path, package: str = "", name: str = "BPF", delete_dest_path: bool = False): - assert_java21() + assert_java22() print("Running jextract") os.makedirs(mod_header_folder, exist_ok=True) combined_header = mod_header_folder / "combined_lib.h"