Skip to content

Commit 5e7deec

Browse files
committed
docs: add 'Global Variables' page to document VariableSpec
Signed-off-by: Timo Beckers <timo@isovalent.com>
1 parent cd64585 commit 5e7deec

File tree

11 files changed

+507
-16
lines changed

11 files changed

+507
-16
lines changed
+121
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
{{ linux_version("5.2", "For all global variable-related BPF operations,
2+
the kernel needs to understand the BPF_PSEUDO_MAP_VALUE value in ldimm64
3+
instructions. This is needed for direct, lookup-free map access." )}}
4+
5+
Like typical C programs, BPF programs allow the use of global variables. These
6+
variables can be initialized from the BPF C code itself, or they can be modified
7+
by the loading user space application before handing it off to the kernel.
8+
9+
The abstraction {{ proj }} provides to interact with global variables is the
10+
{{ godoc('VariableSpec') }}, found in the {{ godoc('CollectionSpec.Variables') }}
11+
field. This page describes how to declare variables in BPF C and how to interact
12+
with them in Go.
13+
14+
## Runtime Constants
15+
16+
{{ linux_version("5.2", "Read-only maps and the BPF_MAP_FREEZE command are needed
17+
for implementing constant variables.") }}
18+
19+
Global runtime constants are typically used for configuration values that
20+
influence the functionality of a BPF program. Think all sorts of network or
21+
hardware addresses for network filtering, or timeouts for rate limiting. The C
22+
compiler will reject any runtime modifications to these variables in the BPF
23+
program, like a typical const.
24+
25+
Crucially, the BPF verifier will also perform dead code analysis if constants
26+
are used in if statements. If a condition is always true or false, it will
27+
remove unused code paths from the BPF program, reducing verification time and
28+
increasing runtime performance.
29+
30+
This enables many features like portable kfuncs, allowing C code to refer to
31+
kfuncs that may not exist in some kernels, as long as those code paths are
32+
guaranteed not to execute at runtime. Similarly, this can be used to your
33+
advantage to disable code paths that are not needed in certain configurations,
34+
or would result in a verifier error on some kernels or in some contexts.
35+
36+
:ebee-color: Consider the following C BPF program that reads a global constant
37+
and returns it:
38+
39+
{{ c_example('variables_const', title='BPF C program declaring global constant const_u32') }}
40+
41+
??? warning "Why is `const_u32` declared `volatile`?"
42+
43+
In short: without the `volatile` qualifier, the variable would be optimized
44+
away and not appear in the BPF object file, leaving us unable to modify it
45+
from our user space application.
46+
47+
In this program, the compiler (in)correctly deduces two things about `const_u32`:
48+
it is never assigned a value, and it doesn't change over the course of the program.
49+
Implementation details aside, it will now assume that the return value of
50+
`const_example()` is always 0 and omit the variable from the ELF altogether.
51+
52+
For BPF programs, it's common practice to declare all global variables that
53+
need to be accessed from user space as `volatile`, especially non-`const`
54+
globals. Doing so ensures the compiler reliably allocates them in a data
55+
section in the ELF.
56+
57+
:simple-go: First, let's take a look at a full Go example that will comprise the
58+
majority of interactions with constants. In the example below, we'll load a BPF
59+
object from disk, pull out a variable, set its value and call the BPF program
60+
once with an empty context. Variations on this pattern will follow later.
61+
62+
{{ go_example('DocVariablesSetConst', title='Go program modifying a const, loading and running the BPF program') }}
63+
64+
1. Any values passed into {{ godoc('VariableSpec.Set') }} must marshal to a
65+
fixed width. This behaviour is identical to {{ godoc('Map.Put') }} and
66+
friends. Using untyped integers is not supported since their size is platform
67+
dependent. We recommend the same approach in BPF C to keep data size
68+
predictable.
69+
2. A 15-byte context is the minimum the kernel will accept for dry-running a BPF
70+
program. If your BPF program reads from its context, populating this slice is
71+
a great way of doing unit testing without setting up a live testing environment.
72+
73+
## Global Variables
74+
75+
Non-const global variables are mutable and can be modified by both the BPF
76+
program and the user space application. They are typically used for keeping
77+
state like metrics, counters, rate limiting, etc.
78+
79+
These variables can also be initialized from user space, much like their `const`
80+
counterparts, and can be both read and written to from the BPF program as well
81+
as the user space application. More on that in a future section.
82+
83+
:ebee-color: The following C BPF program reads a global variable and returns it:
84+
85+
{{ c_example('variables_global', title='BPF C program declaring global variable global_u16') }}
86+
87+
??? warning "Why is `global_u16` declared `volatile`?"
88+
89+
Similar to `volatile const` in a prior example, `volatile` is used here to
90+
make compiler output more deterministic. Without it, the compiler may
91+
choose to optimize away a variable if it's never assigned to, not knowing
92+
its value is actually provided by user space. The `volatile` qualifier
93+
doesn't change the variable's semantics.
94+
95+
:simple-go: Next, in user space, initialize `global_u16` to 9000:
96+
97+
{{ go_example('DocVariablesSetGlobalU16') }}
98+
99+
Dry-running `global_example()` a few times results in our value increasing on
100+
every invocation:
101+
102+
{{ go_example('DocVariablesSetGlobalRun') }}
103+
104+
## Internal/Hidden Global Variables
105+
106+
By default, all global variables described in an ELF's data sections are exposed
107+
through {{ godoc('CollectionSpec.Variables') }}. However, there may be cases
108+
where you don't want user space to interfere with a variable (either on purpose
109+
or by accident) and you want to keep the variable internal to the BPF program.
110+
111+
{{ c_example('variables_hidden', title='BPF C program declaring internal global variable internal_var') }}
112+
113+
The `__hidden` macro is found in Linux' `<bpf/bpf_helpers.h>` as of version 5.13
114+
and is defined as follows:
115+
116+
```c
117+
#define __hidden __attribute__((visibility("hidden")))
118+
```
119+
120+
This will cause the VariableSpec for `hidden_var` to not be included in
121+
the CollectionSpec.

docs/ebpf/concepts/section-naming.md

+15-16
Original file line numberDiff line numberDiff line change
@@ -93,36 +93,35 @@ called `kprobe/slub_flush`.
9393

9494
#### :material-head-cog: Advanced: Special Map Sections
9595

96-
`.data`
96+
`.data*`
9797

9898
: The LLVM BPF backend implements accesses to mutable global variables as
9999
direct Array Map accesses. Since a single BPF program can be executed
100100
concurrently as a result of the kernel processing packets and other events
101-
asynchronously, `.data` and the global variables it represents are
101+
asynchronously, a data section and the global variables it represents are
102102
considered shared memory.
103103

104-
This section is exposed by {{ godoc('CollectionSpec.Maps') }} as a
105-
single-element BPF array and its contents are accessible through {{
106-
godoc('MapSpec.Contents') }}. Its layout is described by its {{
107-
godoc('MapSpec.Value') }}, a {{ godoc('btf/Datasec') }} containing all
108-
global variables in the compilation unit.
104+
Variables can be emitted to specific sections, like
105+
`#!c SEC(".data.foo") my_var = 123;`, as long as they match the `.data*`
106+
prefix. This can prove useful for isolating certain variables to well-known
107+
sections for Go code generation or custom variable rewriting logic.
109108

110-
The contents of the Map may be modified to control the default values
111-
used for the eBPF program's global variables.
109+
Global, non-hidden variables are emitted to
110+
{{ godoc('CollectionSpec.Variables') }}, where they can be modified before
111+
loading the CollectionSpec into the kernel. See
112+
[Global Variables](../concepts/global-variables.md) for instructions.
112113

113114
`.rodata*`
114115

115-
: Like `.data`, but for constants. This is a prefix and matches sections like
116-
`.rodata.foo`. Constants can be emitted to different sections using e.g.
117-
`#!c SEC(".rodata.foo") volatile const foobar = 123;`. This can prove useful
118-
for isolating certain constants to well-known sections for Go code
119-
generation or custom constant rewriting logic.
116+
: Like `.data*`, but for constants. These become read-only after loading the
117+
CollectionSpec into the kernel, and are also exposed through
118+
{{ godoc('CollectionSpec.Variables') }}.
120119

121120
`.bss`
122121

123122
: Section emitted by the compiler when zero-initialized globals are present in
124-
the ELF. Is typically zero-length. Exposed by {{
125-
godoc('CollectionSpec.Maps') }}, but not really useful.
123+
the ELF. Is typically zero-length in the ELF, and initialized by {{ proj }}
124+
after loading. Also exposed through {{ godoc('CollectionSpec.Variables') }}.
126125

127126
`.rel*`
128127

docs/examples/variables/gen.go

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
package main
2+
3+
//go:generate go run github.com/cilium/ebpf/cmd/bpf2go variables variables.c

docs/examples/variables/main.go

+91
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
package main
2+
3+
import "fmt"
4+
5+
func main() {
6+
DocVariablesSetConst()
7+
DocVariablesSetGlobal()
8+
}
9+
10+
// Full example written to be displayed in its entirety, so is commented generously.
11+
func DocVariablesSetConst() {
12+
// Load the object file from disk using a bpf2go-generated scaffolding.
13+
spec, err := loadVariables()
14+
if err != nil {
15+
panicf("loading CollectionSpec: %s", err)
16+
}
17+
18+
// Set the 'const_u32' variable to 42 in the CollectionSpec.
19+
want := uint32(42) // (1)!
20+
if err := spec.Variables["const_u32"].Set(want); err != nil {
21+
panicf("setting variable: %s", err)
22+
}
23+
24+
// Load the CollectionSpec.
25+
//
26+
// Note: modifying spec.Variables after this point is ineffectual!
27+
// Modifying *Spec resources does not affect loaded/running BPF programs.
28+
var obj variablesPrograms
29+
if err := spec.LoadAndAssign(&obj, nil); err != nil {
30+
panicf("loading BPF program: %s", err)
31+
}
32+
33+
fmt.Println("Running program with const_u32 set to", want)
34+
35+
// Dry-run the BPF program with an empty context.
36+
ret, _, err := obj.ConstExample.Test(make([]byte, 15)) // (2)!
37+
if err != nil {
38+
panicf("running BPF program: %s", err)
39+
}
40+
41+
if ret != want {
42+
panicf("unexpected return value %d", ret)
43+
}
44+
45+
fmt.Println("BPF program returned", ret)
46+
47+
// Output:
48+
// Running program with const_u32 set to 42
49+
// BPF program returned 42
50+
}
51+
52+
func DocVariablesSetGlobal() {
53+
spec, err := loadVariables()
54+
if err != nil {
55+
panicf("loading CollectionSpec: %s", err)
56+
}
57+
58+
// DocVariablesSetGlobalU16 {
59+
set := uint16(9000)
60+
if err := spec.Variables["global_u16"].Set(set); err != nil {
61+
panicf("setting variable: %s", err)
62+
}
63+
// }
64+
65+
var obj variablesPrograms
66+
if err := spec.LoadAndAssign(&obj, nil); err != nil {
67+
panicf("loading BPF program: %s", err)
68+
}
69+
70+
fmt.Println("Running program with global_u16 set to", set)
71+
72+
// DocVariablesSetGlobalRun {
73+
for range 3 {
74+
ret, _, err := obj.GlobalExample.Test(make([]byte, 15))
75+
if err != nil {
76+
panicf("running BPF program: %s", err)
77+
}
78+
fmt.Println("BPF program returned", ret)
79+
}
80+
81+
// Output:
82+
// Running program with global_u16 set to 9000
83+
// BPF program returned 9000
84+
// BPF program returned 9001
85+
// BPF program returned 9002
86+
// }
87+
}
88+
89+
func panicf(format string, args ...interface{}) {
90+
panic(fmt.Sprintf(format, args...))
91+
}

docs/examples/variables/variables.c

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
//go:build ignore
2+
3+
#include <linux/bpf.h>
4+
#include <bpf/bpf_helpers.h>
5+
6+
// Remove when toolchain Docker image ships with 5.13+ headers.
7+
#define __hidden __attribute__((visibility("hidden")))
8+
9+
// variables_const {
10+
volatile const __u32 const_u32;
11+
12+
SEC("socket") int const_example() {
13+
return const_u32;
14+
}
15+
// }
16+
17+
// variables_global {
18+
volatile __u16 global_u16;
19+
20+
SEC("socket") int global_example() {
21+
global_u16++;
22+
return global_u16;
23+
}
24+
// }
25+
26+
// variables_hidden {
27+
__hidden __u64 hidden_var;
28+
29+
SEC("socket") int hidden_example() {
30+
hidden_var++;
31+
return hidden_var;
32+
}
33+
// }

0 commit comments

Comments
 (0)