Functions
When we refer to functions, we mean the functions you write in C or similar programming languages. In eBPF, the terms “function”, “program”, and “subroutine” are often used interchangeably. A program refers to a function with a single parameter (i.e., context), which can be attached to a hook point. A subroutine, also known as a BPF-to-BPF function, is a function with zero to five parameters that cannot be attached to a hook point but are called from a program or a special mechanism.
Calling Conventions
The eBPF instruction set defines the calling conventions for functions, which include programs, subroutines, helper functions, and kfuncs. Regardless of who defines the function, the same calling convention is used.<span>R0</span> register is used as the return value; unless it is a void function, the function should set this register before returning. Registers <span>R1</span>–<span>R5</span> are used to pass parameters, with <span>R1</span> passing the first parameter, <span>R2</span> passing the second parameter, and so on. Unlike the calling conventions of native architectures, parameters are never passed via the stack, so the maximum number of parameters is limited to 5. To pass more data, structures must be used. After a function call, the values of registers <span>R1</span>–<span>R5</span> are destroyed, and the verifier does not allow you to read them until they are set to known values.<span>R6</span>–<span>R9</span> are callee-saved registers, and their values are preserved during the function call.
BPF to BPF Functions (Subroutines)
The ability to call BPF-to-BPF functions was added in version 4.16. This allows BPF programs to reuse logic within the same program. A function can accept up to five parameters, as limited by the calling convention. It will receive a new stack frame, which will be released and reused after the program exits. If a pointer to memory on the caller’s stack is passed as a parameter, the function can also access that memory. The maximum call depth for functions is limited to 8, preventing meaningful recursive calls.
Function Inlining
By default, the compiler chooses to inline functions or treat them as separate functions. You can use attributes like <span>__attribute__((always_inline))/__always_inline</span> or <span>__attribute__((noinline))/__noinline</span> to encourage the compiler to inline or not inline. Inlined functions do not incur the overhead of function calls, as they become part of the calling function. Since parameters are known, inlined functions can also be optimized for each call site.
Per-Function Verification
Before version 5.6, the verifier re-verified the safety of functions at each call site. This means that if you have a function called 10 times, the verifier checks the safety of that function for each call with the given input. This somewhat undermines the purpose of functions, as each call still incurs the complexity of the verifier.
Starting from version 5.6, a distinction was made between “static” and “global” functions. Static functions are those marked with the static keyword in C code, while global functions are regular non-static functions. Static functions are still verified in the usual way. However, global functions undergo “per-function verification”. This means the verifier verifies each function once, and it can even be done out of order. Therefore, each function is verified only once, rather than at each call site. This change reduces verification time and complexity, but the verifier also imposes more restrictions.
The verifier will not assume any information about parameters, as it no longer checks each call site. So, even if a function is only called with the 32-bit unsigned integer 123, the verifier will assume that 0 – 4294967295 are possible values. Therefore, the function may require more input checks to pass the verifier.
The verifier also restricts return types to always be scalar (a number), and parameters must be pointers to program context and scalars. In subsequent kernel versions, this restriction on parameters has been relaxed, as detailed in later sections.
Global Function Replacement
Version 5.6 also introduced the ability to replace global functions. The main use case is libxdp, which utilizes this feature to implement XDP program chains from the scheduler.
For more information, see program type BPF_PROG_TYPE_EXT.
Mixing Tail Calls and Functions
Starting from version 5.10, tail calls and functions can be mixed. Previously, you had to choose one or the other.
Mixing tail calls and functions reduces the available stack size for each function from 512 bytes to 256 bytes. This is because tail calls reuse the current stack frame, but if a tail call occurs in a subfunction, the caller’s stack cannot be reused. To prevent kernel thread (default stack size 8KB) stack overflow, the system actively limits the stack size of a single function.
Mixing tail calls and functions requires support from the JIT for specific architectures. This is because the tail call counter (used to prevent more than 32 tail calls) must propagate into the function so that they can pass it to the next tail call program. The actual result is that support for mixing tail calls and functions has been added in phases. Here is a list of the time each architecture added support:
| Architecture | Version Added |
|---|---|
| x86 | Version 5.10 |
| ARM64 | Version 6.0 |
| s390 | Version 6.3 |
| LoongArch | Version 6.4 |
As of version 6.15, architectures not listed do not support mixing tail calls and functions.
Pointers in Global Functions
In version 5.12, the restrictions on parameter types passed to global functions were relaxed, allowing pointer parameters.
Callers of global functions can pass pointers to stack, map values, or packet data. The pointer is treated as a <span>void *</span> type of known size. The values or type information of memory are not tracked across function boundaries.
Callback Functions
In version 5.13, the verifier was extended to support callback functions. Since then, several helper functions and kernel functions (kfuncs) have been added that can callback to specified functions.
Parameter Annotations
In version 6.8, the feature of parameter annotations for global functions was added. These annotations are a set of (essentially BTF declaration tags) that, when added to attributes, inform the verifier to restrict the input values of the function. Possible tags include:
<span>__arg_ctx</span>— This parameter is a pointer to the program context.<span>__arg_nonnull</span>— This parameter cannot be NULL.<span>__arg_nullable</span>— This parameter can be NULL.<span>__arg_trusted</span>— This parameter must be a trusted value.<span>__arg_arena</span>— This parameter must be a pointer to a memory area.
Src
https://docs.ebpf.io/linux/concepts/functions/
Last updated: April 16, 2025