How this platform works
Your browser does more than you’d think. TCC (Tiny C Compiler) runs client-side via WASM, giving you instant syntax feedback as you type. No server roundtrip needed.
When you hit Run, your code gets saved to local storage and shipped to the backend.
The backend compiles it into a BPF object with clang, then spins up a throwaway VM just for your submission.
The VM
Every submission gets its own ephemeral sandbox:
- 1 vCPU (ARM64), 64MB RAM
- Linux 6.1 kernel with full BPF/tracepoint support
- No network access, complete isolation
- 2 second lifetime, then it’s gone
VMs boot in under 50ms on bare-metal ARM (Graviton), powered by Firecracker. The guest kernel is stripped down to the essentials, just enough to run BPF programs and nothing more.
The VM boots into a statically-linked init process that mounts the minimum filesystems, loads your BPF object, runs the exercise scenario, and streams results back.
Getting events out
Your code includes ep_platform.h, which sets up a BPF ring buffer map behind the scenes.
The DEBUG_STR, DEBUG_NUM, and SUBMIT_STR macros all write structured events into this ring buffer.
Inside the VM, the init process polls the ring buffer and forwards events to the host over vsock. The host streams them to your browser as SSE (Server-Sent Events) in real time.
Developer tools
The same SSE stream feeds a set of inspection tools, so you can see what the kernel actually did with your program — not just its final answer.
Runtime inspection tabs
As soon as your code compiles, the backend disassembles the BPF object and streams it back:
- Bytecode shows the compiled instructions, each mapped to its C source line via the program’s
.BTF.extdebug info. Each instruction group is headed by the actual C source text for that line (not just a line number), so you can see which C statement compiled to which instructions. Conditional and unconditional jumps draw arrows in a gutter; each arrow labels its target with the corresponding C source line (e.g.→ L84) rather than a raw instruction index, and loop back-edges are highlighted differently from forward jumps. When a jump target has no C source mapping (compiler-internal code), it falls back to the instruction index. The disassembler is a pure-Rust eBPF decoder, checked instruction-for-instruction againstllvm-objdump. - Complexity counts instructions, maps, and loop back-edges straight from the ELF, so you can see how close a program sits to the verifier’s limits before it even runs.
Once the program runs in the VM, the loader captures more state and streams it as guest messages:
- Maps shows the contents of your BPF maps after the run, split into two groups. Your maps are the ones you declared with
SEC(".maps"); these are reopened by map id and decoded to hex plus an integer/string view. Compiler internals (collapsed by default) holds the.rodata,.bss, and.datamaps that clang and libbpf synthesize automatically — classified bybpf_map__is_internal()in the loader, not by name. The.rodatastring pool is split on NUL so each string literal is shown separately. If you see a map named.rodata.str1.1that you never wrote, it lives in this collapsed group. - Rate buckets every emitted event by a
CLOCK_MONOTONIC_RAWtimestamp captured in the VM, so the histogram reflects true firing rate. - Steps replays those timestamped events one at a time.
- Flame folds kernel stacks (for programs that record them with
bpf_get_stackid()) into a flame graph, resolving addresses against the guest’s/proc/kallsyms. - Timeline re-renders the kernel verifier’s log as an ordered walk when a program is rejected. When the verifier rejects a program and debug info is available, the diagnostic can also show provenance — which struct member or stack variable the offending access maps back to, pulled from DWARF debug info.
Editor tools
SEC("...")autocomplete is fed by a catalog extracted from the guest kernel itself (its syscall table, trace-event headers, andSystem.map), so it only ever suggests attach points that actually exist.- Helper autocomplete — typing a
bpf_prefix in the C body pops up matching eBPF helper functions. Highlighting one opens a doc panel with its signature, parameters, return value, and a link to the kernel docs. The same panel appears on hover over any helper name you’ve already typed. A searchable “Browse eBPF helpers” palette in the toolbar lets you explore all helpers without writing code first. - Answer diff — when a submission is wrong, the events panel shows the expected answer next to what you submitted, decoded as a string or number, so you can see exactly where you diverged. If your program emits multiple answers, a “multiple answers” note appears instead of a diff.
- Code folding — the gutter has folding arrows so you can collapse blocks you’re not focused on.
- Developer detail toggle — a toolbar toggle switches on extra panels: a compile → load → attach → run → result pipeline strip and timing badges for the compile, boot, and exec phases. These are off by default; enable them when you want to dig into what the platform is doing under the hood.
A rejected run’s verifier message is surfaced inline on the offending line, and a small rule table turns common compile/verifier failures into concrete fix suggestions.