File: global-variables.md

package info (click to toggle)
golang-github-cilium-ebpf 0.17.3%2Bds1-1
  • links: PTS, VCS
  • area: main
  • in suites: experimental
  • size: 4,684 kB
  • sloc: ansic: 1,259; makefile: 127; python: 113; awk: 29; sh: 24
file content (162 lines) | stat: -rw-r--r-- 7,685 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
{{ linux_version("5.2", "For all global variable-related BPF operations,
the kernel needs to understand the BPF_PSEUDO_MAP_VALUE value in ldimm64
instructions. This is needed for direct, lookup-free map access." )}}

Like typical C programs, BPF programs allow the use of global variables. These
variables can be initialized from the BPF C code itself, or they can be modified
by the loading user space application before handing it off to the kernel.

The abstraction {{ proj }} provides to interact with global variables is the
{{ godoc('VariableSpec') }}, found in the {{ godoc('CollectionSpec.Variables') }}
field. This page describes how to declare variables in BPF C and how to interact
with them in Go.

## Runtime Constants

{{ linux_version("5.2", "Read-only maps and the BPF_MAP_FREEZE command are needed
for implementing constant variables.") }}

Global runtime constants are typically used for configuration values that
influence the functionality of a BPF program. Think all sorts of network or
hardware addresses for network filtering, or timeouts for rate limiting. The C
compiler will reject any runtime modifications to these variables in the BPF
program, like a typical const.

Crucially, the BPF verifier will also perform dead code analysis if constants
are used in if statements. If a condition is always true or false, it will
remove unused code paths from the BPF program, reducing verification time and
increasing runtime performance.

This enables many features like portable kfuncs, allowing C code to refer to
kfuncs that may not exist in some kernels, as long as those code paths are
guaranteed not to execute at runtime. Similarly, this can be used to your
advantage to disable code paths that are not needed in certain configurations,
or would result in a verifier error on some kernels or in some contexts.

:ebee-color: Consider the following C BPF program that reads a global constant
and returns it:

{{ c_example('variables_const', title='BPF C program declaring global constant const_u32') }}

??? warning "Why is `const_u32` declared `volatile`?"

    In short: without the `volatile` qualifier, the variable would be optimized
    away and not appear in the BPF object file, leaving us unable to modify it
    from our user space application.

    In this program, the compiler (in)correctly deduces two things about `const_u32`:
    it is never assigned a value, and it doesn't change over the course of the program.
    Implementation details aside, it will now assume that the return value of
    `const_example()` is always 0 and omit the variable from the ELF altogether.

    For BPF programs, it's common practice to declare all global variables that
    need to be accessed from user space as `volatile`, especially non-`const`
    globals. Doing so ensures the compiler reliably allocates them in a data
    section in the ELF.

:simple-go: First, let's take a look at a full Go example that will comprise the
majority of interactions with constants. In the example below, we'll load a BPF
object from disk, pull out a variable, set its value and call the BPF program
once with an empty context. Variations on this pattern will follow later.

{{ go_example('DocVariablesSetConst', title='Go program modifying a const, loading and running the BPF program') }}

1. Any values passed into {{ godoc('VariableSpec.Set') }} must marshal to a
   fixed width. This behaviour is identical to {{ godoc('Map.Put') }} and
   friends. Using untyped integers is not supported since their size is platform
   dependent. We recommend the same approach in BPF C to keep data size
   predictable.
2. A 15-byte context is the minimum the kernel will accept for dry-running a BPF
   program. If your BPF program reads from its context, populating this slice is
   a great way of doing unit testing without setting up a live testing environment.

## Global Variables

Non-const global variables are mutable and can be modified by both the BPF
program and the user space application. They are typically used for keeping
state like metrics, counters, rate limiting, etc.

These variables can also be initialized from user space, much like their `const`
counterparts, and can be both read and written to from the BPF program as well
as the user space application. More on that in a future section.

:ebee-color: The following C BPF program reads a global variable and returns it:

{{ c_example('variables_global', title='BPF C program declaring global variable global_u16') }}

??? warning "Why is `global_u16` declared `volatile`?"

    Similar to `volatile const` in a prior example, `volatile` is used here to
    make compiler output more deterministic. Without it, the compiler may
    choose to optimize away a variable if it's never assigned to, not knowing
    its value is actually provided by user space. The `volatile` qualifier
    doesn't change the variable's semantics.

### Before Loading: Using VariableSpec

For interacting with global variables before loading the BPF program into the
kernel, use the methods on its {{ godoc('VariableSpec') }} found in {{
godoc('CollectionSpec.Variables') }} or injected using {{ godoc('LoadAndAssign')
}}. This ensures the variable is populated before the BPF program has a chance
to execute.

:simple-go: In user space, initialize `global_u16` to 9000:

{{ go_example('DocVariablesSetGlobalU16') }}

Dry-running `global_example()` a few times results in the value increasing on
every invocation:

{{ go_example('DocVariablesSetGlobalRun') }}

Once a CollectionSpec has been loaded into the kernel, further modifications
to a VariableSpec are ineffectual.

### After Loading: Using Variable

After loading the BPF program into the kernel, accessing global variables from
user space can be done through the {{ godoc('Variable') }} abstraction. These
can be injected into an object using {{ godoc('LoadAndAssign') }}, or found in
the {{ godoc('Collection.Variables') }} field.

:simple-go: Building on the previous example, read the incremented variable
using {{ godoc('Variable.Get') }}:

{{ go_example('DocVariablesGetGlobalU16') }}

Modify the Variable at runtime using {{ godoc('Variable.Set') }}.

## Internal/Hidden Global Variables

By default, all global variables described in an ELF's data sections are exposed
through {{ godoc('CollectionSpec.Variables') }}. However, there may be cases
where you don't want user space to interfere with a variable (either on purpose
or by accident) and you want to keep the variable internal to the BPF program.

{{ c_example('variables_hidden', title='BPF C program declaring internal global variable internal_var') }}

The `__hidden` macro is found in Linux' `<bpf/bpf_helpers.h>` as of version 5.13
and is defined as follows:

```c
#define __hidden __attribute__((visibility("hidden")))
```

This will cause the VariableSpec for `hidden_var` to not be included in
the CollectionSpec. 

## Static Global Variables

With the introduction of `bpftool gen object`. BPF received a full-blown static
linker, giving the `static` keyword for declaring objects local to a single .c
file an actual semantic meaning.

{{ proj }} follows the convention set by libbpf to not expose static variables
to user space. In our case, this means that static variables are not included in
the {{ godoc('CollectionSpec.Variables') }} field or emitted in bpf2go-generated
code.

The ELF loader has no way to differentiate function-scoped local variables (also
not exposed) and static variables, since they're both marked with `LOCAL`
linkage in the ELF. If you need to expose a variable to user space, drop the
`static` keyword and declare it in the global scope of your BPF C program.