Very entertaining article, thanks for that. I recognized a lot of my own a-ha moments w.r.t eBPF/bcc reading all the sections (especially the kern_version thing).
You seem to want to avoid either LLVM (understandable) or run-time compilation (would be great, but it's hard as you state).
I'd like to introduce you to ply(1). It's a DSL interpreter (think dtrace) that compiles an awk/dtrace like language to eBPF and wires up the necessary pipes/maps/... Link https://github.com/iovisor/ply.
Please take some time to read the code. It's tiny. Every time I come away with a renewed appreciation for well written C (and wkz).
Please allow me to follow up with one of my own https://wkz.github.io/ply/, where you can see some examples and read through the manual.
I'm currently working on a new version which will be easier to extend and will have a full C-compliant type system. Still tiny though. Hope to have a commit worthy of a git tag soon. :)
Thanks for the article. Your writing style is fun and easy to read. Learning more about tracepoints and bpf is also on my TODO list, and I'll certainly re-read this later.
The 4.15.0-33 output from uname -r does not mean that your kernel version is 4.15.0, it's probably based on a later 4.15. The ubuntu package version is 4.15.0-33.36. I'm not sure what the 36 means here. There are no upstream 4.15 releases any more, the last was 4.15.18, in April 2018.
I know how it works in Debian, which is similar. E.g.:
-$ uname -a
Linux mekker 4.17.0-3-amd64 #1 SMP Debian 4.17.17-1 (2018-08-18) x86_64 GNU/Linux
uname -r says 4.17.0-3, but that's the package name (linux-image-4.17.0-3-amd64). The actual version is 4.17.17-1, which means it's 4.17.17 with Debian patches.
The number 3 is the an ABI compatibility number. So, as long as the kernel ABI keeps the same, newer versions (e.g. 4.17.18) can use the same package name (4.17.0-3). However, if the ABI changes, the -3 is bumped to -4, it causes a new package in the repository and when installing it, the user has to recompile self-made modules.
The only reason for the .0 in the package version name is because there might be something expecting three numbers in that place (like it was with 2.6.x kernels).
My biggest grievance with BCC has been, as noted in the article, that it requires LLVM to be linked in. Also, it requires a Python interpreter on the target machine. I've also wondered why eBPF can't be pre-compiled.
Installing those dependencies on a regular PC is easy, but not so on an embedded system. I tried cross-compiling the dependencies once, but got stuck at some point. It would've been hard to justify the time spent if I'd continued, so I dropped it. I'm really happy that someone has invested time into this issue.
I wrote a not very well known LuaJIT to BPF compiler as a part of bcc precisely because of this. It doesn't depend on anything besides LuaJIT (no LLVM, no C, no libbpf, etc). Caveats: it's Lua, it doesn't fast track all the latest from bcc as it's a "for fun" project.
One of the big reasons is that if you're using kernel headers and internal structures, like with kprobes, they're unstable APIs, and the struct offsets may change between kernel versions.
You can compile for each kernel type you're going to use, but that is more effort and foresight. There's also the rewriting of map file descriptors in the bytecode itself on load time, but libbpf generally handles this.
I do not think that I would mind so much having to re-compile between kernel versions if it's just a matter of running "make" and copying the binaries to the target.
I spent a good chunk of my Christmas break last year trying to get LLVM and bcc working on Amazon Linux without any success. So it can be difficult even on a non-embedded system.
https://qmonnet.github.io/whirl-offload/2016/09/01/dive-into...