Skip to Content
axiomOSmaineBPF SubsystemLoader

Loader

Relevant source files

The loader is a libbpf-free ELF64 reader. It turns an ELF object into one or more LoadedProgram values (flat instruction vectors with a program type) plus map definitions. It does not verify anything; verification is the next stage (Verifier).

Stages

ELF parsing

ElfParser::new rejects data shorter than 64 bytes, a bad magic, a non-64-bit class, an unknown endianness byte, and any e_machine other than EM_BPF (247). Both little- and big-endian ELF files are parsed. Errors are typed in LoadError (for example ElfTooSmall, InvalidMagic, UnsupportedClass, UnsupportedMachine, SectionOutOfBounds).

The loader enforces per-object counts: the defaults are 8 programs and 16 maps on the embedded profile, and 64 programs and 128 maps on the cloud profile (TooManyPrograms, TooManyMaps).

Map definitions

Map definitions are read from a .maps section as fixed 20-byte records (type, key_size, value_size, max_entries, flags, each a native-endian u32). The loader recognises these raw type numbers: 0 unspec, 1 hash, 2 array, 3 prog array, 4 perf event array, 5 per-CPU hash, 6 per-CPU array, 7 stack trace, 8 cgroup array, and 27 ring buffer; types 9, 10 and 11 (LRU hash, LRU per-CPU hash, LPM trie) exist only in cloud builds. Anything else is UnsupportedMapType.

Recognising a map type in an ELF definition is separate from being creatable at runtime. The kernel manager only creates hash, array, ring buffer and time-series maps (see Maps and Helpers). BTF-defined maps are not parsed: the source carries a TODO for BTF parsing on the cloud profile.

Program sections and types

Executable sections (SHF_EXECINSTR) are treated as programs. If at least one executable section is not named .text or text, those two names are considered subprogram sections and are skipped as entry points. The program type is derived from the section name prefix:

Section prefixBpfProgType
socket, sk_SocketFilter
kprobe, kretprobe, fentry, fexitKprobe
tracepoint, tp/, raw_tracepoint, raw_tp/, iter/Tracepoint
xdpXdp
perf_eventPerfEvent
cgroupCgroupSkb
sched_cls, tcSchedCls
lwt_LwtIn
anything elseSocketFilter (default)

The program type does not select the hook: the attach type passed to BPF_PROG_ATTACH does (see Hooks). Note that BpfManager::load_program_authorized takes only the first program found in an object (obj.programs().first()).

Relocations

Relocator handles ELF REL entries against the symbol table:

RelocationHandling
R_BPF_64_64 (1)Map reference: the ld_imm64 is rewritten with src_reg = 1 (pseudo map) and imm set to the index of the named map in the object’s map list; an unknown map name is UndefinedSymbol
R_BPF_64_32 (10)Call: a known helper name is rewritten to the runtime helper ID; a function symbol defined in an executable section becomes a BPF-to-BPF pseudo call
R_BPF_64_ABS64, R_BPF_64_ABS32Accepted and left unchanged
other typesIgnored

Helper names resolve through the runtime HelperId enum, not the Linux uapi numbering. Seventeen names are recognised: bpf_map_lookup_elem, bpf_map_update_elem, bpf_map_delete_elem, bpf_probe_read, bpf_ktime_get_ns, bpf_trace_printk, bpf_get_prandom_u32, bpf_get_smp_processor_id, bpf_get_current_pid_tgid, bpf_get_current_uid_gid, bpf_get_current_comm, bpf_ringbuf_output, bpf_ringbuf_reserve, bpf_ringbuf_submit, bpf_ringbuf_discard, bpf_timeseries_push, and bpf_sensor_last_timestamp. A helper the loader does not know is left unrelocated, so the verifier rejects it rather than risking dispatch to a different helper. Names such as bpf_gpio_set or bpf_pwm_write are not in the relocation table; programs that need them must encode the helper ID directly (the bundled demo does this with kernel_abi constants).

Subprograms and call normalization

When a program section calls functions in other executable sections, those sections are appended after the root section (linked_call_sections) and calls are resolved to relative pseudo-call offsets. normalize then removes pseudo calls entirely by inline expansion, so the verifier only ever sees helper calls. If a program has no subprogram calls it is returned unchanged.

