diff --git a/annotations/src/main/java/me/bechberger/ebpf/annotations/bpf/BuiltinBPFFunction.java b/annotations/src/main/java/me/bechberger/ebpf/annotations/bpf/BuiltinBPFFunction.java index 70e1c19..9a0ff6b 100644 --- a/annotations/src/main/java/me/bechberger/ebpf/annotations/bpf/BuiltinBPFFunction.java +++ b/annotations/src/main/java/me/bechberger/ebpf/annotations/bpf/BuiltinBPFFunction.java @@ -40,7 +40,7 @@ *
  • {@code $strlen$this}: length of the {@code $this} interpreted as a string literal
  • *
  • {@code $strlen$argN}: length of the {@code $argN} interpreted as a string literal
  • *
  • {@code $str$argN}: Asserts that {@code $argN} is a string literal
  • - *
  • {@code $pointery$argN}: if {@code $argN} is not a pointer (or an array or a string), then prefix it with {@code &} and assume that it is an lvalue
  • + *
  • {@code $pointery$argN}: if {@code $argN} is not a pointer (or an array or a string), then prefix it with {@code &} and assign it inline to a variable if needed
  • * *

    * Example: {@snippet : diff --git a/bpf-compiler-plugin/src/main/java/me/bechberger/ebpf/bpf/compiler/MethodTemplate.java b/bpf-compiler-plugin/src/main/java/me/bechberger/ebpf/bpf/compiler/MethodTemplate.java index b9d30d6..c45b54e 100644 --- a/bpf-compiler-plugin/src/main/java/me/bechberger/ebpf/bpf/compiler/MethodTemplate.java +++ b/bpf-compiler-plugin/src/main/java/me/bechberger/ebpf/bpf/compiler/MethodTemplate.java @@ -10,6 +10,7 @@ import me.bechberger.ebpf.bpf.compiler.MethodTemplateCache.TemplateRenderException; import me.bechberger.ebpf.type.Ptr; import me.bechberger.ebpf.type.TypeUtils; +import org.intellij.lang.annotations.RegExp; import org.jetbrains.annotations.Nullable; import javax.lang.model.type.TypeKind; @@ -23,6 +24,32 @@ */ public record MethodTemplate(String methodName, String raw, List parts) { + public class NewVariableContext { + + private record NewVariable(String name, String value) { + } + + private List newVariables = new ArrayList<>(); + + public String request(String value) { + var name = "___pointery__" + newVariables.size(); + newVariables.add(new NewVariable(name, value)); + return name; + } + + @Override + public String toString() { + return newVariables.stream().map(nv -> String.format("auto %s = %s;", nv.name, nv.value)).collect(Collectors.joining(" ")); + } + + public VerbatimExpression wrap(VerbatimExpression expression) { + if (newVariables.isEmpty()) { + return expression; + } + return new VerbatimExpression(String.format("({%s %s;})", this, expression.toPrettyString())); + } + } + public record CallArgs(@Nullable CAST.Expression thisExpression, List arguments, List typeArguments, @@ -39,25 +66,29 @@ public record CallProps(String methodName, CallArgs args) { * A part of a template, modelled after the different placeholders in the template in {@link BuiltinBPFFunction} */ sealed interface TemplatePart { - String render(CallProps props); + default String render(CallProps props) { + return render(props, null); + } + + String render(CallProps props, @Nullable NewVariableContext context); record Verbatim(String verb) implements TemplatePart { @Override - public String render(CallProps props) { + public String render(CallProps props, @Nullable NewVariableContext context) { return verb; } } record Name() implements TemplatePart { @Override - public String render(CallProps props) { + public String render(CallProps props, @Nullable NewVariableContext context) { return props.methodName; } } record Arg(int n) implements TemplatePart { @Override - public String render(CallProps props) { + public String render(CallProps props, @Nullable NewVariableContext context) { if (n >= props.args.arguments.size()) { throw new TemplateRenderException("Argument " + (n + 1) + " not given for $arg" + (n + 1)); } @@ -67,7 +98,7 @@ public String render(CallProps props) { record SubArgs(int n) implements TemplatePart { @Override - public String render(CallProps props) { + public String render(CallProps props, @Nullable NewVariableContext context) { return IntStream.range(n, props.args.arguments.size()) .mapToObj(i -> props.args.arguments.get(i).toPrettyString()) .collect(Collectors.joining(", ")); @@ -76,14 +107,14 @@ public String render(CallProps props) { record Args() implements TemplatePart { @Override - public String render(CallProps props) { - return new SubArgs(0).render(props); + public String render(CallProps props, @Nullable NewVariableContext context) { + return new SubArgs(0).render(props, context); } } record This() implements TemplatePart { @Override - public String render(CallProps props) { + public String render(CallProps props, @Nullable NewVariableContext context) { if (props.args.thisExpression == null) { throw new TemplateRenderException("No this expression given for $this"); } @@ -92,7 +123,7 @@ public String render(CallProps props) { } sealed interface StrLen extends TemplatePart { - static String render(Expression arg) { + static String render(Expression arg, @Nullable NewVariableContext context) { if (arg instanceof Constant.StringConstant constant) { return Integer.toString(constant.value().length()); } @@ -102,24 +133,24 @@ static String render(Expression arg) { record StrLenArg(int n) implements StrLen { @Override - public String render(CallProps props) { - return StrLen.render(props.args.arguments.get(n)); + public String render(CallProps props, @Nullable NewVariableContext context) { + return StrLen.render(props.args.arguments.get(n), context); } } record StrLenThis() implements StrLen { @Override - public String render(CallProps props) { + public String render(CallProps props, @Nullable NewVariableContext context) { if (props.args.thisExpression == null) { throw new TemplateRenderException("No this expression given for $strlen$this"); } - return StrLen.render(props.args.thisExpression); + return StrLen.render(props.args.thisExpression, context); } } record StrArg(int n) implements TemplatePart { @Override - public String render(CallProps props) { + public String render(CallProps props, @Nullable NewVariableContext context) { if (n >= props.args.arguments.size()) { throw new TemplateRenderException("Argument " + (n + 1) + " not given for $str" + (n + 1)); } @@ -133,7 +164,7 @@ public String render(CallProps props) { record TypeArgument(int n) implements TemplatePart { @Override - public String render(CallProps props) { + public String render(CallProps props, @Nullable NewVariableContext context) { if (n >= props.args.typeArguments.size() || props.args.typeArguments.get(n) == null){ throw new TemplateRenderException("Template type argument " + (n + 1) + " not given"); } @@ -143,7 +174,7 @@ public String render(CallProps props) { record ClassTypeArgument(int n) implements TemplatePart { @Override - public String render(CallProps props) { + public String render(CallProps props, @Nullable NewVariableContext context) { if (n >= props.args.classTypeArguments.size() || props.args.classTypeArguments.get(n) == null){ throw new TemplateRenderException("Template class type argument " + (n + 1) + " not given"); } @@ -153,11 +184,15 @@ public String render(CallProps props) { record PointeryArg(int n) implements TemplatePart { @Override - public String render(CallProps props) { + public String render(CallProps props, @Nullable NewVariableContext context) { if (n >= props.args.arguments.size()) { throw new TemplateRenderException("Argument " + (n + 1) + " not given for $pointery" + (n + 1)); } - return "&" + props.args.arguments.get(n).toPrettyString(); + var inner = props.args.arguments.get(n).toPrettyString(); + if (context == null || inner.matches("[(]*[a-zA-z_]+[)]*")) { + return "&" + inner; + } + return "&" + context.request(inner); } } } @@ -304,7 +339,9 @@ static MethodTemplate parse(String methodName, String template, @Nullable Method } public Expression call(CallArgs args) { - List renderedParts = parts.stream().map(part -> part.render(new CallProps(methodName, args))) + NewVariableContext context = new NewVariableContext(); + List renderedParts = parts.stream() + .map(part -> part.render(new CallProps(methodName, args), context)) .toList(); StringBuilder sb = new StringBuilder(); for (int i = 0; i < parts.size(); i++) { @@ -321,6 +358,6 @@ public Expression call(CallArgs args) { sb.append(renderedParts.get(i)); } } - return new VerbatimExpression(sb.toString()); + return context.wrap(new VerbatimExpression(sb.toString())); } } diff --git a/bpf-processor/src/main/java/me/bechberger/ebpf/bpf/processor/CompilationCache.java b/bpf-processor/src/main/java/me/bechberger/ebpf/bpf/processor/CompilationCache.java index 3e71f6b..7d7497d 100644 --- a/bpf-processor/src/main/java/me/bechberger/ebpf/bpf/processor/CompilationCache.java +++ b/bpf-processor/src/main/java/me/bechberger/ebpf/bpf/processor/CompilationCache.java @@ -132,4 +132,8 @@ private List cachedFiles() { throw new RuntimeException(e); } } + + public Path getCacheFolder() { + return cacheFolder; + } } 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 5b68e84..49cef94 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 @@ -547,7 +547,7 @@ private byte[] compile(CombinedCode code, Path ebpfFile) { try { var tempFile = Files.createTempFile("ebpf", ".o"); tempFile.toFile().deleteOnExit(); - List command = List.of(newestClang, "-O2", "-g", "-target", "bpf", "-c", "-o", + List command = List.of(newestClang, "-O2", "-g", "-std=gnu2y", "-target", "bpf", "-c", "-o", tempFile.toString(), "-I", vmlinuxHeader.getParent().toString(), "-D__TARGET_ARCH_" + getArch(), "-Wno-parentheses-equality", "-Wno-unused-value", "-Wreturn-type", "-Wno-incompatible-pointer-types-discards-qualifiers", @@ -625,17 +625,41 @@ private Path obtainPathToVMLinuxHeader() { } // else run bpftool btf dump file /sys/kernel/btf/vmlinux format c // save output to a temp file and return the path to the temp file - var tempDirectory = Files.createTempDirectory("vmlinux"); - tempDirectory.toFile().deleteOnExit(); - var tempFile = tempDirectory.resolve("vmlinux.h"); - var errorFile = tempDirectory.resolve("error.txt"); + var cacheFolder = cache.getCacheFolder(); + var vmLinuxFile = cacheFolder.resolve("vmlinux.h"); + if (Files.exists(vmLinuxFile)) { + return vmLinuxFile; + } + var errorFile = cacheFolder.resolve("vmlinux_error.txt"); var process = new ProcessBuilder("bpftool", "btf", "dump", "file", "/sys/kernel/btf/vmlinux", "format", - "c").redirectOutput(tempFile.toFile()).redirectError(errorFile.toFile()).start(); + "c").redirectOutput(vmLinuxFile.toFile()).redirectError(errorFile.toFile()).start(); if (process.waitFor() != 0) { throw new UnsupportedOperationException("Could not obtain vmlinux.h header file via 'bpftool btf " + "dump file /sys/kernel/btf/vmlinux format c'" + Files.readString(errorFile)); + } else { + Files.delete(errorFile); } - return tempFile; + // comment lines + // typedef _Bool bool; + // enum { + // false = 0, + // true = 1, + // }; + String content = Files.readString(vmLinuxFile); + content = content.replace("typedef _Bool bool;", "// typedef _Bool bool") + .replaceAll(""" + enum \\{ + \\s+false = 0, + \\s+true = 1, + }; + """, """ + // enum { + // false = 0, + // true = 1, + // }; + """); + Files.writeString(vmLinuxFile, content); + return vmLinuxFile; } catch (IOException | InterruptedException e) { throw new RuntimeException(e); } diff --git a/bpf-samples/src/main/java/me/bechberger/ebpf/samples/HashMapSample.java b/bpf-samples/src/main/java/me/bechberger/ebpf/samples/HashMapSample.java index 68f288c..a735ed2 100644 --- a/bpf-samples/src/main/java/me/bechberger/ebpf/samples/HashMapSample.java +++ b/bpf-samples/src/main/java/me/bechberger/ebpf/samples/HashMapSample.java @@ -38,8 +38,7 @@ public void enterOpenat2(int dfd, String filename, Ptr how) { // increment the counter at map[comm] Ptr<@Unsigned Integer> counter = map.bpf_get(comm); if (counter == null) { - @Unsigned int one = 1; - map.put(comm, one); + map.put(comm, 1); } else { counter.set(counter.val() + 1); } diff --git a/bpf/src/main/java/me/bechberger/ebpf/bpf/Scheduler.java b/bpf/src/main/java/me/bechberger/ebpf/bpf/Scheduler.java index dbdcf78..c13bc0f 100644 --- a/bpf/src/main/java/me/bechberger/ebpf/bpf/Scheduler.java +++ b/bpf/src/main/java/me/bechberger/ebpf/bpf/Scheduler.java @@ -139,6 +139,70 @@ public interface Scheduler { /** No such process error code */ final int ESRCH = 3; + final class PerProcessFlags { + /** I'm a virtual CPU */ + public static final int PF_VCPU = 0x00000001; + /** I am an IDLE thread */ + public static final int PF_IDLE = 0x00000002; + /** Getting shut down */ + public static final int PF_EXITING = 0x00000004; + /** Coredumps should ignore this task */ + public static final int PF_POSTCOREDUMP = 0x00000008; + /** Task is an IO worker */ + public static final int PF_IO_WORKER = 0x00000010; + /** I'm a workqueue worker */ + public static final int PF_WQ_WORKER = 0x00000020; + /** Forked but didn't exec */ + public static final int PF_FORKNOEXEC = 0x00000040; + /** Process policy on mce errors */ + public static final int PF_MCE_PROCESS = 0x00000080; + /** Used super-user privileges */ + public static final int PF_SUPERPRIV = 0x00000100; + /** Dumped core */ + public static final int PF_DUMPCORE = 0x00000200; + /** Killed by a signal */ + public static final int PF_SIGNALED = 0x00000400; + /** Allocating memory to free memory. See memalloc\_noreclaim\_save() */ + public static final int PF_MEMALLOC = 0x00000800; + /** set\_user() noticed that RLIMIT\_NPROC was exceeded */ + public static final int PF_NPROC_EXCEEDED = 0x00001000; + /** If unset the fpu must be initialized before use */ + public static final int PF_USED_MATH = 0x00002000; + /** Kernel thread cloned from userspace thread */ + public static final int PF_USER_WORKER = 0x00004000; + /** This thread should not be frozen */ + public static final int PF_NOFREEZE = 0x00008000; + public static final int PF__HOLE__00010000 = 0x00010000; + /** I am kswapd */ + public static final int PF_KSWAPD = 0x00020000; + /** All allocations inherit GFP\_NOFS. See memalloc\_nfs\_save() */ + public static final int PF_MEMALLOC_NOFS = 0x00040000; + /** All allocations inherit GFP\_NOIO. See memalloc\_noio\_save() */ + public static final int PF_MEMALLOC_NOIO = 0x00080000; + /** Throttle writes only against the bdi I write to, I am cleaning dirty pages from some other bdi. */ + public static final int PF_LOCAL_THROTTLE = 0x00100000; + /** I am a kernel thread */ + public static final int PF_KTHREAD = 0x00200000; + /** Randomize virtual address space */ + public static final int PF_RANDOMIZE = 0x00400000; + /** All allocation requests will clear \_\_GFP\_DIRECT\_RECLAIM */ + public static final int PF_MEMALLOC_NORECLAIM = 0x00800000; + /** All allocation requests will inherit \_\_GFP\_NOWARN */ + public static final int PF_MEMALLOC_NOWARN = 0x01000000; + public static final int PF__HOLE__02000000 = 0x02000000; + /** Userland is not allowed to meddle with cpus\_mask */ + public static final int PF_NO_SETAFFINITY = 0x04000000; + /** Early kill for mce process policy */ + public static final int PF_MCE_EARLY = 0x08000000; + /** Allocations constrained to zones which allow long term pinning. See memalloc\_pin\_save() */ + public static final int PF_MEMALLOC_PIN = 0x10000000; + /** plug has ts that needs updating */ + public static final int PF_BLOCK_TS = 0x20000000; + public static final int PF__HOLE__40000000 = 0x40000000; + /** This thread called freeze\_processes() and should not be frozen */ + public static final int PF_SUSPEND_TASK = 0x80000000; + } + /* * scx_bpf_error() wraps the scx_bpf_error_bstr() kfunc with variadic arguments * instead of an array of u64. Invoking this macro will cause the scheduler to 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 052ed7d..459e511 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 @@ -86,8 +86,8 @@ public enum PutMode implements Enum { * Put a value into the map, updates it if its already there *

    Usage in ebpf:

    * Update the value in the map with the given key - * @param key key key if pointery, otherwise an lvalue (like a variable) - * @param value value if pointery, otherwise an lvalue (like a variable) + * @param key key + * @param value value * @return success? * @see me.bechberger.ebpf.runtime.helpers.BPFHelpers#bpf_map_update_elem(Ptr, Ptr, Ptr, long) */ @@ -105,8 +105,8 @@ public boolean put(K key, V value, PutMode mode) { * *

    Usage in ebpf:

    * Update the value in the map with the given key - * @param key key key if pointery, otherwise an lvalue (like a variable) - * @param value value if pointery, otherwise an lvalue (like a variable) + * @param key key + * @param value value * @return success? * @see me.bechberger.ebpf.runtime.helpers.BPFHelpers#bpf_map_update_elem(Ptr, Ptr, Ptr, long) */ @@ -285,7 +285,7 @@ public void forEach(BiConsumer action) { * Obtain a pointer to the element in the map with the given key, * or {@link Ptr#ofNull()} if the key is not present * - * @param key key if pointery, otherwise an lvalue (like a variable) + * @param key key * @return pointer to the value or {@link Ptr#ofNull()} * * @see me.bechberger.ebpf.runtime.helpers.BPFHelpers#bpf_map_lookup_elem(Ptr, Ptr) diff --git a/bpf/src/main/java/me/bechberger/ebpf/bpf/map/BPFQueueAndStack.java b/bpf/src/main/java/me/bechberger/ebpf/bpf/map/BPFQueueAndStack.java index 7b5ad97..eadb11d 100644 --- a/bpf/src/main/java/me/bechberger/ebpf/bpf/map/BPFQueueAndStack.java +++ b/bpf/src/main/java/me/bechberger/ebpf/bpf/map/BPFQueueAndStack.java @@ -41,7 +41,7 @@ public abstract class BPFQueueAndStack extends BPFMap { * Push a value onto the stack or the back of the queue *

    Usage in ebpf:

    * Update the value in the map with the given key - * @param value value if pointery, otherwise an lvalue (like a variable) + * @param value value * @return success? * @see me.bechberger.ebpf.runtime.helpers.BPFHelpers#bpf_map_update_elem(Ptr, Ptr, Ptr, long) */ @@ -73,7 +73,7 @@ public boolean push(V value) { /** * Get the value from the top of the stack or the front of the queue, in eBPF * - * @param value value if pointery, otherwise an lvalue (like a variable) + * @param value value * @return true if the value was peeked, false on error * @see me.bechberger.ebpf.runtime.helpers.BPFHelpers#bpf_map_peek_elem(Ptr, Ptr) */ @@ -102,7 +102,7 @@ public boolean bpf_peek(V value) { /** * Pop the value from the top of the stack or the front of the queue, in eBPF * - * @param value value if pointery, otherwise an lvalue (like a variable) + * @param value value * @return true if the value was popped, false on error * @see me.bechberger.ebpf.runtime.helpers.BPFHelpers#bpf_map_pop_elem(Ptr, Ptr) */ diff --git a/shared/src/main/java/me/bechberger/ebpf/shared/Util.java b/shared/src/main/java/me/bechberger/ebpf/shared/Util.java index 2334fca..422c840 100644 --- a/shared/src/main/java/me/bechberger/ebpf/shared/Util.java +++ b/shared/src/main/java/me/bechberger/ebpf/shared/Util.java @@ -55,4 +55,5 @@ public static String computeEditDistance(String a, String b) { public static String getClosestString(String target, Collection options) { return options.stream().min(Comparator.comparing(a -> computeEditDistance(target, a))).orElse(target); } + }