ConstantValue
BPF_PSEUDO_CALL (src_reg marker)1
FRAME_SIZE per inlined frame512 bytes
MAX_CALL_DEPTH8

Normalization rejects: recursion (RecursiveCall), depth over 8 (CallDepthExceeded), malformed or non-entry call targets (MalformedPseudoCall), expanded size over the profile instruction limit (ExpansionTooLarge), jump offsets that overflow i16 or leave their subprogram (JumpOffsetOverflow, JumpTargetOutOfRange), and direct r10-relative accesses outside the callee’s own frame (StackOffsetOutOfFrame). Each inlined callee’s stack accesses are displaced by its depth times the frame size. A source_map from new to original instruction indices is produced but is not yet threaded to LoadedProgram.

sys_bpf commands

sys_bpf(cmd, attr_ptr, size) requires size of at least size_of::<BpfAttr>(). Before dispatch it checks the caller’s BpfCapabilities. Commands defined in kernel_abi are numerous (mirroring Linux numbering), but the ABI catalog lists these as supported:

CommandValueCapability requiredNotes
BPF_MAP_CREATE0MAP_CREATEprog_type carries the map type, insn_cnt the key size, insns low 32 bits the value size and high 32 bits max_entries
BPF_MAP_LOOKUP_ELEM1MAP_READReturns -2 when the key is absent
BPF_MAP_UPDATE_ELEM2MAP_WRITEWrite access to the map is checked
BPF_MAP_DELETE_ELEM3MAP_WRITE
BPF_PROG_LOAD5PROGRAM_LOADRaw instructions; insn_cnt must be 1 to 4096; rejected when signature enforcement is on
BPF_OBJ_PIN6OBJECT_PINPath up to 256 bytes; offered access in file_flags
BPF_OBJ_GET7OBJECT_PINRequested access must be within the pin’s offered rights
BPF_PROG_ATTACH8any attach capability, then the one for the attach type
BPF_PROG_DETACH9as attach
BPF_OBJ_GET_INFO_BY_FD15MAP_READReturns a BpfObjectInfo (maps only)
BPF_PROG_LOAD_ELF36PROGRAM_LOADCustom command; insn_cnt is the file size (1 byte to 1 MiB) and insns the pointer
BPF_RINGBUF_POLL37MAP_READCustom, nonblocking; returns bytes copied, 0 if empty, -28 if the buffer is too small
BPF_PROG_UNLOAD101PROGRAM_LOADFails with busy while attached or referenced
BPF_MAP_DESTROY102MAP_WRITE
BPF_OBJ_UNPIN103OBJECT_PINOBJECT_ADMIN allows unpinning other owners’ pins
BPF_BENCH_EXEC100PRIVILEGED_VERIFYOnly compiled with the verifier-cost feature

Other command numbers hit the unknown-command path and return -1.

The load path in kernel/src/syscall/bpf.rs derives a BpfLoadAuthorization from the process’s capabilities: PRIVILEGED_VERIFY selects the Privileged verifier tier (otherwise Unprivileged), ACTUATE enables actuation helpers, and MAP_READ / MAP_WRITE decide which maps the program may reference. See Trust Model.

Error mapping (bpf_error_errno): out-of-memory and resource limits map to ENOMEM, busy objects to EBUSY, not-loaded to ENOENT, read-only map, signature rejected and permission denied to EPERM, and everything else to EINVAL. Several paths return a bare -1.

How userspace invokes loading

  • userspace/tools/signed_bpf_loader reads a signed container from /var/lib/rkbpf/programs/startup.rbpf (up to 256 KiB), fills a BpfAttr with insn_cnt as the length and insns as the pointer, and calls BPF_PROG_LOAD_ELF. It prints SIGNED_BPF_LOAD_OK or SIGNED_BPF_LOAD_REJECTED.
  • userspace/tools/bpf_loader is an end-to-end demo that creates an array map and a ring buffer map, loads a hand-assembled raw program with BPF_PROG_LOAD, attaches it to the timer hook, and polls the ring buffer. It also contains negative checks (unprivileged map create, load and attach denials).
  • examples/bpf/README.md is stale in places: it lists attach type 2 as syscall, whereas kernel_abi defines type 2 as GPIO and syscall entry as 5.
Last updated on